create-react-native-library
Version:
CLI to scaffold React Native libraries
229 lines (218 loc) • 10.9 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = generateExampleApp;
var _dedent = _interopRequireDefault(require("dedent"));
var _fsExtra = _interopRequireDefault(require("fs-extra"));
var _getLatestVersion = require("get-latest-version");
var _https = _interopRequireDefault(require("https"));
var _path = _interopRequireDefault(require("path"));
var _constants = require("../constants");
var _sortObjectKeys = _interopRequireDefault(require("../utils/sortObjectKeys"));
var _spawn = require("../utils/spawn");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
const FILES_TO_DELETE = ['__tests__', '.buckconfig', '.eslintrc.js', '.flowconfig', '.git', '.gitignore', '.prettierrc.js', 'App.js', 'App.tsx', 'index.js', 'tsconfig.json'];
const PACKAGES_TO_REMOVE = ['@react-native/eslint-config', '@react-native/new-app-screen', '@tsconfig/react-native', '@types/jest', '@types/react-test-renderer', '@typescript-eslint/eslint-plugin', '@typescript-eslint/parser', 'babel-jest', 'eslint', 'jest', 'prettier', 'react-test-renderer', 'typescript', 'react-native-safe-area-context'];
const PACKAGES_TO_ADD_EXPO_WEB = {
'@expo/metro-runtime': '~5.0.4',
'react-native-web': '~0.21.1'
};
const PACKAGES_TO_ADD_DEV_EXPO_NATIVE = {
'expo-dev-client': '~5.0.3'
};
async function generateExampleApp({
config,
root,
reactNativeVersion = 'latest'
}) {
const directory = _path.default.join(root, 'example');
let args = [];
switch (config.example) {
case 'vanilla':
// `npx @react-native-community/cli init <projectName> --directory example --skip-install`
args = [`@react-native-community/cli`, 'init', `${config.project.name}Example`, '--package-name', `${config.project.package}.example`, '--directory', directory, '--version', reactNativeVersion, '--skip-install', '--skip-git-init', '--pm', 'npm'];
break;
case 'test-app':
{
// Test App requires React Native version to be a semver version
const matchedReactNativeVersion = /(\d+\.\d+[-.0-9a-z]*)/.test(reactNativeVersion) ? reactNativeVersion : await (0, _getLatestVersion.getLatestVersion)('react-native', {
range: reactNativeVersion
});
if (!matchedReactNativeVersion) {
throw new Error(`Could not find a matching version for react-native: ${reactNativeVersion}`);
}
// `npx --package react-native-test-app@latest init --name ${projectName}Example --destination example --version ${reactNativeVersion}`
args = ['--package', `react-native-test-app@latest`, 'init', '--name', `${config.project.name}Example`, `--destination`, directory, '--version', matchedReactNativeVersion, '--platform', 'ios', '--platform', 'android'];
}
break;
case 'expo':
// `npx create-expo-app example --no-install --template blank`
args = ['create-expo-app@latest', directory, '--no-install', '--template', 'blank'];
break;
case undefined:
case null:
{
// Do nothing
}
}
await (0, _spawn.spawn)('npx', args, {
env: {
...process.env,
npm_config_yes: 'true'
}
});
// Remove unnecessary files and folders
for (const file of FILES_TO_DELETE) {
await _fsExtra.default.remove(_path.default.join(directory, file));
}
// Patch the example app's package.json
const pkg = await _fsExtra.default.readJSON(_path.default.join(directory, 'package.json'));
pkg.name = `${config.project.slug}-example`;
// Remove Jest config for now
delete pkg.jest;
// Make sure we have at least empty objects
// Otherwise generation will fails if package doesn't contain these fields
pkg.scripts = pkg.scripts || {};
pkg.dependencies = pkg.dependencies || {};
pkg.devDependencies = pkg.devDependencies || {};
const {
scripts,
dependencies,
devDependencies
} = pkg;
delete scripts.test;
delete scripts.lint;
const SCRIPTS_TO_ADD = config.example === 'expo' ? {
'build:ios': `xcodebuild ONLY_ACTIVE_ARCH=YES -workspace ios/${config.project.name}Example.xcworkspace -UseNewBuildSystem=YES -scheme ${config.project.name}Example -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build -quiet`,
'build:android': 'cd android && ./gradlew assembleDebug -DtestBuildType=debug -Dorg.gradle.jvmargs=-Xmx4g'
} : {
'build:android': 'react-native build-android --extra-params "--no-daemon --console=plain -PreactNativeArchitectures=arm64-v8a"',
'build:ios': `react-native build-ios --mode Debug`
};
if (config.example != null) {
Object.assign(scripts, SCRIPTS_TO_ADD);
}
if (config.example === 'test-app') {
// `react-native-test-app` doesn't bundle application by default in 'Release' mode and also `bundle` command doesn't create a directory.
// `mkdist` script should be removed after stable React Native major contains this fix: https://github.com/facebook/react-native/pull/45182.
const androidBuild = ['npm run mkdist', 'react-native bundle --entry-file index.js --platform android --dev true --bundle-output dist/main.android.jsbundle --assets-dest dist', SCRIPTS_TO_ADD['build:android']].join(' && ');
const iosBuild = ['npm run mkdist', 'react-native bundle --entry-file index.js --platform ios --dev true --bundle-output dist/main.ios.jsbundle --assets-dest dist', SCRIPTS_TO_ADD['build:ios']].join(' && ');
Object.assign(scripts, {
'build:android': androidBuild,
'build:ios': iosBuild
});
const app = await _fsExtra.default.readJSON(_path.default.join(directory, 'app.json'));
app.android = app.android || {};
app.android.package = `${config.project.package}.example`;
app.ios = app.ios || {};
app.ios.bundleIdentifier = `${config.project.package}.example`;
await _fsExtra.default.writeJSON(_path.default.join(directory, 'app.json'), app, {
spaces: 2
});
}
PACKAGES_TO_REMOVE.forEach(name => {
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete devDependencies[name];
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete dependencies[name];
});
const PACKAGES_TO_ADD_DEV = {
'react-native-builder-bob': `^${config.versions.bob}`,
'react-native-monorepo-config': `^${_constants.SUPPORTED_MONOREPO_CONFIG_VERSION}`
};
if (config.project.moduleConfig === 'nitro-modules' || config.project.viewConfig === 'nitro-view') {
const packagesToAddNitro = {
'react-native-nitro-modules': `^${config.versions.nitro || 'latest'}`
};
Object.assign(dependencies, packagesToAddNitro);
}
Object.assign(devDependencies, PACKAGES_TO_ADD_DEV);
if (config.example === 'expo') {
const sdkVersion = dependencies.expo.split('.')[0].replace(/[^\d]/, '');
let bundledNativeModules;
try {
bundledNativeModules = await new Promise((resolve, reject) => {
_https.default.get(`https://raw.githubusercontent.com/expo/expo/sdk-${sdkVersion}/packages/expo/bundledNativeModules.json`, res => {
let data = '';
res.on('data', chunk => data += chunk);
res.on('end', () => {
try {
resolve(JSON.parse(data));
} catch (e) {
// eslint-disable-next-line @typescript-eslint/prefer-promise-reject-errors
reject(e);
}
});
}).on('error', reject);
});
} catch (e) {
bundledNativeModules = {};
}
if (config.project.native) {
Object.entries(PACKAGES_TO_ADD_DEV_EXPO_NATIVE).forEach(([name, version]) => {
devDependencies[name] = bundledNativeModules[name] || version;
});
scripts.start = 'expo start --dev-client';
scripts.android = 'expo run:android';
scripts.ios = 'expo run:ios';
await _fsExtra.default.writeFile(_path.default.join(directory, '.gitignore'), (0, _dedent.default)`
# These folders are generated with prebuild (CNG)
android/
ios/
`);
}
const reactVersion = dependencies.react ?? devDependencies.react;
if (typeof reactVersion !== 'string') {
throw new Error("Couldn't find the package 'react' in the example app.");
}
Object.entries(PACKAGES_TO_ADD_EXPO_WEB).forEach(([name, version]) => {
dependencies[name] = bundledNativeModules[name] || version;
});
dependencies['react-dom'] = reactVersion;
scripts.web = 'expo start --web';
scripts['build:web'] = 'expo export --platform web';
const app = await _fsExtra.default.readJSON(_path.default.join(directory, 'app.json'));
app.expo.name = `${config.project.name} Example`;
app.expo.slug = `${config.project.slug}-example`;
app.expo.android = app.expo.android || {};
app.expo.android.package = `${config.project.package}.example`;
app.expo.ios = app.expo.ios || {};
app.expo.ios.bundleIdentifier = `${config.project.package}.example`;
await _fsExtra.default.writeJSON(_path.default.join(directory, 'app.json'), app, {
spaces: 2
});
}
// Sort the deps by name to match behavior of package managers
// This way the package.json doesn't get updated when installing deps
for (const field of ['dependencies', 'devDependencies']) {
if (pkg[field]) {
pkg[field] = (0, _sortObjectKeys.default)(pkg[field]);
}
}
await _fsExtra.default.writeJSON(_path.default.join(directory, 'package.json'), pkg, {
spaces: 2
});
if (config.example !== 'expo') {
let gradleProperties = await _fsExtra.default.readFile(_path.default.join(directory, 'android', 'gradle.properties'), 'utf8');
// Disable Jetifier.
// Remove this when the app template is updated.
gradleProperties = gradleProperties.replace('android.enableJetifier=true', 'android.enableJetifier=false');
// Enable new arch for iOS and Android
// iOS
// Add ENV['RCT_NEW_ARCH_ENABLED'] = 1 on top of example/ios/Podfile
const podfile = await _fsExtra.default.readFile(_path.default.join(directory, 'ios', 'Podfile'), 'utf8');
await _fsExtra.default.writeFile(_path.default.join(directory, 'ios', 'Podfile'), "ENV['RCT_NEW_ARCH_ENABLED'] = '1'\n\n" + podfile);
// Android
// Make sure newArchEnabled=true is present in android/gradle.properties
if (gradleProperties.split('\n').includes('#newArchEnabled=true')) {
gradleProperties = gradleProperties.replace('#newArchEnabled=true', 'newArchEnabled=true');
} else if (gradleProperties.split('\n').includes('newArchEnabled=false')) {
gradleProperties = gradleProperties.replace('newArchEnabled=false', 'newArchEnabled=true');
} else if (!gradleProperties.split('\n').includes('newArchEnabled=true')) {
gradleProperties += '\nnewArchEnabled=true';
}
await _fsExtra.default.writeFile(_path.default.join(directory, 'android', 'gradle.properties'), gradleProperties);
}
}
//# sourceMappingURL=generateExampleApp.js.map