UNPKG

ignite-jhipster

Version:

A React Native boilerplate for JHipster apps.

315 lines (283 loc) 12.8 kB
const prompts = require('./prompts') const { mergeDeepRight, pipe, assoc, omit, __ } = require('ramda') const { getReactNativeVersion } = require('../lib/react-native-version') const { replacePackageJsonVersions } = require('../lib/merge-package-json') const { patchReactNativeNavigation } = require('../lib/react-native-navigation') const Insight = require('../lib/insight') const generateFiles = require('./files') const rimraf = require('rimraf') const importEntityJdl = require('../import-jdl/index') const { importJDL } = require('../lib/import-jdl') const fs = require('fs-extra') const pkg = require('../../package') /** * Is Android installed? * * $ANDROID_HOME/tools folder has to exist. * * @param {*} context - The gluegun context. * @returns {boolean} */ const isAndroidInstalled = function (context) { const androidHome = process.env['ANDROID_HOME'] const hasAndroidEnv = !context.strings.isBlank(androidHome) const hasAndroid = hasAndroidEnv && context.filesystem.exists(`${androidHome}/tools`) === 'dir' return Boolean(hasAndroid) } /** * Let's install. * * @param {any} context - The gluegun context. */ async function install(context) { const { filesystem, parameters, ignite, reactNative, print, system, prompt, template } = context const perfStart = new Date().getTime() const name = parameters.first print.spin(`Using Ignite JHipster v${pkg.version}`).succeed() const spinner = print.spin(`Generating a React Native client for JHipster apps`).succeed() const props = { jhipsterDirectory: parameters.options['jh-dir'] || '', detox: parameters.options.detox, disableInsight: parameters.options['disable-insight'] || false, } let jhipsterConfig let jhipsterDirectory // if the user is passing in JDL if (parameters.options.jdl) { print.info('Importing JDL') const results = importJDL([`../${parameters.options.jdl}`], null, null, null, print) if (results.exportedApplications.length === 0) { print.error('No JHipster Applications found in the JDL file') process.exit(1) } if (results.exportedApplications.length > 1 || results.exportedDeployments.length > 0) { print.error('Multiple JHipster Applications or Deployments found in the JDL file, not supported for app generation') process.exit(1) } // remove the files generated by import-jdl await rimraf.sync('.yo-rc.json') await rimraf.sync('.jhipster') jhipsterConfig = results.exportedApplications[0] jhipsterDirectory = '' // if the user is passing in a directory } else if (props.jhipsterDirectory) { if (!fs.existsSync(`../${props.jhipsterDirectory}/.yo-rc.json`)) { print.error(`No JHipster configuration file found at ${props.jhipsterDirectory}/.yo-rc.json`) return } print.success(`Found the JHipster config at ${props.jhipsterDirectory}/.yo-rc.json`) const pathPrefix = props.jhipsterDirectory.startsWith('/') ? '' : '../' jhipsterConfig = await fs.readJson(`${pathPrefix}${props.jhipsterDirectory}/.yo-rc.json`) jhipsterDirectory = props.jhipsterDirectory // the user didn't pass in JDL or a path, prompt them for a path } else { // prompt the user until an JHipster configuration file is found while (true) { const jhipsterPathAnswer = await prompt.ask(prompts.jhipsterPath) // strip the trailing slash from the directory jhipsterDirectory = `${jhipsterPathAnswer.filePath}`.replace(/\/$/, ``) const jhipsterConfigPath = `${jhipsterDirectory}/.yo-rc.json` const pathPrefix = jhipsterDirectory.startsWith('/') ? '' : '../' print.info(`Looking for ${pathPrefix}${jhipsterConfigPath}`) if (fs.existsSync(`${pathPrefix}${jhipsterConfigPath}`)) { print.success(`Found JHipster config file at ${jhipsterConfigPath}`) jhipsterConfig = await fs.readJson(`${pathPrefix}${jhipsterConfigPath}`) break } else { print.error(`Could not find JHipster config file, please try again.`) } } props.jhipsterDirectory = jhipsterDirectory } if (!props.disableInsight && Insight.insight.optOut === undefined) { Insight.insight.optOut = !(await prompt.ask(prompts.insight)).insight } if (!props.detox && props.detox !== false) { const detoxPromptResponse = await prompt.ask(prompts.detox) props.detox = detoxPromptResponse.detox } props.skipGit = parameters.options['skip-git'] props.skipCommitHook = parameters.options['skip-commit-hook'] // is the npm flag present, or is yarn not available? const useNpm = Boolean(parameters.options.npm) || !system.which('yarn') print.info(`Using ${useNpm ? 'npm' : 'yarn'} as the package manager`) props.useNpm = useNpm props.disableInsight = Boolean(props.disableInsight) // very hacky but correctly handles both strings and booleans and converts to boolean // needed because it can be true or false, but if not provided it's prompted props.detox = JSON.parse(props.detox) // check for CocoaPods if (process.platform === 'darwin') { // if CocoaPods is installed, install the oauth dependencies const podVersionCommandResult = await system.spawn('pod --version', { stdio: 'ignore' }) if (podVersionCommandResult.status !== 0) { print.error('CocoaPods is required for Ignite JHipster when generating on a Mac') print.info('Please see https://guides.cocoapods.org/using/getting-started.html') process.exit(1) } } // attempt to install React Native or die trying const rnInstall = await reactNative.install({ name, version: getReactNativeVersion(context), useNpm: useNpm, }) if (rnInstall.exitCode > 0) { print.error('Please try again with the --debug flag for a more verbose error') process.exit(rnInstall.exitCode) } // remove the __tests__ directory that come with React Native filesystem.remove('__tests__') filesystem.remove('App.js') props.name = name props.igniteVersion = ignite.version props.reactNativeVersion = rnInstall.version props.jhipsterDirectory = `../${props.jhipsterDirectory}` props.authType = jhipsterConfig['generator-jhipster'].authenticationType props.searchEngine = !!jhipsterConfig['generator-jhipster'].searchEngine props.websockets = !!jhipsterConfig['generator-jhipster'].websocket props.packageVersion = pkg.version await generateFiles(context, props, jhipsterConfig) /** * Merge the package.json from our template into the one provided from react-native init. */ async function mergePackageJsons() { // transform our package.json, using raw JSON to replace versions, and add dependencies from the React Native template const rawJson = await template.generate({ directory: `${ignite.ignitePluginPath()}/boilerplate`, template: 'package.json.ejs', props: props, }) // replace version in the package.json.ejs template from the source file (package.json) let newPackageJson = JSON.parse(rawJson) newPackageJson = await replacePackageJsonVersions(context, newPackageJson, `${ignite.ignitePluginPath()}/boilerplate/package.json`) newPackageJson.devDependencies['ignite-jhipster'] = props.packageVersion // read in the react-native created package.json const currentPackage = filesystem.read('package.json', 'json') // deep merge const newPackage = pipe( assoc('dependencies', mergeDeepRight(currentPackage.dependencies, newPackageJson.dependencies)), assoc('devDependencies', mergeDeepRight(currentPackage.devDependencies, newPackageJson.devDependencies)), assoc('scripts', mergeDeepRight(currentPackage.scripts, newPackageJson.scripts)), mergeDeepRight(__, omit(['dependencies', 'devDependencies', 'scripts'], newPackageJson)), )(currentPackage) // write this out filesystem.write('package.json', newPackage, { jsonIndent: 2 }) } await mergePackageJsons() spinner.stop() spinner.succeed(`project generated`) await patchReactNativeNavigation(context, props) if (!parameters.options.skipInstall) { spinner.text = `▸ installing dependencies` spinner.start() // install any missing dependencies await system.run(useNpm ? 'npm i' : 'yarn', { stdio: 'ignore' }) spinner.succeed(`dependencies installed`) } /** * Append to files */ // https://github.com/facebook/react-native/issues/12724 filesystem.appendAsync('.gitattributes', '*.bat text eol=crlf') filesystem.append('.gitignore', 'coverage/') filesystem.append('.gitignore', '\n# Misc\n#') filesystem.append('.gitignore', '.env\n') filesystem.append('.gitignore', 'ios/Index/DataStore\n') filesystem.append('.gitignore', 'ios/Carthage\n') filesystem.append('.gitignore', 'ios/Pods\n') filesystem.append('.gitignore', 'fastlane/report.xml\n') filesystem.append('.gitignore', 'android/app/bin\n') filesystem.append('.gitignore', '.artifacts\n') try { fs.mkdirSync(`.jhipster`) fs.writeJsonSync('.jhipster/yo-rc.json', jhipsterConfig, { spaces: '\t' }) print.success(`JHipster config saved to your app's .jhipster folder.`) } catch (e) { print.error(e) throw e } // react native link -- must use spawn & stdio: ignore spinner.text = `▸ linking native libraries` spinner.start() let showCocoapodsInstructions = false // if it's a mac if (process.platform === 'darwin' && !parameters.options.skipPodInstall) { // if cocoapods is installed, install the oauth dependencies const podVersionCommandResult = await system.spawn('pod --version', { stdio: 'ignore' }) if (podVersionCommandResult.status === 0) { spinner.text = `▸ running pod install` try { await system.run('cd ios && pod install && cd ..', { stdio: 'ignore' }) spinner.succeed(`pod install succeeded`) } catch (e) { spinner.stopAndPersist({ symbol: '🚨', text: 'pod install failed, please try again manually:' }) print.info(`cd ios && pod install && cd ..`) print.error(e) } } else { showCocoapodsInstructions = true } } spinner.succeed(`linked native libraries`) spinner.stop() // if JDL was passed to generate the app, generate any entities if (parameters.options.jdl) { await importEntityJdl.run(context) } // run prettier to pass lint on generation spinner.text = `▸ running prettier on generated code` spinner.start() // install any missing dependencies await system.run(`${useNpm ? 'npm' : 'yarn'} run prettier`, { stdio: 'ignore' }) const perfDuration = parseInt((new Date().getTime() - perfStart) / 10) / 100 spinner.succeed(`ignited ${print.colors.yellow(name)} in ${perfDuration}s`) Insight.trackAppOptions(context, props) // Wrap it up with our success message. print.info('') print.info('🍽 Time to get cooking!') print.info('') if (props.websockets) { print.info('To enable the websockets example, see https://github.com/ruddell/ignite-jhipster/blob/main/docs/websockets.md') print.info('') } if (props.authType === 'oauth2') { print.info(print.colors.bold(`Before iOS apps can be run, there are steps that must be complete manually`)) if (showCocoapodsInstructions) { print.info(print.colors.blue(`CocoaPods not found, please install CocooaPods and run 'pod install' from your app's ios directory.`)) } print.info( 'For more info on configuring OAuth2 OIDC Login, see https://github.com/ruddell/ignite-jhipster/blob/main/docs/oauth2-oidc.md', ) print.info('') } print.info('To run in iOS:') print.info(print.colors.bold(` cd ${name}`)) print.info(print.colors.bold(' npx react-native run-ios')) print.info('') if (isAndroidInstalled(context)) { print.info('To run in Android:') } else { print.info( `To run in Android, make sure you've followed the latest react-native setup instructions at https://reactnative.dev/docs/environment-setup before using ignite.\nYou won't be able to run ${print.colors.bold( 'npx react-native run-android', )} successfully until you have. Then:`, ) } print.info(print.colors.bold(` cd ${name}`)) print.info(print.colors.bold(' npx react-native run-android')) print.info('') print.info('To see what JHipster generators are available:') print.info(print.colors.bold(` cd ${name}`)) print.info(print.colors.bold(' ignite generate')) print.info('') if (useNpm && context.parameters.options.boilerplate !== 'ignite-jhipster' && context.parameters.options.boilerplate !== 'jhipster') { print.warning( 'NOTE: Using local ignite-jhipster, removing `.git` folder from `node_modules/ignite-jhipster` to prevent issues with `npm i`', ) await rimraf.sync('../node_modules/ignite-jhipster/.git') } } module.exports = { install, }