create-nitro-lib
Version:
CLI tool to generate React Native Nitro Modules templates
320 lines (281 loc) • 7.06 kB
JavaScript
import path from 'node:path';
import fs from 'fs-extra';
import { toPascalCase } from '../utils/string.js';
export async function createNitroConfig(packageDir, config) {
const pascalName = toPascalCase(config.name);
const nitroConfig = {
$schema: 'https://nitro.margelo.com/nitro.schema.json',
cxxNamespace: [pascalName.toLowerCase()],
ios: {
iosModuleName: pascalName,
},
android: {
androidNamespace: [pascalName.toLowerCase()],
androidCxxLibName: pascalName,
},
autolinking: {
[pascalName]: {
swift: `Hybrid${pascalName}`,
kotlin: `Hybrid${pascalName}`,
},
},
ignorePaths: ['**/node_modules'],
};
await fs.writeJson(path.join(packageDir, 'nitro.json'), nitroConfig, {
spaces: 2,
});
}
export async function createPackageConfigFiles(packageDir, config) {
const tsconfigContent = {
include: ['src'],
compilerOptions: {
composite: true,
rootDir: 'src',
allowUnreachableCode: false,
allowUnusedLabels: false,
esModuleInterop: true,
forceConsistentCasingInFileNames: true,
jsx: 'react-jsx',
lib: ['esnext'],
module: 'esnext',
target: 'esnext',
moduleResolution: 'node',
noEmit: false,
noFallthroughCasesInSwitch: true,
noImplicitReturns: true,
noImplicitUseStrict: false,
noStrictGenericChecks: false,
noUncheckedIndexedAccess: true,
noUnusedLocals: true,
noUnusedParameters: true,
resolveJsonModule: true,
skipLibCheck: true,
strict: true,
},
};
const reactNativeConfigContent = `// https://github.com/react-native-community/cli/blob/main/docs/dependencies.md
module.exports = {
dependency: {
platforms: {
/**
* @type {import('@react-native-community/cli-types').IOSDependencyParams}
*/
ios: {},
/**
* @type {import('@react-native-community/cli-types').AndroidDependencyParams}
*/
android: {},
},
},
}
`;
await fs.writeJson(path.join(packageDir, 'tsconfig.json'), tsconfigContent, {
spaces: 2,
});
await fs.writeFile(path.join(packageDir, 'react-native.config.js'), reactNativeConfigContent);
}
export async function createRootConfigFiles(projectDir, config) {
const tsconfigContent = {
compilerOptions: {
target: 'ES2020',
module: 'ESNext',
moduleResolution: 'node',
lib: ['ES2020'],
declaration: true,
outDir: './lib',
rootDir: './src',
strict: true,
esModuleInterop: true,
allowSyntheticDefaultImports: true,
skipLibCheck: true,
forceConsistentCasingInFileNames: true,
resolveJsonModule: true,
jsx: 'react-jsx',
},
include: ['package/src/**/*'],
exclude: [
'node_modules',
'package/lib',
'package/android',
'package/ios',
'example',
],
};
const biomeConfigContent = {
$schema: 'https://biomejs.dev/schemas/1.9.4/schema.json',
formatter: {
enabled: true,
formatWithErrors: false,
indentStyle: 'space',
indentWidth: 2,
lineEnding: 'lf',
lineWidth: 80,
attributePosition: 'auto',
},
organizeImports: {
enabled: true,
},
linter: {
enabled: true,
rules: {
recommended: true,
correctness: {
useExhaustiveDependencies: 'warn',
},
},
},
javascript: {
formatter: {
jsxQuoteStyle: 'double',
quoteProperties: 'asNeeded',
semicolons: 'asNeeded',
arrowParentheses: 'asNeeded',
bracketSpacing: true,
bracketSameLine: false,
quoteStyle: 'single',
attributePosition: 'auto',
lineWidth: 90,
trailingCommas: 'all',
},
},
files: {
include: [
'example/**/*.ts',
'example/**/*.tsx',
'packages/**/*.ts',
'packages/**/*.tsx',
],
},
};
const gitignoreContent = `# OSX
#
.DS_Store
# XDE
.expo/
dist/
expo-env.d.ts
# VSCode
.vscode/
jsconfig.json
# Xcode
#
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata
*.xccheckout
*.moved-aside
DerivedData
*.hmap
*.ipa
*.xcuserstate
# Android/IJ
#
.classpath
.cxx
.gradle
.idea
.project
.settings
local.properties
android.iml
# Example
#
example/ios
example/android
# Native
*.orig.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
# Ruby
example/vendor/
# node.js
#
node_modules/
npm-debug.log
yarn-debug.log
yarn-error.log
# BUCK
buck-out/
\.buckd/
android/app/libs
android/keystores/debug.keystore
# Yarn
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
# Expo
.expo/
# generated by bob
lib/
# typescript
*.tsbuildinfo
# Metro
.metro-health-check*
# debug
npm-debug.*
yarn-debug.*
yarn-error.*
`;
const pascaleName = toPascalCase(config.name);
const readmeContent = `# ${config.packageName}
${config.description}
## Installation
\`\`\`sh
npm install ${config.packageName}
\`\`\`
## Usage
\`\`\`javascript
import { ${pascaleName}Spec } from '${config.packageName}';
import { NitroModules } from 'react-native-nitro-modules';
const ${pascaleName} = NitroModules.create<${pascaleName}Spec>('${pascaleName}');
const result = ${pascaleName}.hello('World');
console.log(result);
\`\`\`
## API
### \`hello(name: string): string\`
Returns a greeting message.
### \`add(a: number, b: number): number\`
Adds two numbers and returns the result.
## Development
This project uses a workspace structure with:
- \`package/\` - The nitro module source code
- \`example/\` - Example app demonstrating usage
### Setup
\`\`\`sh
npm install
npm run build
\`\`\`
### Running the example
\`\`\`sh
cd example
npm run ios
# or
npm run android
\`\`\`
## Contributing
See the [contributing guide](CONTRIBUTING.md) to learn how to contribute to the repository.
## License
MIT
`;
await fs.writeJson(path.join(projectDir, 'tsconfig.json'), tsconfigContent, {
spaces: 2,
});
await fs.writeJson(path.join(projectDir, 'biome.json'), biomeConfigContent, {
spaces: 2,
});
await fs.writeFile(path.join(projectDir, '.gitignore'), gitignoreContent);
await fs.writeFile(path.join(projectDir, 'README.md'), readmeContent);
}