ignite-jhipster
Version:
A React Native boilerplate for JHipster apps.
297 lines (266 loc) • 10.9 kB
JavaScript
const prompts = require('./prompts')
const { merge, pipe, assoc, omit, __ } = require('ramda')
const { getReactNativeVersion } = require('../lib/react-native-version')
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
const spinner = print
.spin(`Generating a React Native client for JHipster apps`)
.succeed()
let 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')
return
}
// 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) {
let jhipsterPathAnswer = await prompt.ask(prompts.jhipsterPath)
// strip the trailing slash from the directory
jhipsterDirectory = `${jhipsterPathAnswer.filePath}`.replace(/\/$/, ``)
let 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) {
props.detox = (await prompt.ask(prompts.detox)).detox
}
props.skipGit = parameters.options['skip-git']
props.skipLint = parameters.options['skip-lint']
// very hacky but correctly handles both strings and booleans and converts to boolean
props.detox = JSON.parse(props.detox)
props.disableInsight = JSON.parse(props.disableInsight)
// attempt to install React Native or die trying
const rnInstall = await reactNative.install({
name,
version: getReactNativeVersion(context)
})
if (rnInstall.exitCode > 0) 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 incase we need to replace variables
const rawJson = await template.generate({
directory: `${ignite.ignitePluginPath()}/boilerplate`,
template: 'package.json.ejs',
props: props
})
const newPackageJson = JSON.parse(rawJson)
// read in the react-native created package.json
const currentPackage = filesystem.read('package.json', 'json')
// deep merge, lol
const newPackage = pipe(
assoc(
'dependencies',
merge(currentPackage.dependencies, newPackageJson.dependencies)
),
assoc(
'devDependencies',
merge(currentPackage.devDependencies, newPackageJson.devDependencies)
),
assoc('scripts', merge(currentPackage.scripts, newPackageJson.scripts)),
merge(
__,
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('yarn', { stdio: 'ignore' })
spinner.succeed(`dependencies installed`)
}
// pass long the debug flag if we're running in that mode
// const debugFlag = parameters.options.debug ? '--debug' : ''
/**
* 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')
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()
await system.spawn('react-native link', { stdio: 'ignore' })
let showCocoapodsInstructions = false
if (props.authType === 'oauth2') {
await system.spawn('react-native link react-native-app-auth', { stdio: 'ignore' })
// if it's a mac
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) {
spinner.text = `▸ running pod install`
await system.run('cd ios && pod install && cd ..', { stdio: 'ignore' })
spinner.succeed(`pod install succeeded`)
} 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)
}
// git configuration
const gitExists = await filesystem.exists('./.git')
if (!gitExists && !props.skipGit) {
// initial git
const spinner = print.spin('configuring git')
try {
await system.run(`git init . && git add . && git commit -m "Initial commit."`)
spinner.succeed(`created a git repository and an initial commit`)
} catch (e) {
spinner.fail(`failed to create a git repository`)
}
}
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/master/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/master/docs/oauth2-oidc.md')
print.info('')
}
print.info('To run in iOS:')
print.info(print.colors.bold(` cd ${name}`))
print.info(print.colors.bold(' 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://facebook.github.io/react-native/docs/getting-started.html before using ignite.\nYou won't be able to run ${print.colors.bold('react-native run-android')} successfully until you have. Then:`)
}
print.info(print.colors.bold(` cd ${name}`))
print.info(print.colors.bold(' 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('')
}
module.exports = {
install
}