UNPKG

nitro-codegen

Version:

The code-generator for react-native-nitro-modules.

187 lines (162 loc) 5.91 kB
import path from 'path' import { prettifyDirectory } from './prettifyDirectory.js' import fs from 'fs/promises' import { existsSync } from 'fs' import { Logger } from './Logger.js' import chalk from 'chalk' import { execSync } from 'child_process' import { randomUUID } from 'crypto' export async function initNewNitroModule( baseDirectory: string, moduleName: string, ref: string = 'main' ): Promise<void> { Logger.info( `⚙️ Creating new Nitro Module "${chalk.bold(moduleName)}" in ${chalk.underline(prettifyDirectory(baseDirectory))}...` ) const directory = path.join(baseDirectory, moduleName) if (existsSync(directory)) { Logger.error( `❌ A folder named "${chalk.underline(moduleName)}" already exists in the directory ${chalk.underline(prettifyDirectory(baseDirectory))}!` ) process.exit(1) } await fs.mkdir(directory) const modulePath = await downloadGitHubFolder( 'mrousavy', 'nitro', ref, 'packages/template', directory ) Logger.info(`🏗️ Constructing template...`) const cleanLibraryName = moduleName.replace('react-native-', '') const cxxNamespace = cleanLibraryName.replaceAll('-', '') let camelCaseName = cleanLibraryName .split('-') .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) .join('') if (!camelCaseName.startsWith('Nitro')) { camelCaseName = 'Nitro' + camelCaseName } await replaceTemplate(modulePath, `cxxNamespace`, cxxNamespace) await replaceTemplate(modulePath, `androidNamespace`, cxxNamespace) await replaceTemplate(modulePath, `androidCxxLibName`, camelCaseName) await replaceTemplate(modulePath, `iosModuleName`, camelCaseName) await replaceTemplate(modulePath, `packageName`, moduleName) await replaceTemplate(modulePath, `packageDescription`, moduleName) Logger.info( `🎉 Created Nitro Module "${chalk.bold(moduleName)}" in ${chalk.underline(prettifyDirectory(directory))}!` ) Logger.info( `👉 To install dependencies, use your package manager of choice (e.g. ${chalk.bold('npm install')})` ) Logger.info( `👉 To create your first Hybrid Object, add a ${chalk.underline(`*.nitro.ts`)} file, declare a TypeScript interface that extends ${chalk.bold('HybridObject<...>')}, and run ${chalk.bold('npx nitro-codegen')}.` ) Logger.info( `👉 To test your module in an app, create a new React Native app somewhere (e.g. in ${chalk.underline(`./${moduleName}/example`)}) and add ${chalk.bold(moduleName)} as a local dependency.` ) } async function isGitInstalled(): Promise<boolean> { try { execSync('git --version') return true } catch (error) { return false } } async function downloadGitHubFolder( owner: string, repo: string, branch: string, folder: string, outputPath: string ): Promise<string> { if (!isGitInstalled()) { console.error( `❌ ${chalk.bold('git')} is not installed or available in the current path! Make sure to install ${chalk.bold('git')} and try again.` ) process.exit(1) } const repoUrl = `https://github.com/${owner}/${repo}` Logger.info(`⏳ Cloning ${chalk.underline(repoUrl)}...`) const tempPath = randomUUID() const tempDir = path.join(outputPath, tempPath) execSync( `git clone --depth 1 --filter=blob:none -b ${branch} --quiet --sparse ${repoUrl} ${tempDir}` ) const prettyOutputPath = prettifyDirectory(outputPath) const initialDir = process.cwd() try { process.chdir(tempDir) Logger.debug(`⏳ Checking out ${chalk.underline(folder)}...`) execSync(`git sparse-checkout set ${folder}`) Logger.debug( `📁 Copying files from ${chalk.underline(`${prettyOutputPath}/${tempPath}`)} to ${chalk.underline(prettyOutputPath)}...` ) const sourcePath = path.join(tempDir, folder) await fs.mkdir(outputPath, { recursive: true }) await copyFolder(sourcePath, outputPath) return outputPath } finally { // change dir back to original process.chdir(initialDir) Logger.debug( `🗑️ Removing temporary folder ${chalk.underline(prettifyDirectory(tempDir))}...` ) await fs.rm(tempDir, { recursive: true, force: true }) } } async function copyFolder(src: string, dest: string) { const entries = await fs.readdir(src, { withFileTypes: true }) for (const entry of entries) { const srcPath = path.join(src, entry.name) const destPath = path.join(dest, entry.name) if (entry.isDirectory()) { await fs.mkdir(destPath, { recursive: true }) await copyFolder(srcPath, destPath) } else { await fs.copyFile(srcPath, destPath) } } } async function replaceTemplate( dir: string, templateName: string, replacementValue: string ): Promise<void> { const replaceInFile = async (filePath: string) => { const content = await fs.readFile(filePath, 'utf8') const updatedContent = content.replaceAll( `$$${templateName}$$`, replacementValue ) if (content !== updatedContent) { await fs.writeFile(filePath, updatedContent, 'utf8') } } const replaceInName = (name: string) => { return name.replaceAll(`$$${templateName}$$`, replacementValue) } const processDirectory = async (currentDir: string) => { const entries = await fs.readdir(currentDir, { withFileTypes: true }) for (const entry of entries) { const oldPath = path.join(currentDir, entry.name) const updatedName = replaceInName(entry.name) const newPath = path.join(currentDir, updatedName) if (oldPath !== newPath) { await fs.rename(oldPath, newPath) } if (entry.isDirectory()) { await processDirectory(newPath) } else if (entry.isFile()) { await replaceInFile(newPath) } } } if (!existsSync(dir)) { throw new Error(`The directory "${dir}" does not exist.`) } await processDirectory(dir) }