[
  {
    "path": ".gitignore",
    "content": "# OSX\n#\n.DS_Store\nnpm-debug.log\nnpm-debug.log*\ncoverage\n.nyc_output\nyarn.lock\nlerna-debug.log\nnode_modules\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\ntestgrounds\nIntegrationTest\nintegration_test\n"
  },
  {
    "path": "boilerplate/.babelrc",
    "content": "{\n  \"presets\": [\"react-native\"],\n  \"env\": {\n    \"production\": {\n      \"plugins\": [\"ignite-ignore-reactotron\"]\n    }\n  }\n}\n"
  },
  {
    "path": "boilerplate/.editorconfig",
    "content": "# EditorConfig is awesome: http://EditorConfig.org\n\n# top-most EditorConfig file\nroot = true\n\n# Unix-style newlines with a newline ending every file\n[*]\nend_of_line = lf\ninsert_final_newline = true\nindent_style = space\nindent_size = 2\ncharset = utf-8\ntrim_trailing_whitespace = true\n\n\n[*.gradle]\nindent_size = 4"
  },
  {
    "path": "boilerplate/App/Components/AlertMessage/AlertMessage.story.tsx",
    "content": "import { storiesOf } from \"@storybook/react-native\";\nimport * as React from \"react\";\n\nimport AlertMessage from \"./AlertMessage\";\n\nstoriesOf(\"AlertMessage\")\n  .add(\"Default\", () => (\n    <AlertMessage\n      title=\"ALERT ALERT\"\n    />\n  ))\n  .add(\"Hidden\", () => (\n    <AlertMessage\n      title=\"ALERT ALERT\"\n      show={false}\n    />\n  ))\n  .add(\"Custom Style\", () => (\n    <AlertMessage\n      title=\"ALERT ALERT\"\n      style={{ backgroundColor: \"red\" }}\n    />\n  ));\n"
  },
  {
    "path": "boilerplate/App/Components/AlertMessage/AlertMessage.tsx",
    "content": "import * as React from \"react\";\nimport { Text, View, ViewStyle } from \"react-native\";\nimport styles from \"./AlertMessageStyles\";\n\ninterface Props {\n  icon?: string;\n  show?: boolean;\n  style?: ViewStyle;\n  title?: string;\n}\n\nconst AlertMessage: React.SFC<Props> = ({ icon, show = true, style, title }: Props) => show ? (\n  <View style={[styles.container, style]}>\n    <View style={styles.contentContainer}>\n      <Text allowFontScaling={false} style={styles.message}>{title && title.toUpperCase()}</Text>\n    </View>\n  </View>\n) : null;\n\nexport default AlertMessage;\n"
  },
  {
    "path": "boilerplate/App/Components/AlertMessage/AlertMessageStyles.ts",
    "content": "import { StyleSheet } from \"react-native\";\nimport { Colors, Fonts, Metrics } from \"../../Themes/\";\n\nexport default StyleSheet.create({\n  container: {\n    justifyContent: \"center\",\n    marginVertical: Metrics.section,\n  },\n  contentContainer: {\n    alignSelf: \"center\",\n    alignItems: \"center\",\n  },\n  message: {\n    marginTop: Metrics.baseMargin,\n    marginHorizontal: Metrics.baseMargin,\n    textAlign: \"center\",\n    fontFamily: Fonts.type.base,\n    fontSize: Fonts.size.regular,\n    fontWeight: \"bold\",\n    color: Colors.steel,\n  },\n  icon: {\n    color: Colors.steel,\n  },\n});\n"
  },
  {
    "path": "boilerplate/App/Components/AlertMessage/AlertMessageTest.tsx",
    "content": "/// <reference types=\"@types/jest\" />\nimport * as React from \"react\";\nimport \"react-native\";\nimport * as renderer from \"react-test-renderer\";\nimport AlertMessage from \"./AlertMessage\";\n\ntest(\"AlertMessage component renders correctly if show is true\", () => {\n  const tree = renderer.create(<AlertMessage title=\"howdy\" />).toJSON();\n  expect(tree).toMatchSnapshot();\n});\n\ntest(\"AlertMessage component does not render if show is false\", () => {\n  const tree = renderer.create(<AlertMessage title=\"howdy\" show={false} />).toJSON();\n  expect(tree).toMatchSnapshot();\n});\n\ntest(\"AlertMessage component renders correctly if backgroundColor prop is set\", () => {\n  const tree = renderer.create(<AlertMessage title=\"howdy\" style={{backgroundColor: \"red\"}} />).toJSON();\n  expect(tree).toMatchSnapshot();\n});\n"
  },
  {
    "path": "boilerplate/App/Components/AlertMessage/index.ts",
    "content": "import AlertMessage from \"./AlertMessage\";\n\nexport default AlertMessage;\n"
  },
  {
    "path": "boilerplate/App/Components/DrawerButton/DrawerButton.story.tsx",
    "content": "import { storiesOf } from \"@storybook/react-native\";\nimport * as React from \"react\";\nimport { View } from \"react-native\";\n\nimport DrawerButton from \"./DrawerButton\";\n\nstoriesOf(\"DrawerButton\")\n  .add(\"Default\", () => (\n    <View style={{ backgroundColor: \"black\" }}>\n      <DrawerButton\n        text=\"Drawer Button\"\n        // tslint:disable-next-line:jsx-no-lambda\n        onPress={() => alert(\"Hi\")}\n      />\n    </View>\n  ));\n"
  },
  {
    "path": "boilerplate/App/Components/DrawerButton/DrawerButton.tsx",
    "content": "import * as React from \"react\";\nimport { Text, TouchableOpacity } from \"react-native\";\nimport ExamplesRegistry from \"../../Services/ExamplesRegistry\";\nimport styles from \"./DrawerButtonStyles\";\n\n// Note that this file (App/Components/DrawerButton) needs to be\n// imported in your app somewhere, otherwise your component won't be\n// compiled and added to the examples dev screen.\n\n// Ignore in coverage report\n/* istanbul ignore next */\nExamplesRegistry.addComponentExample(\"Drawer Button\", () =>\n  (\n  <DrawerButton\n    text=\"Example left drawer button\"\n    // tslint:disable-next-line:jsx-no-lambda\n    onPress={() => window.alert(\"Your drawers are showing\")}\n  />),\n);\n\ninterface Props {\n  onPress: () => void;\n  text: string;\n}\n\n// tslint:disable-next-line:no-empty\nconst DrawerButton: React.SFC<Props> = ({text, onPress = () => {}}: Props) => (\n  <TouchableOpacity onPress={onPress}>\n    <Text style={styles.text}>{text}</Text>\n  </TouchableOpacity>\n);\n\nexport default DrawerButton;\n"
  },
  {
    "path": "boilerplate/App/Components/DrawerButton/DrawerButtonStyles.ts",
    "content": "import Colors from \"../../Themes/Colors\";\nimport Fonts from \"../../Themes/Fonts\";\nimport Metrics from \"../../Themes/Metrics\";\n\nexport default {\n  text: {\n    ...Fonts.style.h5,\n    color: Colors.snow,\n    marginVertical: Metrics.baseMargin,\n  },\n};\n"
  },
  {
    "path": "boilerplate/App/Components/DrawerButton/DrawerButtonTest.tsx",
    "content": "/// <reference types=\"@types/jest\" />\nimport { shallow } from \"enzyme\";\nimport * as React from \"react\";\nimport \"react-native\";\nimport * as renderer from \"react-test-renderer\";\nimport DrawerButton from \"./DrawerButton\";\n\ntest(\"AlertMessage component renders correctly\", () => {\n  const tree = renderer.create(<DrawerButton onPress={() => {}} text=\"hi\" />).toJSON();\n  expect(tree).toMatchSnapshot();\n});\n\ntest(\"onPress\", () => {\n  let i = 0;\n  const onPress = () => i++;\n  const wrapperPress = shallow(<DrawerButton onPress={onPress} text=\"hi\" />);\n\n  expect(wrapperPress.prop(\"onPress\")).toBe(onPress); // uses the right handler\n  expect(i).toBe(0);\n  wrapperPress.simulate(\"press\");\n  expect(i).toBe(1);\n});\n"
  },
  {
    "path": "boilerplate/App/Components/DrawerButton/index.ts",
    "content": "import DrawerButton from \"./DrawerButton\";\n\nexport default DrawerButton;\n"
  },
  {
    "path": "boilerplate/App/Components/FullButton/FullButton.story.tsx",
    "content": "import { storiesOf } from \"@storybook/react-native\";\nimport * as React from \"react\";\n\nimport FullButton from \"./FullButton\";\n\nstoriesOf(\"FullButton\")\n  .add(\"Default\", () => (\n    <FullButton\n      text=\"A simple button\"\n    />\n  ))\n  .add(\"Custom Style\", () => (\n    <FullButton\n      text=\"Style Me Up!\"\n      style={{ backgroundColor: \"blue\" }}\n    />\n  ));\n"
  },
  {
    "path": "boilerplate/App/Components/FullButton/FullButton.tsx",
    "content": "import * as React from \"react\";\nimport { Text, TouchableOpacity, ViewStyle } from \"react-native\";\nimport ExamplesRegistry from \"../../Services/ExamplesRegistry\";\nimport styles from \"./FullButtonStyles\";\n\n// Note that this file (App/Components/FullButton) needs to be\n// imported in your app somewhere, otherwise your component won't be\n// compiled and added to the examples dev screen.\n\n// Ignore in coverage report\n/* istanbul ignore next */\nExamplesRegistry.addComponentExample(\"Full Button\", () => (\n  <FullButton\n    text=\"Hey there\"\n    // tslint:disable-next-line:jsx-no-lambda\n    onPress={() => window.alert(\"Full Button Pressed!\")}\n  />),\n);\n\ninterface Props {\n    onPress?: () => void;\n    style?: ViewStyle;\n    text?: string;\n}\n\n// tslint:disable-next-line:no-empty\nconst FullButton: React.SFC<Props> = ({text, style, onPress = () => {}}: Props) => (\n  <TouchableOpacity style={[styles.button, style]} onPress={onPress}>\n    <Text style={styles.buttonText}>{text && text.toUpperCase()}</Text>\n  </TouchableOpacity>\n);\n\nexport default FullButton;\n"
  },
  {
    "path": "boilerplate/App/Components/FullButton/FullButtonStyles.ts",
    "content": "import { StyleSheet } from \"react-native\";\nimport Colors from \"../../Themes/Colors\";\nimport Fonts from \"../../Themes/Fonts\";\n\nexport default StyleSheet.create({\n  button: {\n    marginVertical: 5,\n    borderTopColor: Colors.fire,\n    borderBottomColor: Colors.bloodOrange,\n    borderTopWidth: 1,\n    borderBottomWidth: 1,\n    backgroundColor: Colors.ember,\n  },\n  buttonText: {\n    margin: 18,\n    textAlign: \"center\",\n    color: Colors.snow,\n    fontSize: Fonts.size.medium,\n    fontFamily: Fonts.type.bold,\n  },\n});\n"
  },
  {
    "path": "boilerplate/App/Components/FullButton/FullButtonTest.tsx",
    "content": "/// <reference types=\"@types/jest\" />\nimport { shallow } from \"enzyme\";\nimport * as React from \"react\";\nimport \"react-native\";\nimport * as renderer from \"react-test-renderer\";\nimport FullButton from \"./FullButton\";\n\ntest(\"FullButton component renders correctly\", () => {\n  const tree = renderer.create(<FullButton onPress={() => {}} text=\"hi\" />).toJSON();\n  expect(tree).toMatchSnapshot();\n});\n\ntest(\"onPress\", () => {\n  let i = 0; // i guess i could have used sinon here too... less is more i guess\n  const onPress = () => i++;\n  const wrapperPress = shallow(<FullButton onPress={onPress} text=\"hi\" />);\n\n  expect(wrapperPress.prop(\"onPress\")).toBe(onPress); // uses the right handler\n  expect(i).toBe(0);\n  wrapperPress.simulate(\"press\");\n  expect(i).toBe(1);\n});\n"
  },
  {
    "path": "boilerplate/App/Components/FullButton/index.ts",
    "content": "import FullButton from \"./FullButton\";\n\nexport default FullButton;\n"
  },
  {
    "path": "boilerplate/App/Components/README.md",
    "content": "### Components Folder\nAll components are stored and organized here\n\n\"In an ideal world, most of your components would be stateless functions because in the future we’ll also be able to make performance optimizations specific to these components by avoiding unnecessary checks and memory allocations. This is the recommended pattern, when possible.\" --React docs"
  },
  {
    "path": "boilerplate/App/Components/RoundedButton/RoundedButton.story.tsx",
    "content": "import { storiesOf } from \"@storybook/react-native\";\nimport * as React from \"react\";\n\nimport RoundedButton from \"./RoundedButton\";\n\nstoriesOf(\"RoundedButton\", module)\n  .add(\"Default\", () => (\n    <RoundedButton\n      text=\"A simple rounded button\"\n    />\n  ))\n  .add(\"Text as children\", () => (\n    <RoundedButton>\n        Hello from the children!\n    </RoundedButton>\n  ));\n"
  },
  {
    "path": "boilerplate/App/Components/RoundedButton/RoundedButton.tsx",
    "content": "import * as React from \"react\";\nimport { Text, TouchableOpacity } from \"react-native\";\nimport ExamplesRegistry from \"../../Services/ExamplesRegistry\";\nimport styles from \"./RoundedButtonStyles\";\n\n// Note that this file (App/Components/RoundedButton) needs to be\n// imported in your app somewhere, otherwise your component won't be\n// compiled and added to the examples dev screen.\n\n// Ignore in coverage report\n/* istanbul ignore next */\nExamplesRegistry.addComponentExample(\"Rounded Button\", () =>\n  (\n    <RoundedButton\n      text=\"real buttons have curves\"\n      // tslint:disable-next-line:jsx-no-lambda\n      onPress={() => window.alert(\"Rounded Button Pressed!\")}\n    />),\n);\n\ninterface Props {\n  onPress?: () => any;\n  text?: string;\n  children?: string;\n}\n\n// tslint:disable-next-line:no-empty\nconst RoundedButton: React.SFC<Props> = ({ text, children, onPress = () => { } }: Props) => {\n  const buttonText = (text || children || \"\").toUpperCase();\n  return (\n    <TouchableOpacity style={styles.button} onPress={onPress}>\n      <Text style={styles.buttonText}>{buttonText}</Text>\n    </TouchableOpacity>\n  );\n};\n\nexport default RoundedButton;\n"
  },
  {
    "path": "boilerplate/App/Components/RoundedButton/RoundedButtonStyles.ts",
    "content": "import { StyleSheet } from \"react-native\";\nimport Colors from \"../../Themes/Colors\";\nimport Fonts from \"../../Themes/Fonts\";\nimport Metrics from \"../../Themes/Metrics\";\n\nexport default StyleSheet.create({\n  button: {\n    height: 45,\n    borderRadius: 5,\n    marginHorizontal: Metrics.section,\n    marginVertical: Metrics.baseMargin,\n    backgroundColor: Colors.fire,\n    justifyContent: \"center\",\n  },\n  buttonText: {\n    color: Colors.snow,\n    textAlign: \"center\",\n    fontWeight: \"bold\",\n    fontSize: Fonts.size.medium,\n    marginVertical: Metrics.baseMargin,\n  },\n});\n"
  },
  {
    "path": "boilerplate/App/Components/RoundedButton/RoundedButtonTest.tsx",
    "content": "/// <reference types=\"@types/jest\" />\nimport { shallow } from \"enzyme\";\nimport * as React from \"react\";\nimport \"react-native\";\nimport * as renderer from \"react-test-renderer\";\nimport RoundedButton from \"./RoundedButton\";\n\ntest(\"RoundedButton component renders correctly\", () => {\n  const tree = renderer.create(<RoundedButton onPress={() => {}} text=\"howdy\" />).toJSON();\n  expect(tree).toMatchSnapshot();\n});\n\ntest(\"RoundedButton component with children renders correctly\", () => {\n  const tree = renderer.create(<RoundedButton onPress={() => {}}>Howdy</RoundedButton>).toJSON();\n  expect(tree).toMatchSnapshot();\n});\n\ntest(\"onPress\", () => {\n  let i = 0; // i guess i could have used sinon here too... less is more i guess\n  const onPress = () => i++;\n  const wrapperPress = shallow(<RoundedButton onPress={onPress} text=\"hi\" />);\n\n  expect(wrapperPress.prop(\"onPress\")).toBe(onPress); // uses the right handler\n  expect(i).toBe(0);\n  wrapperPress.simulate(\"press\");\n  expect(i).toBe(1);\n});\n"
  },
  {
    "path": "boilerplate/App/Components/RoundedButton/index.ts",
    "content": "import RoundedButton from \"./RoundedButton\";\n\nexport default RoundedButton;\n"
  },
  {
    "path": "boilerplate/App/Components/Stories.tsx",
    "content": "import \"./AlertMessage/AlertMessage.story\";\nimport \"./DrawerButton/DrawerButton.story\";\nimport \"./FullButton/FullButton.story\";\nimport \"./RoundedButton/RoundedButton.story\";\n"
  },
  {
    "path": "boilerplate/App/Config/AppConfig.ts",
    "content": "// Simple React Native specific changes\n\nexport default {\n  // font scaling override - RN default is on\n  allowTextFontScaling: true,\n};\n"
  },
  {
    "path": "boilerplate/App/Config/DebugConfig.js",
    "content": "/**\n * This is a .js file because the Ignite scripts need to find it\n */\nexport default {\n  showDevScreens: __DEV__,\n  useFixtures: false,\n  ezLogin: false,\n  yellowBox: __DEV__,\n  reduxLogging: __DEV__,\n  includeExamples: __DEV__,\n  useReactotron: __DEV__,\n};\n"
  },
  {
    "path": "boilerplate/App/Config/README.md",
    "content": "### Config Folder\nAll application specific configuration falls in this folder.\n\n`AppConfig.js` - production values.\n`DebugConfig.js` - development-wide globals.\n`ReactotronConfig.js` - Reactotron client settings.\n`ReduxPersist.js` - rehydrate Redux state.\n"
  },
  {
    "path": "boilerplate/App/Config/ReactotronConfig.ts",
    "content": "import Reactotron from \"reactotron-react-native\";\nimport { reactotronRedux as reduxPlugin } from \"reactotron-redux\";\nimport sagaPlugin from \"reactotron-redux-saga\";\nimport ImmutableObject from \"seamless-immutable\";\nimport Config from \"../Config/DebugConfig\";\n\nif (Config.useReactotron) {\n  // https://github.com/infinitered/reactotron for more options!\n  Reactotron\n    .configure({ name: \"Ignite App\" })\n    .useReactNative()\n    .use(reduxPlugin({ onRestore: ImmutableObject }))\n    .use(sagaPlugin())\n    .connect();\n\n  // Let's clear Reactotron on every time we load the app\n  Reactotron.clear();\n\n  // Totally hacky, but this allows you to not both importing reactotron-react-native\n  // on every file.  This is just DEV mode, so no big deal.\n  (console as any).tron = Reactotron;\n}\n"
  },
  {
    "path": "boilerplate/App/Config/ReduxPersist.ts",
    "content": "import { AsyncStorage } from \"react-native\";\nimport immutablePersistenceTransform from \"../Services/ImmutablePersistenceTransform\";\n\n// More info here:  https://shift.infinite.red/shipping-persistant-reducers-7341691232b1\nconst REDUX_PERSIST = {\n  active: false,\n  reducerVersion: \"1.0\",\n  storeConfig: {\n    storage: AsyncStorage,\n    blacklist: [\"login\", \"search\", \"nav\"], // reducer keys that you do NOT want stored to persistence here\n    // whitelist: [], Optionally, just specify the keys you DO want stored to\n    // persistence. An empty array means 'don't store any reducers' -> infinitered/ignite#409\n    transforms: [immutablePersistenceTransform],\n  },\n};\n\nexport default REDUX_PERSIST;\n"
  },
  {
    "path": "boilerplate/App/Config/index.ts",
    "content": "import { Text } from \"react-native\";\nimport AppConfig from \"./AppConfig\";\nimport DebugConfig from \"./DebugConfig\";\n\n// Allow/disallow font-scaling in app\nif (!Text.defaultProps) {\n  Text.defaultProps = {};\n}\nText.defaultProps.allowFontScaling = AppConfig.allowTextFontScaling;\n\nif (__DEV__) {\n  // If ReactNative's yellow box warnings are too much, it is possible to turn\n  // it off, but the healthier approach is to fix the warnings.  =)\n  console.disableYellowBox = !DebugConfig.yellowBox;\n}\n"
  },
  {
    "path": "boilerplate/App/Containers/App.tsx",
    "content": "import * as React from \"react\";\nimport { Provider } from \"react-redux\";\nimport Reactotron from \"reactotron-react-native\";\nimport \"../Config\";\nimport DebugConfig from \"../Config/DebugConfig\";\nimport createStore from \"../Reducers\";\nimport RootContainer from \"./RootContainer\";\n\n// create our store\nconst store = createStore();\n\n/**\n * Provides an entry point into our application.  Both index.ios.js and index.android.js\n * call this component first.\n *\n * We create our Redux store here, put it into a provider and then bring in our\n * RootContainer.\n *\n * We separate like this to play nice with React Native's hot reloading.\n */\nclass App extends React.Component {\n  public render() {\n    return (\n      <Provider store={store}>\n        <RootContainer />\n      </Provider>\n    );\n  }\n}\n\n// allow reactotron overlay for fast design in dev mode\nexport default DebugConfig.useReactotron\n? Reactotron.overlay(App)\n: App;\n"
  },
  {
    "path": "boilerplate/App/Containers/LaunchScreen/LaunchScreen.tsx",
    "content": "import React, { Component } from \"react\";\nimport { Image, ScrollView, Text, View } from \"react-native\";\nimport DevscreensButton from \"../../../ignite/DevScreens/DevscreensButton\";\n\nimport { Images } from \"../../Themes\";\n\n// Styles\nimport styles from \"./LaunchScreenStyles\";\n\nexport default class LaunchScreen extends Component {\n  public render() {\n    return (\n      <View style={styles.mainContainer}>\n        <Image source={Images.background} style={styles.backgroundImage} resizeMode=\"stretch\" />\n        <ScrollView style={styles.container}>\n          <View style={styles.centered}>\n            <Image source={Images.launch} style={styles.logo} />\n          </View>\n\n          <View style={styles.section} >\n            <Image source={Images.ready} />\n            <Text style={styles.sectionText}>\n              This probably isn't what your app is going to look like. Unless your designer\n              handed you this screen and, in that case, congrats! You're ready to ship.\n              For everyone else, this is where you'll see a live preview of your fully functioning app using Ignite.\n            </Text>\n          </View>\n\n          <DevscreensButton />\n        </ScrollView>\n      </View>\n    );\n  }\n}\n"
  },
  {
    "path": "boilerplate/App/Containers/LaunchScreen/LaunchScreenStyles.ts",
    "content": "import { StyleSheet } from \"react-native\";\nimport { ApplicationStyles, Metrics } from \"../../Themes/index\";\n\nexport default StyleSheet.create({\n  ...ApplicationStyles.screen,\n  container: {\n    paddingBottom: Metrics.baseMargin,\n  },\n  logo: {\n    marginTop: Metrics.doubleSection,\n    height: Metrics.images.logo,\n    width: Metrics.images.logo,\n    resizeMode: \"contain\",\n  },\n  centered: {\n    alignItems: \"center\",\n  },\n});\n"
  },
  {
    "path": "boilerplate/App/Containers/LaunchScreen/index.ts",
    "content": "import LaunchScreen from \"./LaunchScreen\";\n\nexport default LaunchScreen;\n"
  },
  {
    "path": "boilerplate/App/Containers/README.md",
    "content": "### Containers Folder\nA container is what they call a \"Smart Component\" in Redux.  It is a component\nwhich knows about Redux.  They are usually used as \"Screens\".\n\nAlso located in here are 2 special containers: `App.js` and `RootContainer.js`.\n\n`App.js` is first component loaded after `index.ios.js` or `index.android.js`.  The purpose of this file is to setup Redux or any other non-visual \"global\" modules.  Having Redux setup here helps with the hot-reloading process in React Native during development as it won't try to reload your sagas and reducers should your colors change (for example).\n\n`RootContainer.js` is the first visual component in the app.  It is the ancestor of all other screens and components.\n\nYou'll probably find you'll have great mileage in Ignite apps without even touching these 2 files.  They, of course, belong to you, so when you're ready to add something non-visual like Firebase or something visual like an overlay, you have spots to place these additions.\n"
  },
  {
    "path": "boilerplate/App/Containers/RootContainer/RootContainer.tsx",
    "content": "import * as React from \"react\";\nimport { StatusBar, View } from \"react-native\";\nimport { connect } from \"react-redux\";\nimport ReduxPersist from \"../../Config/ReduxPersist\";\nimport ReduxNavigation from \"../../Navigation/ReduxNavigation\";\nimport { StartupActions } from \"../../Reducers/StartupReducers\";\n\n// Styles\nimport styles from \"./RootContainerStyles\";\n\ninterface Props {\n  startup: () => void;\n}\n\ninterface State {\n\n}\n\nexport class RootContainer extends React.Component<Props, State> {\n  public componentDidMount() {\n    // if redux persist is not active fire startup action\n    if (!ReduxPersist.active) {\n      this.props.startup();\n    }\n  }\n\n  public render() {\n    return (\n      <View style={styles.applicationView}>\n        <StatusBar barStyle=\"light-content\" />\n        <ReduxNavigation />\n      </View>\n    );\n  }\n}\n\nconst mapDispatchToProps = (dispatch: any): Props => ({\n  startup: () => dispatch(StartupActions.startup()),\n});\n\nexport default connect(null, mapDispatchToProps)(RootContainer);\n"
  },
  {
    "path": "boilerplate/App/Containers/RootContainer/RootContainerStyles.ts",
    "content": "import {StyleSheet, TextStyle, ViewStyle } from \"react-native\";\nimport {Colors, Fonts, Metrics} from \"../../Themes/index\";\n\nexport default StyleSheet.create({\n  applicationView: {\n    flex: 1,\n  },\n  container: {\n    flex: 1,\n    justifyContent: \"center\",\n    backgroundColor: Colors.background,\n  },\n  welcome: {\n    fontSize: 20,\n    textAlign: \"center\",\n    fontFamily: Fonts.type.base,\n    margin: Metrics.baseMargin,\n  },\n  myImage: {\n    width: 200,\n    height: 200,\n    alignSelf: \"center\",\n  },\n});\n"
  },
  {
    "path": "boilerplate/App/Containers/RootContainer/index.ts",
    "content": "import RootContainer from \"./RootContainer\";\n\nexport default RootContainer;\n"
  },
  {
    "path": "boilerplate/App/Fixtures/README.md",
    "content": "### Fixtures folder\nAll key API responses are housed here.\n\nThese API responses can be used for several reasons.  _E.G._:\n* To bypass logins when building any screen of the application\n* To quickly test API parsing in unit tests\n* To separate Network from Data concerns while coding\n"
  },
  {
    "path": "boilerplate/App/Fixtures/gantman.json",
    "content": "{\n  \"total_count\": 7,\n  \"incomplete_results\": false,\n  \"items\": [\n    {\n      \"login\": \"GantMan\",\n      \"id\": 997157,\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/997157?v=3\",\n      \"gravatar_id\": \"\",\n      \"url\": \"https://api.github.com/users/GantMan\",\n      \"html_url\": \"https://github.com/GantMan\",\n      \"followers_url\": \"https://api.github.com/users/GantMan/followers\",\n      \"following_url\": \"https://api.github.com/users/GantMan/following{/other_user}\",\n      \"gists_url\": \"https://api.github.com/users/GantMan/gists{/gist_id}\",\n      \"starred_url\": \"https://api.github.com/users/GantMan/starred{/owner}{/repo}\",\n      \"subscriptions_url\": \"https://api.github.com/users/GantMan/subscriptions\",\n      \"organizations_url\": \"https://api.github.com/users/GantMan/orgs\",\n      \"repos_url\": \"https://api.github.com/users/GantMan/repos\",\n      \"events_url\": \"https://api.github.com/users/GantMan/events{/privacy}\",\n      \"received_events_url\": \"https://api.github.com/users/GantMan/received_events\",\n      \"type\": \"User\",\n      \"site_admin\": false,\n      \"score\": 122.12115\n    },\n    {\n      \"login\": \"vlad-G\",\n      \"id\": 13520880,\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/13520880?v=3\",\n      \"gravatar_id\": \"\",\n      \"url\": \"https://api.github.com/users/vlad-G\",\n      \"html_url\": \"https://github.com/vlad-G\",\n      \"followers_url\": \"https://api.github.com/users/vlad-G/followers\",\n      \"following_url\": \"https://api.github.com/users/vlad-G/following{/other_user}\",\n      \"gists_url\": \"https://api.github.com/users/vlad-G/gists{/gist_id}\",\n      \"starred_url\": \"https://api.github.com/users/vlad-G/starred{/owner}{/repo}\",\n      \"subscriptions_url\": \"https://api.github.com/users/vlad-G/subscriptions\",\n      \"organizations_url\": \"https://api.github.com/users/vlad-G/orgs\",\n      \"repos_url\": \"https://api.github.com/users/vlad-G/repos\",\n      \"events_url\": \"https://api.github.com/users/vlad-G/events{/privacy}\",\n      \"received_events_url\": \"https://api.github.com/users/vlad-G/received_events\",\n      \"type\": \"User\",\n      \"site_admin\": false,\n      \"score\": 12.69848\n    },\n    {\n      \"login\": \"gantmani\",\n      \"id\": 3034094,\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/3034094?v=3\",\n      \"gravatar_id\": \"\",\n      \"url\": \"https://api.github.com/users/gantmani\",\n      \"html_url\": \"https://github.com/gantmani\",\n      \"followers_url\": \"https://api.github.com/users/gantmani/followers\",\n      \"following_url\": \"https://api.github.com/users/gantmani/following{/other_user}\",\n      \"gists_url\": \"https://api.github.com/users/gantmani/gists{/gist_id}\",\n      \"starred_url\": \"https://api.github.com/users/gantmani/starred{/owner}{/repo}\",\n      \"subscriptions_url\": \"https://api.github.com/users/gantmani/subscriptions\",\n      \"organizations_url\": \"https://api.github.com/users/gantmani/orgs\",\n      \"repos_url\": \"https://api.github.com/users/gantmani/repos\",\n      \"events_url\": \"https://api.github.com/users/gantmani/events{/privacy}\",\n      \"received_events_url\": \"https://api.github.com/users/gantmani/received_events\",\n      \"type\": \"User\",\n      \"site_admin\": false,\n      \"score\": 11.641713\n    },\n    {\n      \"login\": \"sgantman\",\n      \"id\": 5911526,\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/5911526?v=3\",\n      \"gravatar_id\": \"\",\n      \"url\": \"https://api.github.com/users/sgantman\",\n      \"html_url\": \"https://github.com/sgantman\",\n      \"followers_url\": \"https://api.github.com/users/sgantman/followers\",\n      \"following_url\": \"https://api.github.com/users/sgantman/following{/other_user}\",\n      \"gists_url\": \"https://api.github.com/users/sgantman/gists{/gist_id}\",\n      \"starred_url\": \"https://api.github.com/users/sgantman/starred{/owner}{/repo}\",\n      \"subscriptions_url\": \"https://api.github.com/users/sgantman/subscriptions\",\n      \"organizations_url\": \"https://api.github.com/users/sgantman/orgs\",\n      \"repos_url\": \"https://api.github.com/users/sgantman/repos\",\n      \"events_url\": \"https://api.github.com/users/sgantman/events{/privacy}\",\n      \"received_events_url\": \"https://api.github.com/users/sgantman/received_events\",\n      \"type\": \"User\",\n      \"site_admin\": false,\n      \"score\": 7.926345\n    },\n    {\n      \"login\": \"michaelgantman\",\n      \"id\": 16693070,\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/16693070?v=3\",\n      \"gravatar_id\": \"\",\n      \"url\": \"https://api.github.com/users/michaelgantman\",\n      \"html_url\": \"https://github.com/michaelgantman\",\n      \"followers_url\": \"https://api.github.com/users/michaelgantman/followers\",\n      \"following_url\": \"https://api.github.com/users/michaelgantman/following{/other_user}\",\n      \"gists_url\": \"https://api.github.com/users/michaelgantman/gists{/gist_id}\",\n      \"starred_url\": \"https://api.github.com/users/michaelgantman/starred{/owner}{/repo}\",\n      \"subscriptions_url\": \"https://api.github.com/users/michaelgantman/subscriptions\",\n      \"organizations_url\": \"https://api.github.com/users/michaelgantman/orgs\",\n      \"repos_url\": \"https://api.github.com/users/michaelgantman/repos\",\n      \"events_url\": \"https://api.github.com/users/michaelgantman/events{/privacy}\",\n      \"received_events_url\": \"https://api.github.com/users/michaelgantman/received_events\",\n      \"type\": \"User\",\n      \"site_admin\": false,\n      \"score\": 7.926345\n    },\n    {\n      \"login\": \"gantmanis\",\n      \"id\": 19141249,\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/19141249?v=3\",\n      \"gravatar_id\": \"\",\n      \"url\": \"https://api.github.com/users/gantmanis\",\n      \"html_url\": \"https://github.com/gantmanis\",\n      \"followers_url\": \"https://api.github.com/users/gantmanis/followers\",\n      \"following_url\": \"https://api.github.com/users/gantmanis/following{/other_user}\",\n      \"gists_url\": \"https://api.github.com/users/gantmanis/gists{/gist_id}\",\n      \"starred_url\": \"https://api.github.com/users/gantmanis/starred{/owner}{/repo}\",\n      \"subscriptions_url\": \"https://api.github.com/users/gantmanis/subscriptions\",\n      \"organizations_url\": \"https://api.github.com/users/gantmanis/orgs\",\n      \"repos_url\": \"https://api.github.com/users/gantmanis/repos\",\n      \"events_url\": \"https://api.github.com/users/gantmanis/events{/privacy}\",\n      \"received_events_url\": \"https://api.github.com/users/gantmanis/received_events\",\n      \"type\": \"User\",\n      \"site_admin\": false,\n      \"score\": 7.8813524\n    },\n    {\n      \"login\": \"Gantman2014\",\n      \"id\": 7669410,\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/7669410?v=3\",\n      \"gravatar_id\": \"\",\n      \"url\": \"https://api.github.com/users/Gantman2014\",\n      \"html_url\": \"https://github.com/Gantman2014\",\n      \"followers_url\": \"https://api.github.com/users/Gantman2014/followers\",\n      \"following_url\": \"https://api.github.com/users/Gantman2014/following{/other_user}\",\n      \"gists_url\": \"https://api.github.com/users/Gantman2014/gists{/gist_id}\",\n      \"starred_url\": \"https://api.github.com/users/Gantman2014/starred{/owner}{/repo}\",\n      \"subscriptions_url\": \"https://api.github.com/users/Gantman2014/subscriptions\",\n      \"organizations_url\": \"https://api.github.com/users/Gantman2014/orgs\",\n      \"repos_url\": \"https://api.github.com/users/Gantman2014/repos\",\n      \"events_url\": \"https://api.github.com/users/Gantman2014/events{/privacy}\",\n      \"received_events_url\": \"https://api.github.com/users/Gantman2014/received_events\",\n      \"type\": \"User\",\n      \"site_admin\": false,\n      \"score\": 7.8813524\n    }\n  ]\n}"
  },
  {
    "path": "boilerplate/App/Fixtures/rateLimit.json",
    "content": "{\n  \"resources\": {\n    \"core\": {\n      \"limit\": 60,\n      \"remaining\": 42,\n      \"reset\": 1488126913\n    },\n    \"search\": {\n      \"limit\": 10,\n      \"remaining\": 9,\n      \"reset\": 1488126003\n    }\n  },\n  \"rate\": {\n    \"limit\": 60,\n    \"remaining\": 42,\n    \"reset\": 1488126913\n  }\n}"
  },
  {
    "path": "boilerplate/App/Fixtures/root.json",
    "content": "{\n  \"current_user_url\": \"https://api.github.com/user\",\n  \"current_user_authorizations_html_url\": \"https://github.com/settings/connections/applications{/client_id}\",\n  \"authorizations_url\": \"https://api.github.com/authorizations\",\n  \"code_search_url\": \"https://api.github.com/search/code?q={query}{&page,per_page,sort,order}\",\n  \"commit_search_url\": \"https://api.github.com/search/commits?q={query}{&page,per_page,sort,order}\",\n  \"emails_url\": \"https://api.github.com/user/emails\",\n  \"emojis_url\": \"https://api.github.com/emojis\",\n  \"events_url\": \"https://api.github.com/events\",\n  \"feeds_url\": \"https://api.github.com/feeds\",\n  \"followers_url\": \"https://api.github.com/user/followers\",\n  \"following_url\": \"https://api.github.com/user/following{/target}\",\n  \"gists_url\": \"https://api.github.com/gists{/gist_id}\",\n  \"hub_url\": \"https://api.github.com/hub\",\n  \"issue_search_url\": \"https://api.github.com/search/issues?q={query}{&page,per_page,sort,order}\",\n  \"issues_url\": \"https://api.github.com/issues\",\n  \"keys_url\": \"https://api.github.com/user/keys\",\n  \"notifications_url\": \"https://api.github.com/notifications\",\n  \"organization_repositories_url\": \"https://api.github.com/orgs/{org}/repos{?type,page,per_page,sort}\",\n  \"organization_url\": \"https://api.github.com/orgs/{org}\",\n  \"public_gists_url\": \"https://api.github.com/gists/public\",\n  \"rate_limit_url\": \"https://api.github.com/rate_limit\",\n  \"repository_url\": \"https://api.github.com/repos/{owner}/{repo}\",\n  \"repository_search_url\": \"https://api.github.com/search/repositories?q={query}{&page,per_page,sort,order}\",\n  \"current_user_repositories_url\": \"https://api.github.com/user/repos{?type,page,per_page,sort}\",\n  \"starred_url\": \"https://api.github.com/user/starred{/owner}{/repo}\",\n  \"starred_gists_url\": \"https://api.github.com/gists/starred\",\n  \"team_url\": \"https://api.github.com/teams\",\n  \"user_url\": \"https://api.github.com/users/{user}\",\n  \"user_organizations_url\": \"https://api.github.com/user/orgs\",\n  \"user_repositories_url\": \"https://api.github.com/users/{user}/repos{?type,page,per_page,sort}\",\n  \"user_search_url\": \"https://api.github.com/search/users?q={query}{&page,per_page,sort,order}\"\n}"
  },
  {
    "path": "boilerplate/App/Fixtures/skellock.json",
    "content": "{\n  \"total_count\": 1,\n  \"incomplete_results\": false,\n  \"items\": [\n    {\n      \"login\": \"skellock\",\n      \"id\": 68273,\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/68273?v=3\",\n      \"gravatar_id\": \"\",\n      \"url\": \"https://api.github.com/users/skellock\",\n      \"html_url\": \"https://github.com/skellock\",\n      \"followers_url\": \"https://api.github.com/users/skellock/followers\",\n      \"following_url\": \"https://api.github.com/users/skellock/following{/other_user}\",\n      \"gists_url\": \"https://api.github.com/users/skellock/gists{/gist_id}\",\n      \"starred_url\": \"https://api.github.com/users/skellock/starred{/owner}{/repo}\",\n      \"subscriptions_url\": \"https://api.github.com/users/skellock/subscriptions\",\n      \"organizations_url\": \"https://api.github.com/users/skellock/orgs\",\n      \"repos_url\": \"https://api.github.com/users/skellock/repos\",\n      \"events_url\": \"https://api.github.com/users/skellock/events{/privacy}\",\n      \"received_events_url\": \"https://api.github.com/users/skellock/received_events\",\n      \"type\": \"User\",\n      \"site_admin\": false,\n      \"score\": 107.22611\n    }\n  ]\n}"
  },
  {
    "path": "boilerplate/App/Images/README.md",
    "content": "### Images folder\nHolds all images for the applications."
  },
  {
    "path": "boilerplate/App/Lib/README.md",
    "content": "# Lib\n\nAt first glance, this could appear to be a \"miscellaneous\" folder, but we recommend that you treat this as proving ground for components that could be reusable outside your project.\n\nMaybe you're writing a set of utilities that you could use outside your project, but they're not quite ready or battle tested.  This folder would be a great place to put them.  They ideally be pure functions have no dependencies on other things in your App folder.\n"
  },
  {
    "path": "boilerplate/App/Lib/ReduxHelpers.ts",
    "content": "import { ActionCreator, AnyAction, Reducer, ReducersMapObject } from \"redux\";\nimport { createAction, EmptyAction, FluxStandardAction, getType, PayloadAction, TypeGetter } from \"typesafe-actions\";\n\nexport type FluxActionCreator<T extends string, AC extends (...args: any[]) => FluxStandardAction<T> = any> = AC & TypeGetter<T>;\n\ntype ActionCreatorMap<T> = {\n    readonly [P in keyof T]: FluxActionCreator<P>;\n};\n\nexport type ReducerMap<A, S> = {\n    readonly [T in keyof A]: Reducer<S>;\n};\n\nexport function mapReducers<S, R extends ReducersMapObject>(\n    initialState: S,\n    reducers: R,\n    actionCreators: ActionCreatorMap<R>): Reducer<S> {\n    const reducerMap = new Map(Object.entries(actionCreators).map(([key, val]): [string, Reducer<any>] =>\n        [getType(val), reducers[key]]));\n\n    return (state: S = initialState, action: AnyAction) => {\n        if (!(\"type\" in action)) {\n            return state;\n        }\n        const reducer = reducerMap.get(action.type);\n        if (!reducer) {\n            return state;\n        }\n        return reducer(state, action);\n    };\n}\n"
  },
  {
    "path": "boilerplate/App/Navigation/AppNavigation.tsx",
    "content": "import { StackNavigator } from \"react-navigation\";\nimport LaunchScreen from \"../Containers/LaunchScreen\";\n\nimport styles from \"./Styles/NavigationStyles\";\n\n// Manifest of possible screens\nconst PrimaryNav = StackNavigator({\n  LaunchScreen: { screen: LaunchScreen },\n}, {\n  // Default config for all screens\n  headerMode: \"none\",\n  initialRouteName: \"LaunchScreen\",\n  navigationOptions: {\n    headerStyle: styles.header,\n  },\n});\n\nexport default PrimaryNav;\n"
  },
  {
    "path": "boilerplate/App/Navigation/ReduxNavigation.tsx",
    "content": "import * as React from \"react\";\nimport * as ReactNavigation from \"react-navigation\";\nimport { connect } from \"react-redux\";\nimport AppNavigation from \"./AppNavigation\";\n\n// here is our redux-aware smart component\nfunction ReduxNavigation(props) {\n  const { dispatch, nav } = props;\n  const navigation = ReactNavigation.addNavigationHelpers({\n    dispatch,\n    state: nav,\n  });\n\n  return <AppNavigation navigation={navigation} />;\n}\n\nconst mapStateToProps = (state) => ({ nav: state.nav });\nexport default connect(mapStateToProps)(ReduxNavigation);\n"
  },
  {
    "path": "boilerplate/App/Navigation/Styles/NavigationStyles.ts",
    "content": "import { StyleSheet } from \"react-native\";\nimport { Colors } from \"../../Themes/index\";\n\nexport default StyleSheet.create({\n  header: {\n    backgroundColor: Colors.background,\n  },\n});\n"
  },
  {
    "path": "boilerplate/App/Reducers/CreateStore.tsx",
    "content": "import Reactotron from \"reactotron-react-native\";\nimport { applyMiddleware, compose, createStore, Reducer } from \"redux\";\nimport sagaMiddlewareFactory, { Monitor, SagaIterator } from \"redux-saga\";\nimport Config from \"../Config/DebugConfig\";\nimport ScreenTracking from \"./ScreenTrackingMiddleware\";\n\n// creates the store\nexport default (rootReducer: Reducer<any>, rootSaga: () => SagaIterator) => {\n  /* ------------- Redux Configuration ------------- */\n\n  const middleware = [];\n  const enhancers = [];\n\n  /* ------------- Analytics Middleware ------------- */\n  if (Config.useReactotron) {\n    middleware.push(ScreenTracking);\n  }\n  /* ------------- Saga Middleware ------------- */\n\n  let opts = {};\n  if (Config.useReactotron) {\n    const sagaMonitor: Monitor = Reactotron.createSagaMonitor();\n    opts = { sagaMonitor };\n  }\n  const sagaMiddleware = sagaMiddlewareFactory(opts);\n  middleware.push(sagaMiddleware);\n\n  /* ------------- Assemble Middleware ------------- */\n\n  enhancers.push(applyMiddleware(...middleware));\n\n  // if Reactotron is enabled (default for __DEV__), we'll create the store through Reactotron\n  const createAppropriateStore = Config.useReactotron ? Reactotron.createStore : createStore;\n\n  const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;\n\n  const store = createAppropriateStore(rootReducer, composeEnhancers(...enhancers));\n\n  // kick off root saga\n  const sagasManager = sagaMiddleware.run(rootSaga);\n\n  return {\n    store,\n    sagasManager,\n    sagaMiddleware,\n  };\n};\n"
  },
  {
    "path": "boilerplate/App/Reducers/GithubReducers/GithubReducersTest.tsx",
    "content": "/// <reference types=\"@types/jest\" />\nimport { GithubActions, GithubReducer, INITIAL_STATE } from \"./index\";\n\ntest(\"request\", () => {\n  const username = \"taco\";\n  const state = GithubReducer(INITIAL_STATE, GithubActions.userRequest({username}));\n\n  expect(state.fetching).toBe(true);\n  expect(state.username).toBe(username);\n  expect(state.avatar).toBeNull();\n});\n\ntest(\"success\", () => {\n  const avatar = \"http://placekitten.com/200/300\";\n  const state = GithubReducer(INITIAL_STATE, GithubActions.userSuccess({avatar}));\n\n  expect(state.fetching).toBe(false);\n  expect(state.avatar).toBe(avatar);\n  expect(state.error).toBeNull();\n});\n\ntest(\"failure\", () => {\n  const state = GithubReducer(INITIAL_STATE, GithubActions.userFailure());\n\n  expect(state.fetching).toBe(false);\n  expect(state.error).toBe(true);\n  expect(state.avatar).toBeNull();\n});\n"
  },
  {
    "path": "boilerplate/App/Reducers/GithubReducers/index.tsx",
    "content": "import { Action, AnyAction, Reducer } from \"redux\";\nimport * as SI from \"seamless-immutable\";\nimport { createAction, PayloadAction } from \"typesafe-actions\";\nimport { mapReducers, ReducerMap } from \"../../Lib/ReduxHelpers\";\n\n/* ------------- Types and Action Creators ------------- */\ninterface RequestParams {username: string; }\ninterface SuccessParams {avatar: string; }\nconst actions = {\n  userRequest: createAction(\"githubUserRequest\", (params: RequestParams) =>\n    ({type: \"githubUserRequest\", payload: params})),\n  userSuccess: createAction(\"githubUserSuccess\", (params: SuccessParams) =>\n    ({type: \"githubUserSuccess\", payload: params})),\n  userFailure: createAction(\"githubUserFailure\"),\n};\n\nexport const GithubActions = actions;\n\ninterface GithubState {\n  avatar?: string | null;\n  fetching?: boolean | null;\n  error?: boolean | null;\n  username?: string | null;\n}\n\nexport type GithubAction = PayloadAction<string, GithubState>;\n\nexport type ImmutableGithubState = SI.ImmutableObject<GithubState>;\n\n/* ------------- Initial State ------------- */\n\nexport const INITIAL_STATE: ImmutableGithubState = SI.from({\n  avatar: null,\n  fetching: null,\n  error: null,\n  username: null,\n});\n\n/* ------------- Reducers ------------- */\n\n// request the avatar for a user\nexport const userRequest: Reducer<ImmutableGithubState> =\n  (state: ImmutableGithubState, { payload }: AnyAction & {payload?: RequestParams}) =>\n    payload ? state.merge({ fetching: true, username: payload.username, avatar: null }) : state;\n\n// successful avatar lookup\nexport const userSuccess: Reducer<ImmutableGithubState> =\n  (state: ImmutableGithubState, { payload }: AnyAction & {payload?: SuccessParams}) =>\n  payload ? state.merge({ fetching: false, error: null, avatar: payload.avatar }) : state;\n\n// failed to get the avatar\nexport const userFailure: Reducer<ImmutableGithubState> = (state: ImmutableGithubState) =>\n  state.merge({ fetching: false, error: true, avatar: null });\n\n/* ------------- Hookup Reducers To Types ------------- */\n\nconst reducerMap: ReducerMap<typeof actions, ImmutableGithubState> = {\n  userRequest,\n  userSuccess,\n  userFailure,\n};\n\nexport const GithubReducer = mapReducers(INITIAL_STATE, reducerMap, actions);\n\nexport default GithubReducer;\n"
  },
  {
    "path": "boilerplate/App/Reducers/NavigationReducers/index.tsx",
    "content": "import { NavigationAction, NavigationState } from \"react-navigation\";\nimport AppNavigation from \"../../Navigation/AppNavigation\";\n\nexport type NavigationState = NavigationState;\n\nexport const NavigationReducer = (state: NavigationState, action: NavigationAction) => {\n  const newState = AppNavigation.router.getStateForAction(action, state);\n  return newState || state;\n};\n"
  },
  {
    "path": "boilerplate/App/Reducers/ScreenTrackingMiddleware.tsx",
    "content": "import { NavigationActions } from \"react-navigation\";\nimport Reactotron from \"reactotron-react-native\";\n\n// gets the current screen from navigation state\nconst getCurrentRouteName = (navigationState) => {\n  if (!navigationState) {\n    return null;\n  }\n  const route = navigationState.routes[navigationState.index];\n  // dive into nested navigators\n  if (route.routes) {\n    return getCurrentRouteName(route);\n  }\n  return route.routeName;\n};\n\nconst screenTracking = ({ getState }) => (next) => (action) => {\n  if (\n    action.type !== NavigationActions.NAVIGATE &&\n    action.type !== NavigationActions.BACK\n  ) {\n    return next(action);\n  }\n\n  const currentScreen = getCurrentRouteName(getState().nav);\n  const result = next(action);\n  const nextScreen = getCurrentRouteName(getState().nav);\n  if (nextScreen !== currentScreen) {\n    try {\n      Reactotron.log(`NAVIGATING ${currentScreen} to ${nextScreen}`);\n      // Example: Analytics.trackEvent('user_navigation', {currentScreen, nextScreen})\n    } catch (e) {\n      Reactotron.log(e);\n    }\n  }\n  return result;\n};\n\nexport default screenTracking;\n"
  },
  {
    "path": "boilerplate/App/Reducers/StartupReducers/index.tsx",
    "content": "import { createAction } from \"typesafe-actions\";\n\n/* ------------- Types and Action Creators ------------- */\n\nconst actions = {\n  startup: createAction(\"startup\"),\n};\n\nexport const StartupActions = actions;\n"
  },
  {
    "path": "boilerplate/App/Reducers/index.ts",
    "content": "/// <reference types=\"@types/webpack-env\" />\nimport { combineReducers } from \"redux\";\nimport root from \"../Sagas\";\nimport configureStore from \"./CreateStore\";\nimport { GithubReducer, ImmutableGithubState } from \"./GithubReducers\";\nimport { NavigationReducer, NavigationState } from \"./NavigationReducers\";\n\n/* ------------- Assemble The Reducers ------------- */\nexport const reducers = combineReducers({\n  nav: NavigationReducer,\n  github: GithubReducer,\n});\n\nexport interface State {\n  github: ImmutableGithubState;\n  nav: NavigationState;\n}\n\nexport default () => {\n  // tslint:disable-next-line:prefer-const\n  let { store, sagasManager, sagaMiddleware } = configureStore(reducers, root);\n\n  if (module.hot) {\n    module.hot.accept(() => {\n      const nextRootReducer = require(\"./\").reducers;\n      store.replaceReducer(nextRootReducer);\n\n      const newYieldedSagas = require(\"../Sagas\").default;\n      sagasManager.cancel();\n      sagasManager.done.then(() => {\n        sagasManager = sagaMiddleware.run(newYieldedSagas);\n      });\n    });\n  }\n\n  return store;\n};\n"
  },
  {
    "path": "boilerplate/App/Sagas/GithubSagas/GithubSagaTest.ts",
    "content": "/// <reference types=\"@types/jest\" />\nimport { path } from \"ramda\";\nimport { call, put } from \"redux-saga/effects\";\nimport { GithubActions } from \"../../Reducers/GithubReducers\";\nimport FixtureAPI from \"../../Services/FixtureApi\";\nimport { getUserAvatar } from \"./index\";\n\nconst stepper = (fn) => (mock?) => fn.next(mock).value;\n\ntest(\"first calls API\", () => {\n  const step = stepper(getUserAvatar(FixtureAPI, GithubActions.userRequest({username: \"ascorbic\"})));\n  // first yield is API\n  expect(step()).toEqual(call(FixtureAPI.getUser, \"ascorbic\"));\n});\n\ntest(\"success path\", () => {\n  FixtureAPI.getUser(\"ascorbic\").then((response) => {\n\n    // tslint:disable-next-line:max-line-length\n    const step = stepper(getUserAvatar(FixtureAPI, GithubActions.userRequest({username: \"ascorbic\"})));\n    // first step API\n    step();\n    // Second step successful return\n    const stepResponse = step(response);\n    // Get the avatar Url from the response\n    const firstUser = path([\"data\", \"items\"], response)[0];\n    const avatar = firstUser.avatar_url;\n    expect(stepResponse).toEqual(put(GithubActions.userSuccess(avatar)));\n  });\n});\n\ntest(\"failure path\", () => {\n  const response = {ok: false};\n  const step = stepper(getUserAvatar(FixtureAPI, GithubActions.userRequest({username: \"ascorbic\"})));\n  // first step API\n  step();\n  // Second step failed response\n  expect(step(response)).toEqual(put(GithubActions.userFailure()));\n});\n"
  },
  {
    "path": "boilerplate/App/Sagas/GithubSagas/index.ts",
    "content": "import { ApiResponse } from \"apisauce\";\nimport { path } from \"ramda\";\nimport { SagaIterator } from \"redux-saga\";\nimport { call, put } from \"redux-saga/effects\";\nimport { GithubAction, GithubActions } from \"../../Reducers/GithubReducers\";\nimport { GithubApi, GithubResponse, GithubUser } from \"../../Services/GithubApi\";\n\nexport function * getUserAvatar(api: GithubApi, action: GithubAction): SagaIterator {\n  const { payload } = action;\n  // make the call to the api\n  const response: ApiResponse<GithubResponse> = yield call(api.getUser, payload.username);\n\n  if (response.ok) {\n    const firstUser = path<GithubUser[]>([\"data\", \"items\"], response)[0];\n    const avatar = firstUser.avatar_url;\n    // do data conversion here if needed\n    yield put(GithubActions.userSuccess({avatar}));\n  } else {\n    yield put(GithubActions.userFailure());\n  }\n}\n"
  },
  {
    "path": "boilerplate/App/Sagas/StartupSagas/StartupSagaTest.ts",
    "content": "/// <reference types=\"@types/jest\" />\nimport { put, select } from \"redux-saga/effects\";\nimport { GithubActions } from \"../../Reducers/GithubReducers\";\nimport { selectAvatar, startup } from \"./index\";\n\nconst stepper = (fn) => (mock) => fn.next(mock).value;\n\ntest(\"watches for the right action\", () => {\n  const step = stepper(startup());\n  expect(step()).toEqual(select(selectAvatar));\n  expect(step()).toEqual(put(GithubActions.userRequest({username: \"ascorbic\"})));\n});\n"
  },
  {
    "path": "boilerplate/App/Sagas/StartupSagas/index.ts",
    "content": "import { is } from \"ramda\";\nimport Reactotron from \"reactotron-react-native\";\nimport { Action } from \"redux\";\nimport { SagaIterator } from \"redux-saga\";\nimport { put, select } from \"redux-saga/effects\";\nimport { GithubAction, GithubActions } from \"../../Reducers/GithubReducers\";\nimport { StartupActions } from \"../../Reducers/StartupReducers\";\n\n// exported to make available for tests\nexport const selectAvatar = (state: any) => state.github.avatar;\n\n// process STARTUP actions\nexport function * startup(action?: Action): SagaIterator {\n  if (__DEV__) {\n    // straight-up string logging\n    Reactotron.log(\"Hello, I'm an example of how to log via Reactotron.\");\n    Reactotron.log(action);\n    // logging an object for better clarity\n    Reactotron.log({\n      message: \"pass objects for better logging\",\n      someGeneratorFunction: selectAvatar,\n    });\n\n    // fully customized!\n    const subObject = { a: 1, b: [1, 2, 3], c: true, circularDependency: undefined as any};\n    subObject.circularDependency = subObject; // osnap!\n    Reactotron.display({\n      name: \"🔥 IGNITE 🔥\",\n      preview: \"You should totally expand this\",\n      value: {\n        \"💃\": \"Welcome to the future!\",\n        subObject,\n        \"someInlineFunction\": () => true,\n        \"someGeneratorFunction\": startup,\n        \"someNormalFunction\": selectAvatar,\n      },\n    });\n  }\n  const avatar = yield select(selectAvatar);\n  // only get if we don't have it yet\n  if (!is(String, avatar)) {\n    yield put(GithubActions.userRequest({username: \"ascorbic\"}));\n  }\n\n}\n"
  },
  {
    "path": "boilerplate/App/Sagas/index.ts",
    "content": "import { all, takeLatest } from \"redux-saga/effects\";\nimport { getType } from \"typesafe-actions\";\n\nimport DebugConfig from \"../Config/DebugConfig\";\nimport FixtureAPI from \"../Services/FixtureApi\";\nimport {createAPI, GithubApi} from \"../Services/GithubApi\";\n\n/* ------------- Types ------------- */\n\nimport { GithubActions } from \"../Reducers/GithubReducers\";\nimport { StartupActions } from \"../Reducers/StartupReducers\";\n\n/* ------------- Sagas ------------- */\n\nimport { getUserAvatar } from \"./GithubSagas\";\nimport { startup } from \"./StartupSagas\";\n\n/* ------------- API ------------- */\n\n// The API we use is only used from Sagas, so we create it here and pass along\n// to the sagas which need it.\nconst api = DebugConfig.useFixtures ? FixtureAPI : createAPI();\n\n/* ------------- Connect Types To Sagas ------------- */\n\nexport default function * root() {\n  yield all([\n    // some sagas only receive an action\n    takeLatest(getType(StartupActions.startup), startup),\n\n    // some sagas receive extra parameters in addition to an action\n    takeLatest(getType(GithubActions.userRequest), getUserAvatar, api),\n  ]);\n}\n"
  },
  {
    "path": "boilerplate/App/Services/Api.ts",
    "content": "// This is for the devscreens\nimport {createAPI} from \"./GithubApi\";\n\nexport default {\n    create: createAPI,\n};\n"
  },
  {
    "path": "boilerplate/App/Services/ExamplesRegistry.tsx",
    "content": "import R from \"ramda\";\nimport * as React from \"react\";\nimport { Text, View } from \"react-native\";\nimport DebugConfig from \"../Config/DebugConfig\";\nimport { ApplicationStyles } from \"../Themes\";\nconst globalComponentExamplesRegistry = [];\nconst globalPluginExamplesRegistry = [];\n\nexport const addComponentExample = (title, usage = () => {}) => { if (DebugConfig.includeExamples) globalComponentExamplesRegistry.push({title, usage}); }; // eslint-disable-line\n\nexport const addPluginExample = (title, usage = () => {}) => { if (DebugConfig.includeExamples) globalPluginExamplesRegistry.push({title, usage}); }; // eslint-disable-line\n\nconst renderComponentExample = (example) => {\n  return (\n    <View key={example.title}>\n      <View style={ApplicationStyles.darkLabelContainer}>\n        <Text style={ApplicationStyles.darkLabel}>{example.title}</Text>\n      </View>\n      {example.usage.call()}\n    </View>\n  );\n};\n\nconst renderPluginExample = (example) => {\n  return (\n    <View key={example.title}>\n      <View style={ApplicationStyles.darkLabelContainer}>\n        <Text style={ApplicationStyles.darkLabel}>{example.title}</Text>\n      </View>\n      {example.usage.call()}\n    </View>\n  );\n};\n\nexport const renderComponentExamples = () => R.map(renderComponentExample, globalComponentExamplesRegistry);\n\nexport const renderPluginExamples = () => R.map(renderPluginExample, globalPluginExamplesRegistry);\n\n// Default for readability\nexport default {\n  renderComponentExamples,\n  addComponentExample,\n  renderPluginExamples,\n  addPluginExample,\n};\n"
  },
  {
    "path": "boilerplate/App/Services/FixtureAPITest.tsx",
    "content": "/// <reference types=\"@types/jest\" />\nimport * as R from \"ramda\";\nimport FixtureAPI from \"../../App/Services/FixtureApi\";\nimport API from \"../../App/Services/GithubApi\";\n\ntest(\"All fixtures map to actual API\", () => {\n  const fixtureKeys = R.keys(FixtureAPI).sort();\n  const apiKeys = R.keys(API.createAPI());\n\n  const intersection = R.intersection(fixtureKeys, apiKeys).sort();\n\n  // There is no difference between the intersection and all fixtures\n  expect(R.equals(fixtureKeys, intersection)).toBe(true);\n});\n\ntest(\"FixtureAPI getRate returns the right file\", () => {\n  const expectedFile = require(\"../../App/Fixtures/rateLimit.json\");\n\n  return FixtureAPI.getRate().then((data) => expect(data).toEqual({\n    ok: true,\n    data: expectedFile,\n  }));\n});\n\ntest(\"FixtureAPI getUser returns the right file for gantman\", () => {\n  const expectedFile = require(\"../../App/Fixtures/gantman.json\");\n  return FixtureAPI.getUser(\"GantMan\").then((data) => expect(data).toEqual({\n    ok: true,\n    data: expectedFile,\n  }));\n});\n\ntest(\"FixtureAPI getUser returns the right file for skellock as default\", () => {\n  const expectedFile = require(\"../../App/Fixtures/skellock.json\");\n  return FixtureAPI.getUser(\"Whatever\").then((data) => expect(data).toEqual({\n    ok: true,\n    data: expectedFile,\n  }));\n});\n"
  },
  {
    "path": "boilerplate/App/Services/FixtureApi.tsx",
    "content": "import {ApiResponse} from \"apisauce\";\nimport {GithubApi, GithubResponse} from \"./GithubApi\";\n\nexport default {\n  // Functions return fixtures\n  getRoot: (): Promise<ApiResponse<GithubResponse>> => {\n    return Promise.resolve({\n      ok: true,\n      data: require(\"../Fixtures/root.json\"),\n    } as ApiResponse<any>);\n  },\n  getRate: (): Promise<ApiResponse<GithubResponse>> => {\n    return Promise.resolve({\n      ok: true,\n      data: require(\"../Fixtures/rateLimit.json\"),\n    } as ApiResponse<any>);\n  },\n  getUser: (username: string): Promise<ApiResponse<GithubResponse>> => {\n    // This fixture only supports gantman or else returns skellock\n    const gantmanData = require(\"../Fixtures/gantman.json\");\n    const skellockData = require(\"../Fixtures/skellock.json\");\n    return Promise.resolve({\n      ok: true,\n      data: username.toLowerCase() === \"gantman\" ? gantmanData : skellockData,\n    } as ApiResponse<GithubResponse>);\n  },\n} as GithubApi;\n"
  },
  {
    "path": "boilerplate/App/Services/GithubApi.tsx",
    "content": "// a library to wrap and simplify api calls\nimport {ApiResponse, create as apicreate} from \"apisauce\";\n\nexport interface GithubApi {\n  getRoot: () => Promise<ApiResponse<{}>>;\n  getRate: () => Promise<ApiResponse<{}>>;\n  getUser: (username: string) => Promise<ApiResponse<GithubResponse>>;\n}\n\nexport interface GithubUser {\n  login: string;\n  id: number;\n  avatar_url: string;\n  gravatar_id: string;\n  url: string;\n  html_url: string;\n  followers_url: string;\n  following_url: string;\n  gists_url: string;\n  starred_url: string;\n  subscriptions_url: string;\n  organizations_url: string;\n  repos_url: string;\n  events_url: string;\n  received_events_url: string;\n  type: \"User\";\n  site_admin: boolean;\n  score: number;\n}\n\nexport interface GithubResponse {\n  total_count: number;\n  incomplete_results: false;\n  items: GithubUser[];\n}\n\n// our \"constructor\"\nexport const createAPI = (baseURL = \"https://api.github.com/\"): GithubApi => {\n  // ------\n  // STEP 1\n  // ------\n  //\n  // Create and configure an apisauce-based api object.\n  //\n  const api = apicreate({\n    // base URL is read from the \"constructor\"\n    baseURL,\n    // here are some default headers\n    headers: {\n      \"Cache-Control\": \"no-cache\",\n    },\n    // 10 second timeout...\n    timeout: 10000,\n  });\n\n  // ------\n  // STEP 2\n  // ------\n  //\n  // Define some functions that call the api.  The goal is to provide\n  // a thin wrapper of the api layer providing nicer feeling functions\n  // rather than \"get\", \"post\" and friends.\n  //\n  // I generally don't like wrapping the output at this level because\n  // sometimes specific actions need to be take on `403` or `401`, etc.\n  //\n  // Since we can't hide from that, we embrace it by getting out of the\n  // way at this level.\n  //\n  const getRoot = () => api.get<GithubResponse>(\"\");\n  const getRate = () => api.get<GithubResponse>(\"rate_limit\");\n  const getUser = (username: string) => api.get<GithubResponse>(\"search/users\", {q: username});\n\n  // ------\n  // STEP 3\n  // ------\n  //\n  // Return back a collection of functions that we would consider our\n  // interface.  Most of the time it'll be just the list of all the\n  // methods in step 2.\n  //\n  // Notice we're not returning back the `api` created in step 1?  That's\n  // because it is scoped privately.  This is one way to create truly\n  // private scoped goodies in JavaScript.\n  //\n  return {\n    // a list of the API functions from step 2\n    getRoot,\n    getRate,\n    getUser,\n  };\n};\n\n// let's return back our create method as the default.\nexport default {\n  createAPI,\n};\n"
  },
  {
    "path": "boilerplate/App/Services/ImmutablePersistenceTransform.tsx",
    "content": "import R from \"ramda\";\nimport * as SeamlessImmutable from \"seamless-immutable\";\n\n// is this object already Immutable?\nconst isImmutable = R.has(\"asMutable\");\n\n// change this Immutable object into a JS object\nconst convertToJs = (state: any) => state.asMutable({deep: true});\n\n// optionally convert this object into a JS object if it is Immutable\nconst fromImmutable = R.when(isImmutable, convertToJs);\n\n// convert this JS object into an Immutable object\nconst toImmutable = (raw: any) => SeamlessImmutable(raw);\n\n// the transform interface that redux-persist is expecting\nexport default {\n  out: (state: any) => {\n    // console.log({ retrieving: state })\n    return toImmutable(state);\n  },\n  in: (raw: any) => {\n    // console.log({ storing: raw })\n    return fromImmutable(raw);\n  },\n};\n"
  },
  {
    "path": "boilerplate/App/Services/RehydrationServices.tsx",
    "content": "import { AsyncStorage } from \"react-native\";\nimport { persistStore } from \"redux-persist\";\nimport DebugConfig from \"../Config/DebugConfig\";\nimport ReduxPersist from \"../Config/ReduxPersist\";\nimport StartupActions from \"../Redux/StartupRedux\";\n\nconst updateReducers = (store) => {\n  const reducerVersion = ReduxPersist.reducerVersion;\n  const config = ReduxPersist.storeConfig;\n  const startup = () => store.dispatch(StartupActions.startup());\n\n  // Check to ensure latest reducer version\n  AsyncStorage.getItem(\"reducerVersion\").then((localVersion) => {\n    if (localVersion !== reducerVersion) {\n      if (DebugConfig.useReactotron) {\n        console.tron.display({\n          name: \"PURGE\",\n          value: {\n            \"Old Version:\": localVersion,\n            \"New Version:\": reducerVersion,\n          },\n          preview: \"Reducer Version Change Detected\",\n          important: true,\n        });\n      }\n      // Purge store\n      persistStore(store, config, startup).purge();\n      AsyncStorage.setItem(\"reducerVersion\", reducerVersion);\n    } else {\n      persistStore(store, config, startup);\n    }\n  }).catch(() => {\n    persistStore(store, config, startup);\n    AsyncStorage.setItem(\"reducerVersion\", reducerVersion);\n  });\n};\n\nexport default {updateReducers};\n"
  },
  {
    "path": "boilerplate/App/Themes/ApplicationStyles.ts",
    "content": "import { TextStyle, ViewStyle } from \"react-native\";\n\nimport Colors from \"./Colors\";\nimport Fonts from \"./Fonts\";\nimport Metrics from \"./Metrics\";\n\n// This file is for a reusable grouping of Theme items.\n// Similar to an XML fragment layout in Android\n\nconst ApplicationStyles = {\n  screen: {\n    mainContainer: {\n      flex: 1,\n      backgroundColor: Colors.transparent,\n    } as ViewStyle,\n    backgroundImage: {\n      position: \"absolute\",\n      top: 0,\n      left: 0,\n      bottom: 0,\n      right: 0,\n    } as ViewStyle,\n    container: {\n      flex: 1,\n      paddingTop: Metrics.baseMargin,\n      backgroundColor: Colors.transparent,\n    } as ViewStyle,\n    section: {\n      margin: Metrics.section,\n      padding: Metrics.baseMargin,\n    } as ViewStyle,\n    sectionText: {\n      ...Fonts.style.normal,\n      paddingVertical: Metrics.doubleBaseMargin,\n      color: Colors.snow,\n      marginVertical: Metrics.smallMargin,\n      textAlign: \"center\",\n    } as TextStyle,\n    subtitle: {\n      color: Colors.snow,\n      padding: Metrics.smallMargin,\n      marginBottom: Metrics.smallMargin,\n      marginHorizontal: Metrics.smallMargin,\n    } as TextStyle,\n    titleText: {\n      ...Fonts.style.h2,\n      fontSize: 14,\n      color: Colors.text,\n    } as TextStyle,\n  },\n  darkLabelContainer: {\n    padding: Metrics.smallMargin,\n    paddingBottom: Metrics.doubleBaseMargin,\n    borderBottomColor: Colors.border,\n    borderBottomWidth: 1,\n    marginBottom: Metrics.baseMargin,\n  } as ViewStyle,\n  darkLabel: {\n    fontFamily: Fonts.type.bold,\n    color: Colors.snow,\n  } as TextStyle,\n  groupContainer: {\n    margin: Metrics.smallMargin,\n    flexDirection: \"row\",\n    justifyContent: \"space-around\",\n    alignItems: \"center\",\n  } as ViewStyle,\n  sectionTitle: {\n    ...Fonts.style.h4,\n    color: Colors.coal,\n    backgroundColor: Colors.ricePaper,\n    padding: Metrics.smallMargin,\n    marginTop: Metrics.smallMargin,\n    marginHorizontal: Metrics.baseMargin,\n    borderWidth: 1,\n    borderColor: Colors.ember,\n    alignItems: \"center\",\n    textAlign: \"center\",\n  } as TextStyle,\n};\n\nexport default ApplicationStyles;\n"
  },
  {
    "path": "boilerplate/App/Themes/Colors.ts",
    "content": "const colors = {\n  background: \"#1F0808\",\n  clear: \"rgba(0,0,0,0)\",\n  facebook: \"#3b5998\",\n  transparent: \"rgba(0,0,0,0)\",\n  silver: \"#F7F7F7\",\n  steel: \"#CCCCCC\",\n  error: \"rgba(200, 0, 0, 0.8)\",\n  ricePaper: \"rgba(255,255,255, 0.75)\",\n  frost: \"#D8D8D8\",\n  cloud: \"rgba(200,200,200, 0.35)\",\n  windowTint: \"rgba(0, 0, 0, 0.4)\",\n  panther: \"#161616\",\n  charcoal: \"#595959\",\n  coal: \"#2d2d2d\",\n  bloodOrange: \"#fb5f26\",\n  snow: \"white\",\n  ember: \"rgba(164, 0, 48, 0.5)\",\n  fire: \"#e73536\",\n  drawer: \"rgba(30, 30, 29, 0.95)\",\n  eggplant: \"#251a34\",\n  border: \"#483F53\",\n  banner: \"#5F3E63\",\n  text: \"#E0D7E5\",\n};\n\nexport default colors;\n"
  },
  {
    "path": "boilerplate/App/Themes/Fonts.ts",
    "content": "const type = {\n  base: \"Avenir-Book\",\n  bold: \"Avenir-Black\",\n  emphasis: \"HelveticaNeue-Italic\",\n};\n\nconst size = {\n  h1: 38,\n  h2: 34,\n  h3: 30,\n  h4: 26,\n  h5: 20,\n  h6: 19,\n  input: 18,\n  regular: 17,\n  medium: 14,\n  small: 12,\n  tiny: 8.5,\n};\n\nconst style = {\n  h1: {\n    fontFamily: type.base,\n    fontSize: size.h1,\n  },\n  h2: {\n    fontWeight: \"bold\",\n    fontSize: size.h2,\n  },\n  h3: {\n    fontFamily: type.emphasis,\n    fontSize: size.h3,\n  },\n  h4: {\n    fontFamily: type.base,\n    fontSize: size.h4,\n  },\n  h5: {\n    fontFamily: type.base,\n    fontSize: size.h5,\n  },\n  h6: {\n    fontFamily: type.emphasis,\n    fontSize: size.h6,\n  },\n  normal: {\n    fontFamily: type.base,\n    fontSize: size.regular,\n  },\n  description: {\n    fontFamily: type.base,\n    fontSize: size.medium,\n  },\n};\n\nexport default {\n  type,\n  size,\n  style,\n};\n"
  },
  {
    "path": "boilerplate/App/Themes/Images.ts",
    "content": "// leave off @2x/@3x\nconst images = {\n  logo: require(\"../Images/ir.png\"),\n  clearLogo: require(\"../Images/top_logo.png\"),\n  launch: require(\"../Images/launch-icon.png\"),\n  ready: require(\"../Images/your-app.png\"),\n  ignite: require(\"../Images/ignite_logo.png\"),\n  igniteClear: require(\"../Images/ignite-logo-transparent.png\"),\n  tileBg: require(\"../Images/tile_bg.png\"),\n  background: require(\"../Images/BG.png\"),\n  buttonBackground: require(\"../Images/button-bg.png\"),\n  api: require(\"../Images/Icons/icon-api-testing.png\"),\n  components: require(\"../Images/Icons/icon-components.png\"),\n  deviceInfo: require(\"../Images/Icons/icon-device-information.png\"),\n  faq: require(\"../Images/Icons/faq-icon.png\"),\n  home: require(\"../Images/Icons/icon-home.png\"),\n  theme: require(\"../Images/Icons/icon-theme.png\"),\n  usageExamples: require(\"../Images/Icons/icon-usage-examples.png\"),\n  chevronRight: require(\"../Images/Icons/chevron-right.png\"),\n  hamburger: require(\"../Images/Icons/hamburger.png\"),\n  backButton: require(\"../Images/Icons/back-button.png\"),\n  closeButton: require(\"../Images/Icons/close-button.png\"),\n};\n\nexport default images;\n"
  },
  {
    "path": "boilerplate/App/Themes/Metrics.ts",
    "content": "import {Dimensions, Platform} from \"react-native\";\n\nconst { width, height } = Dimensions.get(\"window\");\n\n// Used via Metrics.baseMargin\nconst metrics = {\n  marginHorizontal: 10,\n  marginVertical: 10,\n  section: 25,\n  baseMargin: 10,\n  doubleBaseMargin: 20,\n  smallMargin: 5,\n  doubleSection: 50,\n  horizontalLineHeight: 1,\n  screenWidth: width < height ? width : height,\n  screenHeight: width < height ? height : width,\n  navBarHeight: (Platform.OS === \"ios\") ? 64 : 54,\n  buttonRadius: 4,\n  icons: {\n    tiny: 15,\n    small: 20,\n    medium: 30,\n    large: 45,\n    xl: 50,\n  },\n  images: {\n    small: 20,\n    medium: 40,\n    large: 60,\n    logo: 200,\n  },\n};\n\nexport default metrics;\n"
  },
  {
    "path": "boilerplate/App/Themes/README.md",
    "content": "### Themes Folder\nApplication specific themes\n* Base Styles\n* Fonts\n* Metrics\n* Colors\n\netc.\n"
  },
  {
    "path": "boilerplate/App/Themes/index.ts",
    "content": "import ApplicationStyles from \"./ApplicationStyles\";\nimport Colors from \"./Colors\";\nimport Fonts from \"./Fonts\";\nimport Images from \"./Images\";\nimport Metrics from \"./Metrics\";\n\nexport { Colors, Fonts, Images, Metrics, ApplicationStyles };\n"
  },
  {
    "path": "boilerplate/App/Transforms/ConvertFromKelvin.ts",
    "content": "export default (kelvin: number) => {\n  const celsius = kelvin - 273.15;\n  const fahrenheit = (celsius * 1.8000) + 32;\n\n  return Math.round(fahrenheit);\n};\n"
  },
  {
    "path": "boilerplate/App/Transforms/README.md",
    "content": "# Transforms\n\nA common pattern when working with APIs is to change data to play nice between your app & the API.  \n\nWe've found this to be the case in every project we've worked on.  So much so that we're recommending that you create a folder dedicated to these transformations.\n\nTransforms are not necessarily a bad thing (although an API might have you transforming more than you'd like).\n\nFor example, you may:\n\n* turn appropriate strings to date objects\n* convert snake case to camel case\n* normalize or denormalize things\n* create lookup tables\n"
  },
  {
    "path": "boilerplate/README.md",
    "content": "#  <%= props.name %>\n\n* TypeScript React Native App Utilizing [Ignite](https://github.com/infinitered/ignite)\n\n## :arrow_up: How to Setup\n\n**Step 1:** git clone this repo:\n\n**Step 2:** cd to the cloned repo:\n\n**Step 3:** Install the Application with `yarn` or `npm i`\n\n\n## :arrow_forward: How to Run App\n\n1. cd to the repo\n2. Run `npm run compile`\n3. Run Build for either OS\n  * for iOS\n    * run `react-native run-ios`\n  * for Android\n    * Run Genymotion\n    * run `react-native run-android`\n\n**To Lint on Commit**\n\nThis is implemented using [husky](https://github.com/typicode/husky). There is no additional setup needed.\n\n## :closed_lock_with_key: Secrets\n\nThis project uses [react-native-config](https://github.com/luggit/react-native-config) to expose config variables to your javascript code in React Native. You can store API keys\nand other sensitive information in a `.env` file:\n\n```\nAPI_URL=https://myapi.com\nGOOGLE_MAPS_API_KEY=abcdefgh\n```\n\nand access them from React Native like so:\n\n```\nimport Secrets from 'react-native-config'\n\nSecrets.API_URL  // 'https://myapi.com'\nSecrets.GOOGLE_MAPS_API_KEY  // 'abcdefgh'\n```\n\nThe `.env` file is ignored by git keeping those secrets out of your repo.\n\n### Get started:\n1. Copy .env.example to .env\n2. Add your config variables\n3. Follow instructions at [https://github.com/luggit/react-native-config#setup](https://github.com/luggit/react-native-config#setup)\n4. Done!\n"
  },
  {
    "path": "boilerplate/Tests/Setup.tsx.ejs",
    "content": "/// <reference types=\"@types/jest\" />\n// Mock your external modules here if needed\n<%_ if (props.i18n === 'react-native-i18n') { _%>\njest\n.mock('react-native-i18n', () => {\n  const english = require('../App/I18n/languages/english.json')\n  const keys = require('ramda')\n  const replace = require('ramda')\n  const forEach = require('ramda')\n\n  return {\n    t: (key, replacements) => {\n      let value = english[key]\n      if (!value) return key\n      if (!replacements) return value\n\n      forEach((r) => {\n        value = replace(`{{${r}}}`, replacements[r], value)\n      }, keys(replacements))\n      return value\n    }\n  }\n})\n<%_ } else { _%>\n// jest\n// .mock('react-native-device-info', () => {\n//   return { isTablet: jest.fn(() => { return false }) }\n// })\n<%_ } _%>\n"
  },
  {
    "path": "boilerplate/Tests/StoriesTest.ts",
    "content": "import initStoryshots from \"@storybook/addon-storyshots\";\n\ninitStoryshots();\n"
  },
  {
    "path": "boilerplate/ignite.json.ejs",
    "content": "{\n  \"createdWith\": \"<%= props.igniteVersion %>\",\n  \"examples\": \"classic\",\n  \"navigation\": \"react-navigation\",\n  \"askToOverride\": true\n}\n"
  },
  {
    "path": "boilerplate/index.js.ejs",
    "content": "import \"./App/Config/ReactotronConfig\"\nimport { AppRegistry } from 'react-native';\nimport App from \"./App/Containers/App\";\n\nAppRegistry.registerComponent(\"<%= props.name %>\", () => App);\n"
  },
  {
    "path": "boilerplate/package.json.ejs",
    "content": "{\n  \"version\": \"0.0.1\",\n  \"scripts\": {\n    \"start\": \"node node_modules/react-native/local-cli/cli.js start\",\n    \"test\": \"jest\",\n    \"clean\": \"rm -rf $TMPDIR/react-* && watchman watch-del-all && npm cache clean --force\",\n    \"clean:android\": \"cd android/ && ./gradlew clean && cd .. && react-native run-android\",\n    \"newclear\": \"rm -rf $TMPDIR/react-* && watchman watch-del-all && rm -rf ios/build && rm -rf node_modules/ && npm cache clean --force && npm i\",\n    \"test:watch\": \"jest --watch\",\n    \"updateSnapshot\": \"jest --updateSnapshot\",\n    \"coverage\": \"jest --coverage && open coverage/lcov-report/index.html || xdg-open coverage/lcov-report/index.html\",\n    \"android:build\": \"cd android && ./gradlew assembleRelease\",\n    \"android:install\": \"cd android && ./gradlew assembleRelease && ./gradlew installRelease\",\n    \"android:hockeyapp\": \"cd android && ./gradlew assembleRelease && puck -submit=auto app/build/outputs/apk/app-release.apk\",\n    \"android:devices\": \"$ANDROID_HOME/platform-tools/adb devices\",\n    \"android:logcat\": \"$ANDROID_HOME/platform-tools/adb logcat *:S ReactNative:V ReactNativeJS:V\",\n    \"android:shake\": \"$ANDROID_HOME/platform-tools/adb devices | grep '\\\\t' | awk '{print $1}' | sed 's/\\\\s//g' | xargs -I {} $ANDROID_HOME/platform-tools/adb -s {} shell input keyevent 82\",\n    \"storybook\": \"storybook start -p 7007\",\n    \"lint\": \"tslint --project . -e '**/*.js' -t verbose | snazzy\",\n    \"lintdiff\": \"git diff --name-only --cached --relative | grep '\\\\.tsx?$' | xargs tslint | snazzy\",\n    \"fixcode\": \"tslint --project . -e '**/*.js' --fix\",\n    \"git-hook\": \"npm run lint -s && npm run test -s\"\n  },\n  \"dependencies\": {\n    \"apisauce\": \"^0.14.0\",\n    \"format-json\": \"^1.0.3\",\n    \"lodash\": \"^4.17.2\",\n    \"querystringify\": \"0.0.4\",\n    \"ramda\": \"^0.24.1\",\n    \"react-native-config\": \"^0.6.0\",\n    \"react-native-drawer\": \"^2.3.0\",\n    \"react-navigation\": \"^1.0.0-beta.7\",\n    \"react-redux\": \"^5.0.2\",\n    \"react-redux-typescript\": \"^2.3.0\",\n    \"redux\": \"^3.6.0\",\n    \"redux-persist\": \"^4.1.0\",\n    \"redux-saga\": \"^0.15.6\",\n    \"seamless-immutable\": \"^7.0.1\",\n    \"typesafe-actions\": \"1.1.2\"    \n  },\n  \"devDependencies\": {\n    \"@storybook/addon-storyshots\": \"^3.2.3\",\n    \"@storybook/react-native\": \"^3.2.3\",\n    \"@types/enzyme\": \"^3.1.0\",\n    \"@types/ramda\": \"^0.24.17\",\n    \"@types/react-navigation\": \"^1.0.21\",\n    \"@types/react-redux\": \"^5.0.10\",\n    \"@types/redux\": \"^3.6.0\",\n    \"@types/jest\": \"^21.1.4\",\n    \"@types/react\": \"^16.0.13\",\n    \"@types/react-native\": \"^0.49.2\",\n    \"@types/react-test-renderer\": \"^16.0.0\",\n    \"@types/seamless-immutable\": \"^7.1.1\",\n    \"@types/storybook__react\": \"^3.0.5\",\n    \"@types/webpack-env\": \"^1.13.2\",\n    \"babel-jest\": \"21.2.0\",\n    \"babel-plugin-ignite-ignore-reactotron\": \"^0.3.0\",\n    \"babel-preset-es2015\": \"^6.18.0\",\n    \"babel-preset-react-native\": \"3.0.2\",\n    \"enzyme\": \"^2.6.0\",\n    \"jest-preset-typescript-react-native\": \"^1.2.0\",\n    \"husky\": \"^0.13.1\",\n    \"ignite-animatable\": \"^1.0.0\",\n    \"ignite-dev-screens\": \"^2.2.0\",\n    \"ignite-vector-icons\": \"^1.1.0\",\n    \"jest\": \"^21.2.1\",\n    \"mockery\": \"^2.0.0\",\n    \"react-addons-test-utils\": \"~15.4.1\",\n    \"react-dom\": \"16.0.0-alpha.12\",\n    \"react-test-renderer\": \"16.0.0-beta.5\",\n    \"reactotron-react-native\": \"^1.12.0\",\n    \"reactotron-redux\": \"^1.11.1\",\n    \"reactotron-redux-saga\": \"^1.11.1\",\n    \"snazzy\": \"^7.0.0\",\n    \"ts-jest\": \"^21.1.3\",\n    \"tslint\": \"^5.7.0\",\n    \"tslint-react\": \"^3.2.0\",\n    \"typescript\": \"^2.5.3\",\n    \"react-native-typescript-transformer\": \"^1.1.4\"\n  },\n  \"jest\": {\n    \"preset\": \"jest-preset-typescript-react-native\",\n    \"testMatch\": [\n      \"**/Tests/**/*.ts?(x)\",\n      \"**/App/**/*Test.ts?(x)\"\n    ],\n    \"testPathIgnorePatterns\": [\n      \"\\\\.snap$\",\n      \"<rootDir>/node_modules/\",\n      \"<rootDir>/lib/\",\n      \"Tests/Setup\"\n    ],\n    \"setupFiles\": [\n      \"./Tests/Setup.tsx\"\n    ],\n    \"moduleFileExtensions\": [\n      \"js\",\n      \"jsx\",\n      \"ts\",\n      \"tsx\",\n      \"json\"\n    ],\n    \"cacheDirectory\": \".jest/cache\"\n  },\n  \"config\": {}\n}\n"
  },
  {
    "path": "boilerplate/rn-cli.config.js",
    "content": "module.exports = {  \n    getTransformModulePath() {\n      return require.resolve('react-native-typescript-transformer')\n    },\n    getSourceExts() {\n      return ['ts', 'tsx'];\n    }\n  }"
  },
  {
    "path": "boilerplate/storybook/addons.js",
    "content": "import '@storybook/addon-actions/register'\nimport '@storybook/addon-links/register'\n"
  },
  {
    "path": "boilerplate/storybook/index.js",
    "content": "import StorybookUI from './storybook'\n\nexport default StorybookUI\n"
  },
  {
    "path": "boilerplate/storybook/storybook.ejs",
    "content": "import { AppRegistry } from 'react-native'\nimport { getStorybookUI, configure } from '@storybook/react-native'\n\n// import stories\nconfigure(() => {\n  require('../App/Components/Stories')\n}, module)\n\n// This assumes that storybook is running on the same host as your RN packager,\n// to set manually use, e.g. host: 'localhost' option\nconst StorybookUI = getStorybookUI({ port: 7007, onDeviceUI: true })\nAppRegistry.registerComponent('<%= props.name %>', () => StorybookUI)\nexport default StorybookUI\n"
  },
  {
    "path": "boilerplate/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    /* Basic Options */\n    \"target\": \"ESNEXT\",                          /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */\n    \"module\": \"ESNext\",                     /* Specify module code generation: 'none', commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */\n    // \"lib\": [],                             /* Specify library files to be included in the compilation:  */\n    // \"allowJs\": true,                       /* Allow javascript files to be compiled. */\n    // \"checkJs\": true,                       /* Report errors in .js files. */\n    \"jsx\": \"react-native\",                    /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */\n    // \"declaration\": true,                   /* Generates corresponding '.d.ts' file. */\n    \"sourceMap\": true,                        /* Generates corresponding '.map' file. */\n    // \"outFile\": \"./\",                       /* Concatenate and emit output to single file. */\n    //\"outDir\": \"./dist\",                        /* Redirect output structure to the directory. */\n    // \"rootDir\": \"./src\",                       /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */\n    // \"removeComments\": true,                /* Do not emit comments to output. */\n    // \"noEmit\": true,                        /* Do not emit outputs. */\n    // \"importHelpers\": true,                 /* Import emit helpers from 'tslib'. */\n    // \"downlevelIteration\": true,            /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */\n    // \"isolatedModules\": true,               /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */\n\n    /* Strict Type-Checking Options */\n    \"strict\": true,                            /* Enable all strict type-checking options. */\n    // \"noImplicitAny\": true,                 /* Raise error on expressions and declarations with an implied 'any' type. */\n    // \"strictNullChecks\": true,              /* Enable strict null checks. */\n    // \"noImplicitThis\": true,                /* Raise error on 'this' expressions with an implied 'any' type. */\n    // \"alwaysStrict\": true,                  /* Parse in strict mode and emit \"use strict\" for each source file. */\n\n    /* Additional Checks */\n    // \"noUnusedLocals\": true,                /* Report errors on unused locals. */\n    // \"noUnusedParameters\": true,            /* Report errors on unused parameters. */\n    // \"noImplicitReturns\": true,             /* Report error when not all code paths in function return a value. */\n    // \"noFallthroughCasesInSwitch\": true,    /* Report errors for fallthrough cases in switch statement. */\n\n    /* Module Resolution Options */\n    \"moduleResolution\": \"node\",            /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */\n    \"baseUrl\": \"types\",                       /* Base directory to resolve non-absolute module names. */\n    // \"paths\": {},                           /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */\n    // \"rootDirs\": [],                        /* List of root folders whose combined content represents the structure of the project at runtime. */\n    \"typeRoots\": [\"types\"],                       /* List of folders to include type definitions from. */\n    // \"types\": [],                           /* Type declaration files to be included in compilation. */\n    \"allowSyntheticDefaultImports\": true  /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */\n    // \"preserveSymlinks\": true,              /* Do not resolve the real path of symlinks. */\n\n    /* Source Map Options */\n    // \"sourceRoot\": \"./\",                    /* Specify the location where debugger should locate TypeScript files instead of source locations. */\n    // \"mapRoot\": \"./\",                       /* Specify the location where debugger should locate map files instead of generated locations. */\n    // \"inlineSourceMap\": true,               /* Emit a single file with source maps instead of having a separate file. */\n    // \"inlineSources\": true,                 /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */\n\n    /* Experimental Options */\n    // \"experimentalDecorators\": true,        /* Enables experimental support for ES7 decorators. */\n    // \"emitDecoratorMetadata\": true,         /* Enables experimental support for emitting type metadata for decorators. */\n  }\n  // \"include\": [\n  //   \"index.ts\", \"App/Components/Stories.tsx\"\n  // ]\n}"
  },
  {
    "path": "boilerplate/tslint.json",
    "content": "{\n    \"extends\": [\n        \"tslint:latest\", \"tslint-react\"\n    ],\n    \"rules\": {\n        \"interface-name\": false,\n        \"object-literal-sort-keys\": false,\n        \"no-object-literal-type-assertion\": false,\n        \"no-empty-interface\": false,\n        \"no-submodule-imports\": false\n    }\n}"
  },
  {
    "path": "boilerplate/types/@storybook/react-native.d.ts",
    "content": "// Type definitions for @storybook/react 3.0\n// Project: https://github.com/storybooks/storybook\n// Definitions by: Joscha Feth <https://github.com/joscha>\n// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped\n// TypeScript Version: 2.3\n\n/// <reference types=\"webpack-env\" />\n\nimport * as React from 'react';\n\nexport type Renderable = React.ComponentType | JSX.Element;\nexport type RenderFunction = () => Renderable;\n\nexport type StoryDecorator = (story: RenderFunction, context: { kind: string, story: string }) => Renderable | null;\n\nexport interface Story {\n    readonly kind: string;\n    add(storyName: string, callback: RenderFunction): this;\n    addDecorator(decorator: StoryDecorator): this;\n}\n\nexport function addDecorator(decorator: StoryDecorator): void;\nexport function configure(fn: () => void, module?: NodeModule): void;\nexport function setAddon(addon: object): void;\nexport function storiesOf(name: string, module?: NodeModule): Story;\nexport function storiesOf<T>(name: string, module?: NodeModule): Story & T;\n\nexport interface StoryObject {\n    name: string;\n    render: RenderFunction;\n}\n\nexport interface StoryBucket {\n    kind: string;\n    stories: StoryObject[];\n}\n\nexport function getStorybook(): StoryBucket[];\n"
  },
  {
    "path": "boilerplate/types/reduxsauce/index.d.ts",
    "content": "import { Action, AnyAction, Reducer, ReducersMapObject } from \"redux\";\r\nexport interface ActionTypes {\r\n    [key: string]: string;\r\n}\r\n\r\nexport interface ActionConfig {\r\n    [key: string]: string[] | ActionCreator | {[key: string]: any} | null;\r\n}\r\n\r\nexport type ActionCreator = (...args: any[]) => Action;\r\n\r\nexport interface ActionCreators {\r\n    [key: string]: ActionCreator;\r\n}\r\n\r\nexport function createActions(config: ActionConfig, options?: {prefix?: string}): {Types: ActionTypes, Creators: ActionCreators };\r\n\r\n/**\r\n * Creates a reducer.\r\n * @param {object} initialState - The initial state for this reducer.\r\n * @param {object} handlers - Keys are action types (strings), values are reducers (functions).\r\n * @return {Reducer<S>} A reducer object.\r\n */\r\nexport function createReducer<S>(initialState: S, handlers: ReducersMapObject): Reducer<S>;\r\n\r\nexport function createTypes(types: string, options?: {prefix?: string, [key: string]: any}): ActionTypes;\r\n\r\n/**\r\n * Allows your reducers to be reset.\r\n *\r\n * @param {string} typeToReset - The action type to listen for.\r\n * @param {Reducer<T>} originalReducer - The reducer to wrap.\r\n */\r\nexport function resettableReducer<S>(type: string, originalReducer: Reducer<S>): Reducer<S>;\r\n\r\nexport function resettableReducer<S>(type: string): (originalReducer: Reducer<S>) => Reducer<S>;\r\n"
  },
  {
    "path": "boilerplate.js",
    "content": "const options = require('./options')\nconst { merge, pipe, assoc, omit, __ } = require('ramda')\nconst { getReactNativeVersion } = require('./lib/react-native-version')\n\n/**\n * Is Android installed?\n *\n * $ANDROID_HOME/tools folder has to exist.\n *\n * @param {*} context - The gluegun context.\n * @returns {boolean}\n */\nconst isAndroidInstalled = function (context) {\n  const androidHome = process.env['ANDROID_HOME']\n  const hasAndroidEnv = !context.strings.isBlank(androidHome)\n  const hasAndroid = hasAndroidEnv && context.filesystem.exists(`${androidHome}/tools`) === 'dir'\n\n  return Boolean(hasAndroid)\n}\n\n/**\n * Let's install.\n *\n * @param {any} context - The gluegun context.\n */\nasync function install (context) {\n  const {\n    filesystem,\n    parameters,\n    ignite,\n    reactNative,\n    print,\n    system,\n    prompt,\n    template\n  } = context\n  const { colors } = print\n  const { red, yellow, bold, gray, blue, green } = colors\n\n  const perfStart = (new Date()).getTime()\n\n  const name = parameters.third\n  const logo = red(`\n\n\n                -aeaeaeaeaeae—             \n            -eaeaeaeaeaeaeaeaeaeae-            \n         /aeaeaeaeaeaeaeaeaeaeaeaeae\\\\         \n       /aeaeaeaeaeaeaeaeaeaeaeaeaeaeae\\\\       \n     /eaeaeaeaeaeaeaeaeaeaeaeaeaeaeaeaea\\\\       \n    /aeaeaeaeaeaeaeaeaeaea/ |aeaeaeaeaeae\\\\      \n   /aaeaeaeaeaeaeaeaeaeae/  |aeaeaeaeaeaea\\\\     \n   aeaeaeaeaeaeaeaeaeae/    |eaeaeaeaeaeaea    \n  |aeaeaeaeaeaeaeaeae/      |eaeaeaeaeaeaea|    \n  aeaeaeaeaeaeaeaea/        |eaeaeaeaeaeaeae    \n  eaeaeaeaeaeaeae/`) + yellow(`:`) + red(`\\\\        |aeaeaeaeaeaeaea    \n  aeaeaeaeaeaea/`) + yellow(`::::`) + red(`\\\\       |eaeaeaeaeaeaeae    \n  |aeaeaeaeae/`) + yellow(`:::::::`) + red(`\\\\      |eaeaeaeaeaeaea|    \n   aeaeaeaeaeaeaeaeaea\\\\     |`) + yellow(`::::`) + red(`/aeaeaeaea   \n   \\\\eaeaeaeaeaeaeaeaeaea\\\\   |`) + yellow(`:::`) + red(`/aeaeaeaea/     \n    \\\\aeaeaeaeaeaeaeaeaeae\\\\  |`) + yellow(`::`) + red(`/aeaeaeaea/      \n     \\\\aeaeaeaeaeaeaeaeaeae\\\\ |`) + yellow(`:`) + red(`/eaeeaeaea/       \n       \\\\aeaeaeaeaeaeaeaeaea\\\\|/aeaeaeae/       \n         \\\\aeaeaeaeaeaeaeaeaeaeaeaeae/         \n            -eaeaeaeaeaeaeaeaeaeae-            \n                -aeaeaeaeaeae—      \n\n           __ _  ___ _ __ _  __ _ _ __  \n          / _' |/ _ \\\\ '__| |/ _' | '_ \\\\ \n         | (_| |  __/ |  | | (_| | | | |\n          \\\\__,_|\\\\___|_|  |_|\\\\__,_|_| |_|\n\n`) + green(`\n    🌳   Crafted with care in the Cotswolds.  🌳`) + yellow(`\n\n                https://aerian.com/    \n\n`);\n  \n  print.info(logo)\n  const spinner = print\n    .spin(`using the TypeScript boilerplate from Aerian Studios. You might want to make a cuppa while we get this ready. ☕️`)\n    .succeed()\n\n  // attempt to install React Native or die trying\n  const rnInstall = await reactNative.install({\n    name,\n    version: getReactNativeVersion(context)\n  })\n  if (rnInstall.exitCode > 0) process.exit(rnInstall.exitCode)\n\n  // remove the __tests__ directory and App.js that come with React Native\n  filesystem.remove('__tests__')\n  filesystem.remove('App.js')\n  // copy our App, Tests & storybook directories\n  spinner.text = '▸ copying files'\n  spinner.start()\n  filesystem.copy(`${ignite.ignitePluginPath()}/boilerplate/App`, `${process.cwd()}/App`, {\n    overwrite: true,\n    matching: '!*.ejs'\n  })\n  filesystem.copy(`${ignite.ignitePluginPath()}/boilerplate/Tests`, `${process.cwd()}/Tests`, {\n    overwrite: true,\n    matching: '!*.ejs'\n  })\n  filesystem.copy(`${ignite.ignitePluginPath()}/boilerplate/storybook`, `${process.cwd()}/storybook`, {\n    overwrite: true,\n    matching: '!*.ejs'\n  })\n  filesystem.copy(`${ignite.ignitePluginPath()}/boilerplate/types`, `${process.cwd()}/types`, {\n    overwrite: true,\n    matching: '!*.ejs'\n  })\n  spinner.stop()\n\n  // --max, --min, interactive\n  let answers\n  if (parameters.options.max) {\n    answers = options.answers.max\n  } else if (parameters.options.min) {\n    answers = options.answers.min\n  } else {\n    answers = await prompt.ask(options.questions)\n  }\n\n  // generate some templates\n  spinner.text = '▸ generating files'\n  const templates = [\n    { template: 'index.js.ejs', target: 'index.js' },\n    { template: 'README.md', target: 'README.md' },\n    { template: 'ignite.json.ejs', target: 'ignite/ignite.json' },\n    { template: '.editorconfig', target: '.editorconfig' },\n    { template: '.babelrc', target: '.babelrc' },\n    { template: 'tsconfig.json', target: 'tsconfig.json' },\n    { template: 'tslint.json', target: 'tslint.json' },\n    { template: 'rn-cli.config.js', target: 'rn-cli.config.js' },\n    { template: 'Tests/Setup.tsx.ejs', target: 'Tests/Setup.tsx' },\n    { template: 'storybook/storybook.ejs', target: 'storybook/storybook.js' },\n    { template: '.env.example', target: '.env.example' }\n  ]\n  const templateProps = {\n    name,\n    igniteVersion: ignite.version,\n    reactNativeVersion: rnInstall.version,\n    vectorIcons: answers['vector-icons'],\n    animatable: answers['animatable'],\n    i18n: answers['i18n']\n  }\n  await ignite.copyBatch(context, templates, templateProps, {\n    quiet: false,\n    directory: `${ignite.ignitePluginPath()}/boilerplate`\n  })\n\n  /**\n   * Append to files\n   */\n  // https://github.com/facebook/react-native/issues/12724\n  filesystem.appendAsync('.gitattributes', '*.bat text eol=crlf')\n  filesystem.append('.gitignore', '\\n# Misc\\n#')\n  filesystem.append('.gitignore', '\\n.env.example\\n')\n  filesystem.append('.gitignore', '.env\\n')\n  filesystem.append('.gitignore', 'dist\\n')\n  filesystem.append('.gitignore', '.jest\\n')\n  \n  \n  /**\n   * Merge the package.json from our template into the one provided from react-native init.\n   */\n  async function mergePackageJsons () {\n    // transform our package.json in case we need to replace variables\n    const rawJson = await template.generate({\n      directory: `${ignite.ignitePluginPath()}/boilerplate`,\n      template: 'package.json.ejs',\n      props: templateProps\n    })\n    const newPackageJson = JSON.parse(rawJson)\n\n    // read in the react-native created package.json\n    const currentPackage = filesystem.read('package.json', 'json')\n    \n    // deep merge, lol\n    const newPackage = pipe(\n      assoc(\n        'dependencies',\n        merge(currentPackage.dependencies, newPackageJson.dependencies)\n      ),\n      assoc(\n        'devDependencies',\n        merge(currentPackage.devDependencies, newPackageJson.devDependencies)\n      ),\n      assoc('scripts', merge(currentPackage.scripts, newPackageJson.scripts)),\n      merge(\n        __,\n        omit(['dependencies', 'devDependencies', 'scripts'], newPackageJson)\n      )\n    )(currentPackage)\n\n    // write this out\n    filesystem.write('package.json', newPackage, { jsonIndent: 2 })\n  }\n  await mergePackageJsons()\n\n  spinner.stop()\n\n  // react native link -- must use spawn & stdio: ignore or it hangs!! :(\n  spinner.text = `▸ linking native libraries`\n  spinner.start()\n  await system.spawn('react-native link', { stdio: 'ignore' })\n  spinner.stop()\n\n  // pass long the debug flag if we're running in that mode\n  const debugFlag = parameters.options.debug ? '--debug' : ''\n\n  // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n  // NOTE(steve): I'm re-adding this here because boilerplates now hold permanent files\n  // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n  try {\n    // boilerplate adds itself to get plugin.js/generators etc\n    // Could be directory, npm@version, or just npm name.  Default to passed in values\n    const boilerplate = parameters.options.b || parameters.options.boilerplate || 'ignite-typescript-boilerplate'\n\n    await system.spawn(`ignite add ${boilerplate} ${debugFlag}`, { stdio: 'inherit' })\n\n    // now run install of Ignite Plugins\n    if (answers['dev-screens'] === 'Yes') {\n      await system.spawn(`ignite add dev-screens@\"~>2.2.0\" ${debugFlag}`, {\n        stdio: 'inherit'\n      })\n    }\n\n    if (answers['vector-icons'] === 'react-native-vector-icons') {\n      await system.spawn(`ignite add vector-icons@\"~>1.0.0\" ${debugFlag}`, {\n        stdio: 'inherit'\n      })\n    }\n\n    if (answers['i18n'] === 'react-native-i18n') {\n      await system.spawn(`ignite add i18n@\"~>1.0.0\" ${debugFlag}`, { stdio: 'inherit' })\n    }\n\n    if (answers['animatable'] === 'react-native-animatable') {\n      await system.spawn(`ignite add animatable@\"~>1.0.0\" ${debugFlag}`, {\n        stdio: 'inherit'\n      })\n    }\n\n  } catch (e) {\n    ignite.log(e)\n    throw e\n  }\n\n  // git configuration\n  const gitExists = await filesystem.exists('./.git')\n  if (!gitExists && !parameters.options['skip-git'] && system.which('git')) {\n    // initial git\n    const spinner = print.spin('configuring git')\n\n    // TODO: Make husky hooks optional\n    const huskyCmd = '' // `&& node node_modules/husky/bin/install .`\n    system.run(`git init . && git add . && git commit -m \"Initial commit.\" ${huskyCmd}`)\n\n    spinner.succeed(`configured git`)\n  }\n\n  const perfDuration = parseInt(((new Date()).getTime() - perfStart) / 10) / 100\n  spinner.succeed(`ignited ${yellow(name)} in ${perfDuration}s`)\n\n  const androidInfo = isAndroidInstalled(context) ? ''\n    : `\\n\\nTo 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 ${bold('react-native run-android')} successfully until you have.`\n\n  const successMessage = `\n    ${red('Ignite CLI')} ignited ${yellow(name)} in ${gray(`${perfDuration}s`)}\n\n    To get started:\n\n      cd ${name}\n      react-native run-ios\n      react-native run-android${androidInfo}\n      ignite --help\n\n    ${gray('Read the walkthrough at https://github.com/aerian-studios/ignite-typescript-boilerplate/blob/master/readme.md#boilerplate-walkthrough')}\n\n    ${bold('Now get cooking! 🍽')}\n  `\n\n\n  print.info(successMessage)\n}\n\nmodule.exports = {\n  install\n}\n"
  },
  {
    "path": "commands/component.js",
    "content": "// @cliDescription  Generates a stateless component, styles, and an optional test.\n\nmodule.exports = async function (context) {\n  // grab some features\n  const { parameters, strings, print, ignite } = context\n  const { pascalCase, isBlank } = strings\n  const config = ignite.loadIgniteConfig()\n  const { tests } = config\n\n  // validation\n  if (isBlank(parameters.first)) {\n    print.info(`${context.runtime.brand} generate component <name>\\n`)\n    print.info('A name is required.')\n    return\n  }\n\n  // read some configuration\n  const name = pascalCase(parameters.first)\n  const props = { name }\n  const jobs = [\n    {\n      template: 'component.ejs',\n      target: `App/Components/${name}/${name}.tsx`\n    },\n    {\n      template: 'component-style.ejs',\n      target: `App/Components/${name}/${name}Style.ts`\n    },\n    {\n      template: 'component-index.ejs',\n      target: `App/Components/${name}/index.ts`\n    },\n    {\n      template: 'component-story.ejs',\n      target: `App/Components/${name}/${name}.story.tsx`\n    },\n    tests === 'ava' &&\n    {\n      template: 'component-test-ava.ejs',\n      target: `App/Components/${name}/${name}Test.tsx`\n    },\n    tests === 'jest' &&\n    {\n      template: 'component-test-jest.ejs',\n      target: `App/Components/${name}/${name}Test.tsx`\n    }\n  ]\n\n  await ignite.copyBatch(context, jobs, props)\n}\n"
  },
  {
    "path": "commands/container.js",
    "content": "// @cliDescription  Generates a redux smart component.\n\nconst patterns = require('../lib/patterns')\n\nmodule.exports = async function (context) {\n  // grab some features\n  const { parameters, strings, print, ignite, filesystem } = context\n  const { pascalCase, isBlank } = strings\n  const config = ignite.loadIgniteConfig()\n\n  // validation\n  if (isBlank(parameters.first)) {\n    print.info(`${context.runtime.brand} generate container <name>\\n`)\n    print.info('A name is required.')\n    return\n  }\n\n  const name = pascalCase(parameters.first)\n  const props = { name }\n\n  const jobs = [\n    {\n      template: 'container.ejs',\n      target: `App/Containers/${name}/${name}.tsx`\n    },\n    {\n      template: 'container-style.ejs',\n      target: `App/Containers/${name}/${name}Style.ts`\n    }\n  ]\n\n  await ignite.copyBatch(context, jobs, props)\n\n  // if using `react-navigation` go the extra step\n  // and insert the container into the nav router\n  if (config.navigation === 'react-navigation') {\n    const containerName = name\n    const appNavFilePath = `${process.cwd()}/App/Navigation/AppNavigation.tsx`\n    const importToAdd = `import ${containerName} from \"../Containers/${containerName}\";`\n    const routeToAdd = `  ${containerName}: { screen: ${containerName} },`\n\n    if (!filesystem.exists(appNavFilePath)) {\n      const msg = `No '${appNavFilePath}' file found.  Can't insert container.`\n      print.error(msg)\n      process.exit(1)\n    }\n\n    // insert container import\n    ignite.patchInFile(appNavFilePath, {\n      after: patterns[patterns.constants.PATTERN_IMPORTS],\n      insert: importToAdd\n    })\n\n    // insert container route\n    ignite.patchInFile(appNavFilePath, {\n      after: patterns[patterns.constants.PATTERN_ROUTES],\n      insert: routeToAdd\n    })\n  } else {\n    print.info('Container created, manually add it to your navigation')\n  }\n}\n"
  },
  {
    "path": "commands/list.js",
    "content": "// @cliDescription  Generates a screen with a ListView/Flatlist/SectionList + walkthrough.\n\nconst patterns = require('../lib/patterns')\n\nmodule.exports = async function (context) {\n  // grab some features\n  const { print, parameters, strings, ignite, filesystem } = context\n  const { pascalCase, isBlank } = strings\n  const config = ignite.loadIgniteConfig()\n\n  // validation\n  if (isBlank(parameters.first)) {\n    print.info(`${context.runtime.brand} generate list <name>\\n`)\n    print.info('A name is required.')\n    return\n  }\n\n  const name = pascalCase(parameters.first)\n  const props = { name }\n\n\n  // which type of layout?\n  const typeMessage = 'What kind of List would you like to generate?'\n  const typeChoices = ['Row', 'Grid']\n\n  // Sections or no?\n  const typeDataMessage = 'How will your data be presented on this list?'\n  const typeDataChoices = ['Single', 'Sectioned']\n\n  // Check for parameters to bypass questions\n  let typeCode = parameters.options.codeType\n  let type = parameters.options.type\n  let dataType = parameters.options.dataType\n\n  // only prompt if type is not defined\n  if (!typeCode) {\n    typeCode = 'flatlist';\n  }\n\n  if (!type) {\n    // ask question 2\n    const answers = await context.prompt.ask({\n      name: 'type',\n      type: 'list',\n      message: typeMessage,\n      choices: typeChoices\n    })\n    type = answers.type\n  }\n\n  if (!dataType) {\n    // ask question 3\n    const dataAnswers = await context.prompt.ask({\n      name: 'type',\n      type: 'list',\n      message: typeDataMessage,\n      choices: typeDataChoices\n    })\n    dataType = dataAnswers.type\n  }\n\n  // Sorry the following is so confusing, but so are React Native lists\n  // There are 3 options and therefore 8 possible combinations\n  let componentTemplate = dataType.toLowerCase() === 'sectioned'\n    ? typeCode + '-sections'\n    : typeCode\n  let styleTemplate = ''\n  // Different logic depending on code types\n  if (typeCode === 'flatlist') {\n    /*\n    * The following mess is because FlatList supports numColumns\n    * where SectionList does not.\n    */\n    if (type.toLowerCase() === 'grid' && dataType.toLowerCase() === 'sectioned') {\n      // grid + section means we need wrap\n      styleTemplate = 'listview-grid-style'\n    } else if (type.toLowerCase() === 'grid') {\n      componentTemplate = componentTemplate + '-grid'\n      // grid + single = no wrap, use columns\n      styleTemplate = 'flatlist-grid-style'\n    } else {\n      // no grids, flatlist basic\n      styleTemplate = 'listview-style'\n    }\n  } else {\n    // listview builder\n    styleTemplate = type.toLowerCase() === 'grid'\n      ? 'listview-grid-style'\n      : 'listview-style'\n  }\n\n  const jobs = [\n    {\n      template: `${componentTemplate}.ejs`,\n      target: `App/Containers/${name}/${name}.tsx`\n    },\n    {\n      template: `${styleTemplate}.ejs`,\n      target: `App/Containers/${name}/${name}Style.ts`\n    }\n  ]\n\n  await ignite.copyBatch(context, jobs, props)\n\n  // if using `react-navigation` go the extra step\n  // and insert the screen into the nav router\n  if (config.navigation === 'react-navigation') {\n    const screenName = `${name}`\n    const appNavFilePath = `${process.cwd()}/App/Navigation/AppNavigation.tsx`\n    const importToAdd = `import { ${screenName} } from \"../Containers/${screenName}\";`\n    const routeToAdd = `  ${screenName}: { screen: ${screenName} },`\n\n    if (!filesystem.exists(appNavFilePath)) {\n      const msg = `No '${appNavFilePath}' file found.  Can't insert list screen.`\n      print.error(msg)\n      process.exit(1)\n    }\n\n    // insert list screen import\n    ignite.patchInFile(appNavFilePath, {\n      after: patterns[patterns.constants.PATTERN_IMPORTS],\n      insert: importToAdd\n    })\n\n    // insert list screen route\n    ignite.patchInFile(appNavFilePath, {\n      after: patterns[patterns.constants.PATTERN_ROUTES],\n      insert: routeToAdd\n    })\n  } else {\n    print.info('List screen created, manually add it to your navigation')\n  }\n}\n"
  },
  {
    "path": "commands/reducers.js",
    "content": "// @cliDescription  Generates a action/creator/reducer set for Redux.\n\nmodule.exports = async function (context) {\n  // grab some features\n  const { parameters, ignite, strings, print } = context\n  const { isBlank, pascalCase } = strings\n  const config = ignite.loadIgniteConfig()\n\n  // validation\n  if (isBlank(parameters.first)) {\n    print.info(`${context.runtime.brand} generate reducers <name>\\n`)\n    print.info('A name is required.')\n    return\n  }\n\n  const name = pascalCase(parameters.first)\n  const props = { name }\n\n  const jobs = [{ template: `reducers.ejs`, target: `App/Reducers/${name}Reducers/index.tsx` }]\n  if (config.tests && config.tests !== 'none') {\n    jobs.push({\n      template: `reducers-test-${config.tests}.ejs`,\n      target: `App/Reducers/${name}Reducers/${name}ReducersTest.tsx`\n    })\n  }\n\n  await ignite.copyBatch(context, jobs, props)\n}\n"
  },
  {
    "path": "commands/saga.js",
    "content": "// @cliDescription  Generates a saga with an optional test.\n\nmodule.exports = async function (context) {\n  // grab some features\n  const { parameters, ignite, print, strings } = context\n  const { pascalCase, isBlank } = strings\n  const config = ignite.loadIgniteConfig()\n  const { tests } = config\n\n  // validation\n  if (isBlank(parameters.first)) {\n    print.info(`${context.runtime.brand} generate saga <name>\\n`)\n    print.info('A name is required.')\n    return\n  }\n\n  const name = pascalCase(parameters.first)\n  const props = { name }\n\n  const jobs = [{ template: `saga.ejs`, target: `App/Sagas/${name}Sagas/index.ts` }]\n  if (tests) {\n    jobs.push({\n      template: `saga-test-${tests}.ejs`,\n      target: `App/Sagas/${name}Sagas/${name}SagaTest.ts`\n    })\n  }\n\n  // make the templates\n  await ignite.copyBatch(context, jobs, props)\n}\n"
  },
  {
    "path": "commands/screen.js",
    "content": "// @cliDescription  Generates an opinionated container.\n\nconst patterns = require('../lib/patterns')\n\nmodule.exports = async function (context) {\n  // grab some features\n  const { parameters, print, strings, ignite, filesystem } = context\n  const { pascalCase, isBlank } = strings\n  const config = ignite.loadIgniteConfig()\n\n  // validation\n  if (isBlank(parameters.first)) {\n    print.info(`${context.runtime.brand} generate screen <name>\\n`)\n    print.info('A name is required.')\n    return\n  }\n\n  const name = pascalCase(parameters.first)\n  const screenName = name.endsWith('Screen') ? name : `${name}Screen`\n  const props = { name: screenName }\n\n  const jobs = [\n    {\n      template: `screen.ejs`,\n      target: `App/Containers/${screenName}/${screenName}.tsx`\n    },\n    {\n      template: `screen-style.ejs`,\n      target: `App/Containers/${screenName}/${screenName}Style.tsx`\n    },\n    {\n      template: 'component-index.ejs',\n      target: `App/Containers/${screenName}/index.ts`\n    }\n  ]\n\n  // make the templates\n  await ignite.copyBatch(context, jobs, props)\n\n  // if using `react-navigation` go the extra step\n  // and insert the screen into the nav router\n  if (config.navigation === 'react-navigation') {\n    const appNavFilePath = `${process.cwd()}/App/Navigation/AppNavigation.tsx`\n    const importToAdd = `import ${screenName} from \"../Containers/${screenName}\";`\n    const routeToAdd = `  ${screenName}: { screen: ${screenName} },`\n\n    if (!filesystem.exists(appNavFilePath)) {\n      const msg = `No '${appNavFilePath}' file found.  Can't insert screen.`\n      print.error(msg)\n      process.exit(1)\n    }\n\n    // insert screen import\n    ignite.patchInFile(appNavFilePath, {\n      after: patterns[patterns.constants.PATTERN_IMPORTS],\n      insert: importToAdd\n    })\n\n    // insert screen route\n    ignite.patchInFile(appNavFilePath, {\n      after: patterns[patterns.constants.PATTERN_ROUTES],\n      insert: routeToAdd\n    })\n  } else {\n    print.info(`Screen ${screenName} created, manually add it to your navigation`)\n  }\n}\n"
  },
  {
    "path": "ignite.json",
    "content": "{\n  \"generators\": [\n    \"component\",\n    \"container\",\n    \"list\",\n    \"reducers\",\n    \"saga\",\n    \"screen\"\n  ]\n}\n"
  },
  {
    "path": "lib/patterns.js",
    "content": "const constants = {\n  PATTERN_IMPORTS: 'imports',\n  PATTERN_ROUTES: 'routes'\n}\n\nmodule.exports = {\n  constants,\n  [constants.PATTERN_IMPORTS]: `import[\\\\s\\\\S]*from\\\\s+\"react-navigation\";?`,\n  [constants.PATTERN_ROUTES]: 'const PrimaryNav'\n}\n"
  },
  {
    "path": "lib/react-native-version.js",
    "content": "const { pathOr, is } = require('ramda')\n\n// the default React Native version for this boilerplate\nconst REACT_NATIVE_VERSION = '0.51.0'\n\n// where the version lives under gluegun\nconst pathToVersion = ['parameters', 'options', 'react-native-version']\n\n// accepts the context and returns back the version\nconst getVersionFromContext = pathOr(REACT_NATIVE_VERSION, pathToVersion)\n\n/**\n * Gets the React Native version to use.\n *\n * Attempts to read it from the command line, and if not there, falls back\n * to the version we want for this boilerplate.  For example:\n *\n *   $ ignite new Custom --react-native-version 0.44.1\n *\n * @param {*} context - The gluegun context.\n */\nconst getReactNativeVersion = (context = {}) => {\n  const version = getVersionFromContext(context)\n  return is(String, version) ? version : REACT_NATIVE_VERSION\n}\n\nmodule.exports = {\n  REACT_NATIVE_VERSION,\n  getReactNativeVersion\n}\n"
  },
  {
    "path": "options.js",
    "content": "/**\n * The questions to ask during the install process.\n */\nconst questions = [\n  {\n    name: 'dev-screens',\n    message: 'Would you like Ignite Development Screens?',\n    type: 'list',\n    choices: ['No', 'Yes']\n  },\n  {\n    name: 'vector-icons',\n    message: 'What vector icon library will you use?',\n    type: 'list',\n    choices: ['none', 'react-native-vector-icons']\n  },\n  {\n    name: 'i18n',\n    message: 'What internationalization library will you use?',\n    type: 'list',\n    choices: ['none', 'react-native-i18n']\n  },\n  {\n    name: 'animatable',\n    message: 'What animation library will you use?',\n    type: 'list',\n    choices: ['none', 'react-native-animatable']\n  },\n  {\n    name: 'tests',\n    message: 'What test library will you use?',\n    type: 'list',\n    choices: ['none', 'jest']\n  }\n]\n\n/**\n * The max preset.\n */\nconst max = {\n  'dev-screens': 'Yes',\n  'vector-icons': 'react-native-vector-icons',\n  i18n: 'react-native-i18n',\n  animatable: 'react-native-animatable',\n  tests: 'jest'\n}\n\n/**\n * The min preset.\n */\nconst min = {\n  'dev-screens': 'No',\n  'vector-icons': 'none',\n  i18n: 'none',\n  animatable: 'none',\n  tests: 'none'\n}\n\nmodule.exports = {\n  questions,\n  answers: { min, max }\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"ignite-typescript-boilerplate\",\n  \"description\": \"TypeScript boilerplate for React Native.\",\n  \"license\": \"MIT\",\n  \"repository\": \"aerian-studios/ignite-typescript-boilerplate\",\n  \"homepage\": \"https://github.com/aerian-studios/ignite-typescript-boilerplate\",\n  \"version\": \"0.1.5\",\n  \"files\": [\n    \"boilerplate\",\n    \"commands\",\n    \"lib\",\n    \"templates\",\n    \"boilerplate.js\",\n    \"ignite.json\",\n    \"options.js\",\n    \"readme.md\",\n    \"plugin.js\"\n  ],\n  \"author\": {\n    \"name\": \"Aerian Studios\",\n    \"email\": \"matt.kane@aerian.com\",\n    \"url\": \"https://github.com/aerian-studios/ignite-typescript-boilerplate\"\n  },\n  \"scripts\": {\n    \"lint\": \"tslint\",\n    \"test\": \"jest\",\n    \"watch\": \"jest --runInBand --watch\",\n    \"coverage\": \"jest --runInBand --coverage\",\n    \"shipit\": \"np\"\n  },\n  \"devDependencies\": {\n    \"@types/jest\": \"^21.1.5\",\n    \"@types/ramda\": \"^0.24.18\",\n    \"@types/react\": \"^16.0.18\",\n    \"@types/react-native\": \"^0.49.2\",\n    \"@types/react-navigation\": \"^1.0.21\",\n    \"@types/react-redux\": \"^5.0.10\",\n    \"@types/redux\": \"^3.6.0\",\n    \"@types/seamless-immutable\": \"^7.1.1\",\n    \"@types/webpack-env\": \"^1.13.2\",\n    \"fs-jetpack\": \"^1.0.0\",\n    \"jest\": \"^20.0.4\",\n    \"np\": \"^2.15.0\",\n    \"react\": \"16.0.0-beta.5\",\n    \"redux\": \"^3.7.2\",\n    \"redux-saga\": \"^0.16.0\",\n    \"reduxsauce\": \"^0.7.0\",\n    \"seamless-immutable\": \"^7.1.2\",\n    \"sinon\": \"^2.3.1\",\n    \"tempy\": \"^0.1.0\",\n    \"tslint\": \"^5.8.0\",\n    \"tslint-react\": \"^3.2.0\",\n    \"typesafe-actions\": \"^1.1.2\",\n    \"typescript\": \"^2.6.1\"\n  },\n  \"dependencies\": {\n    \"ramda\": \"^0.23.0\"\n  }\n}\n"
  },
  {
    "path": "plugin.js",
    "content": "// Ignite CLI plugin for Ts\n// ----------------------------------------------------------------------------\n\n\nconst add = async function (context) {\n  // No-op, as we do this all in `boilerplate.js`\n}\n\n/**\n * Remove yourself from the project.\n */\nconst remove = async function (context) {\n  // No-op, as we do this all in `boilerplate.js`  \n}\n\n// Required in all Ignite CLI plugins\nmodule.exports = { add, remove }\n"
  },
  {
    "path": "readme.md",
    "content": "## Ignite TypeScript Boilerplate for React Native\n\n### The easiest way to develop React Native apps in TypeScript.\nGet up and running with TypeScript React Native development in minutes. A batteries-included, opinionated starter project, and code generators for your components, reducers, sagas and more. \nOriginally based on a port of the [Ignite IR Boilerplate](https://github.com/infinitered/ignite-ir-boilerplate) to TypeScript.\n\nCurrently includes:\n\n* React Native 0.51.0 (but you can change this if you want to experiment)\n* React Navigation\n* Redux\n* Redux Sagas\n* And more!\n\n## Quick Start\n\nWhen you've installed the [Ignite CLI](https://github.com/infinitered/ignite), (tl;dr: `npm install -g ignite-cli`) you can get started with this boilerplate like this:\n\n```sh\nignite new MyLatestCreation --b ignite-typescript-boilerplate\n```\n\nYou can also change the React Native version, just keep in mind, we may not have tested this just yet.\n\n```sh\nignite new MyLatestCreation --b ignite-typescript-boilerplate --react-native-version 0.46.0-rc.2\n```\n\nBy default we'll ask you some questions during install as to which features you'd like.  If you just want them all, you can skip the questions:\n\n```sh\nignite new MyLatestCreation --b ignite-typescript-boilerplate --max\n```\n\nIf you want very few of these extras:\n\n```sh\nignite new MyLatestCreation --b ignite-typescript-boilerplate --min\n```\n\n## Using TypeScript with React Native\n\nThanks to the beauty of [react-native-typescript-transformer](https://github.com/ds300/react-native-typescript-transformer), we can seamlessly use TypeScript in our React Native project. Source maps and hot reloading all work just like you would expect.\n\n## Coding style\n\nWe use `tslint` to enforce coding style, with rules based on [Palantir's tslint-react](https://github.com/palantir/tslint-react), \nand a few changes to accommodate some Ignite quirks. If you install a plugin, your editor can probably automatically fix problems. \nIn VS Code, set `\"tslint.autoFixOnSave\": true` in your \nworkspace settings. You can run the linter from the command line. `npm run lint` runs the linter, while `npm run fixcode` tries to autofix problems.\n\n## Boilerplate walkthrough\n\nYour `App` folder is where most of the goodies are found in an Ignite app. Let's walk through them in more detail. Start with `Containers/App.tsx` (described below) and work your way down the walkthrough in order.\n\n### Components\n\nReact components go here. We generate these as stateless functional components by default, as recommended by the React team.\n\n### Containers\n\nContainers are Redux-connected components, and are mostly full screens.\n\n* `App.tsx` - your main application. We create a Redux store and configure it here\n* `RootContainer.tsx` - main view of your application. Contains your status bar and navigation component\n* `LaunchScreen.tsx` - this is the first screen shown in your application. It's loaded into the Navigation component\n\n### Navigation\n\nYour primary and other navigation components reside here.\n\n* `AppNavigation.tsx` - loads in your initial screen and creates your menu(s) in a StackNavigation\n* `Styles` - styling for the navigation\n\n### Storybook\n\n[Storybook](https://storybook.js.org/) has been setup to show off components in the different states. Storybook is a great way to develop and test components outside of use in your app. Simply run `yarn run storybook` to get started. All stories are contained in the `*.story.tsx` files along side the components.\n\n### Themes\n\nStyling themes used throughout your app styles.\n\n* `ApplicationStyles.ts` - app-wide styles\n* `Colors.ts` - defined colors for your app\n* `Fonts.ts` - defined fonts for your app\n* `Images.ts` - loads and caches images used in your app\n* `Metrics.ts` - useful measurements of things like navBarHeight\n\n### Config\n\nInitialize and configure things here.\n\n* `AppConfig.ts` - simple React Native configuration here\n* `DebugConfig.js` - define how you want your debug environment to act. This is a .js file because that's what\nIgnite expects to find.\n* `ReactotronConfig.ts` - configures [Reactotron](https://github.com/infinitered/reactotron) in your project (Note: this [will be extracted](https://github.com/infinitered/ignite/issues/779) into a plugin in the future)\n\n### Fixtures\n\nContains json files that mimic API responses for quicker development. These are used by the `Services/FixtureApi.ts` object to mock API responses.\n\n### Redux, Sagas\n\nContains a preconfigured Redux and Redux-Sagas setup. Review each file carefully to see how Redux interacts with your application. You will find these in the Reducers and Sagas folders. We use [typesafe-actions](https://github.com/piotrwitek/typesafe-actions) to get lovely\ntype checking of our reducers and actions. Take a look at `Lib/ReduxHelpers.ts` for some extra functions that\nwe use to make them more Ignite-y.\n\n### Services\n\nContains your API service and other important utilities for your application.\n\n* `Api.tsx` - main API service, giving you an interface to communicate with your back end\n* `ExamplesRegistry.tsx` - lets you view component and Ignite plugin examples in your app\n* `FixtureApi.tsx` - mocks your API service, making it faster to develop early on in your app\n\n\n### Lib\n\nWe recommend using this folder for modules that can be extracted into their own NPM packages at some point.\n\n### Images\n\nContains actual images (usually png) used in your application.\n\n### Transforms\n\nHelpers for transforming data between API and your application and vice versa. An example is provided that you can look at to see how it works.\n\n### Tests\n\nWe create Jest tests alongside the components, reducers and sagas. Enable this by adding `\"tests\": \"jest\"` to `ignite/ignite.json`.\n\n### Code generation\n\nCurrently, the following code generation commands work properly:\n* `ignite generate component MyComponent` - generates a stateless functional component.\n* `ignite generate container MyContainer` - generates a Redux-connected React.Component, with state and view lifecycle.\n* `ignite generate screen MyScreen` - generates a Redux-connected React.Component, with state, view lifecycle and react-navigation.\n* `ignite generate reducers MyNew` - generates a set of Redux reducers.\n* `ignite generate saga MySaga` - generates a Redux Saga\n* `ignite generate list MyList` - generates a FlatList, formatted either as a grid or list.\n\n### Further reading\n\nA comprehensive guide to best practice with TypeScript in React is [the React Redux TypeScript Guide](https://github.com/piotrwitek/react-redux-typescript-guide), which covers a lot more than just Redux. We have adopted a lot of the patterns from this. The `typesafe-actions` library that we use was created by @piotrwitek, the author of the guide.\n\nMicrosoft created [TypeScript React Native Starter](https://github.com/Microsoft/TypeScript-React-Native-Starter), which includes a walkthrough on switching projects to TypeScript.\n\n[React TypeScript Tutorial](https://github.com/DanielRosenwasser/React-TypeScript-Tutorial) is React rather than React Native, but has useful guides.\n\n[This post](http://blog.novanet.no/easy-typescript-with-react-native/) is a good run-through of the [react-native-typescript-transfomer](https://github.com/ds300/react-native-typescript-transformer), which allows us to skip the transpile step that we were using before. Thanks [@wormyy] for the heads-up on this.\n\n### Credits\nCreated by [Matt Kane](https://github.com/ascorbic) at [Aerian Studios](https://www.aerian.com). Based on [Ignite IR Boilerplate](https://github.com/infinitered/ignite-ir-boilerplate), by Infinite Red. "
  },
  {
    "path": "templates/component-index.ejs",
    "content": "import <%= props.name %> from \"./<%= props.name %>\";\n\nexport default <%= props.name %>;\n"
  },
  {
    "path": "templates/component-story.ejs",
    "content": "import { storiesOf } from \"@storybook/react-native\";\nimport * as React from \"react\";\n\nimport <%= props.name %> from \"./<%= props.name %>\";\n\nstoriesOf(\"<%= props.name %>\", module)\n  .add(\"Default\", () => (\n    <<%= props.name %> />\n  ));"
  },
  {
    "path": "templates/component-style.ejs",
    "content": "import { StyleSheet } from \"react-native\";\n\nexport default StyleSheet.create({\n  container: {\n    flex: 1,\n  },\n});\n"
  },
  {
    "path": "templates/component-test-jest.ejs",
    "content": "/// <reference types=\"@types/jest\" />\nimport * as React from \"react\";\nimport { <%= props.name %> } from \"./<%= props.name %>\";\nimport * as renderer from \"react-test-renderer\";\n\n\ntest(\"<%= props.name %> component renders correctly\", () => {\n  const tree = renderer.create(<<%= props.name %> someProperty=\"howdy\" anotherProperty={false} />).toJSON();\n  expect(tree).toMatchSnapshot();\n});"
  },
  {
    "path": "templates/component.ejs",
    "content": "import * as React from \"react\";\nimport { Text, View } from \"react-native\";\nimport styles from \"./<%= props.name %>Style\";\n\ninterface Props {\n  someProperty: string;\n  anotherProperty: boolean;\n}\n\nconst <%= props.name %>: React.SFC<Props> = ({someProperty, anotherProperty}: Props) =>  (\n  <View style={styles.container}>\n    <Text><%= props.name %> Component</Text>\n  </View>\n);\n\nexport default <%= props.name %>;\n"
  },
  {
    "path": "templates/container-style.ejs",
    "content": "import { StyleSheet } from \"react-native\";\nimport { Colors, Metrics } from \"../../Themes/\";\n\nexport default StyleSheet.create({\n  container: {\n    flex: 1,\n    marginTop: Metrics.navBarHeight,\n    backgroundColor: Colors.background,\n  },\n});\n"
  },
  {
    "path": "templates/container.ejs",
    "content": "import * as React from \"react\";\nimport { Text, View } from \"react-native\";\nimport { connect } from \"react-redux\";\nimport * as Redux from \"redux\";\nimport { RootState } from \"../../Reducers\";\nimport { Images } from \"../../Themes\";\nimport Metrics from \"../../Themes/Metrics\";\n\n// Styles\nimport styles from \"./<%= props.name %>Style\";\n\n/**\n * The properties passed to the component\n */\nexport interface OwnProps {\n\n}\n/**\n * The properties mapped from Redux dispatch\n */\nexport interface DispatchProps {\n\n}\n\n/**\n * The properties mapped from the global state\n */\nexport interface StateProps {\n\n}\n\n/**\n * The local state\n */\nexport interface State {\n\n}\n\ntype Props = StateProps & DispatchProps & OwnProps;\n\nclass <%= props.name %> extends\n  React.Component<Props, State> {\n  public state = {\n\n  }\n\n  public render() {\n    return (\n      <View style={styles.container}>\n        <Text>Hello <%= props.name %></Text>\n      </View>\n    );\n  }\n}\n\nconst mapDispatchToProps = (dispatch: Redux.Dispatch<RootState>): DispatchProps => ({\n  \n});\n\nconst mapStateToProps = (state: RootState, ownProps: OwnProps): StateProps => {\n  return {};\n};\n\nexport default connect(mapStateToProps, mapDispatchToProps)(<%= props.name %>) as React.ComponentClass<OwnProps>;\n"
  },
  {
    "path": "templates/flatlist-grid-style.ejs",
    "content": "import { StyleSheet } from 'react-native'\nimport { ApplicationStyles, Metrics, Colors } from '../../../Themes'\n\nexport default StyleSheet.create({\n  ...ApplicationStyles.screen,\n  container: {\n    flex: 1,\n    backgroundColor: Colors.background\n  },\n  row: {\n    flex: 1,\n    backgroundColor: Colors.fire,\n    marginVertical: Metrics.smallMargin,\n    justifyContent: 'center',\n    margin: 10,\n    padding: 5,\n    paddingVertical: 10,\n    borderRadius: Metrics.smallMargin\n  },\n  boldLabel: {\n    fontWeight: 'bold',\n    alignSelf: 'center',\n    color: Colors.snow,\n    textAlign: 'center',\n    marginBottom: Metrics.smallMargin\n  },\n  label: {\n    textAlign: 'center',\n    color: Colors.snow\n  },\n  listContent: {\n    marginTop: Metrics.baseMargin\n  }\n})\n"
  },
  {
    "path": "templates/flatlist-grid.ejs",
    "content": "import * as React from \"react\";\nimport { View, Text, FlatList } from \"react-native\";\nimport { connect } from \"react-redux\";\n\n// More info here: https://facebook.github.io/react-native/docs/flatlist.html\n\n// Styles\nimport styles from \"./<%= props.name %>Style\";\n\nexport interface RowItem {\n  title: string;\n  description: string;\n}\ninterface Props {\n  data: RowItem[];\n}\n\nexport default class <%= props.name %> extends React.PureComponent<Props> {\n\n  /* ***********************************************************\n  * `renderRow` function. How each cell/row should be rendered\n  * It's our best practice to place a single component here:\n  *\n  * e.g.\n    return <MyCustomCell title={item.title} description={item.description} />\n  *************************************************************/\n  renderRow ({item}: {item: RowItem}) {\n    return (\n      <View style={styles.row}>\n        <Text style={styles.boldLabel}>{item.title}</Text>\n        <Text style={styles.label}>{item.description}</Text>\n      </View>\n    )\n  }\n\n  // Render a header?\n  renderHeader = () =>\n    <Text style={[styles.label, styles.sectionHeader]}> - Header - </Text>\n\n  // Render a footer?\n  renderFooter = () =>\n    <Text style={[styles.label, styles.sectionHeader]}> - Footer - </Text>\n\n  // Show this when data is empty\n  renderEmpty = () =>\n    <Text style={styles.label}> - Nothing to See Here - </Text>\n\n  renderSeparator = () =>\n    <Text style={styles.label}> - ~~~~~ - </Text>\n\n  // The default function if no Key is provided is index\n  // an identifiable key is important if you plan on\n  // item reordering.  Otherwise index is fine\n  keyExtractor = (item: RowItem, index: number) => index\n\n  // How many items should be kept im memory as we scroll?\n  oneScreensWorth = 20\n\n  // extraData is for anything that is not indicated in data\n  // for instance, if you kept \"favorites\" in `this.state.favs`\n  // pass that in, so changes in favorites will cause a re-render\n  // and your renderItem will have access to change depending on state\n  // e.g. `extraData`={this.state.favs}\n\n  // Optimize your list if the height of each item can be calculated\n  // by supplying a constant height, there is no need to measure each\n  // item after it renders.  This can save significant time for lists\n  // of a size 100+\n  // e.g. itemLayout={(data, index) => (\n  //   {length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index}\n  // )}\n\n  render () {\n    return (\n      <View style={styles.container}>\n        <FlatList\n          contentContainerStyle={styles.listContent}\n          data={this.props.data}\n          renderItem={this.renderRow}\n          numColumns={2}\n          keyExtractor={this.keyExtractor}\n          initialNumToRender={this.oneScreensWorth}\n          ListHeaderComponent={this.renderHeader}\n          ListFooterComponent={this.renderFooter}\n          ListEmptyComponent={this.renderEmpty}\n          ItemSeparatorComponent={this.renderSeparator}\n        />\n      </View>\n    )\n  }\n}\n"
  },
  {
    "path": "templates/flatlist-sections.ejs",
    "content": "import * as React from \"react\";\nimport { View, SectionList, Text } from \"react-native\";\n\n// More info here: https://facebook.github.io/react-native/docs/sectionlist.html\n\n// Styles\nimport styles from \"./<%= props.name %>Style\";\n\nexport interface Section {\n  key: string;\n  data: RowItem[];\n}\nexport interface RowItem {\n  title: string;\n  description: string;\n}\ninterface Props {\n  data: Section[];\n}\n\nclass <%= props.name %> extends React.PureComponent {\n\n\n  /* ***********************************************************\n  * `renderItem` function - How each cell should be rendered\n  * It's our best practice to place a single component here:\n  *\n  * e.g.\n  *   return <MyCustomCell title={item.title} description={item.description} />\n  *\n  * For sections with different cells (heterogeneous lists), you can do branch\n  * logic here based on section.key OR at the data level, you can provide\n  * `renderItem` functions in each section.\n  *\n  * Note: You can remove section/separator functions and jam them in here\n  *************************************************************/\n  renderItem ({section, item}: {section: Section, item: RowItem}) {\n    return (\n      <View style={styles.row}>\n        <Text style={styles.boldLabel}>Section {section.key} - {item.title}</Text>\n        <Text style={styles.label}>{item.description}</Text>\n      </View>\n    )\n  }\n\n  // Conditional branching for section headers, also see step 3\n  renderSectionHeader ({section}: {section: Section}) {\n    switch (section.key) {\n      case \"First\":\n        return <View style={styles.sectionHeader}><Text style={styles.boldLabel}>First Section</Text></View>\n      default:\n        return <View style={styles.sectionHeader}><Text style={styles.boldLabel}>Second Section</Text></View>\n    }\n  }\n\n  /* ***********************************************************\n  * Consider the configurations we've set below.  Customize them\n  * to your liking!  Each with some friendly advice.\n  *\n  * Removing a function here will make SectionList use default\n  *************************************************************/\n  // Render a header?\n  renderHeader = () =>\n    <Text style={[styles.label, styles.sectionHeader]}> - Full List Header - </Text>\n\n  // Render a footer?\n  renderFooter = () =>\n    <Text style={[styles.label, styles.sectionHeader]}> - Full List Footer - </Text>\n\n  // Does each section need a footer?\n  renderSectionFooter = () =>\n    <Text style={styles.label}> END SECTION!!!! </Text>\n\n  // Show this when data is empty\n  renderEmpty = () =>\n    <Text style={styles.label}> - Nothing to See Here - </Text>\n\n  renderSeparator = () =>\n    <Text style={styles.label}> - ~~~~~ - </Text>\n\n  renderSectionSeparator = () =>\n    <Text style={styles.label}> \\/\\/\\/\\/\\/\\/\\/\\/ </Text>\n\n  // The default function if no Key is provided is index\n  // an identifiable key is important if you plan on\n  // item reordering.  Otherwise index is fine\n  keyExtractor = (item, index) => index\n\n  // How many items should be kept im memory as we scroll?\n  oneScreensWorth = 20\n\n  // extraData is for anything that is not indicated in data\n  // for instance, if you kept \"favorites\" in `this.state.favs`\n  // pass that in, so changes in favorites will cause a re-render\n  // and your renderItem will have access to change depending on state\n  // e.g. `extraData`={this.state.favs}\n\n  // Optimize your list if the height of each item can be calculated\n  // by supplying a constant height, there is no need to measure each\n  // item after it renders.  This can save significant time for lists\n  // of a size 100+\n  // e.g. itemLayout={(data, index) => (\n  //   {length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index}\n  // )}\n\n  render () {\n    return (\n      <View style={styles.container}>\n        <SectionList\n          renderSectionHeader={this.renderSectionHeader}\n          sections={this.props.data}\n          contentContainerStyle={styles.listContent}\n          data={this.props.data}\n          renderItem={this.renderItem}\n          keyExtractor={this.keyExtractor}\n          initialNumToRender={this.oneScreensWorth}\n          ListHeaderComponent={this.renderHeader}\n          SectionSeparatorComponent={this.renderSectionSeparator}\n          ListFooterComponent={this.renderFooter}\n          ListEmptyComponent={this.renderEmpty}\n          ItemSeparatorComponent={this.renderSeparator}\n          renderSectionFooter={this.renderSectionFooter}\n        />\n      </View>\n    )\n  }\n}\n"
  },
  {
    "path": "templates/flatlist.ejs",
    "content": "import * as React from \"react\";\nimport { View, Text, FlatList } from \"react-native\";\n\n// More info here: https://facebook.github.io/react-native/docs/flatlist.html\n\n// Styles\nimport styles from \"./<%= props.name %>Style\";\n\nexport interface RowItem {\n  title: string;\n  description: string;\n}\ninterface Props {\n  data: RowItem[];\n}\n\nexport default class <%= props.name %> extends React.PureComponent<Props> {\n\n  /* ***********************************************************\n  * `renderRow` function. How each cell/row should be rendered\n  * It's our best practice to place a single component here:\n  *\n  * e.g.\n    return <MyCustomCell title={item.title} description={item.description} />\n  *************************************************************/\n  renderRow ({item}: {item: RowItem}) {\n    return (\n      <View style={styles.row}>\n        <Text style={styles.boldLabel}>{item.title}</Text>\n        <Text style={styles.label}>{item.description}</Text>\n      </View>\n    )\n  }\n\n  // Render a header?\n  renderHeader = () =>\n    <Text style={[styles.label, styles.sectionHeader]}> - Header - </Text>\n\n  // Render a footer?\n  renderFooter = () =>\n    <Text style={[styles.label, styles.sectionHeader]}> - Footer - </Text>\n\n  // Show this when data is empty\n  renderEmpty = () =>\n    <Text style={styles.label}> - Nothing to See Here - </Text>\n\n  renderSeparator = () =>\n    <Text style={styles.label}> - ~~~~~ - </Text>\n\n  // The default function if no Key is provided is index\n  // an identifiable key is important if you plan on\n  // item reordering.  Otherwise index is fine\n  keyExtractor = (item: RowItem, index: number) => index\n\n  // How many items should be kept im memory as we scroll?\n  oneScreensWorth = 20\n\n  // extraData is for anything that is not indicated in data\n  // for instance, if you kept \"favorites\" in `this.state.favs`\n  // pass that in, so changes in favorites will cause a re-render\n  // and your renderItem will have access to change depending on state\n  // e.g. `extraData`={this.state.favs}\n\n  // Optimize your list if the height of each item can be calculated\n  // by supplying a constant height, there is no need to measure each\n  // item after it renders.  This can save significant time for lists\n  // of a size 100+\n  // e.g. itemLayout={(data, index) => (\n  //   {length: ITEM_HEIGHT, offset: ITEM_HEIGHT * index, index}\n  // )}\n\n  render () {\n    return (\n      <View style={styles.container}>\n        <FlatList\n          contentContainerStyle={styles.listContent}\n          data={this.props.data}\n          renderItem={this.renderRow}\n          keyExtractor={this.keyExtractor}\n          initialNumToRender={this.oneScreensWorth}\n          ListHeaderComponent={this.renderHeader}\n          ListFooterComponent={this.renderFooter}\n          ListEmptyComponent={this.renderEmpty}\n          ItemSeparatorComponent={this.renderSeparator}\n        />\n      </View>\n    )\n  }\n}\n"
  },
  {
    "path": "templates/listview-grid-style.ejs",
    "content": "import { StyleSheet } from \"react-native\";\nimport { ApplicationStyles, Metrics, Colors } from \"../../../Themes\";\n\nexport default StyleSheet.create({\n  ...ApplicationStyles.screen,\n  container: {\n    flex: 1,\n    backgroundColor: Colors.background\n  },\n  row: {\n    width: 100,\n    height: 100,\n    justifyContent: 'center',\n    alignItems: 'center',\n    margin: Metrics.baseMargin,\n    backgroundColor: Colors.fire,\n    borderRadius: Metrics.smallMargin\n  },\n  sectionHeader: {\n    paddingTop: Metrics.doubleBaseMargin,\n    width: Metrics.screenWidth,\n    alignSelf: 'center',\n    margin: Metrics.baseMargin,\n    backgroundColor: Colors.background\n  },\n  boldLabel: {\n    fontWeight: 'bold',\n    alignSelf: 'center',\n    color: Colors.snow,\n    textAlign: 'center',\n    marginBottom: Metrics.smallMargin\n  },\n  label: {\n    alignSelf: 'center',\n    color: Colors.snow,\n    textAlign: 'center'\n  },\n  listContent: {\n    justifyContent: 'space-around',\n    flexDirection: 'row',\n    flexWrap: 'wrap'\n  }\n})\n"
  },
  {
    "path": "templates/reducers-test-jest.ejs",
    "content": "import <%= props.name %>Actions, <%= props.name %>Reducer as reducer, INITIAL_STATE from \"./<%= props.name %>Reducers\";\n\nit(\"attempt\", () => {\n  const state = reducer(INITIAL_STATE, <%= props.name %>Actions.request('data'))\n\n  expect(state.fetching).toBe(true)\n})\n\nit(\"success\", () => {\n  const state = reducer(INITIAL_STATE, <%= props.name %>Actions.success('hi'))\n\n  expect(state.payload).toBe('hi')\n})\n\nit(\"failure\", () => {\n  const state = reducer(INITIAL_STATE, <%= props.name %>Actions.failure())\n\n  expect(state.fetching).toBe(false)\n  expect(state.error).toBe(true)\n})\n"
  },
  {
    "path": "templates/reducers.ejs",
    "content": "import { Action, AnyAction, Reducer } from \"redux\";\nimport * as SI from \"seamless-immutable\";\nimport { createAction, PayloadAction } from \"typesafe-actions\";\nimport { mapReducers, ReducerMap } from \"../../Lib/ReduxHelpers\";\n\n/* ------------- Types and Action Creators ------------- */\ninterface <%= pascalCase(props.name) %>SuccessParams {data: string; }\n\nconst actionCreators = {\n  request: createAction(\"<%= snakeCase(props.name).toUpperCase() %>_REQUEST\"),\n  success: (payload: <%= pascalCase(props.name) %>SuccessParams)  => ({type: \"<%= snakeCase(props.name).toUpperCase() %>_SUCCESS\", payload})),\n  failure: createAction(\"<%= snakeCase(props.name).toUpperCase() %>_FAILURE\"),\n};\n\nexport const <%= pascalCase(props.name) %>Actions = actionCreators;\n\nexport interface <%= pascalCase(props.name) %>State {\n  data?: string | null;\n  error?: boolean | null;\n  fetching?: boolean | null;\n}\n\nexport type <%= pascalCase(props.name) %>Action = PayloadAction<string, <%= pascalCase(props.name) %>State>;\n\nexport type Immutable<%= pascalCase(props.name) %>State = SI.ImmutableObject<<%= pascalCase(props.name) %>State>;\n\n/* ------------- Initial State ------------- */\n\nexport const INITIAL_STATE: Immutable<%= pascalCase(props.name) %>State = SI.from({\n  data: null,\n  error: null,\n  fetching: null,\n});\n\n/* ------------- Reducers ------------- */\n\nexport const request: Reducer<Immutable<%= pascalCase(props.name) %>State> =\n(state: Immutable<%= pascalCase(props.name) %>State) =>\n  state.merge({ fetching: true });\n\nexport const success: Reducer<Immutable<%= pascalCase(props.name) %>State> =\n(state: Immutable<%= pascalCase(props.name) %>State, action: AnyAction & {payload?: <%= pascalCase(props.name) %>SuccessParams}) => {\n  if (!action.payload) {\n    return failure(state, action);\n  }\n  const { data } = action.payload;\n  \n  return state.merge({ fetching: false, error: null, data });\n};\n\nexport const failure: Reducer<Immutable<%= pascalCase(props.name) %>State> = (state: Immutable<%= pascalCase(props.name) %>State) =>\n  state.merge({ fetching: false, error: true, data: null });\n\n/* ------------- Hookup Reducers To Types ------------- */\n\nconst reducerMap: ReducerMap<typeof actionCreators, Immutable<%= pascalCase(props.name) %>State> = {\n  request,\n  failure,\n  success,\n};\n\nexport const <%= pascalCase(props.name) %>Reducer = mapReducers(INITIAL_STATE, reducerMap, actionCreators);\n\nexport default <%= pascalCase(props.name) %>Reducer;\n"
  },
  {
    "path": "templates/saga-test-jest.ejs",
    "content": "/* ***********************************************************\n* Wiring Instructions\n* To make this test work, you'll need to:\n*  - Add a Fixture named get<%= props.name %> to the\n*    ./App/Services/FixtureApi file. You can just keep adding\n*    functions to that file.\n*************************************************************/\n\nimport FixtureAPI from '../../Services/FixtureApi'\nimport { call, put } from 'redux-saga/effects'\nimport { get<%= props.name %> } from './index'\nimport <%= props.name %>Actions from '../../Reducers/<%= props.name %>Reducers'\n\nconst stepper = (fn) => (mock) => fn.next(mock).value\n\nit('first calls API', () => {\n  const step = stepper(get<%= props.name %>(FixtureAPI, {data: 'taco'}))\n  // first yield is the API\n  expect(step()).toEqual(call(FixtureAPI.get<%= props.name %>, 'taco'))\n})\n\nit('success path', () => {\n  const response = FixtureAPI.get<%= props.name %>('taco')\n  const step = stepper(get<%= props.name %>(FixtureAPI, {data: 'taco'}))\n  // Step 1: Hit the api\n  step()\n  // Step 2: Successful return and data!\n  expect(step(response)).toEqual(put(<%= pascalCase(props.name) %>Actions.<%= camelCase(props.name) %>Success(21)))\n})\n\nit('failure path', () => {\n  const response = {ok: false}\n  const step = stepper(get<%= props.name %>(FixtureAPI, {data: 'taco'}))\n  // Step 1: Hit the api\n  step()\n  // Step 2: Failed response.\n  expect(step(response)).toEqual(put(<%= pascalCase(props.name) %>Actions.<%= camelCase(props.name) %>Failure()))\n})\n"
  },
  {
    "path": "templates/saga.ejs",
    "content": "/* ***********************************************************\n* A short word on how to use this automagically generated file.\n* We're often asked in the ignite gitter channel how to connect\n* to a to a third party api, so we thought we'd demonstrate - but\n* you should know you can use sagas for other flow control too.\n*\n* Other points:\n*  - You'll need to add this saga to sagas/index.ts\n*  - This template uses the api declared in sagas/index.ts, so\n*    you'll need to define a constant in that file.\n*************************************************************/\nimport { ApiResponse } from \"apisauce\";\nimport { SagaIterator } from \"redux-saga\";\nimport { call, put } from \"redux-saga/effects\";\nimport { <%= props.name %>Actions, <%= props.name %>Action } from \"../../Reducers/<%= props.name %>Reducers\";\nimport { <%= props.name %>Api, <%= props.name %>Response } from \"../../Services/<%= props.name %>Api\";\n\n\nexport function * get<%= props.name %> (api: <%= props.name %>Api, action: <%= props.name %>Action): SagaIterator {\n  const { data } = action;\n  // make the call to the api\n  const response: ApiResponse<<%= props.name %>Response>  = yield call(api.get<%= camelCase(props.name) %>, data);\n\n  // success?\n  if (response.ok) {\n    // You might need to change the response here - do this with a 'transform',\n    // located in ../../Transforms/. Otherwise, just pass the data back from the api.\n    yield put(<%= props.name %>Actions.success({data: response.data}));\n  } else {\n    yield put(<%= props.name %>Actions.failure());\n  }\n}\n"
  },
  {
    "path": "templates/screen-index.ejs",
    "content": "import <%= props.name %> from \"./<%= props.name %>\";\n\nexport default <%= props.name %>;\n"
  },
  {
    "path": "templates/screen-style.ejs",
    "content": "import { StyleSheet } from \"react-native\";\nimport { Colors, Metrics } from \"../../Themes/\";\n\nexport default StyleSheet.create({\n  container: {\n    flex: 1,\n    marginTop: Metrics.navBarHeight,\n    backgroundColor: Colors.background,\n  },\n});\n"
  },
  {
    "path": "templates/screen.ejs",
    "content": "import * as React from \"react\";\nimport { Alert, Image, Text, TouchableOpacity, View } from \"react-native\";\nimport Icon from \"react-native-vector-icons/FontAwesome\";\nimport { NavigationAction, NavigationDrawerScreenOptions, NavigationScreenProps, NavigationState } from \"react-navigation\";\nimport { connect } from \"react-redux\";\nimport * as Redux from \"redux\";\nimport { RootState } from \"../../Reducers\";\nimport { Images } from \"../../Themes\";\nimport Metrics from \"../../Themes/Metrics\";\n\n// Styles\nimport styles from \"./<%= props.name %>Style\";\n\n/**\n * The properties passed to the component\n */\nexport interface OwnProps {\n\n}\n/**\n * The properties mapped from Redux dispatch\n */\nexport interface DispatchProps {\n\n}\n\n/**\n * The properties mapped from the global state\n */\nexport interface StateProps {\n\n}\n\n/**\n * The local state\n */\nexport interface State {\n\n}\n\ntype Props = StateProps & DispatchProps & OwnProps & NavigationScreenProps<{}>;\n\nclass <%= props.name %> extends\n  React.Component<Props, State> {\n  \n  public state = {\n\n  };\n\n  public static navigationOptions: NavigationDrawerScreenOptions = {\n    drawerLabel: \"Welcome\",\n    drawerIcon: ({ tintColor, focused }: {tintColor: string, focused: boolean}) => (\n      <Icon\n        name=\"home\"\n        size={20}\n        style={{ color: tintColor }}\n      />\n    ),\n  };\n\n  public render() {\n    return (\n      <View style={styles.mainContainer}>\n        <Text>Hello <%= props.name %></Text>\n      </View>\n    );\n  }\n}\n\nconst mapDispatchToProps = (dispatch: Redux.Dispatch<RootState>): DispatchProps => ({\n  \n});\n\nconst mapStateToProps = (state: RootState, ownProps: OwnProps): StateProps => {\n  return {};\n};\n\nexport default connect(mapStateToProps, mapDispatchToProps)(<%= props.name %>) as React.ComponentClass<OwnProps>;\n"
  },
  {
    "path": "test/generators-integration.test.js",
    "content": "const execa = require('execa')\nconst jetpack = require('fs-jetpack')\nconst tempy = require('tempy')\n\nconst IGNITE = 'ignite'\nconst APP = 'IntegrationTest'\nconst BOILERPLATE = `${__dirname}/..`\nconsole.warn(BOILERPLATE)\n// calling the ignite cli takes a while\njasmine.DEFAULT_TIMEOUT_INTERVAL = 600000\n\ndescribe('without a linter', () => {\n  beforeAll(async () => {\n    // creates a new temp directory\n    process.chdir(tempy.directory())\n    await execa(IGNITE, ['new', APP, '--min', '--skip-git', '--no-lint', '--boilerplate', BOILERPLATE])\n    process.chdir(APP)\n  })\n\n  test('does not have a linting script', async () => {\n    expect(jetpack.read('package.json', 'json')['scripts']['lint']).toBe(undefined)\n  })\n})\n\ndescribe('generators', () => {\n  beforeAll(async () => {\n    // creates a new temp directory\n    process.chdir(tempy.directory())\n    await execa(IGNITE, ['new', APP, '--min', '--skip-git', '--boilerplate', BOILERPLATE])\n    process.chdir(APP)\n  })\n\n  test('generates a component', async () => {\n    await execa(IGNITE, ['g', 'component', 'Test'], { preferLocal: false })\n    expect(jetpack.exists('App/Components/Test.tsx')).toBe('file')\n    expect(jetpack.exists('App/Components/Styles/TestStyle.ts')).toBe('file')\n    const lint = await execa('npm', ['-s', 'run', 'lint'])\n    expect(lint.stderr).toBe('')\n  })\n\n  test('generate listview of type row works', async () => {\n    await execa(IGNITE, ['g', 'list', 'TestRow', '--type=Row', '--codeType=listview', '--dataType=Single'], { preferLocal: false })\n    expect(jetpack.exists('App/Containers/TestRow.tsx')).toBe('file')\n    expect(jetpack.exists('App/Containers/Styles/TestRowStyle.ts')).toBe('file')\n    const lint = await execa('npm', ['run', 'lint'])\n    expect(lint.stderr).toBe('')\n  })\n\n  test('generate flatlist of type row works', async () => {\n    await execa(IGNITE, ['g', 'list', 'TestFlatRow', '--type=Row', '--codeType=flatlist', '--dataType=Single'], { preferLocal: false })\n    expect(jetpack.exists('App/Containers/TestFlatRow.tsx')).toBe('file')\n    expect(jetpack.exists('App/Containers/Styles/TestFlatRowStyle.ts')).toBe('file')\n    const lint = await execa('npm', ['run', 'lint'])\n    expect(lint.stderr).toBe('')\n  })\n\n  test('generate listview of sections works', async () => {\n    await execa(IGNITE, ['g', 'list', 'TestSection', '--type=Row', '--codeType=listview', '--dataType=Sectioned'], { preferLocal: false })\n    expect(jetpack.exists('App/Containers/TestSection.tsx')).toBe('file')\n    expect(jetpack.exists('App/Containers/Styles/TestSectionStyle.ts')).toBe('file')\n    const lint = await execa('npm', ['run', 'lint'])\n    expect(lint.stderr).toBe('')\n  })\n\n  test('generate flatlist of sections works', async () => {\n    await execa(IGNITE, ['g', 'list', 'TestFlatSection', '--type=Row', '--codeType=flatlist', '--dataType=Sectioned'], { preferLocal: false })\n    expect(jetpack.exists('App/Containers/TestFlatSection.tsx')).toBe('file')\n    expect(jetpack.exists('App/Containers/Styles/TestFlatSectionStyle.ts')).toBe('file')\n    const lint = await execa('npm', ['run', 'lint'])\n    expect(lint.stderr).toBe('')\n  })\n\n  test('generate listview of type grid works', async () => {\n    await execa(IGNITE, ['g', 'list', 'TestGrid', '--type=Grid', '--codeType=listview', '--dataType=Single'], { preferLocal: false })\n    expect(jetpack.exists('App/Containers/TestGrid.tsx')).toBe('file')\n    expect(jetpack.exists('App/Containers/Styles/TestGridStyle.ts')).toBe('file')\n    const lint = await execa('npm', ['run', 'lint'])\n    expect(lint.stderr).toBe('')\n  })\n\n  test('generate redux works', async () => {\n    await execa(IGNITE, ['g', 'redux', 'Test'], { preferLocal: false })\n    expect(jetpack.exists('App/Redux/TestRedux.tsx')).toBe('file')\n    const lint = await execa('npm', ['run', 'lint'])\n    expect(lint.stderr).toBe('')\n  })\n\n  test('generate container works', async () => {\n    await execa(IGNITE, ['g', 'container', 'Container'], { preferLocal: false })\n    expect(jetpack.exists('App/Containers/Container.tsx')).toBe('file')\n    expect(jetpack.exists('App/Containers/Styles/ContainerStyle.ts')).toBe('file')\n    const lint = await execa('npm', ['run', 'lint'])\n    expect(lint.stderr).toBe('')\n  })\n\n  test('generate saga works', async () => {\n    await execa(IGNITE, ['g', 'saga', 'Test'], { preferLocal: false })\n    expect(jetpack.exists('App/Sagas/TestSagas.tsx')).toBe('file')\n    const lint = await execa('npm', ['run', 'lint'])\n    expect(lint.stderr).toBe('')\n  })\n\n  test('generate screen works', async () => {\n    await execa(IGNITE, ['g', 'screen', 'Test'], { preferLocal: false })\n    expect(jetpack.exists('App/Containers/TestScreen.tsx')).toBe('file')\n    expect(jetpack.exists('App/Containers/Styles/TestScreenStyle.ts')).toBe('file')\n    const lint = await execa('npm', ['run', 'lint'])\n    expect(lint.stderr).toBe('')\n  })\n})\n"
  },
  {
    "path": "test/interface.test.js",
    "content": "const boilerplate = require('../boilerplate')\nconst plugin = require('../plugin')\n\ntest('boilerplate interface', async () => {\n  expect(typeof boilerplate.install).toBe('function')\n})\n\ntest('plugin interface', async () => {\n  expect(typeof plugin.add).toBe('function')\n  expect(typeof plugin.remove).toBe('function')\n})\n"
  },
  {
    "path": "test/react-native-version.test.js",
    "content": "const boilerplate = require('../lib/react-native-version')\n\n// grab a few things from the boilerplate module\nconst get = boilerplate.getReactNativeVersion\nconst DEFAULT = boilerplate.REACT_NATIVE_VERSION\n\n/**\n * Runs with a valid gluegun context and a staged version number.\n *\n * @param {*} reactNativeVersion The React Native version to use.\n * @return {string} The version number we should be using.\n */\nconst mock = reactNativeVersion => get({\n  parameters: {\n    options: {\n      'react-native-version': reactNativeVersion\n    }\n  }\n})\n\n// this would only happen if we screwed something up in our boilerplate.js\ntest('it handles strange inputs from code', () => {\n  expect(get()).toBe(DEFAULT)\n  expect(get(null)).toBe(DEFAULT)\n  expect(get(true)).toBe(DEFAULT)\n  expect(get(8)).toBe(DEFAULT)\n  expect(get('hello')).toBe(DEFAULT)\n  expect(get([])).toBe(DEFAULT)\n  expect(get({})).toBe(DEFAULT)\n  expect(get(() => true)).toBe(DEFAULT)\n})\n\n// this could happen because it's valid input via minimist from the user\ntest('it handles strange input from the user', () => {\n  expect(mock(true)).toBe(DEFAULT)\n  expect(mock(false)).toBe(DEFAULT)\n  expect(mock([])).toBe(DEFAULT)\n  expect(mock({})).toBe(DEFAULT)\n})\n\n// very edge-casey\ntest('it handles not-quite semver numbers', () => {\n  expect(mock(0)).toBe(DEFAULT)\n  expect(mock(0.25)).toBe(DEFAULT)\n})\n\n// happy path\ntest('it handles valid versions', () => {\n  expect(mock('0.41.0')).toBe('0.41.0')\n  expect(mock('0.41.0-beta.1')).toBe('0.41.0-beta.1')\n  expect(mock(DEFAULT)).toBe(DEFAULT)\n  expect(mock('next')).toBe('next')\n})\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n      /* Basic Options */\n      \"target\": \"ESNEXT\",                          /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */\n      \"module\": \"ESNext\",                     /* Specify module code generation: 'none', commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */\n      // \"lib\": [],                             /* Specify library files to be included in the compilation:  */\n      \"allowJs\": true,                       /* Allow javascript files to be compiled. */\n      // \"checkJs\": true,                       /* Report errors in .js files. */\n      \"jsx\": \"react-native\",                    /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */\n      // \"declaration\": true,                   /* Generates corresponding '.d.ts' file. */\n      \"sourceMap\": true,                        /* Generates corresponding '.map' file. */\n      // \"outFile\": \"./\",                       /* Concatenate and emit output to single file. */\n      \"outDir\": \"./dist\",                        /* Redirect output structure to the directory. */\n      // \"rootDir\": \"./src\",                       /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */\n      // \"removeComments\": true,                /* Do not emit comments to output. */\n      // \"noEmit\": true,                        /* Do not emit outputs. */\n      // \"importHelpers\": true,                 /* Import emit helpers from 'tslib'. */\n      // \"downlevelIteration\": true,            /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */\n      // \"isolatedModules\": true,               /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */\n  \n      /* Strict Type-Checking Options */\n      \"strict\": true,                            /* Enable all strict type-checking options. */\n      // \"noImplicitAny\": true,                 /* Raise error on expressions and declarations with an implied 'any' type. */\n      // \"strictNullChecks\": true,              /* Enable strict null checks. */\n      // \"noImplicitThis\": true,                /* Raise error on 'this' expressions with an implied 'any' type. */\n      // \"alwaysStrict\": true,                  /* Parse in strict mode and emit \"use strict\" for each source file. */\n  \n      /* Additional Checks */\n      // \"noUnusedLocals\": true,                /* Report errors on unused locals. */\n      // \"noUnusedParameters\": true,            /* Report errors on unused parameters. */\n      // \"noImplicitReturns\": true,             /* Report error when not all code paths in function return a value. */\n      // \"noFallthroughCasesInSwitch\": true,    /* Report errors for fallthrough cases in switch statement. */\n  \n      /* Module Resolution Options */\n      \"moduleResolution\": \"node\",            /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */\n      \"baseUrl\": \"./boilerplate/\",                       /* Base directory to resolve non-absolute module names. */\n      // \"paths\": {},                           /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */\n      // \"rootDirs\": [],                        /* List of root folders whose combined content represents the structure of the project at runtime. */\n      \"typeRoots\": [\"./boilerplate/types/\", \"./node_modules/\"],                       /* List of folders to include type definitions from. */\n      // \"types\": [],                           /* Type declaration files to be included in compilation. */\n      \"allowSyntheticDefaultImports\": true  /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */\n      // \"preserveSymlinks\": true,              /* Do not resolve the real path of symlinks. */\n  \n      /* Source Map Options */\n      // \"sourceRoot\": \"./\",                    /* Specify the location where debugger should locate TypeScript files instead of source locations. */\n      // \"mapRoot\": \"./\",                       /* Specify the location where debugger should locate map files instead of generated locations. */\n      // \"inlineSourceMap\": true,               /* Emit a single file with source maps instead of having a separate file. */\n      // \"inlineSources\": true,                 /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */\n  \n      /* Experimental Options */\n      // \"experimentalDecorators\": true,        /* Enables experimental support for ES7 decorators. */\n      // \"emitDecoratorMetadata\": true,         /* Enables experimental support for emitting type metadata for decorators. */\n    }\n  }"
  }
]