From 265df79be53a5b00142ef1e121d31aa0b6e69eb2 Mon Sep 17 00:00:00 2001 From: MrigWed Date: Tue, 6 Oct 2020 16:55:54 +0530 Subject: [PATCH 1/6] added react-native template and commands --- bin/react-generate.js | 2 +- bin/react-native-generate.js | 210 +++++++++++++++++++++++++++++++++++ 2 files changed, 211 insertions(+), 1 deletion(-) create mode 100644 bin/react-native-generate.js diff --git a/bin/react-generate.js b/bin/react-generate.js index 784eff2..597f313 100755 --- a/bin/react-generate.js +++ b/bin/react-generate.js @@ -13,7 +13,7 @@ const TEST_UTIL = 'tUtil'; const LOADABLE = 'loadable'; const INJECT_SAGA = 'injectSaga'; -const generator = path.join(__dirname, '../generators/index.js'); +const generator = path.join(__dirname, '../generators/react/index.js'); const plopGen = ['--plopfile', generator]; const [, , ...args] = process.argv; diff --git a/bin/react-native-generate.js b/bin/react-native-generate.js new file mode 100644 index 0000000..fdc0038 --- /dev/null +++ b/bin/react-native-generate.js @@ -0,0 +1,210 @@ +#! /usr/bin/env node +const shell = require('shelljs'); +const fs = require('fs'); +const childProcess = require('child_process'); +const process = require('process'); +const _ = require('lodash'); +const path = require('path'); + +const COMPONENT = 'component'; +const CONTAINER = 'container'; +const WEBPACK_BASE_BABEL = 'webpackBaseBabel'; +const TEST_UTIL = 'tUtil'; +const INJECT_SAGA = 'injectSaga'; + +const generator = path.join(__dirname, '../generators/react-native/index.js'); +const plopGen = ['--plopfile', generator]; + +const [, , ...args] = process.argv; +let commandLineArgs = args.toString().split(','); +const stdioInherit = { stdio: 'inherit' }; + +function execShell(commandArray) { + childProcess.execFileSync( + path.join(__dirname, '../node_modules/.bin/plop'), + commandArray, + { ...stdioInherit }, + ); +} + +// validate input +if (!commandLineArgs[0]) { + shell.exec( + `echo Sorry! react-native-generate requires an argument to be passed. Run react-native-generate --help for more details`, + ); + return; +} + +// get the type of generator +shell.env.GENERATOR_TYPE = _.includes(commandLineArgs[0], 't') + ? 'existing' + : 'new'; +let directoryName = 'react-native-template'; +switch (commandLineArgs[0]) { + case 'init': + shell.exec( + `git clone https://github.com/wednesday-solutions/react-native-template`, + ); + shell.cd(process.cwd()); + if (commandLineArgs[1]) { + shell.exec(`mkdir ${commandLineArgs[1]}`); + fs.rename(`react-native-template`, commandLineArgs[1], err => { + if (err) { + throw new Error('Error while renaming'); + } + }); + directoryName = commandLineArgs[1]; + const json = path.join(__dirname, '../node_modules/.bin/json'); + + shell.exec( + `${json} -I -f ${directoryName}/package.json -e "this.name='${directoryName}'"`, + ); + shell.exec( + `${json} -I -f ${directoryName}/package.json -e "this.homepage='""'"`, + ); + shell.exec( + `${json} -I -f ${directoryName}/package.json -e "this.author='""'"`, + ); + shell.exec( + `${json} -I -f ${directoryName}/package.json -e "this.repository='{}'"`, + ); + + commandLineArgs = _.drop(commandLineArgs); + } + shell.cd(directoryName); + shell.exec(`git remote remove origin`); + execShell([ + '-f', + ...plopGen, + WEBPACK_BASE_BABEL, + ..._.drop(commandLineArgs), + ]); + shell.exec(`npm run initialize`); + break; + case 'gt': + execShell(plopGen); + break; + case 'gtcom': + execShell([...plopGen, COMPONENT, ..._.drop(commandLineArgs)]); + break; + case 'gtcon': + execShell([...plopGen, CONTAINER, ..._.drop(commandLineArgs)]); + break; + case 'gtf': + execShell(['-f', ...plopGen, ..._.drop(commandLineArgs)]); + break; + case 'gtcomf': + execShell(['-f', ...plopGen, COMPONENT, ..._.drop(commandLineArgs)]); + break; + case 'gtconf': + execShell(['-f', ...plopGen, CONTAINER, ..._.drop(commandLineArgs)]); + break; + case 'g': + execShell([...plopGen, ..._.drop(commandLineArgs)]); + break; + case 'gcom': + execShell([...plopGen, COMPONENT, ..._.drop(commandLineArgs)]); + break; + case 'gcon': + execShell([...plopGen, CONTAINER, ..._.drop(commandLineArgs)]); + break; + case 'gf': + execShell(['-f', ...plopGen, ..._.drop(commandLineArgs)]); + break; + case 'gcomf': + execShell(['-f', ...plopGen, COMPONENT, ..._.drop(commandLineArgs)]); + break; + case 'gconf': + execShell(['-f', ...plopGen, CONTAINER, ..._.drop(commandLineArgs)]); + break; + case 'gtutil': + execShell(['-f', ...plopGen, TEST_UTIL, ..._.drop(commandLineArgs)]); + break; + case 'ginjectsaga': + execShell(['-f', ...plopGen, INJECT_SAGA, ..._.drop(commandLineArgs)]); + break; + case '--help': + shell.echo( + `Generate tests for existing and new react native components\n\n` + + `init: Create a new react native application\n` + + `gt: Creating a test for a container or component\n` + + `gtf: Forcefully creating a test for a container or component\n` + + `gtcom: Creating a test for an existing component\n` + + `gtcomf: Forcefully creating a test for an existing component\n` + + `gtcon: Creating a test for an existing container\n` + + `gtconf : Forcefully creating a test for an existing component\n` + + `g: Creating a container or component\n` + + `gf: Forcefully creating a container or component\n` + + `gcom: Creating a component\n` + + `gcomf: Forcefully creating a component\n` + + `gcon: Creating a container\n` + + `gconf: Forcefully creating a container\n` + + `--all: Adding tests for all existing containers or components.\n` + + `gtutil: Create a test util file with some test utility functions.\n` + + `ginjectsaga: Create an injector for sagas that work with hooks.\n\n` + + `-------\n\n` + + `Creating a test by specifying type, path and name: react-native-generate gt component src/app Button\n` + + `Creating a test for an existing component by specifying path and name: react-native-generate gtcon src/app Button\n` + + `Creating a test for an existing container by specifying path and name: react-native-generate gtcom src/app HomePage\n` + + `Creating a component/container by specifying type, path and name: react-native-generate g component src/app Button\n` + + `Creating a component by specifying path and name: react-native-generate gcon src/app Button\n` + + `Creating a container by specifying path and name: react-native-generate gcom src/app HomePage\n` + + `Generate test for all components in directory: react-native-generate --all component src/app/components\n` + + `Generate test for all containers in directory: react-native-generate --all containers src/app/containers`, + ); + break; + case '--all': + { + commandLineArgs = _.drop(commandLineArgs); + if (!commandLineArgs[0] || !commandLineArgs[1] || commandLineArgs[2]) { + shell.exec( + `echo Sorry! react-native-generate --all requires 2 commandLineArgs to be passed. Run react-native-generate --help for more details`, + ); + return; + } + let cwd; + let directories; + switch (commandLineArgs[0]) { + case COMPONENT: + cwd = shell.exec('pwd').stdout; + shell.cd(`./${commandLineArgs[1]}`); + directories = shell.ls(); + shell.cd(cwd); + directories.forEach(component => { + if (!_.includes(component, '.')) { + shell.exec( + `react-native-generate gtcomf ${_.drop( + commandLineArgs, + )} ${component}`, + ); + } + }); + break; + case CONTAINER: + cwd = shell.exec('pwd').stdout; + shell.cd(`./${commandLineArgs[1]}`); + directories = shell.ls(); + shell.cd(cwd); + directories.forEach(component => { + if (!_.includes(component, '.')) { + shell.echo(`Component name: ${component}`); + childProcess.execSync( + `react-native-generate gtconf ${_.drop( + commandLineArgs, + )} ${component}`, + { + ...stdioInherit, + }, + ); + } + }); + break; + default: + shell.exec(`echo ${commandLineArgs[0]} is not a valid argument`); + } + } + break; + default: + shell.exec(`echo Sorry ${commandLineArgs[0]} is not a valid command`); + break; +} From 3aa92f9a6bdae1e5fbecdfe073f9e6bffa74f30a Mon Sep 17 00:00:00 2001 From: MrigWed Date: Tue, 6 Oct 2020 17:08:05 +0530 Subject: [PATCH 2/6] added react-native template and commands --- .../component/existing/index.js | 0 .../react-native/component/index.js.hbs | 32 ++++ .../{ => react-native}/component/new/index.js | 0 .../react-native/component/stories.js.hbs | 16 ++ generators/react-native/component/test.js.hbs | 18 ++ .../container/existing/index.js | 0 .../react-native/container/index.js.hbs | 62 ++++++ .../react-native/container/new/index.js | 88 +++++++++ .../container/reducer.js.hbs | 0 .../container/reducer.test.js.hbs | 0 .../{ => react-native}/container/saga.js.hbs | 0 .../container/saga.test.js.hbs | 0 .../container/selectors.js.hbs | 0 .../container/selectors.test.js.hbs | 0 .../{ => react-native}/container/test.js.hbs | 0 generators/{ => react-native}/index.js | 0 .../{ => react-native}/injectSaga/index.js | 0 .../injectSaga/injectSaga.js.hbs | 0 .../injectSaga/sagaInjectors.js.hbs | 0 .../{ => react-native}/loadable/index.js | 0 .../loadable/loadable.js.hbs | 0 .../{ => react-native}/testUtil/index.js | 0 .../testUtil/testUtils.js.hbs | 0 .../webpack/base/babel/babel.js.hbs | 0 .../webpack/base/babel/index.js | 0 generators/react/component/existing/index.js | 66 +++++++ generators/{ => react}/component/index.js.hbs | 0 generators/react/component/new/index.js | 48 +++++ .../{ => react}/component/stories.js.hbs | 0 generators/{ => react}/component/test.js.hbs | 0 .../{ => react}/container/Loadable.js.hbs | 0 generators/react/container/existing/index.js | 81 ++++++++ generators/{ => react}/container/index.js.hbs | 0 generators/{ => react}/container/new/index.js | 0 generators/react/container/reducer.js.hbs | 27 +++ .../react/container/reducer.test.js.hbs | 25 +++ generators/react/container/saga.js.hbs | 13 ++ generators/react/container/saga.test.js.hbs | 18 ++ generators/react/container/selectors.js.hbs | 14 ++ .../react/container/selectors.test.js.hbs | 16 ++ generators/react/container/test.js.hbs | 26 +++ generators/react/index.js | 65 +++++++ generators/react/injectSaga/index.js | 42 +++++ generators/react/injectSaga/injectSaga.js.hbs | 60 ++++++ .../react/injectSaga/sagaInjectors.js.hbs | 111 +++++++++++ generators/react/loadable/index.js | 36 ++++ generators/react/loadable/loadable.js.hbs | 13 ++ generators/react/testUtil/index.js | 36 ++++ generators/react/testUtil/testUtils.js.hbs | 51 +++++ .../react/webpack/base/babel/babel.js.hbs | 177 ++++++++++++++++++ generators/react/webpack/base/babel/index.js | 28 +++ package.json | 3 +- 52 files changed, 1171 insertions(+), 1 deletion(-) rename generators/{ => react-native}/component/existing/index.js (100%) create mode 100644 generators/react-native/component/index.js.hbs rename generators/{ => react-native}/component/new/index.js (100%) create mode 100644 generators/react-native/component/stories.js.hbs create mode 100644 generators/react-native/component/test.js.hbs rename generators/{ => react-native}/container/existing/index.js (100%) create mode 100644 generators/react-native/container/index.js.hbs create mode 100644 generators/react-native/container/new/index.js rename generators/{ => react-native}/container/reducer.js.hbs (100%) rename generators/{ => react-native}/container/reducer.test.js.hbs (100%) rename generators/{ => react-native}/container/saga.js.hbs (100%) rename generators/{ => react-native}/container/saga.test.js.hbs (100%) rename generators/{ => react-native}/container/selectors.js.hbs (100%) rename generators/{ => react-native}/container/selectors.test.js.hbs (100%) rename generators/{ => react-native}/container/test.js.hbs (100%) rename generators/{ => react-native}/index.js (100%) rename generators/{ => react-native}/injectSaga/index.js (100%) rename generators/{ => react-native}/injectSaga/injectSaga.js.hbs (100%) rename generators/{ => react-native}/injectSaga/sagaInjectors.js.hbs (100%) rename generators/{ => react-native}/loadable/index.js (100%) rename generators/{ => react-native}/loadable/loadable.js.hbs (100%) rename generators/{ => react-native}/testUtil/index.js (100%) rename generators/{ => react-native}/testUtil/testUtils.js.hbs (100%) rename generators/{ => react-native}/webpack/base/babel/babel.js.hbs (100%) rename generators/{ => react-native}/webpack/base/babel/index.js (100%) create mode 100644 generators/react/component/existing/index.js rename generators/{ => react}/component/index.js.hbs (100%) create mode 100644 generators/react/component/new/index.js rename generators/{ => react}/component/stories.js.hbs (100%) rename generators/{ => react}/component/test.js.hbs (100%) rename generators/{ => react}/container/Loadable.js.hbs (100%) create mode 100644 generators/react/container/existing/index.js rename generators/{ => react}/container/index.js.hbs (100%) rename generators/{ => react}/container/new/index.js (100%) create mode 100644 generators/react/container/reducer.js.hbs create mode 100644 generators/react/container/reducer.test.js.hbs create mode 100644 generators/react/container/saga.js.hbs create mode 100644 generators/react/container/saga.test.js.hbs create mode 100644 generators/react/container/selectors.js.hbs create mode 100644 generators/react/container/selectors.test.js.hbs create mode 100644 generators/react/container/test.js.hbs create mode 100644 generators/react/index.js create mode 100644 generators/react/injectSaga/index.js create mode 100644 generators/react/injectSaga/injectSaga.js.hbs create mode 100644 generators/react/injectSaga/sagaInjectors.js.hbs create mode 100644 generators/react/loadable/index.js create mode 100644 generators/react/loadable/loadable.js.hbs create mode 100644 generators/react/testUtil/index.js create mode 100644 generators/react/testUtil/testUtils.js.hbs create mode 100644 generators/react/webpack/base/babel/babel.js.hbs create mode 100644 generators/react/webpack/base/babel/index.js diff --git a/generators/component/existing/index.js b/generators/react-native/component/existing/index.js similarity index 100% rename from generators/component/existing/index.js rename to generators/react-native/component/existing/index.js diff --git a/generators/react-native/component/index.js.hbs b/generators/react-native/component/index.js.hbs new file mode 100644 index 0000000..a25d8cb --- /dev/null +++ b/generators/react-native/component/index.js.hbs @@ -0,0 +1,32 @@ +/** + * + * {{ properCase name }} + * + */ + +{{#if memo}} +import React, { memo } from 'react' +{{else}} +import React from 'react' +{{/if}} +import { View, Text } from 'react-native'; +// import PropTypes from 'prop-types' +// import styled from 'styled-components' + +import { FormattedMessage as T } from 'react-intl' + +function {{ properCase name }}() { + return ( + + + + ) +} + +{{ properCase name }}.propTypes = {} + +{{#if memo}} +export default memo({{ properCase name }}) +{{else}} +export default {{ properCase name }} +{{/if}} diff --git a/generators/component/new/index.js b/generators/react-native/component/new/index.js similarity index 100% rename from generators/component/new/index.js rename to generators/react-native/component/new/index.js diff --git a/generators/react-native/component/stories.js.hbs b/generators/react-native/component/stories.js.hbs new file mode 100644 index 0000000..b2f0ab6 --- /dev/null +++ b/generators/react-native/component/stories.js.hbs @@ -0,0 +1,16 @@ +/** + * + * Stories for {{ properCase name }} + * + * @see https://github.com/storybookjs/storybook + * + */ + +import React from 'react' +import { View } from 'react-native'; +import { storiesOf } from '@storybook/react-native' +import { text } from '@storybook/addon-knobs' +import {{ properCase name }} from '../index' + + +storiesOf('{{properCase name}}').add('simple', () =><{{properCase name}} id={text('id', '{{properCase name}}')}/>) \ No newline at end of file diff --git a/generators/react-native/component/test.js.hbs b/generators/react-native/component/test.js.hbs new file mode 100644 index 0000000..8605fb9 --- /dev/null +++ b/generators/react-native/component/test.js.hbs @@ -0,0 +1,18 @@ +/** + * + * Tests for {{ properCase name }} + * + */ + +import React from 'react' +// import { fireEvent } from '@testing-library/dom' +import { renderWithIntl } from '@utils/testUtils' +import {{ properCase name }} from '../index' + +describe('<{{ properCase name }} />', () => { + + it('should render and match the snapshot', () => { + const { baseElement } = renderWithIntl(<{{ properCase name }} />) + expect(baseElement).toMatchSnapshot() + }) +}) \ No newline at end of file diff --git a/generators/container/existing/index.js b/generators/react-native/container/existing/index.js similarity index 100% rename from generators/container/existing/index.js rename to generators/react-native/container/existing/index.js diff --git a/generators/react-native/container/index.js.hbs b/generators/react-native/container/index.js.hbs new file mode 100644 index 0000000..aec21a9 --- /dev/null +++ b/generators/react-native/container/index.js.hbs @@ -0,0 +1,62 @@ +/** + * + * {{properCase name }} + * + */ + +{{#if memo}} +import React, { memo } from 'react' +{{else}} +import React from 'react' +{{/if}} +import { View, Text } from 'react-native'; +// import PropTypes from 'prop-types' +import { connect } from 'react-redux' +import { injectIntl } from 'react-intl' +import { FormattedMessage as T } from 'react-intl' +{{#if wantActionsAndReducer}} +import { createStructuredSelector } from 'reselect' +{{/if}} +import { compose } from 'redux' +{{#if wantActionsAndReducer}} +import makeSelect{{properCase name}} from './selectors' +{{/if}} + +export function {{ properCase name }}() { + + return ( + + + + ) +} + +{{ properCase name }}.propTypes = { +} + +{{#if wantActionsAndReducer}} +const mapStateToProps = createStructuredSelector({ + {{ camelCase name }}: makeSelect{{properCase name}}(), +}) +{{/if}} + +function mapDispatchToProps(dispatch) { + return { + dispatch, + } +} + +{{#if wantActionsAndReducer}} +const withConnect = connect(mapStateToProps, mapDispatchToProps) +{{else}} +const withConnect = connect(null, mapDispatchToProps) +{{/if}} + +export default compose( + withConnect, +{{#if memo}} + memo, +{{/if}} +)({{ properCase name }}) + +export const {{ properCase name }}Test = compose(injectIntl)({{ properCase name }}) \ No newline at end of file diff --git a/generators/react-native/container/new/index.js b/generators/react-native/container/new/index.js new file mode 100644 index 0000000..b4d1ca2 --- /dev/null +++ b/generators/react-native/container/new/index.js @@ -0,0 +1,88 @@ +/** + * Container Generator + */ +const existing = require('../existing'); + +const cwd = process.cwd(); +module.exports = { + description: 'Add a container component', + prompts: [ + { + type: 'input', + name: 'path', + message: 'What is the container directory? (app/containers)', + default: 'app/containers', + }, + { + type: 'input', + name: 'name', + message: 'What should it be called?', + default: 'Form', + }, + { + type: 'confirm', + name: 'memo', + default: false, + message: 'Do you want to wrap your container in React.memo?', + }, + { + type: 'confirm', + name: 'wantActionsAndReducer', + default: true, + message: + 'Do you want an actions/constants/selectors/reducer tuple for this container?', + }, + { + type: 'confirm', + name: 'wantSaga', + default: true, + message: 'Do you want sagas for asynchronous flows? (e.g. fetching data)', + }, + ], + actions: data => { + // Generate index.js and index.test.js + const actions = [ + { + type: 'add', + path: `${cwd}/{{path}}/{{properCase name}}/index.js`, + templateFile: './container/index.js.hbs', + abortOnFail: true, + }, + ]; + + // If they want actions and a reducer, generate reducer.js and the + // corresponding tests for actions and the reducer + if (data.wantActionsAndReducer) { + // Selectors + actions.push({ + type: 'add', + path: `${cwd}/{{path}}/{{properCase name}}/selectors.js`, + templateFile: './container/selectors.js.hbs', + abortOnFail: true, + }); + + // Reducer + actions.push({ + type: 'add', + path: `${cwd}/{{path}}/{{properCase name}}/reducer.js`, + templateFile: './container/reducer.js.hbs', + abortOnFail: true, + }); + } + + // Sagas + if (data.wantSaga) { + actions.push({ + type: 'add', + path: `${cwd}/{{path}}/{{properCase name}}/saga.js`, + templateFile: './container/saga.js.hbs', + abortOnFail: true, + }); + } + + actions.push(...existing.actions(data)); + actions.push(existing.prettier()); + + return actions; + }, +}; diff --git a/generators/container/reducer.js.hbs b/generators/react-native/container/reducer.js.hbs similarity index 100% rename from generators/container/reducer.js.hbs rename to generators/react-native/container/reducer.js.hbs diff --git a/generators/container/reducer.test.js.hbs b/generators/react-native/container/reducer.test.js.hbs similarity index 100% rename from generators/container/reducer.test.js.hbs rename to generators/react-native/container/reducer.test.js.hbs diff --git a/generators/container/saga.js.hbs b/generators/react-native/container/saga.js.hbs similarity index 100% rename from generators/container/saga.js.hbs rename to generators/react-native/container/saga.js.hbs diff --git a/generators/container/saga.test.js.hbs b/generators/react-native/container/saga.test.js.hbs similarity index 100% rename from generators/container/saga.test.js.hbs rename to generators/react-native/container/saga.test.js.hbs diff --git a/generators/container/selectors.js.hbs b/generators/react-native/container/selectors.js.hbs similarity index 100% rename from generators/container/selectors.js.hbs rename to generators/react-native/container/selectors.js.hbs diff --git a/generators/container/selectors.test.js.hbs b/generators/react-native/container/selectors.test.js.hbs similarity index 100% rename from generators/container/selectors.test.js.hbs rename to generators/react-native/container/selectors.test.js.hbs diff --git a/generators/container/test.js.hbs b/generators/react-native/container/test.js.hbs similarity index 100% rename from generators/container/test.js.hbs rename to generators/react-native/container/test.js.hbs diff --git a/generators/index.js b/generators/react-native/index.js similarity index 100% rename from generators/index.js rename to generators/react-native/index.js diff --git a/generators/injectSaga/index.js b/generators/react-native/injectSaga/index.js similarity index 100% rename from generators/injectSaga/index.js rename to generators/react-native/injectSaga/index.js diff --git a/generators/injectSaga/injectSaga.js.hbs b/generators/react-native/injectSaga/injectSaga.js.hbs similarity index 100% rename from generators/injectSaga/injectSaga.js.hbs rename to generators/react-native/injectSaga/injectSaga.js.hbs diff --git a/generators/injectSaga/sagaInjectors.js.hbs b/generators/react-native/injectSaga/sagaInjectors.js.hbs similarity index 100% rename from generators/injectSaga/sagaInjectors.js.hbs rename to generators/react-native/injectSaga/sagaInjectors.js.hbs diff --git a/generators/loadable/index.js b/generators/react-native/loadable/index.js similarity index 100% rename from generators/loadable/index.js rename to generators/react-native/loadable/index.js diff --git a/generators/loadable/loadable.js.hbs b/generators/react-native/loadable/loadable.js.hbs similarity index 100% rename from generators/loadable/loadable.js.hbs rename to generators/react-native/loadable/loadable.js.hbs diff --git a/generators/testUtil/index.js b/generators/react-native/testUtil/index.js similarity index 100% rename from generators/testUtil/index.js rename to generators/react-native/testUtil/index.js diff --git a/generators/testUtil/testUtils.js.hbs b/generators/react-native/testUtil/testUtils.js.hbs similarity index 100% rename from generators/testUtil/testUtils.js.hbs rename to generators/react-native/testUtil/testUtils.js.hbs diff --git a/generators/webpack/base/babel/babel.js.hbs b/generators/react-native/webpack/base/babel/babel.js.hbs similarity index 100% rename from generators/webpack/base/babel/babel.js.hbs rename to generators/react-native/webpack/base/babel/babel.js.hbs diff --git a/generators/webpack/base/babel/index.js b/generators/react-native/webpack/base/babel/index.js similarity index 100% rename from generators/webpack/base/babel/index.js rename to generators/react-native/webpack/base/babel/index.js diff --git a/generators/react/component/existing/index.js b/generators/react/component/existing/index.js new file mode 100644 index 0000000..c337c78 --- /dev/null +++ b/generators/react/component/existing/index.js @@ -0,0 +1,66 @@ +/** + * Component Generator + */ + +/* eslint strict: ["off"] */ + +'use strict'; + +const cwd = process.cwd(); + +const storyPrompt = { + type: 'confirm', + name: 'wantStories', + default: true, + message: 'Do you want stories for your component?', +}; +const pathPrompt = { + type: 'input', + name: 'path', + message: 'What is the component directory? (app/components)', + default: 'app/components', +}; + +const prompts = [ + { + type: 'input', + name: 'name', + message: 'What is the name of the component you want to add tests for?', + default: 'Button', + }, +]; +prompts.push(storyPrompt); +prompts.push(pathPrompt); + +module.exports = { + description: 'Add tests for an existing component', + storyPrompt, + pathPrompt, + prompts, + actions: data => { + // index.test.js + const actions = [ + { + type: 'add', + path: `${cwd}/{{path}}/{{properCase name}}/tests/index.test.js`, + templateFile: './component/test.js.hbs', + abortOnFail: true, + }, + ]; + + if (data.wantStories) { + actions.push({ + type: 'add', + path: `${cwd}/{{path}}/{{properCase name}}/stories/{{properCase name}}.stories.js`, + templateFile: './component/stories.js.hbs', + abortOnFail: true, + }); + } + + return actions; + }, + prettier: () => ({ + type: 'prettify', + path: `{{path}}/`, + }), +}; diff --git a/generators/component/index.js.hbs b/generators/react/component/index.js.hbs similarity index 100% rename from generators/component/index.js.hbs rename to generators/react/component/index.js.hbs diff --git a/generators/react/component/new/index.js b/generators/react/component/new/index.js new file mode 100644 index 0000000..a6f1b1e --- /dev/null +++ b/generators/react/component/new/index.js @@ -0,0 +1,48 @@ +/** + * Component Generator + */ + +/* eslint strict: ["off"] */ + +('use strict'); + +const existing = require('../existing'); +const cwd = process.cwd(); + +const prompts = [ + { + type: 'input', + name: 'name', + message: 'What should it be called?', + default: 'Button', + }, + { + type: 'confirm', + name: 'memo', + default: false, + message: 'Do you want to wrap your component in React.memo?', + }, +]; +prompts.unshift(existing.pathPrompt); +prompts.push(existing.storyPrompt); + +module.exports = { + description: 'Add an unconnected component', + prompts, + actions: data => { + // Generate index.js and index.test.js + const actions = [ + { + type: 'add', + path: `${cwd}/{{path}}/{{properCase name}}/index.js`, + templateFile: './component/index.js.hbs', + abortOnFail: true, + }, + ]; + + actions.push(...existing.actions(data)); + actions.push(existing.prettier()); + + return actions; + }, +}; diff --git a/generators/component/stories.js.hbs b/generators/react/component/stories.js.hbs similarity index 100% rename from generators/component/stories.js.hbs rename to generators/react/component/stories.js.hbs diff --git a/generators/component/test.js.hbs b/generators/react/component/test.js.hbs similarity index 100% rename from generators/component/test.js.hbs rename to generators/react/component/test.js.hbs diff --git a/generators/container/Loadable.js.hbs b/generators/react/container/Loadable.js.hbs similarity index 100% rename from generators/container/Loadable.js.hbs rename to generators/react/container/Loadable.js.hbs diff --git a/generators/react/container/existing/index.js b/generators/react/container/existing/index.js new file mode 100644 index 0000000..c823787 --- /dev/null +++ b/generators/react/container/existing/index.js @@ -0,0 +1,81 @@ +/** + * Container Generator + */ + +const cwd = process.cwd(); +module.exports = { + description: 'Add test for an existing container component', + prompts: [ + { + type: 'input', + name: 'path', + message: 'What is the container directory? (app/containers)', + default: 'app/containers', + }, + { + type: 'input', + name: 'name', + message: 'Which container do you want to add tests for?', + default: 'Form', + }, + { + type: 'confirm', + name: 'wantActionsAndReducer', + default: true, + message: + 'Do you want add tests for actions, selectors & reducer tuple for this container?', + }, + { + type: 'confirm', + name: 'wantSaga', + default: true, + message: 'Do you want to add tests for sagas?', + }, + ], + actions: data => { + // Generate index.js and index.test.js + const actions = [ + { + type: 'add', + path: `${cwd}/{{path}}/{{properCase name}}/tests/index.test.js`, + templateFile: './container/test.js.hbs', + abortOnFail: true, + }, + ]; + + // If they want actions and a reducer, generate reducer.js, + // and the corresponding tests for actions and the reducer + if (data.wantActionsAndReducer) { + // Selectors + actions.push({ + type: 'add', + path: `${cwd}/{{path}}/{{properCase name}}/tests/selectors.test.js`, + templateFile: './container/selectors.test.js.hbs', + abortOnFail: true, + }); + + // Reducer + actions.push({ + type: 'add', + path: `${cwd}/{{path}}/{{properCase name}}/tests/reducer.test.js`, + templateFile: './container/reducer.test.js.hbs', + abortOnFail: true, + }); + } + + // Sagas + if (data.wantSaga) { + actions.push({ + type: 'add', + path: `${cwd}/{{path}}/{{properCase name}}/tests/saga.test.js`, + templateFile: './container/saga.test.js.hbs', + abortOnFail: true, + }); + } + return actions; + }, + prettier: () => ({ + type: 'prettify', + path: `{{path}}/`, + }), +}; diff --git a/generators/container/index.js.hbs b/generators/react/container/index.js.hbs similarity index 100% rename from generators/container/index.js.hbs rename to generators/react/container/index.js.hbs diff --git a/generators/container/new/index.js b/generators/react/container/new/index.js similarity index 100% rename from generators/container/new/index.js rename to generators/react/container/new/index.js diff --git a/generators/react/container/reducer.js.hbs b/generators/react/container/reducer.js.hbs new file mode 100644 index 0000000..e633aa6 --- /dev/null +++ b/generators/react/container/reducer.js.hbs @@ -0,0 +1,27 @@ +/* + * + * {{ properCase name }} reducer + * + */ +import produce from 'immer' +import { fromJS } from 'immutable' +import { createActions } from 'reduxsauce' + +export const initialState = fromJS({}) + +export const { Types: {{ camelCase name }}Types, Creators: {{ camelCase name }}Creators } = createActions({ + defaultAction: ['somePayload'] +}) + +/* eslint-disable default-case, no-param-reassign */ +export const {{ camelCase name }}Reducer = (state = initialState, action) => + produce(state, (/* draft */) => { + switch (action.type) { + case {{ camelCase name}}Types.DEFAULT_ACTION: + return state.set('somePayload', action.somePayload) + default: + return state + } + }) + +export default {{ camelCase name }}Reducer diff --git a/generators/react/container/reducer.test.js.hbs b/generators/react/container/reducer.test.js.hbs new file mode 100644 index 0000000..fbdb10b --- /dev/null +++ b/generators/react/container/reducer.test.js.hbs @@ -0,0 +1,25 @@ +// import produce from 'immer' +import { fromJS } from 'immutable'; +import { {{ camelCase name }}Reducer, {{ camelCase name }}Types, initialState } from '../reducer' + +/* eslint-disable default-case, no-param-reassign */ +describe('{{ properCase name }} reducer tests', () => { + let state + beforeEach(() => { + state = initialState + }) + + it('should return the initial state', () => { + expect({{ camelCase name }}Reducer(undefined, {})).toEqual(state) + }) + + it('should return the update the state when an action of type DEFAULT is dispatched', () => { + const expectedResult = fromJS(state.toJS()).set('somePayload', 'Mohammed Ali Chherawalla') + expect( + {{ camelCase name }}Reducer(state, { + type: {{ camelCase name}}Types.DEFAULT_ACTION, + somePayload: 'Mohammed Ali Chherawalla' + }) + ).toEqual(expectedResult) + }) +}) diff --git a/generators/react/container/saga.js.hbs b/generators/react/container/saga.js.hbs new file mode 100644 index 0000000..3af9fee --- /dev/null +++ b/generators/react/container/saga.js.hbs @@ -0,0 +1,13 @@ +import { takeLatest } from 'redux-saga/effects' +import { {{ camelCase name}}Types } from './reducer' +// Individual exports for testing +const { DEFAULT_ACTION } = {{ camelCase name }}Types + +export function *defaultFunction (/* action */) { + // console.log('Do something here') + +} + +export default function* {{ camelCase name }}Saga() { + yield takeLatest(DEFAULT_ACTION, defaultFunction) +} \ No newline at end of file diff --git a/generators/react/container/saga.test.js.hbs b/generators/react/container/saga.test.js.hbs new file mode 100644 index 0000000..7c6acb1 --- /dev/null +++ b/generators/react/container/saga.test.js.hbs @@ -0,0 +1,18 @@ +/** + * Test {{ camelCase name }} sagas + */ + +/* eslint-disable redux-saga/yield-effects */ +import { takeLatest } from 'redux-saga/effects' +import {{ camelCase name}}Saga, { defaultFunction } from '../saga' +import { {{ camelCase name}}Types } from '../reducer' + +describe('{{ properCase name }} saga tests', () => { + const generator = {{ camelCase name }}Saga() + + it('should start task to watch for DEFAULT_ACTION action', () => { + expect(generator.next().value).toEqual( + takeLatest({{ camelCase name }}Types.DEFAULT_ACTION, defaultFunction) + ) + }) +}) \ No newline at end of file diff --git a/generators/react/container/selectors.js.hbs b/generators/react/container/selectors.js.hbs new file mode 100644 index 0000000..69dcf73 --- /dev/null +++ b/generators/react/container/selectors.js.hbs @@ -0,0 +1,14 @@ +import { createSelector } from 'reselect' +import { initialState } from './reducer' + +/** + * Direct selector to the {{ camelCase name }} state domain + */ + +const select{{ properCase name }}Domain = state => (state.{{ camelCase name }} || initialState).toJS() + +const makeSelect{{ properCase name }} = () => + createSelector(select{{ properCase name }}Domain, substate => substate) + +export default makeSelect{{ properCase name }} +export { select{{ properCase name }}Domain } diff --git a/generators/react/container/selectors.test.js.hbs b/generators/react/container/selectors.test.js.hbs new file mode 100644 index 0000000..f690cd1 --- /dev/null +++ b/generators/react/container/selectors.test.js.hbs @@ -0,0 +1,16 @@ +import { fromJS } from 'immutable' +import { select{{ properCase name }}Domain } from '../selectors' + +describe('{{ properCase name }} selector tests', () => { + let mockedState + + beforeEach(() => { + mockedState = { + {{ camelCase name }}: fromJS({}) + } + }) + + it('should select the user state', () => { + expect(select{{ properCase name }}Domain(mockedState)).toEqual(mockedState.{{ camelCase name }}.toJS()) + }) +}) \ No newline at end of file diff --git a/generators/react/container/test.js.hbs b/generators/react/container/test.js.hbs new file mode 100644 index 0000000..0446d14 --- /dev/null +++ b/generators/react/container/test.js.hbs @@ -0,0 +1,26 @@ +/** + * + * Tests for {{ properCase name }} + * + * + */ + + +import React from 'react' +import { renderProvider } from '@utils/testUtils' +// import { fireEvent } from '@testing-library/dom' +import { {{ properCase name }}Test as {{ properCase name }} } from '../index' + +describe('<{{ properCase name }} /> container tests', () => { + // let submitSpy + + beforeEach(() => { + // submitSpy = jest.fn() + }) + it('should render and match the snapshot', () => { + const { baseElement } = renderProvider( + <{{ properCase name }} /> + ) + expect(baseElement).toMatchSnapshot() + }) +}) \ No newline at end of file diff --git a/generators/react/index.js b/generators/react/index.js new file mode 100644 index 0000000..c208ed4 --- /dev/null +++ b/generators/react/index.js @@ -0,0 +1,65 @@ +/** + * generator/index.js + * + * Exports the generators so plop knows them + */ + +const fs = require('fs'); +const path = require('path'); +const shell = require('shelljs'); +const { execSync } = require('child_process'); +const prettier = path.join(__dirname, '../node_modules/.bin/prettier'); +const componentGenerator = require(`./component/${ + shell.env.GENERATOR_TYPE +}/index.js`); +const containerGenerator = require(`./container/${ + shell.env.GENERATOR_TYPE +}/index.js`); +const testUtilGenerator = require(`./testUtil/index.js`); +const loadableUtilGenerator = require(`./loadable/index.js`); +const injectSagaUtilGenerator = require(`./injectSaga/index.js`); +const webpackBaseBabelGenerator = require(`./webpack/base/babel/index.js`); + +/** + * Every generated backup file gets this extension + * @type {string} + */ +const BACKUPFILE_EXTENSION = 'rbgen'; + +module.exports = plop => { + plop.setGenerator('component', componentGenerator); + plop.setGenerator('container', containerGenerator); + plop.setGenerator('tUtil', testUtilGenerator); + plop.setGenerator('loadable', loadableUtilGenerator); + plop.setGenerator('injectSaga', injectSagaUtilGenerator); + plop.setGenerator('webpackBaseBabel', webpackBaseBabelGenerator); + + plop.addHelper('directory', comp => { + try { + fs.accessSync( + path.join(__dirname, `../../app/containers/${comp}`), + fs.F_OK, + ); + return `containers/${comp}`; + } catch (e) { + return `components/${comp}`; + } + }); + plop.addHelper('curly', (object, open) => (open ? '{' : '}')); + plop.setActionType('prettify', answers => { + const folderPath = `${path.join( + `${process.cwd()}/${answers.path}/`, + plop.getHelper('properCase')(answers.name), + '**/*.*.js', + )}`; + + try { + execSync(`${prettier} --write -- "${folderPath}"`); + return folderPath; + } catch (err) { + throw new Error(`Prettier failed`); + } + }); +}; + +module.exports.BACKUPFILE_EXTENSION = BACKUPFILE_EXTENSION; diff --git a/generators/react/injectSaga/index.js b/generators/react/injectSaga/index.js new file mode 100644 index 0000000..ceedaf3 --- /dev/null +++ b/generators/react/injectSaga/index.js @@ -0,0 +1,42 @@ +/** + * TestUtil Generator + */ + +/* eslint strict: ["off"] */ + +'use strict'; + +const cwd = process.cwd(); +module.exports = { + description: 'Add support for injecting sagas', + prompts: [ + { + type: 'input', + name: 'path', + message: 'What is the utils directory? (app/utils)', + default: 'app/utils', + }, + ], + actions: () => { + const actions = [ + { + type: 'add', + path: `${cwd}/{{path}}/injectSaga.js`, + templateFile: './injectSaga/injectSaga.js.hbs', + abortOnFail: true, + }, + { + type: 'add', + path: `${cwd}/{{path}}/sagaInjectors.js`, + templateFile: './injectSaga/sagaInjectors.js.hbs', + abortOnFail: true, + }, + ]; + + return actions; + }, + prettier: () => ({ + type: 'prettify', + path: `{{path}}/`, + }), +}; diff --git a/generators/react/injectSaga/injectSaga.js.hbs b/generators/react/injectSaga/injectSaga.js.hbs new file mode 100644 index 0000000..e734a04 --- /dev/null +++ b/generators/react/injectSaga/injectSaga.js.hbs @@ -0,0 +1,60 @@ +import React from 'react'; +import hoistNonReactStatics from 'hoist-non-react-statics'; +import { ReactReduxContext } from 'react-redux'; + +import getInjectors from './sagaInjectors'; + +/** + * Dynamically injects a saga, passes component's props as saga arguments + * + * @param {string} key A key of the saga + * @param {function} saga A root saga that will be injected + * @param {string} [mode] By default (constants.DAEMON) the saga will be started + * on component mount and never canceled or started again. Another two options: + * - constants.RESTART_ON_REMOUNT — the saga will be started on component mount and + * cancelled with `task.cancel()` on component unmount for improved performance, + * - constants.ONCE_TILL_UNMOUNT — behaves like 'RESTART_ON_REMOUNT' but never runs it again. + * + */ +export default ({ key, saga, mode }) => WrappedComponent => { + class InjectSaga extends React.Component { + static WrappedComponent = WrappedComponent; + + static contextType = ReactReduxContext; + + static displayName = `withSaga(${WrappedComponent.displayName || + WrappedComponent.name || + 'Component'})`; + + constructor(props, context) { + super(props, context); + + this.injectors = getInjectors(context.store); + + this.injectors.injectSaga(key, { saga, mode }, this.props); + } + + componentWillUnmount() { + this.injectors.ejectSaga(key); + } + + render() { + return ; + } + } + + return hoistNonReactStatics(InjectSaga, WrappedComponent); +}; + +const useInjectSaga = ({ key, saga, mode }) => { + const context = React.useContext(ReactReduxContext); + React.useEffect(() => { + const injectors = getInjectors(context.store); + injectors.injectSaga(key, { saga, mode }); + return () => { + injectors.ejectSaga(key); + }; + }, []); +}; + +export { useInjectSaga }; diff --git a/generators/react/injectSaga/sagaInjectors.js.hbs b/generators/react/injectSaga/sagaInjectors.js.hbs new file mode 100644 index 0000000..a13bf2f --- /dev/null +++ b/generators/react/injectSaga/sagaInjectors.js.hbs @@ -0,0 +1,111 @@ +import invariant from 'invariant'; +import { isEmpty, isFunction, isObject, isString, conformsTo } from 'lodash'; + + +const RESTART_ON_REMOUNT = '@@saga-injector/restart-on-remount'; +const DAEMON = '@@saga-injector/daemon'; +const ONCE_TILL_UNMOUNT = '@@saga-injector/once-till-unmount'; + +const allowedModes = [RESTART_ON_REMOUNT, DAEMON, ONCE_TILL_UNMOUNT]; + +const checkKey = key => + invariant( + isString(key) && !isEmpty(key), + '(app/utils...) injectSaga: Expected `key` to be a non empty string' + ); + +const checkDescriptor = descriptor => { + const shape = { + saga: isFunction, + mode: mode => isString(mode) && allowedModes.includes(mode) + }; + invariant( + conformsTo(descriptor, shape), + '(app/utils...) injectSaga: Expected a valid saga descriptor' + ); +}; + +export function injectSagaFactory(store, isValid) { + return function injectSaga(key, descriptor = {}, args) { + if (!isValid) checkStore(store); + + const newDescriptor = { + ...descriptor, + mode: descriptor.mode || DAEMON + }; + const { saga, mode } = newDescriptor; + + checkKey(key); + checkDescriptor(newDescriptor); + + let hasSaga = Reflect.has(store.injectedSagas, key); + + if (process.env.NODE_ENV !== 'production') { + const oldDescriptor = store.injectedSagas[key]; + // enable hot reloading of daemon and once-till-unmount sagas + if (hasSaga && oldDescriptor.saga !== saga) { + oldDescriptor.task.cancel(); + hasSaga = false; + } + } + + if ( + !hasSaga || + (hasSaga && mode !== DAEMON && mode !== ONCE_TILL_UNMOUNT) + ) { + /* eslint-disable no-param-reassign */ + store.injectedSagas[key] = { + ...newDescriptor, + task: store.runSaga(saga, args) + }; + /* eslint-enable no-param-reassign */ + } + }; +} + +export function ejectSagaFactory(store, isValid) { + return function ejectSaga(key) { + if (!isValid) checkStore(store); + + checkKey(key); + + if (Reflect.has(store.injectedSagas, key)) { + const descriptor = store.injectedSagas[key]; + if (descriptor.mode && descriptor.mode !== DAEMON) { + descriptor.task.cancel(); + // Clean up in production; in development we need `descriptor.saga` for hot reloading + if (process.env.NODE_ENV === 'production') { + // Need some value to be able to detect `ONCE_TILL_UNMOUNT` sagas in `injectSaga` + store.injectedSagas[key] = 'done'; // eslint-disable-line no-param-reassign + } + } + } + }; +} + +export default function getInjectors(store) { + checkStore(store); + return { + injectSaga: injectSagaFactory(store, true), + ejectSaga: ejectSagaFactory(store, true) + }; +} + +/** + * Validate the shape of redux store + */ +function checkStore(store) { + const shape = { + dispatch: isFunction, + subscribe: isFunction, + getState: isFunction, + replaceReducer: isFunction, + runSaga: isFunction, + injectedReducers: isObject, + injectedSagas: isObject + }; + invariant( + conformsTo(store, shape), + '(app/utils...) injectors: Expected a valid redux store' + ); +} \ No newline at end of file diff --git a/generators/react/loadable/index.js b/generators/react/loadable/index.js new file mode 100644 index 0000000..f03edf3 --- /dev/null +++ b/generators/react/loadable/index.js @@ -0,0 +1,36 @@ +/** + * Loadable Generator + */ + +/* eslint strict: ["off"] */ + +'use strict'; + +const cwd = process.cwd(); +module.exports = { + description: 'Create a loadable util file ', + prompts: [ + { + type: 'input', + name: 'path', + message: 'What is the utils directory? (app/utils)', + default: 'app/utils', + }, + ], + actions: () => { + const actions = [ + { + type: 'add', + path: `${cwd}/{{path}}/loadable.js`, + templateFile: './loadable/loadable.js.hbs', + abortOnFail: true, + }, + ]; + + return actions; + }, + prettier: () => ({ + type: 'prettify', + path: `{{path}}/`, + }), +}; diff --git a/generators/react/loadable/loadable.js.hbs b/generators/react/loadable/loadable.js.hbs new file mode 100644 index 0000000..f8e956d --- /dev/null +++ b/generators/react/loadable/loadable.js.hbs @@ -0,0 +1,13 @@ +import React, { lazy, Suspense } from 'react' + +const loadable = (importFunc, { fallback = null } = { fallback: null }) => { + const LazyComponent = lazy(importFunc) + + return props => ( + + + + ) +} + +export default loadable diff --git a/generators/react/testUtil/index.js b/generators/react/testUtil/index.js new file mode 100644 index 0000000..c46723a --- /dev/null +++ b/generators/react/testUtil/index.js @@ -0,0 +1,36 @@ +/** + * TestUtil Generator + */ + +/* eslint strict: ["off"] */ + +'use strict'; + +const cwd = process.cwd(); +module.exports = { + description: 'Create a test util file', + prompts: [ + { + type: 'input', + name: 'path', + message: 'What is the utils directory? (app/utils)', + default: 'app/utils', + }, + ], + actions: () => { + const actions = [ + { + type: 'add', + path: `${cwd}/{{path}}/testUtils.js`, + templateFile: './testUtil/testUtils.js.hbs', + abortOnFail: true, + }, + ]; + + return actions; + }, + prettier: () => ({ + type: 'prettify', + path: `{{path}}/`, + }), +}; diff --git a/generators/react/testUtil/testUtils.js.hbs b/generators/react/testUtil/testUtils.js.hbs new file mode 100644 index 0000000..954bd8a --- /dev/null +++ b/generators/react/testUtil/testUtils.js.hbs @@ -0,0 +1,51 @@ +import React from 'react' +import { IntlProvider } from 'react-intl' +import { render } from '@testing-library/react' +import { Provider } from 'react-redux' +import configureStore from '@app/configureStore' +import { DEFAULT_LOCALE, translationMessages } from '@app/i18n' +import ConnectedLanguageProvider from '@containers/LanguageProvider' +import { BrowserRouter as Router, browserHistory } from 'react-router-dom' +import { ThemeProvider } from 'styled-components' + +export const timeout = ms => new Promise(resolve => setTimeout(resolve, ms)) + +export const apiResponseGenerator = (ok, data) => ({ + ok, + data +}) + +export const renderProvider = children => { + const store = configureStore({}, browserHistory) + const theme = {main: 'violet'} + return render( + + + + {children} + + + + ) +} + +export const renderWithIntl = children => + render( + + {children} + + ) + +export const getComponentStyles = (Component, props = {}) => { + renderWithIntl(Component(props)) + const { styledComponentId } = Component(props).type + const componentRoots = document.getElementsByClassName(styledComponentId) + // eslint-disable-next-line no-underscore-dangle + return window.getComputedStyle(componentRoots[0])._values +} + +export const renderWithRouterAndIntl = children => + renderWithIntl({children}); diff --git a/generators/react/webpack/base/babel/babel.js.hbs b/generators/react/webpack/base/babel/babel.js.hbs new file mode 100644 index 0000000..9336399 --- /dev/null +++ b/generators/react/webpack/base/babel/babel.js.hbs @@ -0,0 +1,177 @@ +/** + * COMMON WEBPACK CONFIGURATION + */ +const path = require('path'); +const webpack = require('webpack'); +const dotenv = require('dotenv'); +const colors = require('../../app/themes/colors'); + +const dotEnvFile = + process.env.NODE_ENV === 'production' + ? `.env` + : `.env.${process.env.NODE_ENV}`; +const env = dotenv.config({ path: dotEnvFile }).parsed; +const envKeys = { + ...Object.keys(process.env).reduce((prev, next) => { + prev[`process.env.${next}`] = JSON.stringify(process.env[next]); + return prev; + }, {}), + ...Object.keys(env).reduce((prev, next) => { + prev[`process.env.${next}`] = JSON.stringify(env[next]); + return prev; + }, {}) +}; + +module.exports = options => ({ + mode: options.mode, + entry: options.entry, + output: Object.assign( + { + // Compile into js/build.js + path: path.resolve(process.cwd(), 'build'), + publicPath: '/' + }, + options.output + ), // Merge with env dependent settings + optimization: options.optimization, + module: { + rules: [ + { + test: /\.jsx?$/, // Transform all .js and .jsx files required somewhere with Babel + exclude: /node_modules/, + use: { + loader: 'babel-loader', + options: options.babelQuery + } + }, + { + // Preprocess our own .css files + // This is the place to add your own loaders (e.g. sass/less etc.) + // for a list of loaders, see https://webpack.js.org/loaders/#styling + test: /\.css$/, + exclude: /node_modules/, + use: ['style-loader', 'css-loader'] + }, + { + test: /\.less$/, + use: [ + { + loader: 'style-loader' + }, + { + loader: 'css-loader' + }, + { + loader: 'less-loader', + options: { + lessOptions: { + javascriptEnabled: true, + modifyVars: { + 'primary-color': colors.secondary + } + } + } + } + ] + }, + { + // Preprocess 3rd party .css files located in node_modules + test: /\.css$/, + include: /node_modules/, + use: ['style-loader', 'css-loader'] + }, + { + test: /\.(eot|otf|ttf|woff|woff2)$/, + use: 'file-loader' + }, + { + test: /\.svg$/, + use: [ + { + loader: 'svg-url-loader', + options: { + // Inline files smaller than 10 kB + limit: 10 * 1024, + noquotes: true + } + } + ] + }, + { + test: /\.(jpg|png|gif)$/, + use: [ + { + loader: 'url-loader', + options: { + // Inline files smaller than 10 kB + limit: 10 * 1024 + } + }, + { + loader: 'image-webpack-loader', + options: { + mozjpeg: { + enabled: false + // NOTE: mozjpeg is disabled as it causes errors in some Linux environments + // Try enabling it in your environment by switching the config to: + // enabled: true, + // progressive: true, + }, + gifsicle: { + interlaced: false + }, + optipng: { + optimizationLevel: 7 + }, + pngquant: { + quality: '65-90', + speed: 4 + } + } + } + ] + }, + { + test: /\.html$/, + use: 'html-loader' + }, + { + test: /\.(mp4|webm)$/, + use: { + loader: 'url-loader', + options: { + limit: 10000 + } + } + } + ] + }, + plugins: options.plugins.concat([ + // Always expose NODE_ENV to webpack, in order to use `process.env.NODE_ENV` + // inside your code for any environment checks; Terser will automatically + // drop any unreachable code. + new webpack.EnvironmentPlugin({ + NODE_ENV: 'development' + }), + new webpack.DefinePlugin(envKeys) + ]), + resolve: { + modules: ['node_modules', 'app'], + alias: { + '@app': path.resolve(__dirname, '../../app'), + '@components': path.resolve(__dirname, '../../app/components'), + '@containers': path.resolve(__dirname, '../../app/containers'), + '@utils': path.resolve(__dirname, '../../app/utils'), + '@services': path.resolve(__dirname, '../../app/services'), + '@themes': path.resolve(__dirname, '../../app/themes'), + '@images': path.resolve(__dirname, '../../app/images'), + '@hooks': path.resolve(__dirname, '../../app/hooks'), + moment$: path.resolve(__dirname, '../../node_modules/moment/moment.js') + }, + extensions: ['.js', '.jsx', '.react.js'], + mainFields: ['browser', 'jsnext:main', 'main'] + }, + devtool: options.devtool, + target: 'web', // Make web variables accessible to webpack, e.g. window + performance: options.performance || {} +}); \ No newline at end of file diff --git a/generators/react/webpack/base/babel/index.js b/generators/react/webpack/base/babel/index.js new file mode 100644 index 0000000..7a03ab1 --- /dev/null +++ b/generators/react/webpack/base/babel/index.js @@ -0,0 +1,28 @@ +/** + * TestUtil Generator + */ + +/* eslint strict: ["off"] */ + +'use strict'; + +const cwd = process.cwd(); +module.exports = { + prompts: [], + actions: () => { + const actions = [ + { + type: 'add', + path: `${cwd}/internals/webpack/webpack.base.babel.js`, + templateFile: './webpack/base/babel/babel.js.hbs', + abortOnFail: true, + }, + ]; + + return actions; + }, + prettier: () => ({ + type: 'prettify', + path: `${cwd}/`, + }), +}; diff --git a/package.json b/package.json index c93d911..f05d1a7 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,7 @@ }, "@babel/cli": "7.4.3", "bin": { - "react-generate": "bin/react-generate.js" + "react-generate": "bin/react-generate.js", + "react-native-generate": "bin/react-native-generate.js" } } From 5d6bec09df58ecab2f4c34c18d8fb92fb0b643a4 Mon Sep 17 00:00:00 2001 From: MrigWed Date: Wed, 7 Oct 2020 00:51:21 +0530 Subject: [PATCH 3/6] fixed prettier --- generators/react-native/index.js | 2 +- generators/react/index.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/generators/react-native/index.js b/generators/react-native/index.js index c208ed4..aad50c9 100644 --- a/generators/react-native/index.js +++ b/generators/react-native/index.js @@ -8,7 +8,7 @@ const fs = require('fs'); const path = require('path'); const shell = require('shelljs'); const { execSync } = require('child_process'); -const prettier = path.join(__dirname, '../node_modules/.bin/prettier'); +const prettier = path.join(__dirname, '../../node_modules/.bin/prettier'); const componentGenerator = require(`./component/${ shell.env.GENERATOR_TYPE }/index.js`); diff --git a/generators/react/index.js b/generators/react/index.js index c208ed4..aad50c9 100644 --- a/generators/react/index.js +++ b/generators/react/index.js @@ -8,7 +8,7 @@ const fs = require('fs'); const path = require('path'); const shell = require('shelljs'); const { execSync } = require('child_process'); -const prettier = path.join(__dirname, '../node_modules/.bin/prettier'); +const prettier = path.join(__dirname, '../../node_modules/.bin/prettier'); const componentGenerator = require(`./component/${ shell.env.GENERATOR_TYPE }/index.js`); From 818facc2209318c70539bbdbdeb50b0d39666dae Mon Sep 17 00:00:00 2001 From: MrigWed Date: Thu, 8 Oct 2020 15:59:32 +0530 Subject: [PATCH 4/6] fixed command with --all flag --- bin/react-generate.js | 8 ++++++-- bin/react-native-generate.js | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/bin/react-generate.js b/bin/react-generate.js index 597f313..536b89e 100755 --- a/bin/react-generate.js +++ b/bin/react-generate.js @@ -177,8 +177,12 @@ switch (commandLineArgs[0]) { shell.cd(cwd); directories.forEach(component => { if (!_.includes(component, '.')) { - shell.exec( + shell.echo(`Component name: ${component}`); + childProcess.execSync( `react-generate gtcomf ${_.drop(commandLineArgs)} ${component}`, + { + ...stdioInherit, + }, ); } }); @@ -190,7 +194,7 @@ switch (commandLineArgs[0]) { shell.cd(cwd); directories.forEach(component => { if (!_.includes(component, '.')) { - shell.echo(`Component name: ${component}`); + shell.echo(`Container name: ${component}`); childProcess.execSync( `react-generate gtconf ${_.drop(commandLineArgs)} ${component}`, { diff --git a/bin/react-native-generate.js b/bin/react-native-generate.js index fdc0038..f99d103 100644 --- a/bin/react-native-generate.js +++ b/bin/react-native-generate.js @@ -172,10 +172,14 @@ switch (commandLineArgs[0]) { shell.cd(cwd); directories.forEach(component => { if (!_.includes(component, '.')) { - shell.exec( + shell.echo(`Component name: ${component}`); + childProcess.execSync( `react-native-generate gtcomf ${_.drop( commandLineArgs, )} ${component}`, + { + ...stdioInherit, + }, ); } }); @@ -187,7 +191,7 @@ switch (commandLineArgs[0]) { shell.cd(cwd); directories.forEach(component => { if (!_.includes(component, '.')) { - shell.echo(`Component name: ${component}`); + shell.echo(`Container name: ${component}`); childProcess.execSync( `react-native-generate gtconf ${_.drop( commandLineArgs, From 59a73fa76e1a62101ff996739bb930d899a0b8ce Mon Sep 17 00:00:00 2001 From: MrigWed Date: Thu, 8 Oct 2020 16:46:50 +0530 Subject: [PATCH 5/6] updated README.md --- bin/react-generate.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/react-generate.js b/bin/react-generate.js index 536b89e..8ce1842 100755 --- a/bin/react-generate.js +++ b/bin/react-generate.js @@ -155,7 +155,7 @@ switch (commandLineArgs[0]) { `Creating a component by specifying path and name: react-generate gcon src/app Button\n` + `Creating a container by specifying path and name: react-generate gcom src/app HomePage\n` + `Generate test for all components in directory: react-generate --all component src/app/components\n` + - `Generate test for all containers in directory: react-generate --all containers src/app/containers`, + `Generate test for all containers in directory: react-generate --all container src/app/containers`, ); break; case '--all': From 2682120a964922bf333446b95da8a39ff00d944a Mon Sep 17 00:00:00 2001 From: MrigWed Date: Wed, 14 Oct 2020 11:50:57 +0530 Subject: [PATCH 6/6] resolved PR comments --- bin/react-generate.js | 8 ++++---- bin/react-native-generate.js | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/bin/react-generate.js b/bin/react-generate.js index 8ce1842..2301014 100755 --- a/bin/react-generate.js +++ b/bin/react-generate.js @@ -192,11 +192,11 @@ switch (commandLineArgs[0]) { shell.cd(`./${commandLineArgs[1]}`); directories = shell.ls(); shell.cd(cwd); - directories.forEach(component => { - if (!_.includes(component, '.')) { - shell.echo(`Container name: ${component}`); + directories.forEach(container => { + if (!_.includes(container, '.')) { + shell.echo(`Container name: ${container}`); childProcess.execSync( - `react-generate gtconf ${_.drop(commandLineArgs)} ${component}`, + `react-generate gtconf ${_.drop(commandLineArgs)} ${container}`, { ...stdioInherit, }, diff --git a/bin/react-native-generate.js b/bin/react-native-generate.js index f99d103..1ad407b 100644 --- a/bin/react-native-generate.js +++ b/bin/react-native-generate.js @@ -189,13 +189,13 @@ switch (commandLineArgs[0]) { shell.cd(`./${commandLineArgs[1]}`); directories = shell.ls(); shell.cd(cwd); - directories.forEach(component => { - if (!_.includes(component, '.')) { - shell.echo(`Container name: ${component}`); + directories.forEach(container => { + if (!_.includes(container, '.')) { + shell.echo(`Container name: ${container}`); childProcess.execSync( `react-native-generate gtconf ${_.drop( commandLineArgs, - )} ${component}`, + )} ${container}`, { ...stdioInherit, },