import * as zlib from 'zlib' ; import * as stream from 'stream' ; import { extract } from 'tar' ; import { promisify } from 'util' ; import * as fsExtra from 'fs-extra' ; import fetch from 'node-fetch' ; import * as inquirer from 'inquirer' ; import { BaseConfig , baseConfig } from './assets/base-config' ; import configDependencyPackageMap from './assets/pkg-map' ; import * as questions from './questions' ; import { newLineSuffix } from './lib/formatters' ; import { callout } from './lib/colors' ; const TEMPLATE_URL = 'https://github.com/brandingbrand/flagship-template/archive/master.tar.gz' ; const pipeline = promisify ( stream . pipeline ) ; type Answers = questions . AssociatedDomainsAnswers & questions . BundleIdsAnswers & questions . CodePushAnswers & questions . DoExtendedConfigAnswers & questions . ExceptionDomainsAnswers & questions . NamesAnswers & questions . UsageDescriptionsAnswers & questions . ZendeskChatAnswers ; type UserConfig = Answers [ 'config' ] ; type Config = UserConfig & BaseConfig ; const clearDirectory = async ( ) => { const contents = await fsExtra . readdir ( './' ) ; if ( contents . length !== 0 && ! ( contents . length === 1 && contents [ 0 ] === '.git' ) ) { const { clearContents , clearContentsConfirm } = await inquirer . prompt < questions . ClearContentsAnswers > ( questions . clearContents ) ; if ( ! clearContents || ! clearContentsConfirm ) { console . error ( 'Aborting...' ) ; process . exit ( 1 ) ; } else { await fsExtra . emptyDir ( process . cwd ( ) ) ; } } } ; const populateRepo = async ( ) => { const response = await fetch ( TEMPLATE_URL ) ; if ( ! response . ok || ! response . body ) { throw new Error ( 'bad response' ) ; } await pipeline ( response . body , zlib . createUnzip ( ) , extract ( { strip : 1 } ) ) ; } ; const populateConfigs = async ( ) : Promise < Answers > => { const { associatedDomains , bundleIds , codepush , doExtendedConfig , exceptionDomains , names , usageDescriptions , zendeskChat , onlyWhenExtendedConfig } = questions ; const orderedQuestions = [ ... names , ... bundleIds , ... exceptionDomains , ... usageDescriptions , ... doExtendedConfig , ... associatedDomains . map ( onlyWhenExtendedConfig ) , ... codepush . map ( onlyWhenExtendedConfig ) , ... zendeskChat . map ( onlyWhenExtendedConfig ) ] ; const answers = await inquirer . prompt < Answers > ( orderedQuestions . map ( newLineSuffix ) ) ; return answers ; } ; const formatConfig = ( config ) => { if ( config . usageDescriptionIOS ) { return { ... config , usageDescriptionIOS : Object . keys ( config . usageDescriptionIOS ) . filter ( ( key ) : key is keyof typeof config . usageDescriptionIOS => ( config . usageDescriptionIOS . hasOwnProperty ( key ) ) ) . map ( key => ( { key , string : config . usageDescriptionIOS [ key ] } ) ) } ; } return config ; } ; const replaceConfig = async ( config ) => { const formattedConfig = formatConfig ( config ) ; await fsExtra . writeJson ( './env/common.json' , formattedConfig , { spaces : 2 } ) ; await fsExtra . remove ( './env/common.js' ) ; } ; const getLatestDependency = async ( pkg ) : Promise < { [ key ] : string ; } > => { const res = await fetch ( `https://registry.npmjs.com/${ pkg }` ) ; const json = await res . json ( ) ; if ( json && typeof json === 'object' ) { const distTags = ( json as { [ key ] : unknown ; } ) [ 'dist-tags' ] ; if ( distTags && typeof distTags === 'object' ) { const { latest } = distTags as { [ key ] : unknown ; } ; return { [ pkg ] : `^${ latest }` } ; } } throw new Error ( `Unable to find latest version number for package ${ pkg }` ) ; } ; const getPackageJson = async ( config ) => { const packageJsonStr = await fsExtra . readFile ( './package.json' , 'UTF-8' ) ; const packageJson = JSON . parse ( packageJsonStr ) ; packageJson . name = config . name ; delete packageJson . author ; delete packageJson . repository ; if ( config . bundleIds && config . bundleIds . android ) { const compileCmd = `react-native run-android --no-packager --appId ${ config . bundleIds . android }` ; packageJson . scripts [ 'compile-android' ] = compileCmd ; } const promises = Object . keys ( configDependencyPackageMap ) . filter ( ( key ) : key is keyof typeof configDependencyPackageMap => { return config . hasOwnProperty ( key ) && configDependencyPackageMap . hasOwnProperty ( key ) ; } ) . map ( key => getLatestDependency ( configDependencyPackageMap [ key ] ) ) ; const newDeps = await Promise . all ( promises ) ; packageJson . dependencies = { ... packageJson . dependencies , ... newDeps . reduce ( ( deps , dep ) => ( { ... deps , ... dep } ) , { } ) } ; return packageJson ; } ; const showPostInstallMsg = ( ) => { console . log ( `
You're almost done! There's a couple more steps you may want to complete.

To add an icon for your app, follow the instructions at ${ callout ( 'https://github.com/brandingbrand/flagship/wiki/Creating-App-Icons' ) }.

To add splash/launch screens, follow the instructions at ${ callout ( 'https://github.com/brandingbrand/flagship/wiki/Creating-Launch-Screens' ) }.

You'll need to sign your apps in order to publish to an app store or a distribution service such as Hockeyapp. For more info on how to do that, see ${ callout ( 'https://github.com/brandingbrand/flagship/wiki/Signing-Your-Apps' ) }.

Once you've finished making another other changes to your configs, you need to install dependencies and initialize the project by running:
"${ callout ( 'yarn install && yarn run init' ) }"

Once those complete, you'll be able to run your project by doing:
"${ callout ( 'yarn android' ) }" or "${ callout ( 'yarn ios' ) }"
` ) ; } ; const main = async ( ) => { await clearDirectory ( ) ; await populateRepo ( ) ; const answers = await populateConfigs ( ) ; const config = { ... baseConfig , ... answers . config } ; await replaceConfig ( config ) ; const packageJson = await getPackageJson ( config ) ; await fsExtra . writeJSON ( './package.json' , packageJson , { spaces : 2 } ) ; showPostInstallMsg ( ) ; } ; main ( ) . catch ( e => console . error ( e ) ) ;