brick-codegen
Version:
Better React Native native module development
872 lines (865 loc) β’ 34.4 kB
JavaScript
import { c as Generator, d as formatErrors, f as getErrorSummary, l as BrickCodegenError, m as __require, n as PlatformRegistry, p as wrapError, s as Parser, t as Scanner, u as createCodegenError } from "./scanner-DT8NcN4x.js";
import { program } from "@commander-js/extra-typings";
import { readFileSync } from "fs";
import path, { dirname, resolve } from "path";
import pc from "picocolors";
import { fileURLToPath } from "url";
import { execa } from "execa";
import fs from "fs-extra";
import { glob } from "glob";
//#region src/config-loader.ts
/**
* Config Loader for brick.json
* Loads and validates brick configuration from project root
*/
var BrickConfigLoader = class BrickConfigLoader {
static CONFIG_FILE_NAME = "brick.json";
static DEFAULT_CONFIG = { output: {
typescript: ".brick",
ios: "ios/.brick",
android: "android/.brick",
androidAppPath: "android/app"
} };
/**
* Load brick.json configuration from project root
*/
static load(projectRoot) {
const configPath = path.join(projectRoot, BrickConfigLoader.CONFIG_FILE_NAME);
if (!fs.existsSync(configPath)) {
BrickConfigLoader.log("No brick.json found, using default paths");
return {};
}
try {
const configContent = fs.readFileSync(configPath, "utf-8");
const config = JSON.parse(configContent);
BrickConfigLoader.validateConfig(config, projectRoot);
const normalizedConfig = BrickConfigLoader.normalizeConfig(config, projectRoot);
BrickConfigLoader.log(`Loaded configuration from ${BrickConfigLoader.CONFIG_FILE_NAME}`);
return normalizedConfig;
} catch (error) {
if (error instanceof SyntaxError) throw new Error(`Invalid JSON in ${BrickConfigLoader.CONFIG_FILE_NAME}: ${error.message}`);
throw error;
}
}
/**
* Get default configuration
*/
static getDefaults() {
return { ...BrickConfigLoader.DEFAULT_CONFIG };
}
/**
* Merge configuration with defaults
*/
static mergeWithDefaults(config) {
const defaults = BrickConfigLoader.getDefaults();
return { output: {
typescript: config.output?.typescript || defaults.output.typescript,
ios: config.output?.ios || defaults.output.ios,
android: config.output?.android || defaults.output.android,
androidAppPath: config.output?.androidAppPath || defaults.output.androidAppPath
} };
}
/**
* Validate configuration
*/
static validateConfig(config, _projectRoot) {
if (config.output) {
const { typescript, ios, android, androidAppPath } = config.output;
if (typescript && path.isAbsolute(typescript)) throw new Error(`output.typescript must be a relative path, got: ${typescript}`);
if (ios && path.isAbsolute(ios)) throw new Error(`output.ios must be a relative path, got: ${ios}`);
if (android && path.isAbsolute(android)) throw new Error(`output.android must be a relative path, got: ${android}`);
if (androidAppPath && path.isAbsolute(androidAppPath)) throw new Error(`output.androidAppPath must be a relative path, got: ${androidAppPath}`);
const dangerousPaths = [
".",
"..",
"/",
"~"
];
if (typescript && dangerousPaths.includes(typescript)) throw new Error(`output.typescript cannot be '${typescript}'`);
if (ios && dangerousPaths.includes(ios)) throw new Error(`output.ios cannot be '${ios}'`);
if (android && dangerousPaths.includes(android)) throw new Error(`output.android cannot be '${android}'`);
if (androidAppPath && dangerousPaths.includes(androidAppPath)) throw new Error(`output.androidAppPath cannot be '${androidAppPath}'`);
if (typescript?.startsWith("..")) BrickConfigLoader.warn(`output.typescript goes outside project root: ${typescript}`);
if (ios?.startsWith("..")) BrickConfigLoader.warn(`output.ios goes outside project root: ${ios}`);
if (android?.startsWith("..")) BrickConfigLoader.warn(`output.android goes outside project root: ${android}`);
if (androidAppPath?.startsWith("..")) BrickConfigLoader.warn(`output.androidAppPath goes outside project root: ${androidAppPath}`);
}
}
/**
* Normalize configuration paths
* Keep paths relative for external use, but validate they're valid
*/
static normalizeConfig(config, _projectRoot) {
if (!config.output) return config;
return {
...config,
output: config.output ? {
typescript: config.output.typescript,
ios: config.output.ios,
android: config.output.android,
androidAppPath: config.output.androidAppPath
} : void 0
};
}
/**
* Get absolute path from config path
*/
static getAbsolutePath(configPath, projectRoot, defaultPath) {
const relativePath = configPath || defaultPath;
return path.join(projectRoot, relativePath);
}
static log(message) {
console.log(`${pc.bold(pc.cyan("[Brick]"))} ${message}`);
}
static warn(message) {
console.warn(`${pc.bold(pc.yellow("[Brick]"))} Warning: ${message}`);
}
};
//#endregion
//#region src/library-generator.ts
/**
* LibraryGenerator - Handles library-specific code generation
* Only generates types/protocols for the current library package
*/
var LibraryGenerator = class {
config;
platformRegistry;
constructor(config, platformRegistry) {
this.config = config;
this.platformRegistry = platformRegistry;
}
/**
* Main library generation method - Generates only types/protocols for current library
*/
async generateLibraryCode(modules) {
const allGeneratedFiles = [];
if (!this.platformRegistry) {
this.log("β οΈ No platform registry available, skipping library generation");
return allGeneratedFiles;
}
this.log(`π¦ Generating library code for ${modules.length} modules...`);
if (this.shouldGenerateIos()) try {
const iosFiles = await this.generateIosLibraryCode(modules);
allGeneratedFiles.push(...iosFiles);
this.log(`β
Generated ${iosFiles.length} iOS library files`);
} catch (error) {
this.logError(`iOS library generation failed: ${error instanceof Error ? error.message : String(error)}`);
}
if (this.shouldGenerateAndroid()) try {
const androidFiles = await this.generateAndroidLibraryCode(modules);
allGeneratedFiles.push(...androidFiles);
this.log(`β
Generated ${androidFiles.length} Android library files`);
} catch (error) {
this.logError(`Android library generation failed: ${error instanceof Error ? error.message : String(error)}`);
}
return allGeneratedFiles;
}
/**
* Generates iOS library code (Swift types and protocols)
*/
async generateIosLibraryCode(modules) {
const generatedFiles = [];
const iosDir = path.join(this.config.projectRoot, "ios/typegen");
await fs.ensureDir(iosDir);
for (const module of modules) try {
const typesFile = await this.generateSwiftTypes(module, iosDir);
generatedFiles.push(typesFile);
const protocolFile = await this.generateSwiftProtocol(module, iosDir);
generatedFiles.push(protocolFile);
this.log(`π± Generated iOS library files for ${module.moduleName}`);
} catch (error) {
this.logError(`Failed to generate iOS library files for ${module.moduleName}: ${error instanceof Error ? error.message : String(error)}`);
}
return generatedFiles;
}
/**
* Generates Android library code (Kotlin types and interfaces)
*/
async generateAndroidLibraryCode(modules) {
const generatedFiles = [];
const baseAndroidDir = path.join(this.config.projectRoot, "android", "src", "main", "kotlin");
for (const module of modules) try {
const androidPackage = module.sourceModule?.brickConfig?.android?.package || module.sourceModule?.androidPackage;
if (!androidPackage) {
this.log(`β οΈ Skipping Android generation for ${module.moduleName}: no android.package defined`);
continue;
}
const packagePath = androidPackage.replace(/\./g, "/");
const androidDir = path.join(baseAndroidDir, packagePath, "typegen");
await fs.ensureDir(androidDir);
const typesFile = await this.generateKotlinTypes(module, androidDir);
generatedFiles.push(typesFile);
const interfaceFile = await this.generateKotlinInterface(module, androidDir);
generatedFiles.push(interfaceFile);
this.log(`π€ Generated Android library files for ${module.moduleName} in ${androidDir}`);
} catch (error) {
this.logError(`Failed to generate Android library files for ${module.moduleName}: ${error instanceof Error ? error.message : String(error)}`);
}
return generatedFiles;
}
/**
* Generates Swift types file for library
*/
async generateSwiftTypes(module, outputDir) {
const filename = `${module.moduleName}Types.swift`;
const outputPath = path.join(outputDir, filename);
if (this.platformRegistry) {
const iosPlatform = this.platformRegistry.getIOSPlatform();
if (iosPlatform) {
const content = await iosPlatform.generateLibraryTypes(module);
await fs.writeFile(outputPath, content);
this.logGenerated(outputPath);
}
}
return outputPath;
}
/**
* Generates Swift protocol file for library
*/
async generateSwiftProtocol(module, outputDir) {
const filename = `${module.moduleName}Spec.swift`;
const outputPath = path.join(outputDir, filename);
if (this.platformRegistry) {
const iosPlatform = this.platformRegistry.getIOSPlatform();
if (iosPlatform) {
let content = await iosPlatform.generateLibraryProtocol(module);
if (module.events && module.events.length > 0) {
const eventHelpers = this.generateEventHelpersExtension(module);
content += `\n${eventHelpers}`;
}
await fs.writeFile(outputPath, content);
this.logGenerated(outputPath);
}
}
return outputPath;
}
/**
* Generates event helper extension for module
*/
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 Kotlin types file for library
*/
async generateKotlinTypes(module, outputDir) {
const filename = `${module.moduleName}Types.kt`;
const outputPath = path.join(outputDir, filename);
if (this.platformRegistry) {
const androidPlatform = this.platformRegistry.getAndroidPlatform();
if (androidPlatform) {
const content = await androidPlatform.generateLibraryTypes(module);
await fs.writeFile(outputPath, content);
this.logGenerated(outputPath);
}
}
return outputPath;
}
/**
* Generates Kotlin interface file for library
*/
async generateKotlinInterface(module, outputDir) {
const filename = `${module.moduleName}Spec.kt`;
const outputPath = path.join(outputDir, filename);
if (this.platformRegistry) {
const androidPlatform = this.platformRegistry.getAndroidPlatform();
if (androidPlatform) {
const content = await androidPlatform.generateLibraryInterface(module);
await fs.writeFile(outputPath, content);
this.logGenerated(outputPath);
}
}
return outputPath;
}
/**
* Generate types for local modules in app projects
* This is called for modules found via *.brick.ts scanning in app projects
*/
async generateLocalModuleTypes(modules) {
const generatedFiles = [];
const localModules = modules.filter((module) => {
const isFromNodeModules = (module.sourceModule?.path || "").includes("node_modules");
const isLibraryPackage = module.sourceModule?.brickConfig !== void 0;
return !isFromNodeModules && !isLibraryPackage;
});
if (localModules.length === 0) {
this.log(`π¦ No local modules found (all modules are from packages)`);
return generatedFiles;
}
this.log(`π¦ Generating types for ${localModules.length} local modules to typegen/...`);
try {
const iosFiles = await this.generateIosLibraryCode(localModules);
generatedFiles.push(...iosFiles);
this.log(`β
Generated ${iosFiles.length} iOS type files for local modules`);
} catch (error) {
this.logError(`iOS type generation failed for local modules: ${error instanceof Error ? error.message : String(error)}`);
}
try {
const androidFiles = await this.generateAndroidLibraryCode(localModules);
generatedFiles.push(...androidFiles);
this.log(`β
Generated ${androidFiles.length} Android type files for local modules`);
} catch (error) {
this.logError(`Android type generation failed for local modules: ${error instanceof Error ? error.message : String(error)}`);
}
return generatedFiles;
}
shouldGenerateIos() {
const selectedPlatforms = this.config.selectedPlatforms;
return !selectedPlatforms || selectedPlatforms.includes("ios");
}
shouldGenerateAndroid() {
const selectedPlatforms = this.config.selectedPlatforms;
return !selectedPlatforms || selectedPlatforms.includes("android");
}
log(message) {
console.log(`${pc.bold(pc.green("[LibraryGen]"))} ${message}`);
}
logError(message) {
console.error(`${pc.bold(pc.green("[LibraryGen]"))} ${pc.red(message)}`);
}
logGenerated(filepath) {
const absolutePath = path.resolve(filepath);
console.log(`${pc.bold(pc.green("[LibraryGen]"))} Generated artifact: ${absolutePath}`);
}
};
//#endregion
//#region src/signature-mapper.ts
/**
* React Native Codegenμ΄ μμ±ν ν€λ νμΌμμ private μ κ·Ό μ μ΄μλ₯Ό μ κ±°
*/
function removePrivateAccessModifiers(headerContent) {
return headerContent.replace(/(\s*)private:\s*\n(\s*)NSDictionary \*_v;/g, "$1$2NSDictionary *_v;");
}
//#endregion
//#region src/source-map.ts
var SourceMapRegistry = class {
mappings = /* @__PURE__ */ new Map();
/**
* Add a source mapping
*/
addMapping(mapping) {
this.mappings.set(mapping.generatedName, mapping);
}
/**
* Get source mapping by generated name
*/
getMapping(generatedName) {
return this.mappings.get(generatedName);
}
/**
* Find mapping by partial match (e.g., "onAsd" matches "Calculator_onAsd")
*/
findMapping(partialName) {
const exact = this.mappings.get(partialName);
if (exact) return exact;
const lowerPartial = partialName.toLowerCase();
for (const [key, value] of this.mappings.entries()) if (key.toLowerCase().includes(lowerPartial)) return value;
}
/**
* Get all mappings
*/
getAllMappings() {
return Array.from(this.mappings.values());
}
/**
* Clear all mappings
*/
clear() {
this.mappings.clear();
}
/**
* Export mappings to JSON for debugging
*/
toJSON() {
const result = {};
for (const [key, value] of this.mappings.entries()) result[key] = value;
return result;
}
};
/**
* Global source map registry instance
*/
const globalSourceMap = new SourceMapRegistry();
//#endregion
//#region src/main-index.ts
/**
* Brick Codegen - Main Entry Point
* Clean architecture with original functionality preserved
*/
var BrickCodegen = class {
config;
scanner;
parser;
generator;
libraryGenerator;
platformRegistry;
sourceMap;
constructor(config) {
this.sourceMap = new SourceMapRegistry();
const brickConfig = BrickConfigLoader.load(config.projectRoot);
const mergedBrickConfig = BrickConfigLoader.mergeWithDefaults(brickConfig);
this.config = {
projectRoot: config.projectRoot,
outputTypescript: config.outputTypescript || mergedBrickConfig.output.typescript,
outputIos: config.outputIos || mergedBrickConfig.output.ios,
outputAndroid: config.outputAndroid || mergedBrickConfig.output.android,
outputAndroidAppPath: config.outputAndroidAppPath || mergedBrickConfig.output.androidAppPath,
debug: config.debug || false,
platforms: config.platforms,
clean: config.clean !== false
};
this.scanner = new Scanner(this.createComponentConfig());
this.parser = new Parser(this.createComponentConfig());
this.platformRegistry = new PlatformRegistry(this.createComponentConfig());
this.generator = new Generator(this.createComponentConfig(), this.platformRegistry);
this.libraryGenerator = new LibraryGenerator(this.createComponentConfig(), this.platformRegistry);
}
/**
* Creates configuration format for internal components
*/
createComponentConfig() {
const selectedPlatforms = this.config.platforms;
return {
projectRoot: this.config.projectRoot,
outputTypescript: this.config.outputTypescript,
outputIos: this.config.outputIos,
outputAndroid: this.config.outputAndroid,
outputAndroidAppPath: this.config.outputAndroidAppPath,
libraryMode: this.config.libraryMode,
debug: this.config.debug || false,
selectedPlatforms
};
}
/**
* Clean output directories before generation
*/
async cleanOutputDirectories(isLibrary) {
const dirsToClean = [];
if (isLibrary) {
if (!this.config.platforms || this.config.platforms.includes("ios")) {
const iosTypegen = path.join(this.config.projectRoot, "ios", "typegen");
dirsToClean.push(iosTypegen);
}
if (!this.config.platforms || this.config.platforms.includes("android")) {
const androidTypegen = path.join(this.config.projectRoot, "android", "src", "main", "kotlin");
if (await fs.pathExists(androidTypegen)) {
const typeDirs = await glob("**/typegen", {
cwd: androidTypegen,
absolute: true
});
dirsToClean.push(...typeDirs);
}
}
} else {
const typescriptOut = path.join(this.config.projectRoot, this.config.outputTypescript);
dirsToClean.push(typescriptOut);
if (!this.config.platforms || this.config.platforms.includes("ios")) {
const iosOut = path.join(this.config.projectRoot, this.config.outputIos);
dirsToClean.push(iosOut);
}
if (!this.config.platforms || this.config.platforms.includes("android")) {
const androidOut = path.join(this.config.projectRoot, this.config.outputAndroid);
dirsToClean.push(androidOut);
}
}
const projectRoot = path.resolve(this.config.projectRoot);
for (const dir of dirsToClean) {
const resolvedDir = path.resolve(dir);
if (resolvedDir === projectRoot || resolvedDir === path.dirname(projectRoot) || resolvedDir.endsWith("/src") || resolvedDir.endsWith("/src/")) {
this.log(`β οΈ Skipping dangerous path for cleaning: ${resolvedDir}`);
continue;
}
if (await fs.pathExists(resolvedDir)) {
this.log(`ποΈ Cleaning ${path.relative(projectRoot, resolvedDir)}/`);
try {
await fs.remove(resolvedDir);
} catch (error) {
this.log(`β οΈ Failed to clean ${resolvedDir}: ${error instanceof Error ? error.message : String(error)}`);
}
}
}
}
/**
* Main generation method
*/
async generate() {
const startTime = Date.now();
const result = {
success: false,
moduleCount: 0,
errors: [],
warnings: [],
generatedFiles: [],
duration: 0
};
try {
const packageJsonPath = path.join(this.config.projectRoot, "package.json");
let isLibrary = false;
if (await fs.pathExists(packageJsonPath)) isLibrary = !!(await fs.readJson(packageJsonPath)).brickModule;
this.config.libraryMode = isLibrary;
if (this.config.clean) {
this.log("π§Ή Cleaning output directories...");
await this.cleanOutputDirectories(isLibrary);
this.log("β
Output directories cleaned");
}
if (this.config.platforms) {
const selectedPlatforms = this.config.platforms.join(", ");
this.log(`π¦ ${isLibrary ? "Library" : "App"} mode - generating for selected platforms: ${selectedPlatforms}`);
} else this.log(`π¦ ${isLibrary ? "Library" : "App"} mode - generating for all platforms`);
const componentConfig = this.createComponentConfig();
this.scanner = new Scanner(componentConfig);
this.parser = new Parser(componentConfig);
this.platformRegistry = new PlatformRegistry(componentConfig);
this.generator = new Generator(componentConfig, this.platformRegistry);
this.libraryGenerator = new LibraryGenerator(componentConfig, this.platformRegistry);
this.log("π Scanning for Brick modules...");
const scannedModules = await this.scanner.scanBrickModules();
if (this.config.debug) console.log(`[scanner] Found ${scannedModules.length} modules:`, scannedModules.map((m) => m.name));
if (scannedModules.length === 0) {
result.warnings.push("No Brick modules found in dependencies");
result.success = true;
result.duration = Date.now() - startTime;
return result;
}
this.log(`π¦ Found ${scannedModules.length} Brick modules`);
this.log("π Parsing module specifications...");
const parsedModules = [];
const parsingErrors = [];
for (const module of scannedModules) try {
const parsed = await this.parser.parseModuleSpec(module);
parsedModules.push(...parsed);
for (const parsedModule of parsed) if (parsedModule.events) for (const event of parsedModule.events) {
const generatedName = `${parsedModule.moduleName}_on${event.name.charAt(0).toUpperCase()}${event.name.slice(1)}`;
this.sourceMap.addMapping({
generatedName,
sourceFile: event.sourceFile || parsedModule.specPath,
sourceName: event.name,
sourceLine: event.sourceLine,
sourceColumn: event.sourceColumn,
moduleName: parsedModule.moduleName,
type: "event"
});
}
} catch (error) {
const brickError = error instanceof BrickCodegenError ? error : wrapError(error, {
filePath: module.specPath,
moduleName: module.name
});
parsingErrors.push(brickError);
result.errors.push(brickError.message);
}
if (parsingErrors.length > 0) {
result.detailedErrors = parsingErrors;
result.success = false;
result.duration = Date.now() - startTime;
return result;
}
if (parsedModules.length === 0) {
result.errors.push("No valid module specifications found");
result.duration = Date.now() - startTime;
return result;
}
result.moduleCount = parsedModules.length;
this.platformRegistry.setScannedModules(scannedModules);
if (isLibrary) {
this.log("ποΈ Library mode: Generating types to typegen/...");
const libraryFiles = await this.libraryGenerator.generateLibraryCode(parsedModules);
result.generatedFiles.push(...libraryFiles);
result.success = true;
this.log("β
Library type generation to typegen/ completed successfully!");
} else {
this.log("β‘ User project mode: Generating bridge code to .brick/...");
const generatedFiles = await this.generator.generateAll(parsedModules);
result.generatedFiles.push(...generatedFiles);
this.log("π Running React Native codegen...");
try {
await this.runCodegen();
await this.moveCodegenResults();
this.log("β
React Native codegen completed successfully!");
this.log("π§ Post-processing generated header files...");
await this.postProcessHeaderFiles();
this.log("β
Header file post-processing completed!");
} catch (error) {
const codegenError = createCodegenError(error instanceof Error ? error.message : String(error), path.join(this.config.projectRoot, this.config.outputTypescript, "src/spec/NativeBrickModule.ts"), this.sourceMap);
if (!result.detailedErrors) result.detailedErrors = [];
result.detailedErrors.push(codegenError);
result.errors.push(codegenError.message);
this.log("β© React Native codegen failed - see errors above");
throw codegenError;
}
result.success = true;
this.log("β
Code generation completed successfully!");
}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
result.errors.push(`Code generation failed: ${errorMessage}`);
this.log("β Code generation failed:", error);
}
result.duration = Date.now() - startTime;
return result;
}
async runCodegen() {
const brickDir = path.join(this.config.projectRoot, this.config.outputTypescript);
const selectedPlatforms = this.config.platforms;
try {
const codegenScriptPath = __require.resolve("react-native/scripts/generate-codegen-artifacts.js", { paths: [this.config.projectRoot] });
if (!selectedPlatforms || selectedPlatforms.includes("ios")) {
this.log("π± Running React Native codegen for iOS...");
const iosResult = await execa("node", [
codegenScriptPath,
"-p",
brickDir,
"-t",
"ios",
"-o",
"ios"
], { cwd: this.config.projectRoot });
if (iosResult.stdout) this.log("iOS codegen output:", iosResult.stdout);
}
if (!selectedPlatforms || selectedPlatforms.includes("android")) {
this.log("π€ Running React Native codegen for Android...");
const androidResult = await execa("node", [
codegenScriptPath,
"-p",
brickDir,
"-t",
"android",
"-o",
"android"
], { cwd: this.config.projectRoot });
if (androidResult.stdout) this.log("Android codegen output:", androidResult.stdout);
}
} catch (error) {
throw new Error(`React Native codegen execution failed: ${error instanceof Error ? error.message : String(error)}`);
}
}
async moveCodegenResults() {
const selectedPlatforms = this.config.platforms;
if (!selectedPlatforms || selectedPlatforms.includes("ios")) await this.moveIOSCodegenResults();
if (!selectedPlatforms || selectedPlatforms.includes("android")) await this.moveAndroidCodegenResults();
}
async moveIOSCodegenResults() {
const iosBrickDir = path.join(this.config.projectRoot, this.config.outputIos);
const buildDir = path.join(this.config.projectRoot, "ios", "build");
const generatedDir = path.join(buildDir, "generated", "ios");
try {
await fs.ensureDir(iosBrickDir);
if (await fs.pathExists(generatedDir)) {
const generatedFiles = await fs.readdir(generatedDir);
for (const file of generatedFiles) {
const sourcePath = path.join(generatedDir, file);
const targetPath = path.join(iosBrickDir, file);
if (await fs.pathExists(targetPath)) await fs.remove(targetPath);
await fs.move(sourcePath, targetPath);
const absolutePath = path.resolve(targetPath);
console.log(`${pc.bold(pc.green("[Brick]"))} Generated artifact: ${absolutePath}`);
}
await this.filterBrickModuleFiles(iosBrickDir);
if (await fs.pathExists(buildDir)) {
await fs.remove(buildDir);
this.log("ποΈ Cleaned up build directory");
}
} else this.log("β οΈ No iOS codegen results found to move");
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
this.log("β οΈ Failed to move iOS codegen results:", errorMessage);
}
}
async moveAndroidCodegenResults() {
const androidBuildDir = path.join(this.config.projectRoot, "android", "android", "app", "build");
const generatedDir = path.join(androidBuildDir, "generated", "source", "codegen");
const brickDir = path.join(this.config.projectRoot, this.config.outputAndroid);
const javaTargetDir = path.join(brickDir, "src", "main", "java");
try {
if (await fs.pathExists(generatedDir)) {
this.log("π Processing Android codegen results...");
const jniSourceDir = path.join(generatedDir, "jni");
this.log(`π Checking JNI source directory: ${jniSourceDir}`);
if (await fs.pathExists(jniSourceDir)) {
const jniTargetDir = path.join(this.config.projectRoot, this.config.outputAndroidAppPath, "build/generated/autolinking/src/main/jni");
this.log(`π JNI target directory: ${jniTargetDir}`);
await fs.ensureDir(jniTargetDir);
await fs.copy(jniSourceDir, jniTargetDir, { overwrite: true });
this.log("π Copied React Native Codegen JNI files to autolinking directory");
const jniFiles = await glob("**/*.{cpp,h}", { cwd: jniTargetDir });
for (const file of jniFiles) {
const absolutePath = path.resolve(jniTargetDir, file);
console.log(`${pc.bold(pc.green("[Brick]"))} Generated artifact: ${absolutePath}`);
}
} else this.log(`β οΈ JNI source directory not found: ${jniSourceDir}`);
const javaSourceDir = path.join(generatedDir, "java");
if (await fs.pathExists(javaSourceDir)) {
await fs.ensureDir(javaTargetDir);
const facebookDir = path.join(javaSourceDir, "com", "facebook");
if (await fs.pathExists(facebookDir)) {
const targetFacebookDir = path.join(javaTargetDir, "com", "facebook");
await fs.ensureDir(targetFacebookDir);
await fs.copy(facebookDir, targetFacebookDir, { overwrite: true });
this.log("π Copied Facebook spec files to app directory");
const specFiles = await glob("**/*.java", { cwd: targetFacebookDir });
for (const file of specFiles) {
const absolutePath = path.resolve(targetFacebookDir, file);
console.log(`${pc.bold(pc.green("[Brick]"))} Generated artifact: ${absolutePath}`);
}
}
}
if (await fs.pathExists(androidBuildDir)) {
await fs.remove(androidBuildDir);
this.log("ποΈ Cleaned up Android build directory");
}
} else this.log("β οΈ No Android codegen results found to move");
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
this.log("β οΈ Failed to move Android codegen results:", errorMessage);
}
}
async filterBrickModuleFiles(iosBrickDir) {
const KEEP_PATTERNS = [
/^BrickModule.*$/,
/^BrickCodegen.*$/,
/^BrickModuleProvider.*$/,
/^BrickModuleSpec.*$/,
/^Types$/
];
const SYSTEM_FILES_TO_REMOVE = [
"RCTAppDependencyProvider.h",
"RCTAppDependencyProvider.mm",
"RCTModulesConformingToProtocolsProvider.h",
"RCTModulesConformingToProtocolsProvider.mm",
"RCTModuleProviders.h",
"RCTModuleProviders.mm",
"RCTThirdPartyComponentsProvider.h",
"RCTThirdPartyComponentsProvider.mm",
"RCTUnstableModulesRequiringMainQueueSetupProvider.h",
"RCTUnstableModulesRequiringMainQueueSetupProvider.mm",
"ReactAppDependencyProvider.podspec",
"ReactCodegen.podspec"
];
try {
for (const fileName of SYSTEM_FILES_TO_REMOVE) {
const filePath = path.join(iosBrickDir, fileName);
if (await fs.pathExists(filePath)) await fs.remove(filePath);
}
const allFiles = await fs.readdir(iosBrickDir);
for (const file of allFiles) {
const filePath = path.join(iosBrickDir, file);
if ((await fs.stat(filePath)).isDirectory() && file.match(/^BrickModuleSpec$/)) continue;
if (!KEEP_PATTERNS.some((pattern) => pattern.test(file))) {
await fs.remove(filePath);
this.log(`ποΈ Removed non-brick file: ${file}`);
}
}
this.log("β
Filtered to keep only Brick module files");
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
this.log("β οΈ Failed to filter files:", errorMessage);
}
}
async postProcessHeaderFiles() {
const iosBrickDir = path.join(this.config.projectRoot, this.config.outputIos);
try {
const headerFiles = await glob("**/*.h", {
cwd: iosBrickDir,
absolute: true
});
for (const headerFile of headerFiles) try {
const content = await fs.readFile(headerFile, "utf8");
const processedContent = removePrivateAccessModifiers(content);
if (processedContent !== content) {
await fs.writeFile(headerFile, processedContent, "utf8");
this.log(`π§ Processed header file: ${path.relative(iosBrickDir, headerFile)}`);
}
} catch {}
} catch (error) {
const errorMessage = error instanceof Error ? error.message : String(error);
this.log("β οΈ Failed to post-process header files:", errorMessage);
}
}
log(...args) {
console.log(pc.gray("[brick-codegen]"), ...args);
}
};
//#endregion
//#region src/cli.ts
/**
* Brick Codegen CLI
* Simple code generation for Brick modules
*/
const __dirname = dirname(fileURLToPath(import.meta.url));
const packageJson = JSON.parse(readFileSync(resolve(__dirname, "..", "package.json"), "utf-8"));
function log(message) {
console.log(`${pc.bold(pc.green("[Brick]"))} ${message}`);
}
function logError(message) {
console.error(`${pc.bold(pc.green("[Brick]"))} ${pc.red(message)}`);
}
program.name("brick-codegen").description("Brick Module Framework - React Native TurboModule Code Generator").version(packageJson.version).option("--debug", "Enable debug logging", false).option("--output-ios <path>", "Custom iOS output directory").option("--output-android <path>", "Custom Android output directory").option("--platform <platform>", "Target platform: ios or android").option("--projectRoot <path>", "Explicit project root (recommended for brownfield/bundled setups)").option("--clean", "Clean output directories before generating code (default: true)", true).option("--no-clean", "Skip cleaning output directories before generating code");
program.action(async (options) => {
await runGenerate(options);
});
async function runGenerate(options) {
try {
const result = await new BrickCodegen(await buildConfig(options)).generate();
if (result.success) log("Done.");
else {
if (result.detailedErrors && result.detailedErrors.length > 0) {
console.error(`\n${pc.bold(pc.red("β Code generation failed with errors:\n"))}`);
const formattedErrors = formatErrors(result.detailedErrors);
console.error(formattedErrors);
const summary = getErrorSummary(result.detailedErrors, result.moduleCount);
console.error(`\n${pc.bold(pc.yellow("π Summary:"))}`);
console.error(pc.yellow(summary));
} else if (result.errors.length > 0) {
console.error(`\n${pc.bold(pc.red("β Errors:\n"))}`);
for (const error of result.errors) logError(error);
}
console.error("");
process.exit(1);
}
} catch (error) {
logError(`${error instanceof Error ? error.message : "Unknown error"}`);
process.exit(1);
}
}
async function buildConfig(options) {
const projectRoot = options.projectRoot || process.cwd();
const platforms = parsePlatformOptions(options);
return {
projectRoot,
outputIos: options.outputIos,
outputAndroid: options.outputAndroid,
debug: options.debug || false,
platforms,
clean: options.clean !== false
};
}
function parsePlatformOptions(options) {
if (!options.platform) return;
const platform = options.platform.trim().toLowerCase();
if (!["ios", "android"].includes(platform)) {
logError(`Invalid platform: ${platform}. Supported platforms: ios, android`);
process.exit(1);
}
return [platform];
}
process.on("uncaughtException", (error) => {
logError(`Uncaught exception: ${error.message}`);
process.exit(1);
});
process.on("unhandledRejection", (reason) => {
logError(`Unhandled rejection: ${reason}`);
process.exit(1);
});
program.parse();
//#endregion
export { };
//# sourceMappingURL=cli.js.map