nitro-codegen
Version:
The code-generator for react-native-nitro-modules.
124 lines (123 loc) • 5.97 kB
JavaScript
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, moduleName, ref = 'main') {
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() {
try {
execSync('git --version');
return true;
}
catch (error) {
return false;
}
}
async function downloadGitHubFolder(owner, repo, branch, folder, outputPath) {
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, dest) {
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, templateName, replacementValue) {
const replaceInFile = async (filePath) => {
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) => {
return name.replaceAll(`$$${templateName}$$`, replacementValue);
};
const processDirectory = async (currentDir) => {
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);
}