UNPKG

brick-codegen

Version:

Better React Native native module development

872 lines (865 loc) β€’ 34.4 kB
#!/usr/bin/env node 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