brick-codegen
Version:
Better React Native native module development
4,909 lines • 181 kB
JavaScript
import { createRequire } from "node:module";
import path from "path";
import pc from "picocolors";
import fs from "fs-extra";
import { glob } from "glob";
import crypto from "crypto";
import { Project } from "ts-morph";
import ts from "typescript";
//#region rolldown:runtime
var __require = /* @__PURE__ */ createRequire(import.meta.url);
//#endregion
//#region src/errors.ts
/**
* Custom error class for brick-codegen with rich context information
*/
var BrickCodegenError = class BrickCodegenError extends Error {
context;
originalError;
constructor(message, context = {}, originalError) {
super(message);
this.name = "BrickCodegenError";
this.context = context;
this.originalError = originalError;
if (Error.captureStackTrace) Error.captureStackTrace(this, BrickCodegenError);
}
/**
* Create a formatted error message with context
*/
toFormattedString(useColors = true) {
const parts = [];
if (this.context.filePath) {
const location = this.context.filePath;
const lineCol = this.context.line !== void 0 ? `:${this.context.line}${this.context.column !== void 0 ? `:${this.context.column}` : ""}` : "";
parts.push(`File: ${location}${lineCol}`);
}
if (this.context.moduleName) parts.push(`Module: ${this.context.moduleName}`);
if (this.context.memberName) {
const memberType = this.context.parameterName ? "Method" : "Property";
parts.push(`${memberType}: ${this.context.memberName}`);
}
if (this.context.parameterName) parts.push(`Parameter: ${this.context.parameterName}`);
if (this.context.line !== void 0 && !this.context.filePath) {
const location = `Line ${this.context.line}${this.context.column !== void 0 ? `, Column ${this.context.column}` : ""}`;
parts.push(location);
}
parts.push("");
parts.push(`Error: ${this.message}`);
if (this.context.details) {
parts.push("");
parts.push(this.context.details);
}
return parts.join("\n ");
}
/**
* Get a clickable file location string for terminals
*/
getFileLocation() {
if (!this.context.filePath) return;
let location = this.context.filePath;
if (this.context.line !== void 0) {
location += `:${this.context.line}`;
if (this.context.column !== void 0) location += `:${this.context.column}`;
}
return location;
}
};
/**
* Format multiple errors into a user-friendly output with colors
*/
function formatErrors(errors) {
if (errors.length === 0) return "";
const separator = pc.gray("─".repeat(70));
return errors.map((error, index) => {
const parts = [];
const fileName = error.context.filePath || "unknown file";
parts.push(pc.bold(pc.cyan(`\n${index + 1}. Error in ${fileName}`)));
const location = error.getFileLocation();
if (location) parts.push(pc.dim(` at ${location}`));
parts.push("");
if (error.context.moduleName) parts.push(pc.blue(` Module: ${error.context.moduleName}`));
if (error.context.memberName) {
const memberType = error.context.parameterName ? "Method" : "Member";
parts.push(pc.blue(` ${memberType}: ${error.context.memberName}`));
}
if (error.context.parameterName) parts.push(pc.blue(` Parameter: ${error.context.parameterName}`));
parts.push("");
parts.push(pc.red(` ✗ ${error.message}`));
if (error.context.details) {
parts.push("");
parts.push(pc.dim(` ${error.context.details}`));
}
return parts.join("\n");
}).join(`\n${separator}\n`);
}
/**
* Create error summary statistics
*/
function getErrorSummary(errors, successCount) {
const errorCount = errors.length;
const fileCount = new Set(errors.map((e) => e.context.filePath).filter((f) => !!f)).size;
const parts = [];
if (errorCount > 0) parts.push(`Found ${errorCount} error${errorCount === 1 ? "" : "s"}${fileCount > 0 ? ` in ${fileCount} file${fileCount === 1 ? "" : "s"}` : ""}.`);
if (successCount > 0) parts.push(`${successCount} module${successCount === 1 ? "" : "s"} generated successfully.`);
return parts.join(" ");
}
/**
* Helper to wrap any error with context
*/
function wrapError(error, context, message) {
if (error instanceof BrickCodegenError) return new BrickCodegenError(message || error.message, {
...error.context,
...context
}, error.originalError);
return new BrickCodegenError(message || (error instanceof Error ? error.message : String(error)), context, error instanceof Error ? error : void 0);
}
/**
* Parse React Native Codegen error and extract useful information
*/
function parseReactNativeCodegenError(errorMessage) {
const match = errorMessage.match(/\[Codegen\]\s+(\w+):\s+Module\s+(\w+):\s+(.+)/);
if (!match) return null;
const [, errorType, moduleName, restMessage] = match;
let propertyName;
const cleanMessage = restMessage || "";
let details;
const propMatch = restMessage?.match(/property\s+'([^']+)'/);
if (propMatch) propertyName = propMatch[1];
return {
type: errorType || "CodegenError",
module: moduleName || "Unknown",
property: propertyName,
message: cleanMessage,
details
};
}
/**
* Create a BrickCodegenError from React Native Codegen error
*/
function createCodegenError(errorMessage, specFilePath, sourceMap) {
const parsed = parseReactNativeCodegenError(errorMessage);
if (!parsed) return new BrickCodegenError(`React Native Codegen failed: ${errorMessage}`, { filePath: specFilePath });
let finalContext = {
filePath: specFilePath,
moduleName: parsed.module,
memberName: parsed.property,
details: parsed.details
};
if (sourceMap && parsed.property) {
const mapping = sourceMap.findMapping(parsed.property);
if (mapping) finalContext = {
filePath: mapping.sourceFile,
moduleName: mapping.moduleName,
memberName: mapping.sourceName,
line: mapping.sourceLine,
column: mapping.sourceColumn,
details: `Invalid event definition: '${mapping.sourceName}'
The event '${mapping.sourceName}' was converted to '${mapping.generatedName}' but React Native
Codegen requires EventEmitter properties with concrete types.
Use CodegenTypes.EventEmitter instead:
readonly on${mapping.sourceName.charAt(0).toUpperCase()}${mapping.sourceName.slice(1)}: CodegenTypes.EventEmitter<{ /* payload */ }>;`
};
}
return new BrickCodegenError(`[React Native Codegen] ${parsed.type}: ${parsed.message}`, finalContext);
}
//#endregion
//#region src/generator.ts
/**
* Generator - Simplified TypeScript generation for NativeBrickModule.ts only
* Only generates the TurboModule specification file needed for React Native Codegen
*/
var Generator = class {
config;
platformRegistry;
constructor(config, platformRegistry) {
this.config = config;
this.platformRegistry = platformRegistry;
}
/**
* Main generation method - Generates .brick folder structure with package.json and NativeBrickModule.ts
*/
async generateTypeScriptFiles(modules) {
try {
const brickDir = path.join(this.config.projectRoot, this.config.outputTypescript);
const srcDir = path.join(brickDir, "src");
const specDir = path.join(srcDir, "spec");
const generatedFiles = [];
await fs.ensureDir(specDir);
const packageJsonFile = await this.generatePackageJson(brickDir);
generatedFiles.push(packageJsonFile);
const turboSpecFile = await this.generateTurboModuleSpec(modules, specDir);
generatedFiles.push(turboSpecFile);
return generatedFiles;
} catch (error) {
if (error instanceof BrickCodegenError) throw error;
throw new BrickCodegenError("Failed to generate TypeScript files", {}, error instanceof Error ? error : void 0);
}
}
/**
* Generates package.json for .brick folder
*/
async generatePackageJson(brickDir) {
const projectPackageJsonPath = path.join(this.config.projectRoot, "package.json");
let projectName = Math.random().toString(36).substring(2, 15);
try {
const projectPackageJson = await fs.readFile(projectPackageJsonPath, "utf-8");
projectName = JSON.parse(projectPackageJson).name || Math.random().toString(36).substring(2, 15);
} catch (error) {
console.error(error instanceof Error ? error.message : String(error));
}
const packageJson = {
name: `brick-${crypto.createHash("md5").update(projectName).digest("hex").substring(0, 8)}`,
codegenConfig: {
name: "BrickModuleSpec",
type: "modules",
jsSrcsDir: "src/spec"
}
};
const outputPath = path.join(brickDir, "package.json");
await fs.writeFile(outputPath, JSON.stringify(packageJson, null, 2));
this.logGenerated(outputPath);
return outputPath;
}
/**
* Generates TurboModule specification for React Native Codegen
* This is the only file generated by brick-codegen
*/
async generateTurboModuleSpec(modules, specDir) {
const methods = this.getAllMethods(modules);
const constants = this.getAllConstants(modules);
const events = this.getAllEvents(modules);
const interfaceDefinitions = [];
const seenInterfaces = /* @__PURE__ */ new Set();
for (const module of modules) for (const interfaceDef of module.interfaceDefinitions) if (!interfaceDef.name.includes("ModuleSpec") && !interfaceDef.name.includes("Events")) {
const prefixedName = `${module.moduleName}_${interfaceDef.name}`;
if (!seenInterfaces.has(prefixedName)) {
seenInterfaces.add(prefixedName);
const properties = interfaceDef.properties.map((prop) => {
const optionalMark = prop.optional ? "?" : "";
let propType = prop.type;
for (const otherInterface of module.interfaceDefinitions) if (propType.includes(otherInterface.name) && !otherInterface.name.includes("ModuleSpec") && !otherInterface.name.includes("Events")) propType = propType.replace(new RegExp(`\\b${otherInterface.name}\\b`, "g"), `${module.moduleName}_${otherInterface.name}`);
return ` ${prop.name}${optionalMark}: ${propType};`;
}).join("\n");
interfaceDefinitions.push(`export interface ${prefixedName} {\n${properties}\n}`);
}
}
const typeMappings = /* @__PURE__ */ new Map();
for (const module of modules) for (const interfaceDef of module.interfaceDefinitions) if (!interfaceDef.name.includes("ModuleSpec") && !interfaceDef.name.includes("Events")) typeMappings.set(interfaceDef.name, `${module.moduleName}_${interfaceDef.name}`);
const methodDeclarations = methods.map((method) => {
const methodKey = `${method.moduleName}_${method.name}`;
let parameterPart = method.signature.substring(method.name.length);
typeMappings.forEach((prefixedName, originalName) => {
const regex = new RegExp(`\\b${originalName}\\b`, "g");
parameterPart = parameterPart.replace(regex, prefixedName);
});
parameterPart = parameterPart.replace(/\bAnyObject\b/g, "{}");
parameterPart = parameterPart.replace(/\bAny\b/g, "{}");
if (!method.isAsync) {
if (parameterPart.match(/\):\s*[^;]+$/)) parameterPart = parameterPart.replace(/\):\s*[^;]+$/, "): {}");
}
return ` ${methodKey}${parameterPart};`;
}).join("\n");
const eventDeclarations = events.length > 0 ? `\n // ========== EVENTS ==========\n${events.map((event) => {
const eventKey = `${event.moduleName}_on${this.capitalizeFirst(event.name)}`;
let payloadType = event.payload || "{}";
typeMappings.forEach((prefixedName, originalName) => {
const regex = new RegExp(`\\b${originalName}\\b`, "g");
payloadType = payloadType.replace(regex, prefixedName);
});
payloadType = payloadType.replace(/\bAnyObject\b/g, "{}");
payloadType = payloadType.replace(/\bAny\b/g, "{}");
return ` readonly ${eventKey}: CodegenTypes.EventEmitter<${payloadType}>;`;
}).join("\n")}\n` : "";
const constantsDeclarations = constants.length > 0 ? `\n // ========== CONSTANTS ==========\n readonly getConstants: () => {\n${constants.map((constant) => {
return ` ${`${constant.moduleName}_${constant.name}`}: ${constant.type};`;
}).join("\n")}\n };\n` : "";
const specCode = `// This file is automatically generated by brick-codegen for React Native Codegen
// Do not edit manually - regenerate using: brick-codegen
// Note: This uses the same format as NativeBrickModule.ts for consistency
import type { TurboModule, CodegenTypes } from 'react-native';
import { TurboModuleRegistry } from 'react-native';
${interfaceDefinitions.length > 0 ? `\n// ========== TYPE DEFINITIONS ==========\n${interfaceDefinitions.join("\n\n")}\n` : ""}
/**
* TurboModule specification for React Native Codegen
* This file is used by React Native's official codegen to generate native bindings
*/
export interface Spec extends TurboModule {
// ========== MODULE MANAGEMENT ==========
/**
* Gets list of all registered Brick modules
*/
getRegisteredModules(): string[];
${constantsDeclarations}${eventDeclarations}
// ========== GENERATED MODULE METHODS ==========
${methodDeclarations}
}
export default TurboModuleRegistry.getEnforcing<Spec>('BrickModule');
`;
const outputPath = path.join(specDir, "NativeBrickModule.ts");
await fs.writeFile(outputPath, specCode);
this.logGenerated(outputPath);
return outputPath;
}
getAllEvents(modules) {
const events = [];
for (const module of modules) {
if (!module || !module.events || !Array.isArray(module.events)) continue;
for (const event of module.events) events.push({
...event,
moduleName: module.moduleName
});
}
return events;
}
capitalizeFirst(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
/**
* Gets all methods from all modules
*/
getAllMethods(modules) {
const methods = [];
for (const module of modules) for (const method of module.methods) methods.push({
...method,
moduleName: module.moduleName
});
return methods;
}
/**
* Gets all constants from all modules
*/
getAllConstants(modules) {
const constants = [];
for (const module of modules) for (const constant of module.constants) constants.push({
moduleName: module.moduleName,
name: constant.name,
type: constant.type,
readonly: constant.readonly
});
return constants;
}
/**
* Generates bridge code for all platforms using DI pattern
*/
async generateBridgeCode(modules) {
const result = {
ios: [],
android: [],
allFiles: []
};
if (!this.platformRegistry) return result;
const selectedPlatforms = this.config.selectedPlatforms;
if (!selectedPlatforms || selectedPlatforms.includes("ios")) try {
const iosFiles = await this.platformRegistry.generateForPlatform("ios", modules);
result.ios = iosFiles;
result.allFiles.push(...iosFiles);
} catch (error) {
console.error(`${pc.bold(pc.cyan("[Brick]"))} ${pc.red(`iOS bridge generation failed: ${error instanceof Error ? error.message : String(error)}`)}`);
}
if (!selectedPlatforms || selectedPlatforms.includes("android")) try {
const androidFiles = await this.platformRegistry.generateForPlatform("android", modules);
result.android = androidFiles;
result.allFiles.push(...androidFiles);
} catch (error) {
console.error(`${pc.bold(pc.cyan("[Brick]"))} ${pc.red(`Android bridge generation: ${error instanceof Error ? error.message : String(error)}`)}`);
}
return result;
}
/**
* Main unified generation method - Generates all files including TypeScript specs and bridge code
*/
async generateAll(modules) {
const allGeneratedFiles = [];
const tsFiles = await this.generateTypeScriptFiles(modules);
allGeneratedFiles.push(...tsFiles);
const bridgeResult = await this.generateBridgeCode(modules);
allGeneratedFiles.push(...bridgeResult.allFiles);
return allGeneratedFiles;
}
log(message) {
console.log(`${pc.bold(pc.green("[Brick]"))} ${message}`);
}
logGenerated(filepath) {
const absolutePath = path.resolve(filepath);
console.log(`${pc.bold(pc.green("[Brick]"))} Generated artifact: ${absolutePath}`);
}
};
//#endregion
//#region src/utils/type-analysis.ts
/**
* Creates a type analyzer for semantic TypeScript type checking
*/
function createTypeAnalyzer() {
/**
* Helper to check if type matches a specific symbol name
* Handles type aliases by following the alias chain
*/
function isSymbol(type, symbolName) {
if (type.getSymbol()?.getName() === symbolName) return true;
let aliasSymbol = type.getAliasSymbol();
while (aliasSymbol != null) {
if (aliasSymbol.getName() === symbolName) return true;
const nextAlias = aliasSymbol.getAliasedSymbol();
if (nextAlias === aliasSymbol) break;
aliasSymbol = nextAlias;
}
return false;
}
/**
* Check if type is a Promise
*/
function isPromise(type) {
return isSymbol(type, "Promise");
}
/**
* Check if type is a Record
*/
function isRecord(type) {
return isSymbol(type, "Record");
}
/**
* Check if type is an array
*/
function isArray(type) {
return type.isArray();
}
/**
* Check if type is ArrayBuffer
*/
function isArrayBuffer(type) {
return isSymbol(type, "ArrayBuffer");
}
/**
* Check if type is Date
*/
function isDate(type) {
return isSymbol(type, "Date");
}
/**
* Check if type is Map
*/
function isMap(type) {
return isSymbol(type, "Map");
}
/**
* Check if type is Error
*/
function isError(type) {
return isSymbol(type, "Error");
}
/**
* Check if type is a custom user-defined type (interface/class)
*/
function isCustomType(type) {
return type.getSymbol() != null && !isArray(type) && type.isObject();
}
/**
* Check if type is a synchronous function
*/
function isSyncFunction(type) {
const signatures = type.getCallSignatures();
if (signatures.length === 0) return false;
return signatures.some((sig) => {
return !isPromise(sig.getReturnType());
});
}
/**
* Check if type is an asynchronous function (returns Promise)
*/
function isAsyncFunction(type) {
return type.getCallSignatures().some((sig) => {
return isPromise(sig.getReturnType());
});
}
/**
* Check if type is void
*/
function isVoid(type) {
return type.getText() === "void" || type.isUndefined() || type.isNull();
}
/**
* Check if type is boolean
*/
function isBoolean(type) {
return type.isBoolean() || type.isBooleanLiteral();
}
/**
* Check if type is string
*/
function isString(type) {
return type.isString() || type.isStringLiteral();
}
/**
* Check if type is number
*/
function isNumber(type) {
return type.isNumber() || type.isNumberLiteral();
}
/**
* Check if type is a primitive (string, number, boolean, void)
*/
function isPrimitive(type) {
return isString(type) || isNumber(type) || isBoolean(type) || isVoid(type);
}
/**
* Check if type is an object (not primitive)
*/
function isObject(type) {
return type.isObject() && !isArray(type);
}
/**
* Check if type is a union type
*/
function isUnion(type) {
return type.isUnion();
}
/**
* Check if type is optional (includes undefined)
*/
function isOptional(type) {
if (isUnion(type)) return type.getUnionTypes().some((t) => t.isUndefined() || t.isNull());
return false;
}
/**
* Get the resolved type from a Promise
* @returns The type wrapped by Promise, or undefined if not a Promise
*/
function getPromiseResolvedType(type) {
if (!isPromise(type)) return;
return type.getTypeArguments()[0];
}
/**
* Get the element type from an array
* @returns The element type, or undefined if not an array
*/
function getArrayElementType(type) {
if (!isArray(type)) return;
return type.getArrayElementType();
}
/**
* Get the text representation of a type
*/
function getTypeText(type) {
return type.getText();
}
/**
* Get function signature information
* @returns Parameter and return type information, or undefined if not a function
*/
function getFunctionSignature(type) {
const signatures = type.getCallSignatures();
if (signatures.length === 0) return;
const signature = signatures[0];
if (!signature) return;
const declaration = signature.getDeclaration();
return {
parameters: signature.getParameters().map((param) => {
const paramType = declaration ? param.getTypeAtLocation(declaration) : param.getDeclaredType();
return {
name: param.getName(),
type: paramType.getText(),
isRest: param.getName().startsWith("...") || false
};
}),
returnType: signature.getReturnType().getText()
};
}
return {
isPromise,
isRecord,
isArray,
isArrayBuffer,
isDate,
isMap,
isError,
isCustomType,
isSyncFunction,
isAsyncFunction,
isVoid,
isBoolean,
isString,
isNumber,
isPrimitive,
isObject,
isUnion,
isOptional,
getPromiseResolvedType,
getArrayElementType,
getTypeText,
getFunctionSignature
};
}
//#endregion
//#region src/parser.ts
/**
* Parser - TypeScript spec file parser for Brick modules
* Parses spec.brick-module.ts files and extracts method signatures, types, and metadata
*/
var Parser = class {
config;
interfaceDefinitions = /* @__PURE__ */ new Map();
generatedInterfaces = /* @__PURE__ */ new Map();
typeAnalyzer;
morphProject = null;
morphSourceFile = null;
currentSourceFile = null;
currentModuleName = null;
constructor(config) {
this.config = config;
this.typeAnalyzer = createTypeAnalyzer();
}
/**
* Extracts line and column information from a TypeScript node
*/
getNodeLocation(node) {
if (!this.currentSourceFile) return {
line: 0,
column: 0
};
const { line, character } = this.currentSourceFile.getLineAndCharacterOfPosition(node.getStart(this.currentSourceFile));
return {
line: line + 1,
column: character + 1
};
}
/**
* Creates error context from current parsing state
*/
createErrorContext(node, memberName, parameterName) {
const context = {
filePath: this.currentSourceFile?.fileName,
moduleName: this.currentModuleName || void 0,
memberName,
parameterName
};
if (node && this.currentSourceFile) {
const location = this.getNodeLocation(node);
context.line = location.line;
context.column = location.column;
}
return context;
}
/**
* Parses a module specification file
*/
async parseModuleSpec(module) {
try {
this.log(`📝 Parsing spec file: ${module.specPath}`);
this.interfaceDefinitions.clear();
this.generatedInterfaces.clear();
this.morphProject = new Project({
skipAddingFilesFromTsConfig: true,
compilerOptions: { target: ts.ScriptTarget.Latest }
});
this.morphSourceFile = this.morphProject.addSourceFileAtPath(module.specPath);
const specContent = await fs.readFile(module.specPath, "utf8");
const sourceFile = this.createSourceFile(module.specPath, specContent);
this.currentSourceFile = sourceFile;
const parsedModules = this.extractModuleInfo(sourceFile, module);
for (const parsed of parsedModules) {
this.currentModuleName = parsed.moduleName;
this.validateParsedModule(parsed);
}
this.log(`✅ Successfully parsed ${parsedModules.length} modules with total ${parsedModules.reduce((sum, m) => sum + m.methods.length, 0)} methods`);
return parsedModules;
} catch (error) {
if (error instanceof BrickCodegenError) throw error;
throw wrapError(error, {
filePath: module.specPath,
moduleName: module.name
});
} finally {
this.morphProject = null;
this.morphSourceFile = null;
this.currentSourceFile = null;
this.currentModuleName = null;
}
}
/**
* Creates TypeScript source file
*/
createSourceFile(fileName, sourceText) {
try {
return ts.createSourceFile(fileName, sourceText, ts.ScriptTarget.Latest, true);
} catch (error) {
throw new BrickCodegenError("TypeScript parsing failed. Please check the syntax of your .brick.ts file.", { filePath: fileName }, error instanceof Error ? error : void 0);
}
}
/**
* Extracts module information from source file
*/
extractModuleInfo(sourceFile, sourceModule) {
const modules = [];
const modulesByName = /* @__PURE__ */ new Map();
const extractedInterfaces = [];
ts.forEachChild(sourceFile, (node) => {
if (ts.isInterfaceDeclaration(node)) {
this.interfaceDefinitions.set(node.name.text, node);
const interfaceDef = this.extractInterfaceDefinition(node);
if (interfaceDef && !interfaceDef.name.includes("ModuleSpec") && !interfaceDef.name.includes("Events")) extractedInterfaces.push(interfaceDef);
}
});
ts.forEachChild(sourceFile, (node) => {
if (ts.isInterfaceDeclaration(node) && this.isModuleSpecInterface(node)) {
const tempResult = {
name: sourceModule.name,
moduleName: "",
version: sourceModule.version,
specPath: sourceModule.specPath,
constants: [],
methods: [],
events: [],
sourceModule,
interfaceDefinitions: extractedInterfaces
};
this.processInterface(node, tempResult);
if (tempResult.moduleName) modulesByName.set(tempResult.moduleName, tempResult);
}
});
modules.push(...modulesByName.values());
for (const module of modules) module.interfaceDefinitions.push(...this.generatedInterfaces.values());
if (modules.length === 0) {
const defaultModule = {
name: sourceModule.name,
moduleName: this.capitalizeFirst(sourceModule.name.replace(/-/g, "")),
version: sourceModule.version,
specPath: sourceModule.specPath,
constants: [],
methods: [],
events: [],
sourceModule,
interfaceDefinitions: extractedInterfaces
};
this.visitNode(sourceFile, defaultModule);
defaultModule.interfaceDefinitions.push(...this.generatedInterfaces.values());
modules.push(defaultModule);
}
return modules;
}
/**
* Visits a TypeScript AST node
*/
visitNode(node, result) {
if (ts.isInterfaceDeclaration(node)) {
this.interfaceDefinitions.set(node.name.text, node);
this.processInterface(node, result);
} else if (ts.isTypeAliasDeclaration(node)) this.processTypeAlias(node, result);
else if (ts.isVariableStatement(node)) this.processVariableStatement(node, result);
ts.forEachChild(node, (child) => this.visitNode(child, result));
}
/**
* Processes interface declaration
*/
processInterface(node, result) {
if (!this.isModuleSpecInterface(node)) return;
const interfaceName = node.name.text;
this.log(`Processing interface: ${interfaceName}`);
if (!result.moduleName) result.moduleName = this.extractModuleNameFromInterface(interfaceName);
for (const member of node.members) if (ts.isPropertySignature(member)) this.extractProperty(member, result);
else if (ts.isMethodSignature(member)) this.extractMethod(member, result);
}
/**
* Processes type alias declaration
*/
processTypeAlias(node, result) {
if (!this.isModuleSpecType(node)) return;
if (ts.isTypeLiteralNode(node.type)) {
for (const member of node.type.members) if (ts.isPropertySignature(member)) this.extractProperty(member, result);
else if (ts.isMethodSignature(member)) this.extractMethod(member, result);
}
}
/**
* Processes variable statement
*/
processVariableStatement(node, result) {
if (this.hasExportModifier(node)) {
for (const declaration of node.declarationList.declarations) if (ts.isIdentifier(declaration.name) && declaration.initializer) {
const constant = {
name: declaration.name.text,
type: this.inferTypeFromValue(declaration.initializer),
value: this.extractLiteralValue(declaration.initializer),
readonly: (node.declarationList.flags & ts.NodeFlags.Const) !== 0,
jsDocComment: this.extractJSDocComment(node)
};
result.constants.push(constant);
}
}
}
/**
* Checks if an interface is a module spec interface
*/
isModuleSpecInterface(node) {
const name = node.name.text;
return name.includes("Spec") || name.includes("Module") || name.includes("Interface");
}
/**
* Extracts module name from interface name
* Example: "CalculatorModuleSpec" -> "Calculator"
*/
extractModuleNameFromInterface(interfaceName) {
for (const suffix of [
"ModuleSpec",
"Module",
"Spec",
"Interface",
"Type"
]) if (interfaceName.endsWith(suffix)) return interfaceName.slice(0, -suffix.length);
return interfaceName;
}
/**
* Checks if a type alias is a module spec type
*/
isModuleSpecType(node) {
const name = node.name.text;
return name.includes("Spec") || name.includes("Module") || name.includes("Type");
}
/**
* Checks if node has export modifier
*/
hasExportModifier(node) {
if (ts.canHaveModifiers(node)) return ts.getModifiers(node)?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) ?? false;
return false;
}
/**
* Extracts property from interface/type
*/
extractProperty(node, result) {
if (!node.name || !ts.isIdentifier(node.name)) return;
const propertyName = node.name.text;
ts.canHaveModifiers(node) && ts.getModifiers(node)?.some((mod) => mod.kind === ts.SyntaxKind.ReadonlyKeyword);
this.getTypeString(node.type);
if (propertyName.startsWith("on") && propertyName.length > 2 && propertyName[2] === propertyName[2]?.toUpperCase() && node.type && ts.isTypeReferenceNode(node.type)) {
const typeRef = node.type;
const t = typeRef.typeName;
if (ts.isQualifiedName(t) && ts.isIdentifier(t.left) && t.left.text === "CodegenTypes" && ts.isIdentifier(t.right) && t.right.text === "EventEmitter" || ts.isIdentifier(t) && t.text === "EventEmitter") {
if (!result.events) result.events = [];
const eventBase = propertyName.slice(2);
const eventName = eventBase.charAt(0).toLowerCase() + eventBase.slice(1);
const payloadType = typeRef.typeArguments && typeRef.typeArguments[0] ? this.getTypeString(typeRef.typeArguments[0]) : void 0;
result.events.push({
name: eventName,
payload: payloadType,
jsDocComment: this.extractJSDocComment(node)
});
return;
}
}
if (node.type && ts.isFunctionTypeNode(node.type)) {
this.convertPropertyToMethod(node, result);
return;
}
switch (propertyName) {
case "moduleName":
if (node.type && ts.isLiteralTypeNode(node.type) && ts.isStringLiteral(node.type.literal)) result.moduleName = node.type.literal.text;
break;
case "version": break;
case "constants":
if (node.type && ts.isTypeLiteralNode(node.type)) this.extractConstantsFromType(node.type, result);
break;
default: break;
}
}
/**
* Extracts method from interface
*/
extractMethod(node, result) {
if (!node.name || !ts.isIdentifier(node.name)) return;
const methodName = node.name.text;
if (methodName === "getConstants") {
if (node.type && ts.isTypeLiteralNode(node.type)) this.extractConstantsFromType(node.type, result);
return;
}
const params = this.extractParameters(node.parameters, methodName);
const returnType = this.getTypeStringWithMethodContext(node.type, methodName, "Response");
const isAsync = node.type ? this.isPromiseTypeNode(node.type) : this.isPromiseType(returnType);
const method = {
name: methodName,
params,
returnType,
isAsync,
isSync: !isAsync,
signature: this.buildMethodSignature(methodName, params, returnType),
jsDocComment: this.extractJSDocComment(node),
deprecated: this.isDeprecated(node)
};
result.methods.push(method);
}
/**
* Converts a property with function type to a method
* Handles properties like: methodName: (param: Type) => ReturnType
*/
convertPropertyToMethod(node, result) {
if (!node.name || !ts.isIdentifier(node.name)) return;
if (!node.type || !ts.isFunctionTypeNode(node.type)) return;
const methodName = node.name.text;
const functionType = node.type;
const params = this.extractParameters(functionType.parameters, methodName);
const returnType = this.getTypeStringWithMethodContext(functionType.type, methodName, "Response");
const isAsync = functionType.type ? this.isPromiseTypeNode(functionType.type) : this.isPromiseType(returnType);
const method = {
name: methodName,
params,
returnType,
isAsync,
isSync: !isAsync,
signature: this.buildMethodSignature(methodName, params, returnType),
jsDocComment: this.extractJSDocComment(node),
deprecated: this.isDeprecated(node)
};
result.methods.push(method);
}
/**
* Extracts constants from a type literal
*/
extractConstantsFromType(typeLiteral, result) {
for (const member of typeLiteral.members) if (ts.isPropertySignature(member) && member.name && ts.isIdentifier(member.name)) {
const constant = {
name: member.name.text,
type: this.getTypeString(member.type),
readonly: ts.canHaveModifiers(member) ? ts.getModifiers(member)?.some((mod) => mod.kind === ts.SyntaxKind.ReadonlyKeyword) ?? true : true,
jsDocComment: this.extractJSDocComment(member)
};
result.constants.push(constant);
}
}
/**
* Extracts supported events from array literal
*/
extractSupportedEvents(node, result) {
if (!result.events) result.events = [];
if (node.type && ts.isTupleTypeNode(node.type)) {
for (const element of node.type.elements) if (ts.isLiteralTypeNode(element) && ts.isStringLiteral(element.literal)) {
const eventName = element.literal.text;
const location = this.getNodeLocation(element);
result.events.push({
name: eventName,
jsDocComment: this.extractJSDocComment(node),
sourceFile: this.currentSourceFile?.fileName,
sourceLine: location.line,
sourceColumn: location.column
});
}
} else if (node.type && ts.isArrayTypeNode(node.type)) {
const elementType = node.type.elementType;
if (ts.isLiteralTypeNode(elementType) && ts.isStringLiteral(elementType.literal)) {
const location = this.getNodeLocation(elementType);
result.events.push({
name: elementType.literal.text,
jsDocComment: this.extractJSDocComment(node),
sourceFile: this.currentSourceFile?.fileName,
sourceLine: location.line,
sourceColumn: location.column
});
}
}
this.log(`Extracted ${result.events.length} supported events`);
}
/**
* Extracts parameters from method signature
*/
extractParameters(parameters, methodName) {
const params = [];
for (const param of parameters) if (ts.isIdentifier(param.name)) {
const typeString = methodName ? this.getTypeStringWithMethodContext(param.type, methodName, param.name.text) : this.getTypeString(param.type);
let functionType;
let isFunction = false;
if (param.type && ts.isFunctionTypeNode(param.type)) {
const fnParamTypes = param.type.parameters.map((p) => this.getTypeString(p.type));
const fnParamRestFlags = param.type.parameters.map((p) => !!p.dotDotDotToken);
functionType = {
paramTypes: fnParamTypes,
returnType: this.getTypeString(param.type.type),
paramRestFlags: fnParamRestFlags
};
isFunction = true;
}
params.push({
name: param.name.text,
type: typeString,
optional: !!param.questionToken,
defaultValue: param.initializer ? this.extractDefaultValue(param.initializer) : void 0,
jsDocComment: this.extractJSDocComment(param),
functionType,
isFunction
});
}
return params;
}
/**
* Extracts default value from expression
*/
extractDefaultValue(node) {
if (ts.isNumericLiteral(node)) return Number(node.text);
if (ts.isStringLiteral(node)) return node.text;
if (node.kind === ts.SyntaxKind.TrueKeyword) return true;
if (node.kind === ts.SyntaxKind.FalseKeyword) return false;
if (node.kind === ts.SyntaxKind.NullKeyword) return null;
if (node.kind === ts.SyntaxKind.UndefinedKeyword) return void 0;
}
/**
* Gets type string from type node
*/
getTypeString(typeNode) {
if (!typeNode) return "any";
return this.typeToString(typeNode);
}
/**
* Generates an interface name for nested object types
*/
generateNestedInterfaceName(parentName, propertyName) {
return `${parentName}${propertyName.charAt(0).toUpperCase() + propertyName.slice(1)}`;
}
/**
* Extracts nested object type as interface definition
*/
extractNestedInterface(typeLiteral, parentName, propertyName) {
const interfaceName = this.generateNestedInterfaceName(parentName, propertyName);
if (this.generatedInterfaces.has(interfaceName)) return interfaceName;
const properties = [];
for (const member of typeLiteral.members) if (ts.isPropertySignature(member) && member.name && ts.isIdentifier(member.name)) {
const propName = member.name.text;
let propType;
if (member.type && ts.isTypeLiteralNode(member.type)) propType = this.extractNestedInterface(member.type, interfaceName, propName);
else propType = this.getTypeString(member.type);
const optional = !!member.questionToken;
properties.push({
name: propName,
type: propType,
optional,
jsDocComment: this.extractJSDocComment(member)
});
}
const interfaceDef = {
name: interfaceName,
properties,
jsDocComment: `Auto-generated interface for ${parentName}.${propertyName}`
};
this.generatedInterfaces.set(interfaceName, interfaceDef);
return interfaceName;
}
/**
* Converts TypeScript type to string representation
*/
typeToString(type, parentContext) {
switch (type.kind) {
case ts.SyntaxKind.StringKeyword: return "string";
case ts.SyntaxKind.NumberKeyword: return "number";
case ts.SyntaxKind.BooleanKeyword: return "boolean";
case ts.SyntaxKind.VoidKeyword: return "void";
case ts.SyntaxKind.AnyKeyword: return "any";
case ts.SyntaxKind.UnknownKeyword: return "unknown";
case ts.SyntaxKind.NullKeyword: return "null";
case ts.SyntaxKind.UndefinedKeyword: return "undefined";
}
if (ts.isArrayTypeNode(type)) return `${this.typeToString(type.elementType)}[]`;
if (ts.isTypeReferenceNode(type) && ts.isIdentifier(type.typeName)) {
const typeName = type.typeName.text;
if (typeName === "Promise" && type.typeArguments?.[0]) return `Promise<${this.typeToString(type.typeArguments[0])}>`;
return typeName;
}
if (ts.isLiteralTypeNode(type)) {
if (ts.isStringLiteral(type.literal)) return `"${type.literal.text}"`;
if (ts.isNumericLiteral(type.literal)) return type.literal.text;
if (type.literal.kind === ts.SyntaxKind.TrueKeyword) return "true";
if (type.literal.kind === ts.SyntaxKind.FalseKeyword) return "false";
}
if (ts.isUnionTypeNode(type)) return type.types.map((t) => this.typeToString(t)).join(" | ");
if (ts.isTypeLiteralNode(type)) {
if (parentContext) return this.extractNestedInterface(type, parentContext.parentName, parentContext.propertyName);
return `{${type.members.map((member) => {
if (ts.isPropertySignature(member) && member.name && ts.isIdentifier(member.name)) {
const key = member.name.text;
const valueType = member.type ? this.typeToString(member.type) : "any";
const optional = member.questionToken ? "?" : "";
return `${ts.canHaveModifiers(member) && ts.getModifiers(member)?.some((mod) => mod.kind === ts.SyntaxKind.ReadonlyKeyword) ? "readonly " : ""}${key}${optional}: ${valueType}`;
}
return "";
}).filter(Boolean).join("; ")}}`;
}
return type.getText?.() || "any";
}
/**
* Gets type string with method context for generating anonymous interfaces
* Handles anonymous object types in method parameters and return types
*/
getTypeStringWithMethodContext(typeNode, methodName, paramOrReturnName) {
if (!typeNode) return "any";
const capitalizedMethod = methodName.charAt(0).toUpperCase() + methodName.slice(1);
if (ts.isTypeLiteralNode(typeNode)) return this.extractNestedInterface(typeNode, capitalizedMethod, paramOrReturnName);
if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName) && typeNode.typeName.text === "Promise" && typeNode.typeArguments?.[0]) {
const innerType = typeNode.typeArguments[0];
if (ts.isTypeLiteralNode(innerType)) return `Promise<${this.extractNestedInterface(innerType, capitalizedMethod, paramOrReturnName)}>`;
}
return this.typeToString(typeNode);
}
/**
* Checks if a type is a Promise type (string-based for backward compatibility)
* For new code, prefer using ts-morph type analysis directly
*/
isPromiseType(typeString) {
return typeString.startsWith("Promise<");
}
/**
* Checks if a type node is a Promise using semantic analysis
*/
isPromiseTypeNode(typeNode) {
if (ts.isTypeReferenceNode(typeNode) && ts.isIdentifier(typeNode.typeName)) return typeNode.typeName.text === "Promise";
return false;
}
/**
* Builds method signature string
*/
buildMethodSignature(name, params, returnType) {
return `${name}(${params.map((param) => {
const optional = param.optional ? "?" : "";
return `${param.name}${optional}: ${param.type}`;
}).join(", ")}): ${returnType}`;
}
/**
* Extracts JSDoc comment from node
* Cleans up asterisk prefixes from each line
*/
extractJSDocComment(node) {
const fullText = node.getSourceFile().getFullText();
const nodeStart = node.getFullStart();
const nodeEnd = node.getStart();
const jsDocMatch = fullText.substring(nodeStart, nodeEnd).match(/\/\*\*([\s\S]*?)\*\//);
if (jsDocMatch?.[1]) return jsDocMatch[1].split("\n").map((line) => line.replace(/^\s*\*\s?/, "")).filter((line, index, arr) => {
if (index === 0 || index === arr.length - 1) return line.trim().length > 0;
return true;
}).join("\n").trim();
}
/**
* Checks if node is marked as deprecated
*/
isDeprecated(node) {
return this.extractJSDocComment(node)?.includes("@deprecated") || false;
}
/**
* Infers type from value expression
*/
inferTypeFromValue(node) {
if (ts.isStringLiteral(node)) return "string";
if (ts.isNumericLiteral(node)) return "number";
if (node.kind === ts.SyntaxKind.TrueKeyword || node.kind === ts.SyntaxKind.FalseKeyword) return "boolean";
if (ts.isArrayLiteralExpression(node)) return "any[]";
if (ts.isObjectLiteralExpression(node)) return "object";
return "any";
}
/**
* Extracts literal value from expression
*/
extractLiteralValue(node) {
if (ts.isStringLiteral(node)) return node.text;
if (ts.isNumericLiteral(node)) return Number(node.text);
if (node.kind === ts.SyntaxKind.TrueKeyword) return true;
if (node.kind === ts.SyntaxKind.FalseKeyword) return false;
if (node.kind === ts.SyntaxKind.NullKeyword) return null;
}
/**
* Validates parsed module structure
*/
validateParsedModule(parsed) {
if (!parsed.moduleName) throw new BrickCodegenError("Module name not found in spec", this.createErrorContext(), void 0);
if (parsed.methods.length === 0) throw new BrickCodegenError("No methods found in module spec", this.createErrorContext(), void 0);
const methodNames = /* @__PURE__ */ new Set();
for (const method of parsed.methods) {
if (methodNames.has(method.name)) throw new BrickCodegenError(`Duplicate method name: ${method.name}`, this.createErrorContext(void 0, method.name), void 0);
methodNames.add(method.name);
if (!this.isValidMethodName(method.name)) throw new BrickCodegenError(`Invalid method name: ${method.name}. Method names must start with a letter and contain only letters, numbers, and underscores.`, this.createErrorContext(void 0, method.name), void 0);
}
const constantNames = /* @__PURE__ */ new Set();
for (const constant of parsed.constants) {
if (constantNames.has(constant.name)) throw new BrickCodegenError(`Duplicate constant name: ${constant.name}`, this.createErrorContext(void 0, constant.name), void 0);
constantNames.add(constant.name);
}
if (parsed.events && parsed.events.length > 0) {
const eventNames = /* @__PURE__ */ new Set();
for (const event of parsed.events) {
if (eventNames.has(event.name)) throw new BrickCodegenError(`Duplicate event name: ${event.name}`, this.createErrorContext(void 0, event.name), void 0);
eventNames.add(event.name);
if (!this.isValidEventName(event.name)) throw new BrickCodegenError(`Invalid event name: ${event.name}. Event names must start with a letter and contain only letters and numbers (camelCase preferred).`, this.createErrorContext(void 0, event.name), void 0);
}
}
}
/**
* Validates method name conventions
*/
isValidMethodName(name) {
return /^[a-zA-Z][a-zA-Z0-9_]*$/.test(name);
}
/**
* Validates event name conventions
*/
isValidEventName(name) {
return /^[a-zA-Z][a-zA-Z0-9]*$/.test(name);
}
/**
* Validates naming conventions
*/
validateNamingConventions(parsed) {
const warnings = [];
if (!this.isCapitalCase(parsed.moduleName)) warnings.push(`Module name '${parsed.moduleName}' should be in PascalCase`);
for (const method of parsed.methods) {
if (!this.isCamelCase(method.name)) warnings.push(`Method '${method.name}' should be in camelCase`);
if (method.isAsync && !method.name.startsWith("get") && !method.name.includes("async")) warnings.push(`Async method '${method.name}' should indicate async nature in name`);
}
for (const constant of parsed.constants) if (!this.isConstantCase(constant.name)) warnings.push(`Constant '${constant.name}' should be in CONSTANT_CASE`);
return warnings;
}
/**
* Validates types in the parsed module
*/
async validateTypes(parsed) {
const errors = [];
for (const method of parsed.methods) {
if (!this.isValidType(method.returnType)) errors.push(`Invalid return type '${method.returnType}' in method '${method.name}'`);
for (const param of method.params) if (!this.isValidType(param.type)) errors.push(`Invalid parameter type '${param.type}' for '${param.name}' in method '${method.name}'`);
}
return errors;
}
/**
* Checks if type is valid
*/
isValidType(type) {
return [
"string",
"number",
"boolean",
"void",
"any",
"unknown",
"null",
"undefined",
"object",
"Array",
"Promise"
].some((validType) => type.includes(validType) || type.match(/^[A-Z][a-zA-Z0-9]*$/));
}
isCapitalCase(str) {
return /^[A-Z][a-zA-Z0-9]*$/.test(str);
}
isCamelCase(str) {
return /^[a-z][a-zA-Z0-9]*$/.test(str);
}
isConstantCase(str) {
return /^[A-Z][A-Z0-9_]*$/.test(str);
}
/**
* Extracts interface definition from TypeScript AST
*/
extractInterfaceDefinition(node) {
const name = node.name.text;
const properties = [];
for (const member of node.members) if (ts.isPropertySignature(member) && member.name && ts.isIdentifier(member.name)) {
const propName = member.name.text;
let propType;
if (member.type && ts.isTypeLiteralNode(member.type)) propType = this.extractNestedInterface(member.type, name, propName);
else propType = this.getTypeString(member.type);
const optional = !!member.questionToken;
properties.push({
name: propName,
type: propType,
optional,
jsDocComment: this.extractJSDocComment(member)
});
}
return {
name,
properties,
jsDocComment: this.extractJSDocComment(node)
};
}
capitalizeFirst(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
log(...args) {
if (this.config.debug) console.log(pc.gray("[parser]"), ...args);
}
};
//#endregion
//#region src/utils/type-helpers.ts
/**
* Shared type helper utilities for platform generators
*/
/**
* Returns true if the given TS type string represents an untyped object
* that should be treated as a generic dictionary/map on native bridges.
*
* Supported aliases:
* - '{}' (empty inline object)
* - 'AnyObject' (explicit alias used in specs to mean dictionary-like)
*
* Note: 'any' is NOT included here because it can represent any type
* (including arrays, primitives), not just objects/dictionaries.
*/
function isAnyObjectType(tsType) {
const t = String(tsType || "").trim();
return t === "{}" || t === "AnyObject";
}
/**
* Returns true if the given TS type string represents the Any type.
*
* The Any type can represent any value (primitives, arrays, objects, etc.)
* and should not be converted on native bridges - it's already in the correct format.
*
* Supported values:
* - 'Any' (TypeScript alias from brick-module)
* - 'any' (TypeScript built-in)
*/
function isAnyType(tsType) {
const t = String(tsType || "").trim();
return t === "Any" || t === "any";
}
//#endregion
//#region src/syntax/kotlin/kotlin-type-mapper.ts
/**
* Kotlin type mapper - Functional implementation with dependency injection
* Maps TypeScript types to Kotlin types with support for different contexts
*/
/**
* Creates a Kotlin type mapper with the given options
*/
function createKotlinTypeMapper(options = {}) {
const { moduleName, useBridgeTypes = false, useNullableTypes = true } = options;
/**
* Maps TypeScript types to Kotlin types (for data classes)
*/
function mapTypeScriptToKotlin(tsType) {
if (tsType.startsWith("Promise<") && tsType.endsWith(">")) return mapTypeScriptToKotlin(tsType.slice(8, -1));
switch (tsType) {
case "string": return "String";
case "number": return "Double";
case "boolean": return "Boolean";
case "void": return "Unit";
case "Any": return "Any";
case "any": return "Any";
default:
if (tsType.includes("[]")) return `List<${mapTypeScriptToKotlin(tsType.replace("[]", ""))}>`;
if (tsType.startsWith("{") && tsType.endsWith("}")) {
console.warn(`[Android] Object literal type not converted to interface: ${tsType}`);
return "Any";
}
return tsType;
}
}
/**
* Enhanced Kotlin type mapping that recognizes interface types
*/
function mapTypeScriptToKotlinWithInterfaces(type) {
if (type.startsWith("Promise<") && type.endsWith(">")) return mapTypeScriptToKotlinWithInterfaces(type.slice(8, -1));
if (type === "Any" || type === "any") return "Any";
if (isAnyObjectType(type)) return "WritableMap";
const basicMapping = mapTypeScriptToKotlin(type);
if (basicMapping !== "Any") return basicMapping;
if (isPascalCase(type)) return type;
return "Any";
}
/**
* Maps TypeScript types to React Native bridge types for Kotlin
*/
function mapTypeScriptToReactNativeKotlin(tsType, isReturnType = false, isOptional = false) {
if (tsType.startsWith("Promise<") && tsType.endsWith(">")) return "Unit";
if (tsType === "callback") return "Callback";
let kotlinType;
switch (tsType) {
case "string":
kotlinType = "String";
break;
case "number":
kotlinType = "Double";
break;
case "boolean":
kotlinType = "Boolean";
break;
case "void":
kotlinType = "Unit";
break;
case "Any":
kotlinType = "Any";
break;
case "any":
kotlinType = "Any";
break;
case "string[]":
kotlinType = isReturnType ? "WritableArray" : "ReadableArray";
break;
default: if (tsType.includes("[]")) kotlinType = isReturnType ? "WritableArray" : "ReadableArray";
else if (tsType.startsWith("{") && tsType.endsWith("}")) kotlinType = isReturnType ? "WritableMap" : "ReadableMap";
else kotlinType = isReturnType ? "WritableMap" : "ReadableMap";
}
if (isOptional && !isReturnType && kotlinType !== "Unit") kotlinType += "?";
return kotlinType;
}
/**
* Gets default Kotlin return value for a type
*/
function getDefaultKotlinReturnValue(tsType) {
switch (tsType) {
case "string": return "\"\"";
case "number": return "0.0";
case "boolean": return "false";
case "void": return "";
default:
if (tsType.includes("[]")) return "WritableNativeArray()";
if (tsType.startsWith("{") && tsType.endsWith("}")) return "WritableNativeMap()";
return "null";
}
}
/**
* Checks if a string matches PascalCase pattern (used for interface names)
*/
function isPascalCase(str) {
return /^[A-Z][a-zA-Z0-9]*$/.test(str);
}
/**
* Checks if a type is a primitive type
*/
function isPrimitiveType(tsType) {
let actualType = tsType;
if (tsType.startsWith("Promise<") && tsType.endsWith(">")) actualType = tsType.slice(8, -1);
return [
"string",
"number",
"boolean",
"void"
].includes(actualType);
}
return {
mapTypeScriptToKotlin,
mapTypeScriptToKotlinWithInterfaces,
mapTypeScriptToReactNativeKotlin,
getDefaultKotlinReturnValue,
isPascalCase,
isPrimitiveType
};
}
//#endregion
//#region src/syntax/kotlin/kotlin-code-emitter.ts
/**
* Creates a Kotlin code emitter with the given options
*/
function createKotlinCodeEmitter(options = {}) {
const { indentSize = 4, defaultPublic = true } = options;
function generateFileHeader() {
return `// This file is automatically generated by brick-codegen
// Do not edit manually - regenerate using: brick-codegen`;
}
function generateKotlinImports(imports) {
if (imports.length === 0) return "";
return imports.map((imp) => `import ${imp}`).join("\n");
}
function generateKotlinFile(packageName, imports, content, comment) {
return `${generateFileHeader()}
package ${packageName}
${generateKotlinImports(imports)}${comment ? `\n/**\n * ${comment}\n */\n` : ""}
${content}
`;
}
function indent(code, spaces = indentSize) {
const indentation = " ".repeat(spaces);
return code.split("\n").map((line) => line.trim() ? `${indentation}${line}` : line).join("\n");
}
function wrapInBraces(content, inline = false) {
if (inline) return `{ ${content} }`;
return `{\n${indent(content)}\n}`;
}
function generateKotlinDocComment(comment) {
return `// ${comment}`;
}
function generateKotlinMultilineDocComment(lines) {
return `/**\n${lines.map((line) => ` * ${line}`).join("\n")}\n */`;
}
function generateKotlinList(items) {
if (items.length === 0) return "emptyList()";
return `listOf(${items.join(", ")})`;
}
function generateMarkComment(title) {
return `// MARK: - ${title}`;
}
function capitalizeFirst(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
function toPascalCase(str) {
return capitalizeFirst(str);
}
function generateParameterList(params) {
if (params.length === 0) return "";
return params.map((param) => `${param.name}: ${param.type}`).join(", ");
}
function generateKotlinFunctionSignature(name, params, returnType, isSuspend = false, access = defaultPublic ? "public" : "internal", isOverride = false) {
const paramList = generateParameterList(params);
const suspendKeyword = isSuspend ? "suspend " : "";
const overrideKeyword = isOverride ? "override " : "";
return `${access !== "public" ? `${access} ` : ""}${overrideKeyword}${suspendKeyword}fun ${name}(${paramList})${returnType !== "Unit" ? `: ${returnType}` : ""}`;
}
function generateKotlinProperty(name, type, isVal = true, isPublic = defaultPublic, defaultValue) {
return `${isPublic ? "" : "private "}${isVal ? "val" : "var"} ${name}: ${type}${defaultValue ? ` = ${defaultValue}` : ""}`;
}
function generateKotlinDataClass(name, properties, isPublic = defaultPublic) {
return `${isPublic ? "" : "private "}data class ${name}(
${properties.map((prop) => {
return `${prop.comment ? `${generateKotlinMultilineDocComment(prop.comment.split("\n")).split("\n").map((line) => ` ${line}`).join("\n")}\n` : ""}${prop.serializedName ? ` @SerializedName("${prop.serializedName}")\n` : ""} val ${prop.name}: ${prop.type}`;
}).join(",\n")}
)`;
}
function generateKotlinInterface(name, methods, properties = [], superInterfaces = [], isPublic = defaultPublic) {
return `${isPublic ? "" : "private "}interface ${name}${superInterfaces.length > 0 ? ` : ${superInterfaces.join(", ")}` : ""} {
${properties.length > 0 ? `${properties.map((p) => ` ${p}`).join("\n")}\n\n` : ""}${methods.map((m) => ` ${m}`).join("\n\n")}
}`;
}
return {
generateFileHeader,
generateKotlinImports,
generateKotlinFile,
indent,
wrapInBraces,
generateKotlinDocComment,
generateKotlinMultilineDocComment,
generateKotlinList,
generateMarkComment,
capitalizeFirst,
toPascalCase,
generateParameterList,
generateKotlinFunctionSignature,
generateKotlinProperty,
generateKotlinDataClass,
generateKotlinInterface
};
}
//#endregion
//#region src/syntax/kotlin/kotlin-protocol-generator.ts
/**
* Creates a Kotlin protocol generator with injected dependencies
*/
function createKotlinProtocolGenerator(deps) {
const { typeMapper, codeEmitter } = deps;
/**
* Generates Kotlin interface for library mode (public API)
*/
function generateLibraryModuleInterface(module) {
const constants = module.constants.map((constant) => {
const kotlinType = typeMapper.mapTypeScriptToKotlinWithInterfaces(constant.type);
return `${constant.jsDocComment ? `${codeEmitter.generateKotlinMultilineDocComment(constant.jsDocComment.split("\n"))}\n` : ""}val ${constant.name}: ${kotlinType}`;
}).join("\n");
let needsReactBridgeMapImport = false;
let needsReactBridgeArrayImport = false;
const methods = module.methods.map((method) => {
const params = (method.params || []).map((param) => {
if (param.functionType) {
const argJoined = (param.functionType.paramTypes || []).map((t$1, idx) => {
const tt = t$1.trim();
if (tt.endsWith("[]")) {
const elem = tt.slice(0, -2).trim();
if (param.functionType?.paramRestFlags?.[idx] && elem === "any") return "Array<Any?>";
if (param.functionType?.paramRestFlags?.[idx] && isAnyObjectType(elem)) {
needsReactBridgeMapImport = true;
return "List<WritableMap>";
}
needsReactBridgeArrayImport = true;
return "WritableArray";
}
if (isAnyObjectType(tt)) {
needsReactBridgeMapImport = true;
return "WritableMap";
}
if (tt.startsWith("{")) {
needsReactBridgeMapImport = true;
return "WritableMap";
}
return typeMapper.mapTypeScriptToKotlinWithInterfaces(tt);
}).join(", ");
return `${param.name}: (${argJoined}) -> Unit`;
}
const t = (param.type || "").trim();
if (isAnyObjectType(t)) {
needsReactBridgeMapImport = true;
const optional$1 = param.optional ? "?" : "";
return `${param.name}: ReadableMap${optional$1}`;
}
const kotlinType = typeMapper.mapTypeScriptToKotlinWithInterfaces(t);
const optional = param.optional ? "?" : "";
return `${param.name}: ${kotlinType}${optional}`;
}).join(", ");
const returnType = typeMapper.mapTypeScriptToKotlinWithInterfaces(method.returnType);
if (returnType.includes("WritableMap") || returnType.includes("ReadableMap")) needsReactBridgeMapImport = true;
if (returnType.includes("WritableArray") || returnType.includes("ReadableArray")) needsReactBridgeArrayImport = true;
const suspendKeyword = method.isAsync ? "suspend " : "";
return `${method.jsDocComment ? `${codeEmitter.generateKotlinMultilineDocComment(method.jsDocComment.split("\n"))}\n` : ""}${suspendKeyword}fun ${method.name}(${params}): ${returnType}`;
}).join("\n");
const constantsSection = constants.length > 0 ? `
${codeEmitter.generateMarkComment("Module Constants")}
${codeEmitter.indent(constants)}
` : "";
const methodsSection = methods.length > 0 ? `
${codeEmitter.generateMarkComment("Module Methods")}
${codeEmitter.indent(methods)}
` : "";
const getConstantsOverride = constants.length > 0 ? `
// Override getConstants to provide module constants
override fun getConstants(): Map<String, Any> = mapOf(
${module.constants.map((c) => ` "${c.name}" to ${c.name}`).join(",\n")}
)` : "";
const androidPackage = module.sourceModule.brickConfig?.android?.package || "com.brickmodule.codegen";
const extraImports = [needsReactBridgeMapImport ? "import com.facebook.react.bridge.ReadableMap\nimport com.facebook.react.bridge.WritableMap" : "", needsReactBridgeArrayImport ? "import com.facebook.react.bridge.ReadableArray\nimport com.facebook.react.bridge.WritableArray" : ""].filter(Boolean).join("\n");
const imports = ["import com.brickmodule.BrickModuleBase"];
if (extraImports) imports.push(extraImports);
return `${codeEmitter.generateFileHeader()}
package ${androidPackage}
${imports.join("\n")}
/**
* Generated interface for ${module.moduleName} module
* Implement this interface to create a type-safe ${module.moduleName} module
*/
interface ${module.moduleName}Spec : BrickModuleBase {
companion object {
const val MODULE_NAME = "${module.moduleName}"
}
${constantsSection}${methodsSection}${getConstantsOverride}
}
`;
}
/**
* Generates event helper extension functions for Kotlin (similar to Swift protocol extensions)
*/
function generateKotlinEventHelpers(module) {
if (!module.events || module.events.length === 0) return "";
const helpers = module.events.map((e) => {
const cap = e.name.charAt(0).toUpperCase() + e.name.slice(1);
const eventKey = `on${cap}`;
return `/**
* Emit ${e.name} event to JavaScript
* @param payload Event payload data
*/
fun ${module.moduleName}Spec.emitOn${cap}(payload: Map<String, Any>) {
if (this is com.brickmodule.BrickModuleSpec) {
this.emitEvent("${eventKey}", payload)
}
}`;
}).join("\n\n");
return `
// Typed Event Emit Helpers for ${module.moduleName}
${helpers}
`;
}
return {
generateLibraryModuleInterface,
generateKotlinEventHelpers
};
}
//#endregion
//#region src/syntax/kotlin/kotlin-struct-generator.ts
/**
* Creates a Kotlin struct generator with injected dependencies
*/
function createKotlinStructGenerator(deps) {
const { typeMapper, codeEmitter } = deps;
/**
* Generates all Kotlin types for a module (library mode)
*/
function generateModuleTypes(module) {
const interfaceClasses = module.interfaceDefinitions.map((interfaceDef) => generateLibraryKotlinDataClass(interfaceDef)).join("\n\n");
const androidPackage = module.sourceModule?.brickConfig?.android?.package || module.sourceModule?.androidPackage || "com.brickmodule.codegen";
return codeEmitter.generateKotlinFile(androidPackage, ["com.google.gson.annotations.SerializedName"], `/**
* ${module.moduleName} module types for Brick framework
* Provides type-safe data classes with automatic serialization
*/
${interfaceClasses}`);
}
/**
* Generates Kotlin data class for library mode
*/
function generateLibraryKotlinDataClass(interfaceDef) {
const properties = interfaceDef.properties.map((prop) => {
const kotlinType = typeMapper.mapTypeScriptToKotlinWithInterfaces(prop.type);
const optional = prop.optional ? "?" : "";
const comment = prop.jsDocComment ? prop.jsDocComment : void 0;
return {
name: prop.name,
type: `${kotlinType}${optional}`,
comment,
serializedName: prop.name
};
});
return `${interfaceDef.jsDocComment ? `/**\n * ${interfaceDef.jsDocComment}\n */\n` : ""}${codeEmitter.generateKotlinDataClass(interfaceDef.name, properties, true)}`;
}
/**
* Generates Kotlin data class from TypeScript interface definition (AST-based)
*/
function generateKotlinDataClassFromInterface(interfaceDef) {
const properties = interfaceDef.properties.map((prop) => {
let kotlinType = typeMapper.mapTypeScriptToKotlin(prop.type);
if (prop.optional && !kotlinType.endsWith("?")) kotlinType += "?";
return {
name: prop.name,
type: kotlinType + (prop.optional ? " = null" : ""),
serializedName: prop.name
};
});
return codeEmitter.generateKotlinDataClass(interfaceDef.name, properties, true);
}
/**
* Generates a simple Kotlin data class from inline object type (fallback)
* Note: This is a fallback for inline objects. Most types should come from AST-based interfaces.
*/
function generateKotlinDataClass(className) {
console.warn(`[Android] Generating data class for inline object type: ${className}. Consider extracting as interface.`);
return codeEmitter.generateKotlinDataClass(className, [{
name: "data",
type: "Any",
serializedName: "data"
}], true);
}
return {
generateModuleTypes,
generateLibraryKotlinDataClass,
generateKotlinDataClassFromInterface,
generateKotlinDataClass
};
}
//#endregion
//#region src/syntax/kotlin/kotlin-method-generator.ts
/**
* Creates a Kotlin method generator with injected dependencies
*/
function createKotlinMethodGenerator(deps) {
const { typeMapper } = deps;
/**
* Helper function to generate the correct WritableMap put call for a value based on type
*/
function generatePutValueCall(returnType, valueName = "value") {
if (returnType.startsWith("Promise<") && returnType.endsWith(">")) return generatePutValueCall(returnType.slice(8, -1), valueName);
switch (returnType) {
case "string": return `putString("value", ${valueName})`;
case "number": return `putDouble("value", ${valueName})`;
case "boolean": return `putBoolean("value", ${valueName})`;
case "void": return `putNull("value")`;
case "any":
case "Any": return `convertAnyToMap(${valueName}, this)`;
default:
if (returnType.endsWith("[]")) return `putArray("value", ${valueName})`;
return `putMap("value", ${valueName})`;
}
}
/**
* Generates a Kotlin method for BrickModule (override for New Arch)
*/
function generateKotlinMethod(method, module) {
const methodName = `${module.moduleName}_${method.name}`;
const params = method.params.map((param) => {
const kotlinType = param.isFunction ? "Callback" : typeMapper.mapTypeScriptToReactNativeKotlin(param.type, false, param.optional);
return `${param.name}: ${kotlinType}`;
}).join(", ");
const promiseParam = method.isAsync ? params ? ", promise: Promise" : "promise: Promise" : "";
const returnType = method.isAsync ? "Unit" : "WritableMap";
const callParams = method.params.map((param) => param.name).join(", ");
const asyncParam = method.isAsync ? callParams ? ", promise" : "promise" : "";
const putValueCall = method.isAsync ? "" : generatePutValueCall(method.returnType);
return ` override fun ${methodName}(${params}${promiseParam}): ${returnType} {
${method.isAsync ? `try {
brickModuleImpl.${methodName}(${callParams}${asyncParam})
} catch (e: Exception) {
promise.reject("BRICK_ERROR", e.message, e)
}` : `return try {
val value = brickModuleImpl.${methodName}(${callParams}${asyncParam})
Arguments.createMap().apply {
putBoolean("~sync", true)
putBoolean("success", true)
${putValueCall}
}
} catch (e: Exception) {
Arguments.createMap().apply {
putBoolean("~sync", true)
putBoolean("success", false)
putMap("error", Arguments.createMap().apply {
putString("code", "EXECUTION_ERROR")
putString("message", e.message ?: "Unknown error")
})
}
}`}
}`;
}
/**
* Generates Kotlin implementation method with type conversion
*/
function generateKotlinImplMethod(method, module) {
const methodName = `${module.moduleName}_${method.name}`;
const params = method.params.map((param) => {
const kotlinType = param.isFunction ? "Callback" : typeMapper.mapTypeScriptToReactNativeKotlin(param.type, false, param.optional);
return `${param.name}: ${kotlinType}`;
}).join(", ");
return ` fun ${methodName}(${params}${method.isAsync ? params ? ", promise: Promise" : "promise: Promise" : ""}): ${method.isAsync ? "Unit" : typeMapper.mapTypeScriptToReactNativeKotlin(method.returnType, true)} {
try {
${generateMethodImplementation(method, module)}
} catch (e: Exception) {
${method.isAsync ? `promise.reject("BRICK_ERROR", e.message, e)` : `throw e`}
}
}`;
}
/**
* Generates constant implementation getter using type-safe casting
*/
function generateConstantImplGetter(constant) {
const interfaceName = `${constant.moduleName}Spec`;
return ` fun ${constant.moduleName}_${constant.name}(): ${typeMapper.mapTypeScriptToKotlin(constant.type)} {
try {
val activity = reactContext.currentActivity
?: return ${typeMapper.getDefaultKotlinReturnValue(constant.type)}
if (activity !is BrickModuleRegistrar) {
return ${typeMapper.getDefaultKotlinReturnValue(constant.type)}
}
val module = activity.getModuleRegistry().getModuleInstance("${constant.moduleName}")
?: return ${typeMapper.getDefaultKotlinReturnValue(constant.type)}
val typedModule = module as? ${interfaceName}
?: return ${typeMapper.getDefaultKotlinReturnValue(constant.type)}
return typedModule.${constant.name}
} catch (e: Exception) {
return ${typeMapper.getDefaultKotlinReturnValue(constant.type)}
}
}`;
}
/**
* Generates the method implementation including conversions
*/
function generateMethodImplementation(method, module) {
const paramConversions = generateParameterConversions(method, module);
const interfaceName = `${module.moduleName}Spec`;
const methodParams = method.params.map((param) => {
const bridgeType = typeMapper.mapTypeScriptToReactNativeKotlin(param.type, false, param.optional);
if (param.isFunction) {
const argCount = param.functionType?.paramTypes?.length || 0;
if (argCount === 0) return `{ ${param.name}.invoke() }`;
const argNames = Array.from({ length: argCount }, (_, i) => `v${i}`);
if (argCount === 1 && param.functionType?.paramRestFlags?.[0] === true) {
const t0 = (param.functionType?.paramTypes?.[0] || "").trim();
const elem0 = t0.endsWith("[]") ? t0.slice(0, -2).trim() : t0;
if (elem0 === "any") return `{ v0 -> ${param.name}.invoke(*v0) }`;
if (isAnyObjectType(elem0)) return `{ v0 -> ${param.name}.invoke(*v0.toTypedArray()) }`;
return `{ v0 -> ${param.name}.invoke(v0) }`;
}
const convertArg = (t, idx) => {
const tt = (t || "").trim();
if (tt === "string" || tt === "number" || tt === "boolean") return `v${idx}`;
if (tt.endsWith("[]")) return `convertToWritableArray(v${idx})`;
if (isAnyObjectType(tt)) return `v${idx}`;
if (tt === "WritableMap" || tt === "ReadableMap" || tt === "WritableArray" || tt === "ReadableArray") return `v${idx}`;
if (typeMapper.isPascalCase(tt)) return `convertToWritableMap(v${idx})`;
return `v${idx}`;
};
const convertedArgs = (param.functionType?.paramTypes || []).map((t, idx) => convertArg(t, idx));
return `{ ${argNames.join(", ")} -> ${param.name}.invoke(${convertedArgs.join(", ")}) }`;
}
if (bridgeType === "ReadableMap?" || bridgeType === "ReadableArray?" || bridgeType === "ReadableMap" || bridgeType === "ReadableArray") {
const tt = (param.type || "").trim();
if (isAnyObjectType(tt) || isAnyType(tt)) return param.name;
return `${param.name}Converted`;
}
return param.name;
}).join(", ");
const baseImplementation = `// Get the activity and module registry
val activity = reactContext.currentActivity
?: throw BrickModuleError.ModuleNotFound("No current activity")
if (activity !is BrickModuleRegistrar) {
throw BrickModuleError.ModuleNotFound("Current activity does not implement BrickModuleRegistrar")
}
val module = activity.getModuleRegistry().getModuleInstance("${module.moduleName}")
?: throw BrickModuleError.ModuleNotFound("Module '${module.moduleName}' not found")
// Cast to the expected interface
val typedModule = module as? ${interfaceName}
?: throw BrickModuleError.TypeMismatch("Module does not implement ${interfaceName}")
${paramConversions ? `${paramConversions}\n ` : ""}`;
if (method.isAsync) {
let returnConversion = "promise.resolve(result)";
let actualReturnType = method.returnType;
if (actualReturnType.startsWith("Promise<") && actualReturnType.endsWith(">")) actualReturnType = actualReturnType.slice(8, -1);
if (actualReturnType === "void") returnConversion = "promise.resolve(null)";
else if (isAnyType(actualReturnType)) returnConversion = "promise.resolve(convertAnyValue(result))";
else if (isAnyObjectType(actualReturnType)) returnConversion = "promise.resolve(result)";
else if (actualReturnType.startsWith("{") && actualReturnType.endsWith("}")) returnConversion = "promise.resolve(convertToWritableMap(result))";
else if (actualReturnType.endsWith("[]")) returnConversion = "promise.resolve(convertToWritableArray(result))";
else if (!typeMapper.isPrimitiveType(actualReturnType)) returnConversion = "promise.resolve(convertToWritableMap(result))";
return `${baseImplementation}scope.launch {
try {
val result = typedModule.${method.name}(${methodParams})
${returnConversion}
} catch (e: Exception) {
promise.reject("BRICK_ERROR", e.message, e)
}
}`;
} else {
let returnStatement = `return typedModule.${method.name}(${methodParams})`;
if (isAnyType(method.returnType)) returnStatement = `return typedModule.${method.name}(${methodParams})`;
else if (isAnyObjectType(method.returnType)) returnStatement = `return typedModule.${method.name}(${methodParams})`;
else if (method.returnType.startsWith("{") && method.returnType.endsWith("}")) returnStatement = `val result = typedModule.${method.name}(${methodParams})
return convertToWritableMap(result)`;
else if (method.returnType.endsWith("[]")) returnStatement = `val result = typedModule.${method.name}(${methodParams})
return convertToWritableArray(result)`;
else if (method.returnType !== "string" && method.returnType !== "number" && method.returnType !== "boolean" && method.returnType !== "void" && !method.returnType.includes("Promise")) returnStatement = `val result = typedModule.${method.name}(${methodParams})
return convertToWritableMap(result)`;
return `${baseImplementation}${returnStatement}`;
}
}
/**
* Generates parameter conversions for Kotlin methods with concrete types
*/
function generateParameterConversions(method, module) {
const conversions = [];
for (const param of method.params) {
if (param.isFunction) continue;
const bridgeType = typeMapper.mapTypeScriptToReactNativeKotlin(param.type, false, param.optional);
if (bridgeType === "ReadableMap" || bridgeType === "ReadableMap?") {
const t = (param.type || "").trim();
if (isAnyObjectType(t) || isAnyType(t)) continue;
const className = generateConcreteTypeName(module.moduleName, method.name, param.name, param.type);
conversions.push(`val ${param.name}Converted = convertFromReadableMap(${param.name}, ${className}::class.java)`);
} else if (bridgeType === "ReadableArray" || bridgeType === "ReadableArray?") {
const elementType = param.type.slice(0, -2);
if (elementType === "string") conversions.push(`val ${param.name}Converted = convertStringArrayFromReadableArray(${param.name})`);
else if (elementType === "number") conversions.push(`val ${param.name}Converted = convertDoubleArrayFromReadableArray(${param.name})`);
else {
const elementClassName = generateConcreteTypeName(module.moduleName, method.name, `${param.name}Element`, elementType);
conversions.push(`val ${param.name}Converted = convertFromReadableArray(${param.name}, ${elementClassName}::class.java)`);
}
}
}
return conversions.length > 0 ? conversions.join("\n ") : "";
}
/**
* Generates concrete type name for TypeScript type
*/
function generateConcreteTypeName(moduleName, methodName, paramName, tsType) {
if (!tsType.startsWith("{") && !tsType.endsWith("}") && !tsType.endsWith("[]")) return tsType;
return `${moduleName}${tsType.charAt(0).toUpperCase() + methodName.slice(1)}${paramName.charAt(0).toUpperCase() + paramName.slice(1)}`;
}
return {
generateKotlinMethod,
generateKotlinImplMethod,
generateConstantImplGetter
};
}
//#endregion
//#region src/syntax/kotlin/kotlin-bridge-generator.ts
/**
* Creates a Kotlin bridge generator with injected dependencies
*/
function createKotlinBridgeGenerator(deps) {
const { codeEmitter, methodGenerator } = deps;
/**
* Generates BrickModuleBase.kt interface
*/
function generateBrickModuleBase() {
return `${codeEmitter.generateFileHeader()}
package com.brickmodule.codegen
/**
* Base interface that all Brick modules must implement
* Contains only essential properties for module identification
*/
interface BrickModuleBase {
/// The name of the module (required for registration)
val moduleName: String
}
/**
* Error types for Brick modules
*/
open class BrickModuleError(message: String, val errorCode: String) : Exception(message) {
class TypeMismatch(message: String) : BrickModuleError("Type mismatch: $message", "TYPE_ERROR")
class ExecutionError(message: String) : BrickModuleError("Execution error: $message", "EXECUTION_ERROR")
class InvalidDefinition(message: String) : BrickModuleError("Invalid definition: $message", "DEFINITION_ERROR")
class MethodNotFound(message: String) : BrickModuleError("Method not found: $message", "METHOD_NOT_FOUND")
class ModuleNotFound(message: String) : BrickModuleError("Module not found: $message", "MODULE_NOT_FOUND")
}
`;
}
/**
* Generates event emitter routing method for BrickModule
*/
function generateEventEmitterMethod(allEvents) {
const eventsByModule = /* @__PURE__ */ new Map();
for (const event of allEvents) {
const events = eventsByModule.get(event.moduleName) || [];
events.push(event);
eventsByModule.set(event.moduleName, events);
}
const eventCases = [];
for (const [moduleName, events] of eventsByModule) for (const event of events) {
const eventKey = `on${event.name.charAt(0).toUpperCase() + event.name.slice(1)}`;
const emitMethodName = `emit${moduleName}_${eventKey}`;
eventCases.push(` "${moduleName}" to "${eventKey}" -> ${emitMethodName}(writablePayload)`);
}
return `
/**
* Routes event emissions from modules to auto-generated event emitter methods
* @param moduleName The name of the module emitting the event
* @param eventName The name of the event (e.g., "onCalculationCompleted")
* @param payload The event data as a Map
*/
fun emitEventForModule(moduleName: String, eventName: String, payload: Map<String, Any>) {
// Convert Map to WritableMap using Arguments
val writablePayload = convertMapToWritableMap(payload)
// Route to the appropriate auto-generated emit method
when (moduleName to eventName) {
${eventCases.join("\n")}
else -> {
// Event not registered - log warning
android.util.Log.w("BrickModule", "Unknown event: $moduleName.$eventName")
}
}
}
`;
}
/**
* Generates the main Android bridge file (BrickModule.kt)
*/
function generateAndroidBridge(allMethods, allConstants, allEvents) {
const methodImplementations = allMethods.map((method) => {
const module = {
name: method.moduleName,
moduleName: method.moduleName,
version: "",
specPath: "",
methods: [],
constants: [],
events: [],
interfaceDefinitions: [],
sourceModule: {
name: method.moduleName,
path: "",
specPath: "",
packageJsonPath: "",
version: "",
hasIosImplementation: false,
hasAndroidImplementation: false,
brickConfig: void 0
}
};
return methodGenerator.generateKotlinMethod(method, module);
}).join("\n\n");
const imports = `import com.facebook.fbreact.specs.NativeBrickModuleSpec
import com.brickmodule.codegen.BrickModuleImpl
import com.brickmodule.BrickModuleRegistry
import com.facebook.react.bridge.Callback
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.ReadableType
import com.facebook.react.bridge.WritableArray
import com.facebook.react.bridge.WritableMap
import com.facebook.react.bridge.WritableNativeArray
import com.facebook.react.bridge.WritableNativeMap
`;
const classComment = `/**
* Generated BrickModule for React Native
* Each module method is implemented as override function
*/`;
const classDeclaration = `class BrickModule(private val reactContext: ReactContext) :
NativeBrickModuleSpec(reactContext as ReactApplicationContext) {`;
const eventEmitterMethod = allEvents.length > 0 ? generateEventEmitterMethod(allEvents) : "";
return `${codeEmitter.generateFileHeader()}
package com.brickmodule.codegen
${imports}
${classComment}
${classDeclaration}
private val brickModuleImpl = BrickModuleImpl(reactContext, this)
companion object {
const val NAME = "BrickModule"
}
init {
// Initialize implementation
}
override fun getName(): String {
return NAME
}
${allConstants.length > 0 ? `override fun getTypedExportedConstants(): Map<String, Any?> {
val constants: MutableMap<String, Any?> = HashMap()
${generateNewArchConstants(allConstants)}
return constants
}` : ""}
override fun getRegisteredModules(): WritableArray {
return brickModuleImpl.getRegisteredModules()
}
${eventEmitterMethod}
// ========== HELPER FUNCTIONS ==========
/**
* Converts Any type to WritableMap dynamically based on runtime type
*/
private fun convertAnyToMap(value: Any?, map: WritableMap) {
when (value) {
null -> map.putNull("value")
is String -> map.putString("value", value)
is Boolean -> map.putBoolean("value", value)
is Int -> map.putInt("value", value)
is Double -> map.putDouble("value", value)
is Float -> map.putDouble("value", value.toDouble())
is Long -> map.putDouble("value", value.toDouble())
is WritableMap -> map.putMap("value", value)
is WritableArray -> map.putArray("value", value)
is ReadableMap -> map.putMap("value", value as WritableMap)
is ReadableArray -> map.putArray("value", value as WritableArray)
is Map<*, *> -> {
map.putMap("value", convertMapToWritableMap(value))
}
is List<*> -> {
map.putArray("value", convertListToWritableArray(value))
}
else -> map.putString("value", value.toString())
}
}
/**
* Recursively converts Map to WritableMap
*/
private fun convertMapToWritableMap(value: Map<*, *>): WritableMap {
val writable = Arguments.createMap()
value.forEach { (k, v) ->
val key = k.toString()
when (v) {
null -> writable.putNull(key)
is String -> writable.putString(key, v)
is Boolean -> writable.putBoolean(key, v)
is Int -> writable.putInt(key, v)
is Double -> writable.putDouble(key, v)
is Float -> writable.putDouble(key, v.toDouble())
is Long -> writable.putDouble(key, v.toDouble())
is Map<*, *> -> writable.putMap(key, convertMapToWritableMap(v))
is List<*> -> writable.putArray(key, convertListToWritableArray(v))
else -> writable.putString(key, v.toString())
}
}
return writable
}
/**
* Recursively converts List to WritableArray
*/
private fun convertListToWritableArray(value: List<*>): WritableArray {
val writable = Arguments.createArray()
value.forEach { item ->
when (item) {
null -> writable.pushNull()
is String -> writable.pushString(item)
is Boolean -> writable.pushBoolean(item)
is Int -> writable.pushInt(item)
is Double -> writable.pushDouble(item)
is Float -> writable.pushDouble(item.toDouble())
is Long -> writable.pushDouble(item.toDouble())
is Map<*, *> -> writable.pushMap(convertMapToWritableMap(item))
is List<*> -> writable.pushArray(convertListToWritableArray(item))
else -> writable.pushString(item.toString())
}
}
return writable
}
// ========== GENERATED METHODS ==========
${methodImplementations}
// No DeviceEventEmitter path; events handled via CodegenTypes.EventEmitter
}
`;
}
/**
* Generates setup code for all module event emitters
*/
function generateEventEmitterSetup(allEvents) {
if (allEvents.length === 0) return "";
return `
/**
* Sets up event emitter callbacks for all modules
* Called once during initialization to register event handlers
*/
private fun setupAllModuleEventEmitters() {
val activity = reactContext.currentActivity as? BrickModuleRegistrar ?: return
val registry = activity.getModuleRegistry()
${Array.from(new Set(allEvents.map((e) => e.moduleName))).map((moduleName) => `
// Setup event emitters for ${moduleName}
registry.getModuleInstance("${moduleName}")?.let { module ->
if (module is com.brickmodule.BrickModuleSpec) {
module.eventEmitterCallback = { eventName, payload ->
brickModule.emitEventForModule("${moduleName}", eventName, payload)
}
}
}`).join("\n")}
}`;
}
/**
* Generates Kotlin implementation file (BrickModuleImpl.kt)
*/
function generateKotlinImplementation(allMethods, allConstants, allEvents, libraryImports) {
const methodImplementations = allMethods.map((method) => {
const module = {
name: method.moduleName,
moduleName: method.moduleName,
version: "",
specPath: "",
methods: [],
constants: [],
events: [],
interfaceDefinitions: [],
sourceModule: {
name: method.moduleName,
path: "",
specPath: "",
packageJsonPath: "",
version: "",
hasIosImplementation: false,
hasAndroidImplementation: false,
brickConfig: void 0
}
};
return methodGenerator.generateKotlinImplMethod(method, module);
}).join("\n\n");
const constantGetters = allConstants.map((constant) => methodGenerator.generateConstantImplGetter(constant)).join("\n\n");
const conversionHelpers = generateConversionHelpers();
const implImports = `import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.Callback
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.bridge.ReadableMap
import com.facebook.react.bridge.WritableArray
import com.facebook.react.bridge.WritableMap
import com.facebook.react.bridge.WritableNativeArray
import com.facebook.react.bridge.WritableNativeMap
import com.facebook.react.bridge.Arguments
import com.brickmodule.BrickModuleRegistry
import com.brickmodule.BrickModuleRegistrar
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import java.util.HashMap
import java.util.ArrayList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
${libraryImports}`;
return `${codeEmitter.generateFileHeader()}
package com.brickmodule.codegen
${implImports}
class BrickModuleImpl(private val reactContext: ReactContext, private val brickModule: BrickModule) {
private val gson = Gson()
private val scope = CoroutineScope(Dispatchers.Main + SupervisorJob())
init {
// Setup event emitters for all modules during initialization
setupAllModuleEventEmitters()
}
${generateEventEmitterSetup(allEvents)}
// ========== GENERATED METHOD IMPLEMENTATIONS ==========
${methodImplementations}
// ========== CONSTANT GETTERS ==========
${constantGetters}
// ========== UTILITY METHODS ==========
fun getRegisteredModules(): WritableArray {
val activity = reactContext.currentActivity
?: throw BrickModuleError.ModuleNotFound("No current activity")
if (activity !is BrickModuleRegistrar) {
throw BrickModuleError.ModuleNotFound("Current activity does not implement BrickModuleRegistrar")
}
val moduleNames = activity.getModuleRegistry().getRegisteredModules()
val writableArray = Arguments.createArray()
moduleNames.forEach { moduleName: String ->
writableArray.pushString(moduleName)
}
return writableArray
}
${conversionHelpers}
}
`;
}
/**
* Generates constants for New Architecture (getTypedExportedConstants)
*/
function generateNewArchConstants(constants) {
if (constants.length === 0) return "";
return constants.map((constant) => {
return ` constants["${`${constant.moduleName}_${constant.name}`}"] = brickModuleImpl.${constant.moduleName}_${constant.name}()`;
}).join("\n");
}
/**
* Generates library imports for data types used in BrickModuleImpl
*/
function generateLibraryImports(modules) {
const imports = /* @__PURE__ */ new Set();
for (const module of modules) {
const androidPackage = !module.sourceModule.brickConfig ? "com.brickmodule.codegen" : module.sourceModule.brickConfig?.android?.package || "com.brickmodule";
imports.add(`import ${androidPackage}.${module.moduleName}Spec`);
for (const interfaceDef of module.interfaceDefinitions || []) imports.add(`import ${androidPackage}.${interfaceDef.name}`);
}
return Array.from(imports).join("\n");
}
/**
* Generates conversion helper methods
*/
function generateConversionHelpers() {
return `
// ========== CONVERSION HELPERS ==========
private fun <T> convertFromReadableMap(readableMap: ReadableMap, type: Class<T>): T {
// Convert ReadableMap to a proper HashMap to avoid LinkedHashMap issues
val map = convertReadableMapToHashMap(readableMap)
val jsonString = gson.toJson(map)
return gson.fromJson(jsonString, type)
}
private fun convertToWritableMap(obj: Any): WritableMap {
val jsonString = gson.toJson(obj)
val map = gson.fromJson(jsonString, object : TypeToken<Map<String, Any>>() {}.type) as Map<String, Any>
return Arguments.makeNativeMap(map)
}
private fun convertToWritableArray(obj: Any): WritableArray {
val jsonString = gson.toJson(obj)
val list = gson.fromJson(jsonString, object : TypeToken<List<Any>>() {}.type) as List<Any>
val writableArray = Arguments.createArray()
list.forEach { item ->
when (item) {
is String -> writableArray.pushString(item)
is Double -> writableArray.pushDouble(item)
is Boolean -> writableArray.pushBoolean(item)
is Int -> writableArray.pushInt(item)
is Map<*, *> -> writableArray.pushMap(Arguments.makeNativeMap(item as Map<String, Any>))
is List<*> -> writableArray.pushArray(Arguments.makeNativeArray(item as ArrayList<Any>))
else -> writableArray.pushString(item.toString())
}
}
return writableArray
}
/**
* Converts Any type value to appropriate React Native type dynamically
* Used for Promise<Any> return types
*/
private fun convertAnyValue(value: Any?): Any? {
return when (value) {
null -> null
is String, is Boolean, is Int, is Double, is Float, is Long -> value
is WritableMap, is WritableArray, is ReadableMap, is ReadableArray -> value
is Map<*, *> -> convertToWritableMap(value)
is List<*> -> convertToWritableArray(value)
else -> value
}
}
private fun <T> convertFromReadableArray(readableArray: ReadableArray, elementType: Class<T>): List<T> {
val list = convertReadableArrayToArrayList(readableArray)
val jsonString = gson.toJson(list)
val listType = TypeToken.getParameterized(java.util.List::class.java, elementType).type
return gson.fromJson(jsonString, listType)
}
private fun convertStringArrayFromReadableArray(readableArray: ReadableArray): List<String> {
val list = ArrayList<String>()
for (i in 0..(readableArray.size() - 1)) {
val str = readableArray.getString(i)
if (str != null) {
list.add(str)
}
}
return list
}
private fun convertDoubleArrayFromReadableArray(readableArray: ReadableArray): List<Double> {
val list = ArrayList<Double>()
for (i in 0..(readableArray.size() - 1)) {
list.add(readableArray.getDouble(i))
}
return list
}
private fun convertReadableMapToHashMap(readableMap: ReadableMap?): HashMap<String, Any?>? {
if (readableMap == null) return null
val map = HashMap<String, Any?>()
val keyIterator = readableMap.keySetIterator()
while (keyIterator.hasNextKey()) {
val key = keyIterator.nextKey()
val value = when (readableMap.getType(key)) {
com.facebook.react.bridge.ReadableType.Null -> null
com.facebook.react.bridge.ReadableType.Boolean -> readableMap.getBoolean(key)
com.facebook.react.bridge.ReadableType.Number -> readableMap.getDouble(key)
com.facebook.react.bridge.ReadableType.String -> readableMap.getString(key)
com.facebook.react.bridge.ReadableType.Map -> convertReadableMapToHashMap(readableMap.getMap(key))
com.facebook.react.bridge.ReadableType.Array -> convertReadableArrayToArrayList(readableMap.getArray(key))
}
map.put(key, value)
}
return map
}
private fun convertReadableArrayToArrayList(readableArray: ReadableArray?): ArrayList<Any?>? {
if (readableArray == null) return null
val list = ArrayList<Any?>()
for (i in 0..(readableArray.size() - 1)) {
val value = when (readableArray.getType(i)) {
com.facebook.react.bridge.ReadableType.Null -> null
com.facebook.react.bridge.ReadableType.Boolean -> readableArray.getBoolean(i)
com.facebook.react.bridge.ReadableType.Number -> readableArray.getDouble(i)
com.facebook.react.bridge.ReadableType.String -> readableArray.getString(i)
com.facebook.react.bridge.ReadableType.Map -> convertReadableMapToHashMap(readableArray.getMap(i))
com.facebook.react.bridge.ReadableType.Array -> convertReadableArrayToArrayList(readableArray.getArray(i))
}
list.add(value)
}
return list
}`;
}
/**
* Generate build.gradle
*/
function generateBuildGradle(brickModules) {
const brickModuleDependencies = brickModules.map((module) => ` if (project.rootProject.findProject(':${module.gradleProjectName}')) {
implementation project(':${module.gradleProjectName}')
}`).join("\n");
return `${codeEmitter.generateFileHeader()}
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
namespace "com.brickmodule.generated"
compileSdkVersion 33
defaultConfig {
minSdkVersion 21
targetSdkVersion 33
versionCode 1
versionName "1.0"
}
sourceSets {
main {
java {
srcDirs = ['src/main/java']
}
kotlin {
srcDirs = ['src/main/kotlin']
}
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17
}
kotlinOptions {
jvmTarget = '17'
}
}
dependencies {
api 'com.facebook.react:react-android:+'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.7.10"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4'
implementation 'com.google.code.gson:gson:2.8.9'
// Include brick-module for base interfaces
if (project.rootProject.findProject(':brick-module')) {
implementation project(':brick-module')
}
// Auto-detected brick modules
${brickModuleDependencies}
}
`;
}
return {
generateBrickModuleBase,
generateAndroidBridge,
generateKotlinImplementation,
generateLibraryImports,
generateBuildGradle
};
}
//#endregion
//#region src/syntax/kotlin/kotlin-generator-suite.ts
/**
* Creates a Kotlin generator suite with composed dependencies
*/
function createKotlinGeneratorSuite(options = {}) {
const { typeMappingOptions = {}, codeEmitterOptions = {} } = options;
const typeMapper = createKotlinTypeMapper(typeMappingOptions);
const codeEmitter = createKotlinCodeEmitter(codeEmitterOptions);
const protocolGenerator = createKotlinProtocolGenerator({
typeMapper,
codeEmitter
});
const structGenerator = createKotlinStructGenerator({
typeMapper,
codeEmitter
});
const methodGenerator = createKotlinMethodGenerator({ typeMapper });
const deps = {
typeMapper,
codeEmitter,
protocolGenerator,
structGenerator,
methodGenerator,
bridgeGenerator: createKotlinBridgeGenerator({
codeEmitter,
methodGenerator
})
};
/**
* Generate interface/spec for a module
*/
function generateInterface(module) {
return protocolGenerator.generateLibraryModuleInterface(module);
}
/**
* Generate data class types for a module
*/
function generateDataClasses(module) {
return structGenerator.generateModuleTypes(module);
}
/**
* Generate bridge implementation files
*/
function generateBridge(_modules) {
return {
bridgeModule: "// Use AndroidPlatform.generateFiles() for full bridge generation",
bridgeImplementation: "// Use AndroidPlatform.generateFiles() for full bridge generation",
buildGradle: "// Use AndroidPlatform.generateFiles() for full bridge generation"
};
}
/**
* Generate complete Kotlin implementation for a module
*/
function generateKotlinImplementation(_module) {
return "// Use AndroidPlatform.generateFiles() for full implementation";
}
/**
* Generate library mode interface (public API)
*/
function generateLibraryInterface(module) {
return protocolGenerator.generateLibraryModuleInterface(module);
}
/**
* Generate library mode data classes
*/
function generateLibraryDataClasses(module) {
return structGenerator.generateModuleTypes(module);
}
return {
deps,
generateInterface,
generateDataClasses,
generateBridge,
generateKotlinImplementation,
generateLibraryInterface,
generateLibraryDataClasses
};
}
//#endregion
//#region src/platforms/android-platform.ts
/**
* Android Platform Implementation
* Contains all Android bridge generation logic for React Native modules
*/
var AndroidPlatformGenerator = class {
config;
currentOutputDir = "";
scannedModules = [];
typeMapper;
bridgeGenerator;
constructor(config) {
this.config = config;
this.typeMapper = createKotlinTypeMapper();
const methodGenerator = createKotlinMethodGenerator({ typeMapper: this.typeMapper });
this.bridgeGenerator = createKotlinBridgeGenerator({
codeEmitter: createKotlinCodeEmitter(),
methodGenerator
});
}
getPlatformName() {
return "android";
}
supports(target) {
return target === "android" || target === "java" || target === "kotlin";
}
/**
* Main method - Generates Android bridge files
*/
async generateBridge(modules, scannedModules) {
if (scannedModules) this.scannedModules = scannedModules;
try {
const androidBrickDir = path.join(this.config.projectRoot, this.config.outputAndroid);
const kotlinOutputDir = path.join(androidBrickDir, "src", "main", "kotlin");
this.currentOutputDir = androidBrickDir;
const generatedFiles = [];
await fs.ensureDir(kotlinOutputDir);
try {
const bridgeFile = await this.generateAndroidBridge(modules, kotlinOutputDir);
generatedFiles.push(bridgeFile);
} catch (error) {
throw new Error(`Failed in generateAndroidBridge: ${error.message}`);
}
try {
const implFile = await this.generateKotlinImplementation(modules, kotlinOutputDir);
generatedFiles.push(implFile);
} catch (error) {
throw new Error(`Failed in generateKotlinImplementation: ${error.message}`);
}
try {
const dataClassFiles = await this.generateKotlinDataClasses(modules);
generatedFiles.push(...dataClassFiles);
} catch (error) {
throw new Error(`Failed in generateKotlinDataClasses: ${error.message}`);
}
try {
const localTypeFiles = await this.generateLocalModuleTypes(modules, androidBrickDir);
generatedFiles.push(...localTypeFiles);
} catch (error) {
throw new Error(`Failed in generateLocalModuleTypes: ${error.message}`);
}
try {
await this.generateBrickModuleBase(kotlinOutputDir);
generatedFiles.push(path.join(kotlinOutputDir, "BrickModuleBase.kt"));
} catch (error) {
throw new Error(`Failed in generateBrickModuleBase: ${error.message}`);
}
try {
const gradleFile = await this.generateBuildGradle();
generatedFiles.push(gradleFile);
} catch (error) {
throw new Error(`Failed in generateBuildGradle: ${error.message}`);
}
return generatedFiles;
} catch (error) {
throw new Error(`Failed to generate Android bridge: ${error.message}`);
}
}
isAvailable() {
return true;
}
/**
* Generates Kotlin data classes for complex types and interfaces
*/
async generateKotlinDataClasses(modules) {
const generatedFiles = [];
const allMethods = this.getAllMethods(modules);
const processedTypes = /* @__PURE__ */ new Set();
for (const module of modules) {
const dataClasses = [];
const interfaces = this.extractInterfaceDefinitions(module);
for (const interfaceDef of interfaces) if (!processedTypes.has(interfaceDef.name)) {
const dataClass = this.generateKotlinDataClassFromInterface(interfaceDef);
dataClasses.push(dataClass);
processedTypes.add(interfaceDef.name);
}
if (dataClasses.length > 0) {
const content = `// This file is automatically generated by brick-codegen
// Do not edit manually - regenerate using: brick-codegen
package com.brickmodule.codegen
import com.google.gson.annotations.SerializedName
${dataClasses.join("\n\n")}
`;
const outputPath = path.join(this.currentOutputDir, `${module.moduleName}Types.kt`);
await fs.writeFile(outputPath, content);
this.logGenerated(outputPath);
generatedFiles.push(outputPath);
}
}
for (const { method, module } of allMethods) {
const dataClasses = [];
for (const param of method.params) if (param.type.startsWith("{") && param.type.endsWith("}")) {
const className = `${module.moduleName}${this.capitalize(method.name)}${this.capitalize(param.name)}`;
if (!processedTypes.has(className)) {
const dataClass = this.generateKotlinDataClass(className);
dataClasses.push(dataClass);
processedTypes.add(className);
}
} else if (param.type.endsWith("[]")) {
const elementType = param.type.slice(0, -2);
if (elementType.startsWith("{") && elementType.endsWith("}")) {
const className = `${module.moduleName}${this.capitalize(method.name)}${this.capitalize(param.name)}Element`;
if (!processedTypes.has(className)) {
const dataClass = this.generateKotlinDataClass(className);
dataClasses.push(dataClass);
processedTypes.add(className);
}
}
}
let actualReturnType = method.returnType;
if (actualReturnType.startsWith("Promise<") && actualReturnType.endsWith(">")) actualReturnType = actualReturnType.slice(8, -1);
if (actualReturnType.startsWith("{") && actualReturnType.endsWith("}")) {
const className = `${module.moduleName}${this.capitalize(method.name)}Response`;
if (!processedTypes.has(className)) {
const dataClass = this.generateKotlinDataClass(className);
dataClasses.push(dataClass);
processedTypes.add(className);
}
}
if (dataClasses.length > 0) {
const content = `// This file is automatically generated by brick-codegen
// Do not edit manually - regenerate using: brick-codegen
package com.brickmodule.codegen
import com.google.gson.annotations.SerializedName
${dataClasses.join("\n\n")}
`;
const outputPath = path.join(this.currentOutputDir, `${module.moduleName}${this.capitalize(method.name)}Types.kt`);
await fs.writeFile(outputPath, content);
this.logGenerated(outputPath);
generatedFiles.push(outputPath);
}
}
return generatedFiles;
}
/**
* Generates a single Kotlin data class from TypeScript inline object type
* Note: This is a fallback for inline objects. Most types should come from AST-based interfaces.
*/
generateKotlinDataClass(className) {
console.warn(`[Android] Generating data class for inline object type: ${className}. Consider extracting as interface.`);
return `data class ${className}(
@SerializedName("data")
val data: Any
)`;
}
/**
* Gets available brick modules from scanned modules
* Uses the scanned modules from Scanner instead of re-scanning
*/
async detectBrickModules() {
const brickModules = [];
try {
for (const module of this.scannedModules) if (module.hasAndroidImplementation) {
const gradleProjectName = module.name.replace(/^@/, "").replace(/\//g, "_");
brickModules.push({
npmName: module.name,
gradleProjectName
});
}
} catch (error) {
console.warn(`[ANDROID] Warning: Could not process brick modules: ${error}`);
}
return brickModules;
}
/**
* Generate build.gradle
*/
async generateBuildGradle() {
const brickModules = await this.detectBrickModules();
const content = this.bridgeGenerator.generateBuildGradle(brickModules);
const outputPath = path.join(this.currentOutputDir, "build.gradle");
await fs.writeFile(outputPath, content);
this.logGenerated(outputPath);
return outputPath;
}
mapTypeScriptToKotlin(tsType) {
return this.typeMapper.mapTypeScriptToKotlin(tsType);
}
getAllMethods(modules) {
const methods = [];
if (!modules || !Array.isArray(modules)) return methods;
for (const module of modules) {
if (!module || !module.methods || !Array.isArray(module.methods)) continue;
for (const method of module.methods) methods.push({
method,
module
});
}
return methods;
}
getAllEvents(modules) {
const events = [];
if (!modules || !Array.isArray(modules)) return events;
for (const module of modules) {
if (!module || !module.events || !Array.isArray(module.events)) continue;
for (const event of module.events) events.push({
...event,
moduleName: module.moduleName
});
}
return events;
}
getAllConstants(modules) {
const constants = [];
if (!modules || !Array.isArray(modules)) return constants;
for (const module of modules) {
if (!module || !module.constants || !Array.isArray(module.constants)) continue;
for (const constant of module.constants) constants.push({
...constant,
moduleName: module.moduleName
});
}
return constants;
}
capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
logGenerated(filepath) {
const absolutePath = path.resolve(filepath);
console.log(`${pc.bold(pc.green("[Brick]"))} Generated: ${path.basename(absolutePath)}`);
}
/**
* Generates BrickModuleBase.kt interface
*/
async generateBrickModuleBase(outputDir) {
const content = this.bridgeGenerator.generateBrickModuleBase();
const outputPath = path.join(outputDir, "BrickModuleBase.kt");
await fs.writeFile(outputPath, content);
}
/**
* Generates the main Android bridge file (BrickModule.kt) - like iOS BrickModule.mm
*/
async generateAndroidBridge(modules, outputDir) {
const allMethods = (this.getAllMethods(modules) || []).map(({ method, module }) => ({
...method,
moduleName: module.moduleName
}));
const allConstants = this.getAllConstants(modules) || [];
const allEvents = this.getAllEvents(modules) || [];
const bridgeContent = this.bridgeGenerator.generateAndroidBridge(allMethods, allConstants, allEvents);
const outputPath = path.join(outputDir, "BrickModule.kt");
await fs.writeFile(outputPath, bridgeContent);
this.logGenerated(outputPath);
return outputPath;
}
/**
* Generates library imports for data types used in BrickModuleImpl
*/
generateLibraryImports(modules) {
return this.bridgeGenerator.generateLibraryImports(modules);
}
/**
* Generates Kotlin implementation file (BrickModuleImpl.kt) - like iOS BrickModuleImpl.swift
*/
async generateKotlinImplementation(modules, outputDir) {
const allMethods = this.getAllMethods(modules).map(({ method, module }) => ({
...method,
moduleName: module.moduleName
}));
const allConstants = this.getAllConstants(modules);
const allEvents = this.getAllEvents(modules);
const libraryImports = this.generateLibraryImports(modules);
const implContent = this.bridgeGenerator.generateKotlinImplementation(allMethods, allConstants, allEvents, libraryImports);
const outputPath = path.join(outputDir, "BrickModuleImpl.kt");
await fs.writeFile(outputPath, implContent);
this.logGenerated(outputPath);
return outputPath;
}
/**
* Extracts interface definitions from parsed module AST
*/
extractInterfaceDefinitions(module) {
const interfaces = [];
for (const interfaceDef of module.interfaceDefinitions) {
if (interfaceDef.name.includes("ModuleSpec") || interfaceDef.name.includes("Events")) continue;
const properties = interfaceDef.properties.map((prop) => ({
name: prop.name,
type: prop.type,
optional: prop.optional
}));
interfaces.push({
name: interfaceDef.name,
properties
});
}
return interfaces;
}
/**
* Generates Kotlin data class from TypeScript interface definition
*/
generateKotlinDataClassFromInterface(interfaceDef) {
return `data class ${interfaceDef.name}(
${interfaceDef.properties.map((prop) => {
let kotlinType = this.mapTypeScriptToKotlin(prop.type);
if (prop.optional && !kotlinType.endsWith("?")) kotlinType += "?";
const defaultValue = prop.optional ? " = null" : "";
return ` @SerializedName("${prop.name}")
val ${prop.name}: ${kotlinType}${defaultValue}`;
}).join(",\n")}
)`;
}
/**
* Generates local module types in .brick/src/main/java/com/brickmodule/codegen/ directory
*/
async generateLocalModuleTypes(modules, outputDir) {
const generatedFiles = [];
const typesDir = path.join(outputDir, "src", "main", "java", "com", "brickmodule", "codegen");
const localModules = modules.filter((m) => !m.sourceModule?.brickConfig);
if (localModules.length === 0) return generatedFiles;
await fs.ensureDir(typesDir);
for (const module of localModules) {
const originalModule = { ...module };
if (!module.sourceModule.brickConfig) module.sourceModule.brickConfig = { android: { package: "com.brickmodule.codegen" } };
const interfaceContent = await this.generateLibraryInterface(module);
const interfaceFile = path.join(typesDir, `${module.moduleName}Spec.kt`);
await fs.writeFile(interfaceFile, interfaceContent);
generatedFiles.push(interfaceFile);
this.logGenerated(interfaceFile);
if (module.interfaceDefinitions.length > 0) {
const typesContent = await this.generateLibraryTypes(module);
const typesFile = path.join(typesDir, `${module.moduleName}Types.kt`);
await fs.writeFile(typesFile, typesContent);
generatedFiles.push(typesFile);
this.logGenerated(typesFile);
}
module.sourceModule.brickConfig = originalModule.sourceModule.brickConfig;
}
return generatedFiles;
}
/**
* Generates Kotlin types for library mode (public API)
*/
async generateLibraryTypes(module) {
return createKotlinGeneratorSuite().generateDataClasses(module);
}
/**
* Generates Kotlin interface for library mode (public API)
*/
async generateLibraryInterface(module) {
const suite = createKotlinGeneratorSuite();
return suite.generateInterface(module) + suite.deps.protocolGenerator.generateKotlinEventHelpers(module);
}
};
//#endregion
//#region src/syntax/swift/swift-type-mapper.ts
/**
* Creates a Swift type mapper with the given options
*/
function createSwiftTypeMapper(options = {}) {
const { moduleName, interfaces = [], useObjCTypes = false, useCodableTypes = false } = options;
/**
* Capitalizes first character of a string
*/
function capitalizeFirst(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
/**
* Maps TypeScript types to Swift types (for Codable structs)
*/
function mapTypeScriptToSwift(tsType) {
if (tsType.startsWith("Promise<") && tsType.endsWith(">")) return mapTypeScriptToSwift(tsType.slice(8, -1));
switch (tsType) {
case "string": return "String";
case "number": return "Double";
case "boolean": return "Bool";
case "void": return "Void";
case "Any": return "Any";
case "any": return "Any";
default:
if (tsType.includes("[]")) return `[${mapTypeScriptToSwift(tsType.replace("[]", ""))}]`;
if (tsType.startsWith("{") && tsType.endsWith("}")) return "[String: String]";
if (tsType.match(/^[A-Z][a-zA-Z0-9]*$/)) {
if (moduleName) return `${capitalizeFirst(moduleName)}${tsType}`;
return tsType;
}
return "[String: String]";
}
}
/**
* Maps TypeScript types to Swift types for @objc compatibility
* Uses [String: Any] for complex types instead of Swift structs
*/
function mapTypeScriptToSwiftForObjC(tsType) {
if (tsType === "callback") return "RCTResponseSenderBlock";
if (tsType.startsWith("Promise<") && tsType.endsWith(">")) return mapTypeScriptToSwiftForObjC(tsType.slice(8, -1));
switch (tsType) {
case "string": return "String";
case "number": return "Double";
case "boolean": return "Bool";
case "void": return "Void";
case "Any": return "Any";
case "any": return "Any";
case "AnyObject": return "[String: Any]";
default:
if (tsType.includes("[]")) {
const elementType = tsType.replace("[]", "");
if (elementType.startsWith("{") && elementType.endsWith("}") || elementType.match(/^[A-Z][a-zA-Z0-9]*$/)) return `[Any]`;
else return `[${mapTypeScriptToSwiftForObjC(elementType)}]`;
}
if (tsType.startsWith("{") && tsType.endsWith("}") || tsType.match(/^[A-Z][a-zA-Z0-9]*$/) && tsType !== "AnyObject") return "[String: Any]";
return "[String: Any]";
}
}
/**
* Enhanced Swift type mapping that recognizes interface types
*/
function mapTypeScriptToSwiftWithInterfaces(type) {
if (type.startsWith("Promise<") && type.endsWith(">")) return mapTypeScriptToSwiftWithInterfaces(type.slice(8, -1));
if (type === "Any" || type === "any") return "Any";
if (isAnyObjectType(type)) return "[String: Any]";
if (new Set(interfaces.map((i) => i.name)).has(type)) {
if (moduleName) return `${capitalizeFirst(moduleName)}${type}`;
return type;
}
const basicMapping = mapTypeScriptToSwift(type);
if (basicMapping !== "[String: String]") return basicMapping;
if (type.match(/^[A-Z][a-zA-Z0-9]*$/)) {
if (moduleName) return `${capitalizeFirst(moduleName)}${type}`;
return type;
}
return "[String: String]";
}
/**
* Maps TypeScript types to Objective-C types
*/
function mapTypeScriptToObjC(tsType, isReturnType = false, isAsync = false) {
if (tsType.startsWith("Promise<") && tsType.endsWith(">")) return "void";
if (tsType === "callback") return "RCTResponseSenderBlock";
switch (tsType) {
case "string": return "NSString *";
case "number":
if (isReturnType && !isAsync) return "NSNumber *";
return "double";
case "boolean":
if (isReturnType && !isAsync) return "NSNumber *";
return "BOOL";
case "void": return "void";
case "any": return "id";
default:
if (tsType.includes("[]")) return "NSArray *";
if (tsType.startsWith("{") && tsType.endsWith("}")) return isReturnType ? "id" : "id";
return "id";
}
}
/**
* Maps TS types for callback argument positions to Swift types
* Use [String: Any] for inline objects to avoid Codable constraints in closures
*/
function mapTsToSwiftForCallbackArg(tsType) {
const t = tsType.trim();
if (t === "string") return "String";
if (t === "number") return "Double";
if (t === "boolean") return "Bool";
if (t === "void") return "Void";
if (t.endsWith("[]")) return `[${mapTsToSwiftForCallbackArg(t.slice(0, -2))}]`;
if (t.startsWith("{") && t.endsWith("}")) return "[String: Any]";
if (new Set(interfaces.map((i) => i.name)).has(t)) {
if (moduleName) return `${capitalizeFirst(moduleName)}${t}`;
return t;
}
return "[String: Any]";
}
/**
* Gets Swift default value for a type
*/
function getSwiftDefaultValue(swiftType) {
switch (swiftType) {
case "Double": return "0.0";
case "Int": return "0";
case "Float": return "0.0";
case "Bool": return "false";
case "String": return "\"\"";
case "[String: Any]": return "[String: Any]()";
case "[Any]": return "[Any]()";
default:
if (swiftType.startsWith("[")) return "[]";
return "[String: Any]()";
}
}
/**
* Gets Objective-C default return value for a type
*/
function getDefaultReturnValue(tsType) {
switch (tsType) {
case "string": return "@\"\"";
case "number": return "@(0)";
case "boolean": return "@(NO)";
case "void": return "";
default:
if (tsType.includes("[]")) return "@[]";
return "nil";
}
}
return {
mapTypeScriptToSwift,
mapTypeScriptToSwiftForObjC,
mapTypeScriptToSwiftWithInterfaces,
mapTypeScriptToObjC,
mapTsToSwiftForCallbackArg,
getSwiftDefaultValue,
getDefaultReturnValue,
capitalizeFirst
};
}
//#endregion
//#region src/syntax/swift/swift-code-emitter.ts
/**
* Creates a Swift code emitter with the given options
*/
function createSwiftCodeEmitter(options = {}) {
const { indentSize = 4, defaultPublic = true } = options;
function generateFileHeader() {
return `// This file is automatically generated by brick-codegen
// Do not edit manually - regenerate using: brick-codegen`;
}
function generateSwiftImports(imports) {
if (imports.length === 0) return "";
return imports.map((imp) => `import ${imp}`).join("\n");
}
function generateObjCImports(imports) {
if (imports.length === 0) return "";
return imports.map((imp) => `#import ${imp}`).join("\n");
}
function generateSwiftFile(imports, content, moduleComment) {
return `${generateFileHeader()}
${generateSwiftImports(imports)}${moduleComment ? `\n/// ${moduleComment}` : ""}
${content}
`;
}
function generateObjCFile(imports, content) {
return `${generateFileHeader()}
${generateObjCImports(imports)}
${content}
`;
}
function indent(code, spaces = indentSize) {
const indentation = " ".repeat(spaces);
return code.split("\n").map((line) => line.trim() ? `${indentation}${line}` : line).join("\n");
}
function wrapInBraces(content, inline = false) {
if (inline) return `{ ${content} }`;
return `{\n${indent(content)}\n}`;
}
function generateSwiftDocComment(comment) {
return `/// ${comment}`;
}
function generateSwiftMultilineDocComment(lines) {
return `/**\n${lines.map((line) => ` * ${line}`).join("\n")}\n */`;
}
function generateSwiftArray(items) {
if (items.length === 0) return "[]";
return `[${items.join(", ")}]`;
}
function generateObjCArray(items) {
if (items.length === 0) return "@[]";
return `@[${items.join(", ")}]`;
}
function generateMarkComment(title) {
return `// MARK: - ${title}`;
}
function capitalizeFirst(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
function toPascalCase(str) {
return capitalizeFirst(str);
}
function generateParameterList(params, includeFirst = true) {
if (params.length === 0) return "";
return params.map((param, index) => {
if (index === 0 && includeFirst) return `${param.name}: ${param.type}`;
return `${param.name}: ${param.type}`;
}).join(", ");
}
function generateSwiftFunctionSignature(name, params, returnType, isAsync = false, throws = false, access = defaultPublic ? "public" : "internal") {
return `${access} func ${name}(${generateParameterList(params)})${isAsync ? " async" : ""}${throws ? " throws" : ""}${returnType !== "Void" ? ` -> ${returnType}` : ""}`;
}
function generateObjCMethodSignature(name, params, returnType) {
if (params.length === 0) return `- (${returnType})${name}`;
const firstParam = params[0];
const restParams = params.slice(1);
const firstPart = `- (${returnType})${name}:(${firstParam.type})${firstParam.name}`;
const restParts = restParams.map((param) => `${param.label || param.name}:(${param.type})${param.name}`).join(" ");
return restParts ? `${firstPart} ${restParts}` : firstPart;
}
function wrapObjCLiteral(value) {
if (typeof value === "string") return `@"${value}"`;
if (typeof value === "boolean") return value ? "@YES" : "@NO";
return `@(${value})`;
}
function generateSwiftProperty(name, type, isLet = true, isPublic = defaultPublic, defaultValue) {
return `${isPublic ? "public " : ""}${isLet ? "let" : "var"} ${name}: ${type}${defaultValue ? ` = ${defaultValue}` : ""}`;
}
function generateSwiftStruct(name, properties, protocols = ["Codable"], isPublic = defaultPublic) {
return `${isPublic ? "public " : ""}struct ${name}${protocols.length > 0 ? `: ${protocols.join(", ")}` : ""} {
${properties.map((prop) => {
return `${prop.comment ? ` ${generateSwiftDocComment(prop.comment)}\n` : ""} ${generateSwiftProperty(prop.name, prop.type, true, true)}`;
}).join("\n")}
}`;
}
function generateSwiftProtocol(name, methods, properties = [], isPublic = defaultPublic) {
return `${isPublic ? "public " : ""}protocol ${name} {
${properties.length > 0 ? `${properties.map((p) => ` ${p}`).join("\n")}\n\n` : ""}${methods.map((m) => ` ${m}`).join("\n\n")}
}`;
}
function generateSwiftExtension(typeName, content, protocols = []) {
return `extension ${typeName}${protocols.length > 0 ? `: ${protocols.join(", ")}` : ""} {
${indent(content)}
}`;
}
function generateSwiftTypeAlias(alias, target, isPublic = defaultPublic, comment) {
const access = isPublic ? "public " : "";
return `${comment ? `${generateSwiftDocComment(comment)}\n` : ""}${access}typealias ${alias} = ${target}`;
}
return {
generateFileHeader,
generateSwiftImports,
generateObjCImports,
generateSwiftFile,
generateObjCFile,
indent,
wrapInBraces,
generateSwiftDocComment,
generateSwiftMultilineDocComment,
generateSwiftArray,
generateObjCArray,
generateMarkComment,
capitalizeFirst,
toPascalCase,
generateParameterList,
generateSwiftFunctionSignature,
generateObjCMethodSignature,
wrapObjCLiteral,
generateSwiftProperty,
generateSwiftStruct,
generateSwiftProtocol,
generateSwiftExtension,
generateSwiftTypeAlias
};
}
//#endregion
//#region src/syntax/swift/swift-protocol-generator.ts
/**
* Creates a Swift protocol generator with injected dependencies
*/
function createSwiftProtocolGenerator(deps) {
const { typeMapper, codeEmitter } = deps;
/**
* Generates a complete Swift protocol for a module
*/
function generateModuleProtocol(module, includeImports = true) {
const protocolMethods = module.methods.map((method) => generateProtocolMethod(method, module.moduleName, module.interfaceDefinitions)).join("\n\n");
const constantProperties = module.constants.length > 0 ? module.constants.map((constant) => generateProtocolConstant(constant, module.moduleName)).join("\n\n") : "";
const constantsSection = module.constants.length > 0 ? `\n ${codeEmitter.generateMarkComment("Module Constants")}\n \n${codeEmitter.indent(constantProperties)}\n` : "";
const protocolContent = `/**
* Generated protocol for ${module.moduleName} module
* Implement this protocol to create a type-safe ${module.moduleName} module
*/
public protocol ${module.moduleName}Spec {${constantsSection}
${codeEmitter.generateMarkComment("Module Methods")}
${codeEmitter.indent(protocolMethods)}
}`;
if (!includeImports) return protocolContent;
return codeEmitter.generateSwiftFile(["Foundation", "BrickModule"], protocolContent);
}
/**
* Generates a protocol method signature
*/
function generateProtocolMethod(method, moduleName, interfaces = []) {
const params = method.params.map((param) => generateProtocolParameter(param, moduleName, interfaces)).join(", ");
const returnType = typeMapper.mapTypeScriptToSwiftWithInterfaces(method.returnType);
const asyncKeyword = method.isAsync ? "async " : "";
const comment = method.jsDocComment ? `${codeEmitter.generateSwiftMultilineDocComment(method.jsDocComment.split("\n"))}\n` : "";
const throwsAndReturn = returnType === "Void" ? "throws" : `throws -> ${returnType}`;
return `${comment}${codeEmitter.generateSwiftDocComment(`${method.name} method`)}
func ${method.name}(${params}) ${asyncKeyword}${throwsAndReturn}`;
}
/**
* Generates a protocol parameter
*/
function generateProtocolParameter(param, moduleName, interfaces = []) {
if (param.functionType) {
const argJoined = (param.functionType.paramTypes || []).map((t$1, idx) => {
const tt = (t$1 || "").trim();
if (tt.endsWith("[]")) {
const elem = tt.slice(0, -2).trim();
if (param.functionType?.paramRestFlags?.[idx] && elem === "any") return "[Any]";
}
return typeMapper.mapTsToSwiftForCallbackArg(tt);
}).join(", ");
return `${param.name}: @escaping (${argJoined}) -> Void`;
}
const t = (param.type || "").trim();
if (isAnyObjectType(t)) return `${param.name}: [String: Any]`;
const swiftType = typeMapper.mapTypeScriptToSwiftWithInterfaces(t);
return `${param.name}: ${swiftType}`;
}
/**
* Generates a protocol constant property
*/
function generateProtocolConstant(constant, moduleName) {
const swiftType = typeMapper.mapTypeScriptToSwift(constant.type);
return `${codeEmitter.generateSwiftDocComment(`${constant.name} constant`)}\nvar ${constant.name}: ${swiftType} { get }`;
}
/**
* Generates type aliases for a module
*/
function generateModuleTypeAliases(module) {
const aliases = [];
module.interfaceDefinitions.forEach((interfaceDef) => {
const originalName = interfaceDef.name;
const prefixedName = `${module.moduleName}${originalName}`;
aliases.push(codeEmitter.generateSwiftTypeAlias(originalName, prefixedName, true, `Type alias for ${originalName} from ${module.moduleName} module`));
});
return aliases.join("\n");
}
return {
generateModuleProtocol,
generateProtocolMethod,
generateProtocolConstant,
generateModuleTypeAliases
};
}
//#endregion
//#region src/syntax/swift/swift-struct-generator.ts
/**
* Creates a Swift struct generator with injected dependencies
*/
function createSwiftStructGenerator(deps) {
const { typeMapper, codeEmitter } = deps;
/**
* Generates all module types (Swift structs) from interface definitions
*/
function generateModuleTypes(module, includeImports = true) {
const interfaceStructs = module.interfaceDefinitions.map((interfaceDef) => generateLibrarySwiftStruct(interfaceDef, module.moduleName, module.interfaceDefinitions)).join("\n\n");
const moduleComment = `${module.moduleName} module types for Brick framework`;
if (!includeImports) return interfaceStructs;
return codeEmitter.generateSwiftFile(["Foundation"], interfaceStructs, moduleComment);
}
/**
* Generates a Swift struct from interface definition for library packages
* Includes public initializers and Codable conformance
*/
function generateLibrarySwiftStruct(interfaceDef, moduleName, allInterfaces = []) {
const properties = interfaceDef.properties.map((prop) => {
const swiftType = typeMapper.mapTypeScriptToSwiftWithInterfaces(prop.type);
const optional = prop.optional ? "?" : "";
return `${prop.jsDocComment ? ` ${codeEmitter.generateSwiftDocComment(prop.jsDocComment)}\n` : ""} ${codeEmitter.generateSwiftProperty(prop.name, `${swiftType}${optional}`, true, true)}`;
}).join("\n");
const initParams = interfaceDef.properties.map((prop) => {
const swiftType = typeMapper.mapTypeScriptToSwiftWithInterfaces(prop.type);
const optional = prop.optional ? "?" : "";
return `${prop.name}: ${swiftType}${optional}`;
}).join(", ");
const initAssignments = interfaceDef.properties.map((prop) => ` self.${prop.name} = ${prop.name}`).join("\n");
return `public struct ${moduleName ? `${codeEmitter.capitalizeFirst(moduleName)}${interfaceDef.name}` : interfaceDef.name}: Codable {
${properties}
public init(${initParams}) {
${initAssignments}
}
}`;
}
/**
* Generates a simple Swift struct without module prefix (for local use)
*/
function generateSwiftStruct(interfaceDef, allInterfaces = []) {
const properties = interfaceDef.properties.map((prop) => {
const swiftType = typeMapper.mapTypeScriptToSwiftWithInterfaces(prop.type);
const optional = prop.optional ? "?" : "";
return {
name: prop.name,
type: `${swiftType}${optional}`,
comment: prop.jsDocComment
};
});
return codeEmitter.generateSwiftStruct(interfaceDef.name, properties, ["Codable"], false);
}
return {
generateModuleTypes,
generateLibrarySwiftStruct,
generateSwiftStruct
};
}
//#endregion
//#region src/syntax/swift/swift-method-generator.ts
/**
* Creates a Swift method generator with injected dependencies
*/
function createSwiftMethodGenerator(deps) {
const { typeMapper } = deps;
/**
* Generates a Swift module method implementation for BrickModule extension
*/
function generateSwiftModuleMethod(method, module) {
const fullMethodName = `${module.moduleName}_${method.name}`;
const swiftParams = mapParamsToSwift(method.params);
const swiftReturnType = mapReturnTypeToSwift(method.returnType);
if (method.isAsync) return generateAsyncMethod(method, module, fullMethodName, swiftParams, swiftReturnType);
return generateSyncMethod(method, module, fullMethodName, swiftParams, swiftReturnType);
}
/**
* Generates an async Swift method implementation
*/
function generateAsyncMethod(method, module, fullMethodName, swiftParams, _swiftReturnType) {
const moduleName = module.moduleName;
const paramList = swiftParams.map((p) => `${p.name}: ${p.type}`).concat(["resolve: @escaping RCTPromiseResolveBlock", "reject: @escaping RCTPromiseRejectBlock"]).join(", ");
const methodParams = generateMethodCallParams(method, module, true);
const returnType = method.returnType.replace("Promise<", "").replace(">", "");
const resultHandling = ![
"string",
"number",
"boolean",
"void"
].includes(returnType) && !returnType.includes("[]") && !isAnyObjectType(returnType) && !isAnyType(returnType) ? "resolve(try self.convertToDict(result))" : "resolve(result)";
return `@objc public func ${fullMethodName}(${paramList}) -> Void {
Task {
do {
// Get the module instance
guard let module = brickModuleRegistry?.getModuleInstance("${moduleName}") else {
let error = NSError(domain: "BrickModule", code: 404, userInfo: [
NSLocalizedDescriptionKey: "Module '${moduleName}' not found"
])
reject("MODULE_NOT_FOUND", "Module not found", error)
return
}
// Cast to the expected protocol and call the method
guard let typedModule = module as? ${moduleName}Spec else {
let error = NSError(domain: "BrickModule", code: 500, userInfo: [
NSLocalizedDescriptionKey: "Module does not conform to expected protocol"
])
reject("TYPE_ERROR", "Protocol mismatch", error)
return
}
let result = try await typedModule.${method.name}(${methodParams})
${resultHandling}
} catch {
reject("EXECUTION_ERROR", error.localizedDescription, error)
} }
}`;
}
/**
* Generates a sync Swift method implementation
*/
function generateSyncMethod(method, module, fullMethodName, swiftParams, swiftReturnType) {
const moduleName = module.moduleName;
const paramList = swiftParams.map((p) => `${p.name}: ${p.type}`).join(", ");
const methodParams = generateMethodCallParams(method, module, false);
if (swiftReturnType === "Void") return `@objc public func ${fullMethodName}(${paramList}${paramList ? ", error: NSErrorPointer" : "error: NSErrorPointer"}) -> Bool {
do {
// Get the module instance
guard let module = brickModuleRegistry?.getModuleInstance("${moduleName}") else {
let err = NSError(domain: "BrickModule", code: 404, userInfo: [NSLocalizedDescriptionKey: "Module '${moduleName}' not found"])
error?.pointee = err
return false
}
// Cast to the expected protocol and call the method
guard let typedModule = module as? ${moduleName}Spec else {
let err = NSError(domain: "BrickModule", code: 500, userInfo: [NSLocalizedDescriptionKey: "Module does not conform to ${moduleName}Spec"])
error?.pointee = err
return false
}
try typedModule.${method.name}(${methodParams})
return true
} catch let err {
error?.pointee = err as NSError
return false
}
}`;
const isComplexReturnType = ![
"string",
"number",
"boolean",
"void"
].includes(method.returnType) && !method.returnType.includes("[]") && !isAnyObjectType(method.returnType) && !isAnyType(method.returnType);
const isBooleanReturnType = method.returnType === "boolean";
let resultHandling;
if (isComplexReturnType) resultHandling = `try self.convertToDict(try typedModule.${method.name}(${methodParams}))`;
else if (isBooleanReturnType) resultHandling = `NSNumber(value: try typedModule.${method.name}(${methodParams}))`;
else if (method.returnType === "number") resultHandling = `NSNumber(value: try typedModule.${method.name}(${methodParams}))`;
else resultHandling = `try typedModule.${method.name}(${methodParams})`;
return `@objc public func ${fullMethodName}(${paramList}${paramList ? ", error: NSErrorPointer" : "error: NSErrorPointer"}) -> Any? {
do {
// Get the module instance
guard let module = brickModuleRegistry?.getModuleInstance("${moduleName}") else {
let err = NSError(domain: "BrickModule", code: 404, userInfo: [NSLocalizedDescriptionKey: "Module '${moduleName}' not found"])
error?.pointee = err
return nil
}
// Cast to the expected protocol and call the method
guard let typedModule = module as? ${moduleName}Spec else {
let err = NSError(domain: "BrickModule", code: 500, userInfo: [NSLocalizedDescriptionKey: "Module does not conform to ${moduleName}Spec"])
error?.pointee = err
return nil
}
return ${resultHandling}
} catch let err {
error?.pointee = err as NSError
return nil
}
}`;
}
/**
* Generates method call parameters with conversion logic
*/
function generateMethodCallParams(method, module, _isAsync) {
const moduleName = module.moduleName;
const ifaceNames = new Set((module.interfaceDefinitions || []).map((i) => i.name));
return method.params.map((p) => {
const isComplexType = ![
"string",
"number",
"boolean",
"void"
].includes(p.type) && !p.type.includes("[]") && !p.type.startsWith("Promise");
const isArrayOfComplex = p.type.endsWith("[]") && ![
"string[]",
"number[]",
"boolean[]"
].includes(p.type);
const cbInfo = p.functionType;
if (cbInfo) return generateCallbackParameter(p.name, cbInfo, moduleName, ifaceNames);
const t = (p.type || "").trim();
if (isAnyObjectType(t) || isAnyType(t)) return `${p.name}: ${p.name}`;
if (isComplexType) {
const interfaceName = p.type;
return `${p.name}: try self.convertFromDict(${p.name}, to: ${moduleName}${interfaceName}.self)`;
}
if (isArrayOfComplex) {
const elementType = p.type.slice(0, -2);
return `${p.name}: try ${p.name}.map { try self.convertFromDict($0 as! [String: Any], to: ${moduleName}${elementType}.self) }`;
}
return `${p.name}: ${p.name}`;
}).join(", ");
}
/**
* Generates callback parameter with proper conversion
*/
function generateCallbackParameter(paramName, cbInfo, _moduleName, ifaceNames) {
const argNames = (cbInfo.paramTypes || []).map((_, idx) => `v${idx}`);
const argList = argNames.join(", ");
if (argNames.length === 0) return `${paramName}: { ${paramName}([]) }`;
if (argNames.length === 1 && cbInfo.paramRestFlags?.[0] === true) return `${paramName}: { v0 in ${paramName}(v0) }`;
return `${paramName}: { ${argList} in ${paramName}([${(cbInfo.paramTypes || []).map((t, idx) => {
const tt = (t || "").trim();
const v = `v${idx}`;
if (isAnyObjectType(tt) || tt.startsWith("{") && tt.endsWith("}")) return v;
if (tt.endsWith("[]")) {
const elem = tt.slice(0, -2).trim();
if (ifaceNames.has(elem)) return `${v}.map { (try? self.convertToDict($0)) ?? [:] }`;
return v;
}
if (ifaceNames.has(tt)) return `(try? self.convertToDict(${v})) ?? [:]`;
return v;
}).join(", ")}]) }`;
}
/**
* Generates a constant getter implementation
*/
function generateModuleConstantGetter(constant, moduleName) {
const fullMethodName = `${moduleName}_${constant.name}`;
const swiftType = typeMapper.mapTypeScriptToSwiftForObjC(constant.type);
const defaultValue = typeMapper.getSwiftDefaultValue(swiftType);
return `@objc public func ${fullMethodName}() -> ${swiftType} {
do {
// Get the module instance
guard let module = brickModuleRegistry?.getModuleInstance("${moduleName}") else {
return ${defaultValue}
}
// Cast to the expected protocol and get the constant
if let typedModule = module as? ${moduleName}Spec {
return typedModule.${constant.name}
} else {
return ${defaultValue}
}
} catch {
return ${defaultValue}
}
}`;
}
/**
* Helper method to map params to Swift types
*/
function mapParamsToSwift(params) {
return params.map((param) => ({
name: param.name,
type: param.isFunction ? "@escaping RCTResponseSenderBlock" : typeMapper.mapTypeScriptToSwiftForObjC(param.type)
}));
}
/**
* Helper method to map return type to Swift
*/
function mapReturnTypeToSwift(returnType) {
if (returnType.startsWith("Promise<") && returnType.endsWith(">")) return "Void";
return typeMapper.mapTypeScriptToSwiftForObjC(returnType);
}
return {
generateSwiftModuleMethod,
generateModuleConstantGetter
};
}
//#endregion
//#region src/syntax/swift/swift-bridge-generator.ts
/**
* Creates a Swift bridge generator with injected dependencies
*/
function createSwiftBridgeGenerator(deps) {
const { typeMapper, codeEmitter } = deps;
/**
* Generates BrickCodegen.h umbrella header
*/
function generateBrickCodegenHeader() {
return `${codeEmitter.generateFileHeader()}
// This is the umbrella header for the BrickCodegen module
// Intentionally minimal; Swift is independent and ObjC bridge imports its own header.
`;
}
/**
* Generates BrickModule.h header
*/
function generateBrickModuleHeader(_events) {
return `${codeEmitter.generateFileHeader()}
#import <Foundation/Foundation.h>
#import "BrickModuleSpec.h"
#if __has_include("BrickCodegen/BrickCodegen-Swift.h")
#import "BrickCodegen/BrickCodegen-Swift.h"
#else
#import "BrickCodegen-Swift.h"
#endif
@interface BrickModule : NativeBrickModuleSpecBase<NativeBrickModuleSpec>
@end
`;
}
/**
* Generates BrickModule.mm Objective-C bridge implementation
*/
function generateObjectiveCBridge(allMethods, allConstants, allEvents) {
const newArchMethods = allMethods.map((method) => generateNewArchMethod(method)).join("\n\n");
const newArchConstantsGetter = generateNewArchConstantsGetter(allConstants);
return `${codeEmitter.generateFileHeader()}
#import "BrickModule.h"
@class BrickModuleImpl;
@interface BrickModule () <NativeBrickModuleSpec>
@property (nonatomic, strong) BrickModuleImpl *impl;
@end
@implementation BrickModule
RCT_EXPORT_MODULE()
@synthesize bridge = _bridge;
- (instancetype)init {
self = [super init];
if (self) {
_impl = [BrickModuleImpl new];
[self.impl setEventMap:[self getEventMap]];
}
return self;
}
- (NSDictionary *)getEventMap {
typedef void (^BrickEventHandler)(NSDictionary *);
NSMutableDictionary<NSString *, NSMutableDictionary<NSString *, BrickEventHandler> *> *eventMap = [NSMutableDictionary new];
__weak BrickModule *weakSelf = self;
${generateEventMapEntries(allEvents)}
return eventMap;
}
// No host attachment; app injects registry via KVC
// ========== METHODS ==========
${newArchMethods}
// ========== CONSTANTS ==========
${newArchConstantsGetter}
// ========== EVENT EMITTER WRAPPERS ==========
// No RCTEventEmitter-based methods; events are handled via CodegenTypes.EventEmitter
// TurboModule requirement
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params {
RCTBridge* bridge = params.instance.bridge;
[self.impl setBridge:bridge];
return std::make_shared<facebook::react::NativeBrickModuleSpecJSI>(params);
}
+ (BOOL)requiresMainQueueSetup {
return YES;
}
${generateGetRegisteredModulesMethod()}
${generateSetValueForKeyMethod()}
@end
`;
}
function generateEventMapEntries(events) {
if (!events || events.length === 0) return "";
const eventsByModule = events.reduce((acc, e) => {
if (!acc[e.moduleName]) acc[e.moduleName] = [];
acc[e.moduleName]?.push(e);
return acc;
}, {});
const lines = [];
for (const [moduleName, moduleEvents] of Object.entries(eventsByModule)) {
const moduleVarName = `${moduleName.charAt(0).toLowerCase()}${moduleName.slice(1)}Events`;
lines.push(` NSMutableDictionary *${moduleVarName} = [NSMutableDictionary new];`);
for (const e of moduleEvents) {
const cap = codeEmitter.capitalizeFirst(e.name);
const method = `emit${e.moduleName}_on${cap}`;
lines.push(` ${moduleVarName}[@"on${cap}"] = ^(NSDictionary *payload) { [weakSelf ${method}:payload]; };`);
}
lines.push(` eventMap[@"${moduleName}"] = ${moduleVarName};`);
}
return lines.join("\n");
}
/**
* Generates New Architecture method implementation
*/
function generateNewArchMethod(method) {
const methodName = `${method.moduleName}_${method.name}`;
const params = method.params.map((param, index) => {
const objcType = param.isFunction ? "RCTResponseSenderBlock" : getReactNativeCodegenType(param.type, methodName, param.name);
if (index === 0) return `:(${objcType})${param.name}`;
return `${param.name}:(${objcType})${param.name}`;
}).join(" ");
const returnType = method.isAsync ? "void" : "NSDictionary *";
const resolveReject = method.isAsync ? method.params.length > 0 ? " resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject" : ":(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject" : "";
const conversions = generateParameterConversions(method);
const callParams = generateCallParameters(method);
const withSuffix = method.params.length > 0 ? "With" : method.isAsync ? "WithResolve" : "";
const asyncParams = method.isAsync ? method.params.length > 0 ? " resolve:resolve reject:reject" : ":resolve reject:reject" : "";
const errorCallSuffix = method.isAsync ? "" : method.params.length > 0 ? " error:&error" : "WithError:&error";
return `- (${returnType})${methodName}${params}${resolveReject} {
${conversions} ${method.isAsync ? `[self.impl ${methodName}${withSuffix}${callParams}${asyncParams}];` : method.returnType === "void" ? `NSError *error = nil;
BOOL success = [self.impl ${methodName}${withSuffix}${callParams}${errorCallSuffix}];
if (success) {
return @{
@"~sync": @YES,
@"success": @YES,
@"value": [NSNull null]
};
} else {
return @{
@"~sync": @YES,
@"success": @NO,
@"error": @{
@"code": @"EXECUTION_ERROR",
@"message": error.localizedDescription != nil ? error.localizedDescription : @"Unknown error"
}
};
}` : `NSError *error = nil;
id value = [self.impl ${methodName}${withSuffix}${callParams}${errorCallSuffix}];
if (value != nil) {
return @{
@"~sync": @YES,
@"success": @YES,
@"value": value
};
} else {
return @{
@"~sync": @YES,
@"success": @NO,
@"error": @{
@"code": @"EXECUTION_ERROR",
@"message": error.localizedDescription != nil ? error.localizedDescription : @"Unknown error"
}
};
}`}
}`;
}
/**
* Gets the correct React Native Codegen type for complex objects
*/
function getReactNativeCodegenType(tsType, methodName, paramName) {
if (tsType === "callback") return "RCTResponseSenderBlock";
if (tsType.startsWith("{") && tsType.endsWith("}") || isAnyObjectType(tsType)) {
const parts = methodName.split("_");
return `${`JS::NativeBrickModule::Spec${parts[0]}_${parts.slice(1).join("_")}${codeEmitter.capitalizeFirst(paramName)}`} &`;
}
if (tsType.match(/^[A-Z][a-zA-Z0-9]*$/)) return `${`JS::NativeBrickModule::${methodName.split("_")[0]}_${tsType}`} &`;
return typeMapper.mapTypeScriptToObjC(tsType, false, false);
}
/**
* Generates parameter conversion code for complex objects
*/
function generateParameterConversions(_method) {
return "";
}
/**
* Generates call parameters for Swift method calls
*/
function generateCallParameters(method) {
return method.params.map((param, index) => {
const isInline = param.type.startsWith("{") && param.type.endsWith("}");
const isInterfaceLike = param.type.match(/^[A-Z][a-zA-Z0-9]*$/);
const paramValue = isInline || isInterfaceLike ? `${param.name}._v` : param.name;
if (index === 0) return `${codeEmitter.capitalizeFirst(param.name)}:${paramValue}`;
return ` ${param.name}:${paramValue}`;
}).join("");
}
/**
* Generates constants getter for New Architecture
*/
function generateNewArchConstantsGetter(constants) {
if (constants.length === 0) return "";
return `- (facebook::react::ModuleConstants<JS::NativeBrickModule::Constants::Builder>)constantsToExport {
return [self getConstants];
}
- (facebook::react::ModuleConstants<JS::NativeBrickModule::Constants::Builder>)getConstants {
return facebook::react::typedConstants<JS::NativeBrickModule::Constants::Builder>({
${constants.map((constant) => {
return ` .${`${constant.moduleName}_${constant.name}`} = [self.impl ${constant.moduleName}_${constant.name}],`;
}).join("\n")}
});
}`;
}
function generateGetRegisteredModulesMethod() {
return `- (NSArray<NSString *> *)getRegisteredModules {\n return [self.impl getRegisteredModules];\n}`;
}
function generateSetValueForKeyMethod() {
return `- (void)setValue:(id)value forKey:(NSString *)key {\n if ([key isEqualToString:@"_brickModuleRegistry"]) {\n @try {\n [self.impl setValue:value forKey:@"brickModuleRegistry"];\n } @catch (NSException *exception) {\n }\n return;\n }\n [super setValue:value forKey:key];\n}`;
}
/**
* Generates supported events array
*/
function generateSupportedEventsArray(events) {
if (events.length === 0) return " return @[];";
return ` return @[${events.map((event) => `@"${event.moduleName}_${event.name}"`).join(", ")}];`;
}
/**
* Generates Swift supported events array
*/
function generateSwiftSupportedEventsArray(_events) {
return "[String]()";
}
/**
* Generates main BrickModule.swift file content
*/
function generateBrickModuleSwift(allEvents) {
return `
import Foundation
import BrickModule
// MARK: - BrickModuleImpl Extension for Swift functionality
// Extension for BrickModuleImpl (called by ObjC++ thin wrapper)
extension BrickModuleImpl {
}
// ========== CONVERSION HELPERS ==========
// Generic Codable conversion helpers
extension BrickModuleImpl {
func convertToDict<T: Codable>(_ object: T) throws -> [String: Any] {
let jsonData = try JSONEncoder().encode(object)
guard let dict = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any] else {
throw NSError(domain: "BrickModule", code: 500, userInfo: [NSLocalizedDescriptionKey: "Failed to convert to dictionary"])
}
return dict
}
func convertFromDict<T: Codable>(_ dict: [String: Any], to type: T.Type) throws -> T {
let jsonData = try JSONSerialization.data(withJSONObject: dict, options: [])
return try JSONDecoder().decode(type, from: jsonData)
}
}
`;
}
return {
generateBrickCodegenHeader,
generateBrickModuleHeader,
generateObjectiveCBridge,
generateBrickModuleSwift,
generateSupportedEventsArray,
generateSwiftSupportedEventsArray
};
}
//#endregion
//#region src/syntax/swift/swift-generator-suite.ts
/**
* Creates a Swift generator suite with composed dependencies
*/
function createSwiftGeneratorSuite(options = {}) {
const { typeMappingOptions = {}, codeEmitterOptions = {} } = options;
const typeMapper = createSwiftTypeMapper(typeMappingOptions);
const codeEmitter = createSwiftCodeEmitter(codeEmitterOptions);
const protocolGenerator = createSwiftProtocolGenerator({
typeMapper,
codeEmitter
});
const structGenerator = createSwiftStructGenerator({
typeMapper,
codeEmitter
});
const methodGenerator = createSwiftMethodGenerator({ typeMapper });
const bridgeGenerator = createSwiftBridgeGenerator({
typeMapper,
codeEmitter
});
const deps = {
typeMapper,
codeEmitter,
protocolGenerator,
structGenerator,
methodGenerator,
bridgeGenerator
};
/**
* Generate protocol/interface for a module
*/
function generateProtocol(module) {
return protocolGenerator.generateModuleProtocol(module);
}
/**
* Generate struct types for a module
*/
function generateStructs(module) {
return structGenerator.generateModuleTypes(module);
}
/**
* Generate bridge implementation files
*/
function generateBridge(_modules) {
return {
bridgeHeader: bridgeGenerator.generateBrickCodegenHeader(),
bridgeImplementation: "// Use IOSPlatform.generateFiles() for full bridge generation",
swiftImplementation: "// Use IOSPlatform.generateFiles() for full bridge generation"
};
}
/**
* Generate complete Swift implementation for a module
*/
function generateSwiftImplementation(_module) {
return "// Use IOSPlatform.generateFiles() for full implementation";
}
/**
* Generate library mode protocol (public API)
*/
function generateLibraryProtocol(module) {
return protocolGenerator.generateModuleProtocol(module);
}
/**
* Generate library mode structs (Codable types)
*/
function generateLibraryStructs(module) {
return structGenerator.generateModuleTypes(module);
}
return {
deps,
generateProtocol,
generateStructs,
generateBridge,
generateSwiftImplementation,
generateLibraryProtocol,
generateLibraryStructs
};
}
//#endregion
//#region src/platforms/ios-platform.ts
/**
* iOS Platform Implementation
* Contains all iOS bridge generation logic moved from BridgeGenerator
*/
var IOSPlatformGenerator = class {
config;
scannedModules = [];
typeMapper;
bridgeGenerator;
methodGenerator;
protocolGenerator;
structGenerator;
constructor(config) {
this.config = config;
this.typeMapper = createSwiftTypeMapper();
const codeEmitter = createSwiftCodeEmitter();
this.methodGenerator = createSwiftMethodGenerator({ typeMapper: this.typeMapper });
this.bridgeGenerator = createSwiftBridgeGenerator({
typeMapper: this.typeMapper,
codeEmitter
});
this.protocolGenerator = createSwiftProtocolGenerator({
typeMapper: this.typeMapper,
codeEmitter
});
this.structGenerator = createSwiftStructGenerator({
typeMapper: this.typeMapper,
codeEmitter
});
}
getPlatformName() {
return "ios";
}
supports(target) {
return target === "ios" || target === "swift" || target === "objective-c";
}
/**
* Main method - Generates iOS bridge files (moved from BridgeGenerator.generateIOSBridge)
*/
async generateBridge(modules, scannedModules) {
if (scannedModules) this.scannedModules = scannedModules;
try {
const iosBrickDir = path.join(this.config.projectRoot, this.config.outputIos);
const generatedFiles = [];
await fs.ensureDir(iosBrickDir);
try {
const umbrellaHeaderFile = await this.generateBrickCodegenHeader(iosBrickDir);
generatedFiles.push(umbrellaHeaderFile);
} catch (error) {
throw new Error(`Failed in generateBrickCodegenHeader: ${error.message}`);
}
try {
const allEvents = this.getAllEvents(modules) || [];
const headerFile = await this.generateBrickModuleHeader(iosBrickDir, allEvents);
generatedFiles.push(headerFile);
} catch (error) {
throw new Error(`Failed in generateBrickModuleHeader: ${error.message}`);
}
try {
const bridgeFile = await this.generateObjectiveCBridge(modules, iosBrickDir);
generatedFiles.push(bridgeFile);
} catch (error) {
throw new Error(`Failed in generateObjectiveCBridge: ${error.message}`);
}
try {
const implFiles = await this.generateSwiftImplementationFiles(modules, iosBrickDir);
generatedFiles.push(...implFiles);
} catch (error) {
throw new Error(`Failed in generateSwiftImplementation: ${error.message}`);
}
try {
const localTypeFiles = await this.generateLocalModuleTypes(modules, iosBrickDir);
generatedFiles.push(...localTypeFiles);
} catch (error) {
throw new Error(`Failed in generateLocalModuleTypes: ${error.message}`);
}
try {
const podspecFile = await this.generatePodspec();
generatedFiles.push(podspecFile);
} catch (error) {
throw new Error(`Failed in generatePodspec: ${error.message}`);
}
return generatedFiles;
} catch (error) {
throw new Error(`Failed to generate iOS bridge: ${error.message}`);
}
}
isAvailable() {
return true;
}
/**
* Generates BrickCodegen.h umbrella header file
*/
async generateBrickCodegenHeader(outputDir) {
const headerContent = this.bridgeGenerator.generateBrickCodegenHeader();
const outputPath = path.join(outputDir, "BrickCodegen.h");
await fs.writeFile(outputPath, headerContent);
this.logGenerated(outputPath);
return outputPath;
}
/**
* Generates BrickModule.h header file
*/
async generateBrickModuleHeader(outputDir, events) {
const headerContent = this.bridgeGenerator.generateBrickModuleHeader(events);
const outputPath = path.join(outputDir, "BrickModule.h");
await fs.writeFile(outputPath, headerContent);
this.logGenerated(outputPath);
return outputPath;
}
/**
* Generates the main Objective-C bridge file (BrickModule.mm)
*/
async generateObjectiveCBridge(modules, outputDir) {
const allMethods = this.getAllMethods(modules) || [];
const allConstants = this.getAllConstants(modules) || [];
const allEvents = this.getAllEvents(modules) || [];
const bridgeContent = this.bridgeGenerator.generateObjectiveCBridge(allMethods, allConstants, allEvents);
const outputPath = path.join(outputDir, "BrickModule.mm");
await fs.writeFile(outputPath, bridgeContent);
this.logGenerated(outputPath);
return outputPath;
}
/**
* Generates all Swift implementation files
*/
async generateSwiftImplementationFiles(modules, outputDir) {
const generatedFiles = [];
const implClassFile = await this.generateBrickModuleImplSwift(outputDir);
generatedFiles.push(implClassFile);
const mainFile = await this.generateBrickModuleSwift(modules, outputDir);
generatedFiles.push(mainFile);
for (const module of modules) {
const extFile = await this.generateModuleExtension(module, outputDir);
generatedFiles.push(extFile);
}
return generatedFiles;
}
/**
* Generates main BrickModule.swift file
*/
async generateBrickModuleSwift(modules, outputDir) {
const allEvents = this.getAllEvents(modules);
const swiftContent = this.bridgeGenerator.generateBrickModuleSwift(allEvents);
const outputPath = path.join(outputDir, "BrickModule.swift");
await fs.writeFile(outputPath, swiftContent);
this.logGenerated(outputPath);
return outputPath;
}
/**
* Helper method to get Swift default value for a type
*/
getSwiftDefaultValue(swiftType) {
return this.typeMapper.getSwiftDefaultValue(swiftType);
}
/**
* Generates module-specific extension file (BrickModule+ModuleName.swift)
*/
async generateModuleExtension(module, outputDir) {
const moduleMethods = module.methods;
const moduleConstants = module.constants;
const typeAliases = this.generateModuleTypeAliases(module);
const methodImplementations = moduleMethods.map((method) => this.generateSwiftModuleMethod(method, module)).join("\n\n ");
const constantGetters = moduleConstants.map((constant) => this.generateModuleConstantGetter(constant, module.moduleName)).join("\n\n ");
const cocoapodsModuleName = !module.sourceModule.brickConfig ? null : module.sourceModule.brickConfig?.ios?.moduleName;
const importStatement = cocoapodsModuleName ? `import ${cocoapodsModuleName}` : "";
const swiftContent = `//
// BrickModule+${module.moduleName}.swift
// Pods
//
import Foundation
import BrickModule
import React
${importStatement}
${typeAliases}
extension BrickModuleImpl {
${methodImplementations}
${constantGetters}
}
`;
const outputPath = path.join(outputDir, `BrickModule+${module.moduleName}.swift`);
await fs.writeFile(outputPath, swiftContent);
this.logGenerated(outputPath);
return outputPath;
}
/**
* Generates the BrickModuleImpl.swift implementation holder
*/
async generateBrickModuleImplSwift(outputDir) {
const content = `import Foundation
import React
import BrickModule
@objc public class BrickModuleImpl: NSObject {
@objc public var brickModuleRegistry: BrickModuleRegistry? {
didSet {
// When registry is injected via KVC, set the converted event map
if let convertedMap = _convertedEventMap {
brickModuleRegistry?.setEventMap(convertedMap)
}
if let bridge = _bridge {
brickModuleRegistry?.setBridge(bridge)
}
}
}
private var _convertedEventMap: [String: [String: ([String: Any]) -> Void]]?
private var _bridge: Any?
@objc public override init() {
super.init()
}
@objc public func setBridge(_ bridge: Any) {
self._bridge = bridge
self.brickModuleRegistry?.setBridge(bridge)
}
@objc public func setEventMap(_ map: NSDictionary) {
typealias BlockType = @convention(block) (NSDictionary) -> Void
let convertedMap = (map as? [String: Any])?.compactMapValues { moduleEventsAny -> [String: ([String: Any]) -> Void]? in
guard let eventDict = moduleEventsAny as? [String: Any] else { return nil }
return eventDict.compactMapValues { eventBlock -> (([String: Any]) -> Void)? in
// Store block as AnyObject to maintain reference
let blockObject = eventBlock as AnyObject
// Convert Objective-C block to Swift closure
let block = unsafeBitCast(blockObject, to: BlockType.self)
return { (payload: [String: Any]) in
block(payload as NSDictionary)
}
}
} ?? [:]
self._convertedEventMap = convertedMap
self.brickModuleRegistry?.setEventMap(convertedMap)
}
@objc public func getRegisteredModules() -> [String] {
return brickModuleRegistry?.getRegisteredModules() ?? []
}
}
`;
const outputPath = path.join(outputDir, "BrickModuleImpl.swift");
await fs.writeFile(outputPath, content);
this.logGenerated(outputPath);
return outputPath;
}
/**
* Generates type aliases for a specific module
*/
generateModuleTypeAliases(module) {
return this.protocolGenerator.generateModuleTypeAliases(module);
}
/**
* Generates a Swift method implementation for a specific module
*/
generateSwiftModuleMethod(method, module) {
return this.methodGenerator.generateSwiftModuleMethod(method, module);
}
/**
* Generates a constant getter for a specific module
*/
generateModuleConstantGetter(constant, moduleName) {
return this.methodGenerator.generateModuleConstantGetter(constant, moduleName);
}
/**
* Generates local module types in .brick/Types/ directory
*/
async generateLocalModuleTypes(modules, outputDir) {
const generatedFiles = [];
const typesDir = path.join(outputDir, "Types");
const localModules = modules.filter((m) => !m.sourceModule?.brickConfig);
if (localModules.length === 0) return generatedFiles;
await fs.ensureDir(typesDir);
for (const module of localModules) {
let protocolContent = await this.generateLibraryProtocol(module);
if (module.events && module.events.length > 0) {
const eventHelpers = this.generateEventHelpersExtension(module);
protocolContent += `\n${eventHelpers}`;
}
const protocolFile = path.join(typesDir, `${module.moduleName}Spec.swift`);
await fs.writeFile(protocolFile, protocolContent);
generatedFiles.push(protocolFile);
this.logGenerated(protocolFile);
if (module.interfaceDefinitions.length > 0) {
const typesContent = await this.generateLibraryTypes(module);
const typesFile = path.join(typesDir, `${module.moduleName}Types.swift`);
await fs.writeFile(typesFile, typesContent);
generatedFiles.push(typesFile);
this.logGenerated(typesFile);
}
}
return generatedFiles;
}
/**
* Generates event helper extension for protocol
* Used by both library and local modules
*/
generateEventHelpersExtension(module) {
const helpers = (module.events || []).map((e) => {
const cap = e.name.charAt(0).toUpperCase() + e.name.slice(1);
return ` public func emitOn${cap}(payload: [String: Any]) {\n emit("${`on${cap}`}", payload: payload)\n }`;
}).join("\n\n");
return `// Typed Event Emit Helpers for ${module.moduleName}
extension ${module.moduleName}Spec where Self: BrickModuleBase {
${helpers}
}
`;
}
/**
* Generates Swift types for library mode (public API)
*/
async generateLibraryTypes(module) {
return createSwiftGeneratorSuite({ typeMappingOptions: {
moduleName: module.moduleName,
interfaces: module.interfaceDefinitions
} }).generateStructs(module);
}
/**
* Generates Swift protocol for library mode (public API)
*/
async generateLibraryProtocol(module) {
return createSwiftGeneratorSuite({ typeMappingOptions: {
moduleName: module.moduleName,
interfaces: module.interfaceDefinitions
} }).generateProtocol(module);
}
/**
* Generates BrickModule.podspec for iOS integration with automatic dependencies
*/
async generatePodspec() {
const podspecContent = `folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -DFOLLY_CFG_NO_COROUTINES=1 -Wno-comma -Wno-shorten-64-to-32'
Pod::Spec.new do |s|
s.name = "BrickCodegen"
s.version = "1.0.0"
s.summary = "Generated Brick Module bridge code"
s.description = "Auto-generated bridge code for Brick modules by brick-codegen"
s.homepage = "https://github.com/toss/brick"
s.license = "MIT"
s.platforms = { :ios => '13.0' }
s.author = { "brick-codegen" => "toss" }
s.source = { :git => ".", :tag => "#{s.version}" }
s.swift_version = "5.0"
s.module_name = "BrickCodegen"
s.source_files = "**/*.{h,m,mm,swift}", "Types/**/*.swift"
s.public_header_files = "BrickCodegen.h"
s.preserve_paths = "**/*"
s.dependency "BrickModule"
${await this.generatePodspecDependencies()}
install_modules_dependencies(s)
end`;
const iosBrickDir = path.join(this.config.projectRoot, this.config.outputIos);
const outputPath = path.join(iosBrickDir, "BrickCodegen.podspec");
await fs.ensureDir(iosBrickDir);
await fs.writeFile(outputPath, podspecContent);
this.logGenerated(outputPath);
return outputPath;
}
/**
* Generates podspec dependencies based on discovered Brick modules
* Uses the scanned modules from Scanner instead of re-scanning
*/
async generatePodspecDependencies() {
const dependencies = [];
try {
for (const module of this.scannedModules) if (module.hasIosImplementation && module.brickConfig?.ios?.moduleName) {
const cocoapodsModuleName = module.brickConfig.ios.moduleName;
dependencies.push(` s.dependency "${cocoapodsModuleName}"`);
}
} catch (error) {
console.warn(`Warning: Could not process Brick dependencies: ${error}`);
}
return dependencies.join("\n");
}
getAllMethods(modules) {
const methods = [];
if (!modules || !Array.isArray(modules)) return methods;
for (const module of modules) {
if (!module || !module.methods || !Array.isArray(module.methods)) continue;
for (const method of module.methods) methods.push({
...method,
moduleName: module.moduleName
});
}
return methods;
}
getAllEvents(modules) {
const events = [];
if (!modules || !Array.isArray(modules)) return events;
for (const module of modules) {
if (!module || !module.events || !Array.isArray(module.events)) continue;
for (const event of module.events) events.push({
...event,
moduleName: module.moduleName
});
}
return events;
}
getAllConstants(modules) {
const constants = [];
if (!modules || !Array.isArray(modules)) return constants;
for (const module of modules) {
if (!module || !module.constants || !Array.isArray(module.constants)) continue;
for (const constant of module.constants) constants.push({
...constant,
moduleName: module.moduleName
});
}
return constants;
}
logGenerated(filepath) {
const absolutePath = path.resolve(filepath);
console.log(`${pc.bold(pc.green("[Brick]"))} Generated artifact: ${absolutePath}`);
}
};
//#endregion
//#region src/platforms/platform-interface.ts
/**
* Type guard to check if a generator is for iOS
*/
function isIOSPlatformGenerator(generator) {
return generator.getPlatformName() === "ios";
}
/**
* Type guard to check if a generator is for Android
*/
function isAndroidPlatformGenerator(generator) {
return generator.getPlatformName() === "android";
}
//#endregion
//#region src/platforms/platform-registry.ts
var PlatformRegistry = class {
generators = /* @__PURE__ */ new Map();
scannedModules = [];
constructor(config) {
this.generators.set("ios", new IOSPlatformGenerator(config));
this.generators.set("android", new AndroidPlatformGenerator(config));
}
/**
* Set scanned modules to be passed to platform generators
*/
setScannedModules(modules) {
this.scannedModules = modules;
}
/**
* Get platform generator by name
*/
getGenerator(platform) {
return this.generators.get(platform);
}
/**
* Get platform generator by name (alias for getGenerator)
*/
getPlatform(platform) {
return this.generators.get(platform);
}
/**
* Get iOS platform generator with proper typing
*/
getIOSPlatform() {
const generator = this.generators.get("ios");
return generator && isIOSPlatformGenerator(generator) ? generator : void 0;
}
/**
* Get Android platform generator with proper typing
*/
getAndroidPlatform() {
const generator = this.generators.get("android");
return generator && isAndroidPlatformGenerator(generator) ? generator : void 0;
}
/**
* Get all available platforms
*/
getAvailablePlatforms() {
return Array.from(this.generators.keys()).filter((platform) => {
return this.generators.get(platform)?.isAvailable() ?? false;
});
}
/**
* Generate bridge code for specific platform
*/
async generateForPlatform(platform, modules) {
const generator = this.generators.get(platform);
if (!generator) throw new Error(`Platform '${platform}' is not supported`);
if (!generator.isAvailable()) {
console.warn(`Platform '${platform}' is not available in current environment`);
return [];
}
return await generator.generateBridge(modules, this.scannedModules);
}
/**
* Generate bridge code for all available platforms
*/
async generateForAllPlatforms(modules) {
const results = {};
const availablePlatforms = this.getAvailablePlatforms();
for (const platform of availablePlatforms) try {
const files = await this.generateForPlatform(platform, modules);
if (files.length > 0) results[platform] = files;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
console.warn(`Failed to generate for platform '${platform}':`, errorMessage);
}
return results;
}
/**
* Check if a platform is supported
*/
supports(platform) {
const targetPlatform = platform.toLowerCase();
return this.generators.get(targetPlatform)?.supports(platform) ?? false;
}
/**
* Check if a platform is available
*/
isPlatformAvailable(platform) {
const targetPlatform = platform.toLowerCase();
return this.generators.get(targetPlatform)?.isAvailable() ?? false;
}
/**
* Get platform generator by name
*/
getPlatformGenerator(platform) {
const targetPlatform = platform.toLowerCase();
return this.generators.get(targetPlatform);
}
};
//#endregion
//#region src/scanner.ts
/**
* Scanner - Package.json dependency scanner for Brick modules
* Discovers Brick modules in node_modules and validates their structure
*/
var Scanner = class {
config;
constructor(config) {
this.config = config;
}
/**
* Scans package.json dependencies for Brick modules
* In library mode: only scans current package for .brick.ts files
* In user project mode: scans dependencies for Brick modules
*/
async scanBrickModules() {
const result = [];
try {
const packageJsonPath = path.join(this.config.projectRoot, "package.json");
if (!await fs.pathExists(packageJsonPath)) throw new Error(`package.json not found at ${packageJsonPath}`);
const packageJson = await fs.readJson(packageJsonPath);
if (this.config.libraryMode) {
const libraryModules = await this.scanCurrentLibrary();
result.push(...libraryModules);
} else {
const allDeps = {
...packageJson.dependencies || {},
...packageJson.devDependencies || {},
...packageJson.peerDependencies || {}
};
this.log(`🔍 Scanning ${Object.keys(allDeps).length} dependencies for Brick modules...`);
for (const [depName, version] of Object.entries(allDeps)) try {
const scannedModule = await this.scanSingleModule(depName, version);
if (scannedModule) {
result.push(scannedModule);
this.log(`✅ Found Brick module: ${depName}`);
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
this.log(`⚠️ Error scanning module ${depName}: ${errorMessage}`);
}
const localModules = await this.scanLocalModules();
result.push(...localModules);
}
this.log(`📦 Found ${result.length} total Brick modules`);
return result;
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to scan Brick modules: ${errorMessage}`);
}
}
/**
* Scans a single module to determine if it's a Brick module
*/
async scanSingleModule(depName, version) {
try {
const packageJsonPath = __require.resolve(`${depName}/package.json`, { paths: [this.config.projectRoot] });
const modulePath = path.dirname(packageJsonPath);
const packageJson = await fs.readJson(packageJsonPath);
if (!this.isBrickModule(packageJson)) return null;
const specPath = await this.findSpecFile(modulePath, packageJson);
if (!specPath) {
this.log(`⚠️ Brick module ${depName} missing *.brick.ts file`);
return null;
}
const hasIosImplementation = await this.hasIosImplementation(modulePath);
const hasAndroidImplementation = await this.hasAndroidImplementation(modulePath);
return {
name: depName,
path: modulePath,
specPath,
packageJsonPath,
version: packageJson.version || version,
hasIosImplementation,
hasAndroidImplementation,
brickConfig: packageJson.brickModule,
androidPackage: packageJson.brickModule?.androidPackage,
androidModuleName: packageJson.brickModule?.androidModuleName,
androidPath: hasAndroidImplementation ? path.join(modulePath, "android") : void 0
};
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
this.log(`⚠️ Could not resolve module ${depName}: ${errorMessage}`);
return null;
}
}
/**
* Checks if a package.json indicates a Brick module
*/
isBrickModule(packageJson) {
if (packageJson.brickModule) return true;
if (packageJson.dependencies?.["brick-module"] || packageJson.peerDependencies?.["brick-module"]) return true;
return false;
}
/**
* Finds the spec file for a module
*/
async findSpecFile(modulePath, packageJson) {
if (packageJson?.brickModule?.specSrc) {
const specSrc = packageJson.brickModule.specSrc;
const specDirPath = path.join(modulePath, specSrc);
try {
const stat = await fs.stat(specDirPath);
if (stat.isDirectory()) {
const files = await glob("*.brick.ts", { cwd: specDirPath });
if (files.length > 0 && files[0]) return path.join(specDirPath, files[0]);
} else if (stat.isFile() && specSrc.endsWith(".brick.ts")) return specDirPath;
} catch {}
}
return null;
}
/**
* Checks if module has iOS implementation
*/
async hasIosImplementation(modulePath) {
const iosPath = path.join(modulePath, "ios");
if (!await fs.pathExists(iosPath)) return false;
try {
return (await glob("**/*.swift", { cwd: iosPath })).length > 0;
} catch {
return false;
}
}
/**
* Checks if module has Android implementation
*/
async hasAndroidImplementation(modulePath) {
const androidPath = path.join(modulePath, "android");
if (!await fs.pathExists(androidPath)) return false;
try {
return (await glob("**/*.{java,kt}", { cwd: androidPath })).length > 0;
} catch {
return false;
}
}
/**
* Scans for local Brick modules (for development)
*/
async scanLocalModules() {
const result = [];
try {
const ignorePatterns = [
"node_modules/**",
`${this.config.outputTypescript || ".brick"}/**`,
"build/**",
"lib/**"
];
const specFiles = await glob("**/*.brick.ts", {
cwd: this.config.projectRoot,
ignore: ignorePatterns
});
for (const specFile of specFiles) {
const specPath = path.join(this.config.projectRoot, specFile);
const modulePath = path.dirname(specPath);
const packageJsonPath = path.join(modulePath, "package.json");
let packageJson = {};
if (await fs.pathExists(packageJsonPath)) packageJson = await fs.readJson(packageJsonPath);
const moduleName = packageJson.name || path.basename(modulePath);
const hasAndroidImplementation = await this.hasAndroidImplementation(modulePath);
result.push({
name: moduleName,
path: modulePath,
specPath,
packageJsonPath,
version: packageJson.version,
hasIosImplementation: await this.hasIosImplementation(modulePath),
hasAndroidImplementation,
brickConfig: packageJson.brickModule,
androidPackage: packageJson.brickModule?.android?.package || packageJson.brickModule?.androidPackage,
androidModuleName: packageJson.brickModule?.android?.moduleName || packageJson.brickModule?.androidModuleName,
androidPath: hasAndroidImplementation ? path.join(modulePath, "android") : void 0
});
this.log(`🏠 Found local Brick module: ${moduleName}`);
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
this.log(`⚠️ Error scanning local modules: ${errorMessage}`);
}
return result;
}
/**
* Validates implementations for a module
*/
async validateImplementations(module) {
const errors = [];
try {
if ((await fs.readFile(module.specPath, "utf8")).trim().length === 0) errors.push(`Spec file is empty: ${module.specPath}`);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
errors.push(`Cannot read spec file: ${module.specPath} - ${errorMessage}`);
}
if (!module.hasIosImplementation && !module.hasAndroidImplementation) errors.push(`Module ${module.name} has no platform implementations`);
if (module.hasIosImplementation) {
const iosErrors = await this.validateIosImplementation(module);
errors.push(...iosErrors);
}
if (module.hasAndroidImplementation) {
const androidErrors = await this.validateAndroidImplementation(module);
errors.push(...androidErrors);
}
return errors;
}
/**
* Validates iOS implementation
*/
async validateIosImplementation(module) {
const errors = [];
const iosPath = path.join(module.path, "ios");
try {
const swiftFiles = await glob("**/*.swift", { cwd: iosPath });
if (swiftFiles.length === 0) {
errors.push(`No Swift files found in ${module.name}/ios`);
return errors;
}
let foundBrickModule = false;
for (const swiftFile of swiftFiles) {
const filePath = path.join(iosPath, swiftFile);
if ((await fs.readFile(filePath, "utf8")).includes("BrickCoreModule")) {
foundBrickModule = true;
break;
}
}
if (!foundBrickModule) errors.push(`No BrickCoreModule inheritance found in ${module.name} iOS implementation`);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
errors.push(`Error validating iOS implementation for ${module.name}: ${errorMessage}`);
}
return errors;
}
/**
* Validates Android implementation
*/
async validateAndroidImplementation(module) {
const errors = [];
const androidPath = path.join(module.path, "android");
try {
if ((await glob("**/*.{java,kt}", { cwd: androidPath })).length === 0) errors.push(`No Java/Kotlin files found in ${module.name}/android`);
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
errors.push(`Error validating Android implementation for ${module.name}: ${errorMessage}`);
}
return errors;
}
/**
* Gets detailed scan results with validation
*/
async getDetailedScanResults() {
const result = {
modules: [],
errors: [],
warnings: []
};
try {
result.modules = await this.scanBrickModules();
for (const module of result.modules) {
const validationErrors = await this.validateImplementations(module);
if (validationErrors.length > 0) result.errors.push(...validationErrors.map((err) => `${module.name}: ${err}`));
if (!module.hasIosImplementation) result.warnings.push(`${module.name}: Missing iOS implementation`);
if (!module.hasAndroidImplementation) result.warnings.push(`${module.name}: Missing Android implementation`);
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
result.errors.push(`Scan failed: ${errorMessage}`);
}
return result;
}
/**
* Scans current library for .brick.ts files (library mode only)
*/
async scanCurrentLibrary() {
const result = [];
try {
this.log(`🏗️ Library mode: scanning current package for .brick.ts files...`);
const packageJsonPath = path.join(this.config.projectRoot, "package.json");
if (!await fs.pathExists(packageJsonPath)) throw new Error(`package.json not found in current library: ${packageJsonPath}`);
const packageJson = await fs.readJson(packageJsonPath);
const moduleName = packageJson.name;
if (!this.isBrickModule(packageJson)) {
this.log(`⚠️ Current package is not a Brick module: missing 'brickModule' field`);
return result;
}
const specFiles = await glob("src/**/*.brick.ts", {
cwd: this.config.projectRoot,
absolute: false
});
if (specFiles.length === 0) {
this.log(`⚠️ No .brick.ts files found in current library`);
return result;
}
for (const specFile of specFiles) {
const specPath = path.join(this.config.projectRoot, specFile);
const hasAndroidImplementation = await this.hasAndroidImplementation(this.config.projectRoot);
result.push({
name: moduleName,
path: this.config.projectRoot,
specPath,
packageJsonPath,
version: packageJson.version,
hasIosImplementation: await this.hasIosImplementation(this.config.projectRoot),
hasAndroidImplementation,
brickConfig: packageJson.brickModule,
androidPackage: packageJson.brickModule?.android?.package || packageJson.brickModule?.androidPackage,
androidModuleName: packageJson.brickModule?.android?.moduleName || packageJson.brickModule?.androidModuleName,
androidPath: hasAndroidImplementation ? path.join(this.config.projectRoot, "android") : void 0
});
this.log(`📦 Found current library module: ${moduleName} (${specFile})`);
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
throw new Error(`Failed to scan current library: ${errorMessage}`);
}
return result;
}
log(...args) {
if (this.config.debug) console.log(pc.gray("[scanner]"), ...args);
}
};
//#endregion
export { IOSPlatformGenerator as a, Generator as c, formatErrors as d, getErrorSummary as f, isIOSPlatformGenerator as i, BrickCodegenError as l, __require as m, PlatformRegistry as n, AndroidPlatformGenerator as o, wrapError as p, isAndroidPlatformGenerator as r, Parser as s, Scanner as t, createCodegenError as u };
//# sourceMappingURL=scanner-DT8NcN4x.js.map