├── .gitignore ├── .travis.yml ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── commands ├── generator.js └── remover.js ├── docs ├── action.md ├── base.md ├── component.md ├── reducer.md ├── store.md ├── update_notes.md └── util.md ├── file_types ├── action.js ├── base.js ├── component.js ├── reducer.js ├── store.js └── util.js ├── helpers ├── case_converter.js └── logs.js ├── index.js ├── package.json ├── templates ├── base.js ├── component.js ├── help.js ├── reducer.js └── store.js ├── test ├── file_types-tests │ ├── action-test.js │ ├── base-test.js │ ├── component-test.js │ ├── reducer-test.js │ ├── store-test.js │ └── util-test.js ├── helpers-tests │ ├── case_converter-test.js │ └── logs-test.js ├── mocha.opts └── template-tests │ ├── base-test.js │ ├── component-test.js │ ├── help-test.js │ ├── reducer-test.js │ └── store-test.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | frontend 2 | node_modules 3 | future_features.md 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6.2.1" 4 | branches: 5 | only: 6 | - master 7 | install: 8 | - npm install 9 | script: 10 | - mocha -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at davidhu314@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 David Hu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React-Redux File Generator 2 | 3 | [![npm version](https://badge.fury.io/js/redux-file-gen.svg)][npm_url] 4 | [![downloads](https://img.shields.io/npm/dt/redux-file-gen.svg)][npm_url] 5 | [![license](https://img.shields.io/npm/l/redux-file-gen.svg)][npm_url] 6 | [![Build Status](https://travis-ci.org/davidhu2000/react_redux_generator.svg?branch=master)](https://travis-ci.org/davidhu2000/react_redux_generator) 7 | [![CircleCI](https://circleci.com/gh/davidhu2000/react_redux_generator.svg?style=shield)](https://circleci.com/gh/davidhu2000/react_redux_generator) 8 | 9 | [![Code Triagers Badge](https://www.codetriage.com/davidhu2000/react_redux_generator/badges/users.svg)](https://www.codetriage.com/davidhu2000/react_redux_generator) 10 | [![dependencies Status](https://david-dm.org/davidhu2000/react_redux_generator/status.svg)](https://david-dm.org/davidhu2000/react_redux_generator) 11 | [![devDependencies Status](https://david-dm.org/davidhu2000/react_redux_generator/dev-status.svg)](https://david-dm.org/davidhu2000/react_redux_generator?type=dev) 12 | 13 | [npm_url]: https://www.npmjs.org/package/redux-file-gen 14 | 15 | This generator helps to create the necessary files for a react-redux application. It follows the file structure below. The `frontend` folder is stored at the root directory of the application. 16 | 17 | ## Installation 18 | 19 | npm install -g redux-file-gen 20 | 21 | In order to create the terminal command `redux`, this package needs to be installed globally. 22 | 23 | ## Usage 24 | 25 | redux [command] [fileType] [options] 26 | 27 | ## File structure 28 | 29 | ```file 30 | frontend 31 | |- actions 32 | |- _actions.js 33 | |- components 34 | |- 35 | |- .jsx 36 | |- index.jsx 37 | |- app.jsx 38 | |- root.jsx 39 | |- reducers 40 | |- root_reducer.js 41 | |- _reducer.js 42 | |- store 43 | |- store.js 44 | |- util 45 | |- _util.js 46 | |- .jsx 47 | ``` 48 | 49 | ## Commands 50 | 51 | | Command | Alias | Function | 52 | |-------------|-------|-----------------------------| 53 | | `generate` | `g` | use the file generator | 54 | | `remove` | `r` | remove the generated files | 55 | | `--help` | `-h` | see available commands | 56 | | `--version` | `-v` | see current package version | 57 | 58 | ## FileTypes 59 | 60 | | FileType | Function | 61 | |------------------------------------------|---------------------------------------------------------------------| 62 | | `base ` | generate `app.jsx`, `root.jsx`, `.jsx`, and `store.js` | 63 | | `action [action1] [action2] ...` | generate `_actions.js` with specified actions | 64 | | `component [options]` | generate `.jsx` and `_container.jsx` | 65 | | `reducer [action1] [action2] ...` | generate `_reducer.js` | 66 | | `util [util1] [util2] ...` | generate `_util.js` with specified utils | 67 | 68 | ## Options 69 | 70 | | Option | Alias | Function | 71 | |------------------|-------|-----------------------------------| 72 | | `--functional` | `-f` | create functional component | 73 | | `--no-container` | `-nc` | do not create component container | 74 | 75 | ## For more details regard different types of files 76 | 77 | - [Base](docs/base.md) 78 | - [Action](docs/action.md) 79 | - [Component](docs/component.md) 80 | - [Reducer](docs/reducer.md) 81 | - [Store](docs/store.md) 82 | - [Utility](docs/util.md) 83 | 84 | ## Version notes 85 | 86 | To see what features are added during each update, click [here](docs/update_notes.md) 87 | 88 | ## Contributing 89 | 90 | To request a feature or report an issue, click [here](https://github.com/davidhu2000/react_redux_generator/issues). 91 | -------------------------------------------------------------------------------- /commands/generator.js: -------------------------------------------------------------------------------- 1 | const generateAction = require('../file_types/action.js'); 2 | const generateBase = require('../file_types/base.js'); 3 | const generateComponent = require('../file_types/component.js'); 4 | const generateReducer = require('../file_types/reducer.js'); 5 | const generateStore = require('../file_types/store.js'); 6 | const generateUtil = require('../file_types/util.js'); 7 | 8 | const generator = (type, name, actions) => { 9 | console.log(type); 10 | switch(type) { 11 | case (type.match(/cycles?/) || {}).input: 12 | generateReducer(name, actions); 13 | generateAction(name, actions); 14 | generateComponent(name, actions); 15 | generateUtil(name, actions); 16 | break; 17 | case (type.match(/reducers?/) || {}).input: 18 | generateReducer(name, actions); 19 | break; 20 | case (type.match(/stores?/) || {}).input: 21 | generateStore(); 22 | break; 23 | case (type.match(/actions?/) || {}).input: 24 | generateAction(name, actions); 25 | break; 26 | case (type.match(/utils?/) || {}).input: 27 | generateUtil(name, actions); 28 | break; 29 | case (type.match(/components?/) || {}).input: 30 | generateComponent(name, actions); 31 | break; 32 | case (type.match(/bases?/) || {}).input: 33 | generateBase(name); 34 | generateStore(); 35 | break; 36 | } 37 | }; 38 | 39 | module.exports = generator; 40 | -------------------------------------------------------------------------------- /commands/remover.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | 3 | const logFunctions = require('../helpers/logs.js'); 4 | const caseConverter = require('../helpers/case_converter.js'); 5 | 6 | // function to delete a single file 7 | const deleteFile = path => { 8 | fs.exists(path, exists => { 9 | if(exists) { 10 | fs.unlink(path, err => { 11 | if (!err) { 12 | logFunctions.removedFileLog(path); 13 | } 14 | }); 15 | } else { 16 | logFunctions.noExistFileErrorLog(path); 17 | } 18 | }); 19 | }; 20 | 21 | // function to remove file based on type 22 | const removeFileType = (type, name) => { 23 | if(type === 'store') { 24 | deleteFile('frontend/store/store.js'); 25 | return; 26 | } 27 | if(type[type.length - 1] === 's') { 28 | type = type.slice(0, type.length - 1); 29 | } 30 | 31 | let nameSC = caseConverter.convert(name, caseConverter.toSnakeCase); 32 | 33 | if (type === 'component') { 34 | let componentPath = `frontend/components/${nameSC}/${nameSC}.jsx`; 35 | let containerPath = `frontend/components/${nameSC}/index.jsx`; 36 | deleteFile(containerPath); 37 | deleteFile(componentPath); 38 | } else { 39 | let fileEnding = type; 40 | if (type === 'action') { 41 | fileEnding += 's'; 42 | } 43 | let folder = type; 44 | 45 | if (['action', 'component', 'reducer'].includes(type)) { 46 | folder += 's'; 47 | } 48 | 49 | let path = `frontend/${folder}/${nameSC}_${fileEnding}.js`; 50 | deleteFile(path); 51 | } 52 | }; 53 | 54 | // main remover function 55 | const remover = (type, name) => { 56 | if( /bases?/.test(type) ) { 57 | deleteFile(`frontend/${name}.jsx`); 58 | deleteFile(`frontend/components/app.jsx`); 59 | deleteFile(`frontend/components/root.jsx`); 60 | deleteFile(`frontend/store/store.js`); 61 | } else { 62 | removeFileType(type, name); 63 | } 64 | }; 65 | 66 | module.exports = remover; 67 | -------------------------------------------------------------------------------- /docs/action.md: -------------------------------------------------------------------------------- 1 | # Action 2 | 3 | To generate an actions file, run 4 | 5 | redux g action [action1] [action2] ... 6 | 7 | _Do not enter `_actions.js` as part of the name_ 8 | 9 | For example: 10 | 11 | redux g action song receiveSong playSong 12 | 13 | The generator will create a file at `frontend/actions/song_actions.js`. It will interpret the actions to create constants and action objects. 14 | 15 | ```js 16 | export const RECEIVE_SONG = 'RECEIVE_SONG'; 17 | export const PLAY_SONG = 'PLAY_SONG'; 18 | 19 | export const receiveSong = song => ({ 20 | type: RECEIVE_SONG, 21 | song 22 | }); 23 | 24 | export const playSong = () => ({ 25 | type: PLAY_SONG 26 | }); 27 | ``` 28 | 29 | **Note:** The action generator will look for the word `receive` in the action names, and automatically add the argument name and key-value pair to the function. This is why `receiveSong` has a `song` argument and `song` value in the returned object. 30 | -------------------------------------------------------------------------------- /docs/base.md: -------------------------------------------------------------------------------- 1 | # Base files 2 | 3 | Running 4 | 5 | redux generate base 6 | 7 | or 8 | 9 | redux g base 10 | 11 | will create 4 files. `g` is simply an alias for `generate`. 12 | 13 | ```file 14 | frontend/.jsx 15 | frontend/components/app.jsx 16 | frontend/components/root.jsx 17 | frontend/store/store.js 18 | ``` 19 | 20 | ```js 21 | // project_name.jsx 22 | import React from 'react'; 23 | import ReactDOM from 'react-dom'; 24 | import configureStore from './store/store.js'; 25 | import Root from './components/root.jsx'; 26 | 27 | document.addEventListener('DOMContentLoaded', () => { 28 | const root = document.getElementById('root'); 29 | const store = configureStore(); 30 | 31 | ReactDOM.render(, root); 32 | }); 33 | 34 | // root.jsx 35 | import React from 'react'; 36 | import { Provider } from 'react-redux'; 37 | import App from './app.jsx'; 38 | 39 | const Root = ({ store }) => ( 40 | 41 | 42 | 43 | ); 44 | 45 | export default Root; 46 | 47 | // app.jsx 48 | import React from 'react'; 49 | 50 | const App = ({ store }) => ( 51 |
52 |

App

53 |
54 | ); 55 | 56 | export default App; 57 | ``` 58 | 59 | See [store](store.md) for template information about the store. 60 | -------------------------------------------------------------------------------- /docs/component.md: -------------------------------------------------------------------------------- 1 | # Component 2 | 3 | To generate a component run 4 | 5 | redux g component 6 | 7 | For example, 8 | 9 | redux g component user 10 | 11 | This will generate two files 12 | 13 | - `/frontend/components/user.jsx` 14 | 15 | ```js 16 | // Presentational Component 17 | import React from 'react'; 18 | 19 | class User extends React.Component { 20 | constructor(props) { 21 | super(props); 22 | } 23 | 24 | render() { 25 | return ( 26 |
27 | // your code here... 28 |
29 | ); 30 | } 31 | } 32 | 33 | export default User; 34 | ``` 35 | 36 | - `/frontend/components/index.jsx` 37 | 38 | ```js 39 | // Container Component 40 | import React from 'react'; 41 | import { connect } from 'react-redux'; 42 | import User from './user.jsx'; 43 | 44 | const mapStateToProps = (state, ownProps) => ({ 45 | // your code here... 46 | }); 47 | 48 | const mapDispatchToProps = dispatch => ({ 49 | // your code here... 50 | }); 51 | 52 | export default connect( 53 | mapStateToProps, 54 | mapDispatchToProps 55 | )(User); 56 | ``` 57 | 58 | The component command supports two flags 59 | 60 | - `-f` or `--functional`. This creates a functional component that does not extend `React.Component`. 61 | 62 | ```js 63 | // functional component 64 | import React from 'react'; 65 | 66 | const ListItem = (props) => ( 67 | 68 | ); 69 | 70 | export default ListItem; 71 | ``` 72 | 73 | - `-nc` or `--no-container`. This flag will tell the module not to create a container file. 74 | 75 | For example, 76 | 77 | redux g component map -f-nc 78 | 79 | or 80 | 81 | redux g component map --functional--no-container 82 | 83 | This will create a functional component file without a container. 84 | -------------------------------------------------------------------------------- /docs/reducer.md: -------------------------------------------------------------------------------- 1 | # Reducer 2 | 3 | ## Root Reducer 4 | 5 | To generate a root reducer, run 6 | 7 | redux g reducer root 8 | 9 | This will generate a file `frontend/reducer/root_reducer.js`. The generator will search through the reducers folder to find all the reducers that are already there and create the necessary import statements and key value pairs for `combineReducers`. Whenever a new reducer is created, `root_reducer.js` will update to include the necessary `import` statement and key-value pair for `combineReducers` 10 | 11 | ```js 12 | import { combineReducers } from 'redux'; 13 | import sessionReducer from './session_reducer.js'; 14 | 15 | const rootReducer = combineReducers({ 16 | session: sessionReducer 17 | }); 18 | 19 | export default rootReducer; 20 | ``` 21 | 22 | ## Other Reducers 23 | 24 | To generate a reducer, run 25 | 26 | redux g reducer [action1] [action2] 27 | 28 | _Do not enter `_reducer.js` as part of the name_ 29 | 30 | For example: 31 | 32 | redux g reducer session receiveUser receiveErrors clearErrors 33 | 34 | This will generate a file `frontend/reducer/session_reducer.js`: 35 | 36 | ```js 37 | import { merge } from 'lodash'; 38 | import { 39 | RECEIVE_USER, 40 | RECEIVE_ERRORS, 41 | CLEAR_ERRORS } from "../actions/session_actions.js"; 42 | 43 | const sessionReducer = (state, action) => { 44 | Object.freeze(state); 45 | switch(action.type) { 46 | case RECEIVE_USER: 47 | // your code here 48 | case RECEIVE_ERRORS: 49 | // your code here 50 | case CLEAR_ERRORS: 51 | // your code here 52 | default: 53 | return state; 54 | } 55 | }; 56 | 57 | export default sessionReducer; 58 | ``` 59 | -------------------------------------------------------------------------------- /docs/store.md: -------------------------------------------------------------------------------- 1 | # Store 2 | 3 | To generate a store, run the following code in the terminal: 4 | 5 | redux g store 6 | 7 | This will generate `frontend/store/store.js`: 8 | 9 | ```js 10 | import { createStore, applyMiddleware } from 'redux'; 11 | import thunk from 'react-redux'; 12 | import rootReducer from '../reducers/root_reducer.js'; 13 | 14 | const _defaultState = {}; 15 | 16 | const configureStore = (preloadedState = _defaultState) => ( 17 | createStore( 18 | rootReducer, 19 | preloadedState, 20 | applyMiddleware(thunk) 21 | ) 22 | ); 23 | 24 | export default configureStore; 25 | ``` 26 | -------------------------------------------------------------------------------- /docs/update_notes.md: -------------------------------------------------------------------------------- 1 | # Update Note 2 | 3 | ## New features in version 1.2 4 | 5 | - [Action](action.md) generator detects keyword `receive` in action name and automatically add the argument name and key-value pair to the function. 6 | - [Util](util.md) generator detects keyword `fetch` in util name and automatically add a basic `ajax` request in the body of the function. 7 | - Added command `remove` or `r` to remove generated files. 8 | - [Reducer](reducer.md) now accepts actions and will automatically add the import and case statements. 9 | 10 | ## New features in version 1.1 11 | 12 | - [Component](component.md) generator can accept `-f`/`--functional` and `-nc`/`--no-container` flags. 13 | - When generating `root_reducer.js`, all existing [reducers](reducer.md) will be imported and added to `combineReducers`. 14 | - `root_reducer.js` will update with import statement and key-value pairs whenever a new [reducer](reducer.md) is created. 15 | -------------------------------------------------------------------------------- /docs/util.md: -------------------------------------------------------------------------------- 1 | # Utility 2 | 3 | To generate a utility file, run 4 | 5 | redux g util [util1] [util2] ... 6 | 7 | For example: 8 | 9 | redux g util api fetchUsers getData 10 | 11 | _Do not include `_util.js` as part of the name_ 12 | 13 | This will create a file `frontend/util/api_util.js`: 14 | 15 | ```js 16 | export const fetchUsers = users => ( 17 | $.ajax({ 18 | method: '', 19 | url: '', 20 | data: '' 21 | }) 22 | ); 23 | 24 | export const getData = () => ( 25 | // your code here 26 | ); 27 | ``` 28 | 29 | Having the word `fetch` in the utils will automatically create a blank `ajax` request. 30 | -------------------------------------------------------------------------------- /file_types/action.js: -------------------------------------------------------------------------------- 1 | /* global cd mkdir */ 2 | require('shelljs/global'); 3 | const fs = require('fs'); 4 | 5 | const caseConverter = require('../helpers/case_converter.js'); 6 | const logFunctions = require('../helpers/logs.js'); 7 | 8 | const writeConstant = constName => ( 9 | `export const ${constName} = '${constName}';` 10 | ); 11 | 12 | const writeAction = (actionName, constName) => { 13 | let arg = '()'; 14 | let data = ''; 15 | 16 | if(/receive/.test(actionName)) { 17 | arg = actionName.replace('receive', ''); 18 | arg = caseConverter.convert(arg, caseConverter.toCamelCase); 19 | data = `,\n ${arg}`; 20 | } 21 | 22 | return ( 23 | `export const ${actionName} = ${arg} => ({ 24 | type: ${constName}${data} 25 | }); 26 | ` 27 | ); 28 | }; 29 | 30 | const generateAction = (name, actions) => { 31 | let nameSC = caseConverter.convert(name, caseConverter.toSnakeCase); 32 | 33 | let fileName = `${nameSC}_actions.js`; 34 | let fullPath = `frontend/actions/${fileName}`; 35 | 36 | fs.exists(fullPath, exists => { 37 | if(exists) { 38 | logFunctions.fileExistErrorLog(fileName); 39 | } else { 40 | mkdir('-p', 'frontend/actions/'); 41 | cd('frontend/actions'); 42 | 43 | let writeStream = fs.createWriteStream(fileName); 44 | 45 | let constNames = actions.map( action => ( 46 | caseConverter.convert(action, caseConverter.toScreamingSnakeCase) 47 | )); 48 | 49 | let actionNames = actions.map( action => ( 50 | caseConverter.convert(action, caseConverter.toCamelCase) 51 | )); 52 | 53 | let data = constNames.map( constName => ( 54 | writeConstant(constName)) 55 | ).join('\n'); 56 | 57 | data += '\n\n'; 58 | data += actionNames.map( (action, idx) => ( 59 | writeAction(action, constNames[idx]) 60 | )).join('\n'); 61 | 62 | writeStream.write(data); 63 | 64 | writeStream.close(); 65 | 66 | logFunctions.createFileLog(fullPath); 67 | cd('../..'); 68 | } 69 | }); 70 | }; 71 | 72 | module.exports = generateAction; 73 | -------------------------------------------------------------------------------- /file_types/base.js: -------------------------------------------------------------------------------- 1 | /* global cd mkdir */ 2 | require('shelljs/global'); 3 | const fs = require('fs'); 4 | 5 | const caseConverter = require('../helpers/case_converter.js'); 6 | const logFunctions = require('../helpers/logs.js'); 7 | const baseTemplate = require('../templates/base.js'); 8 | 9 | const generateOne = (path, name) => { 10 | fs.exists(`${path}/${name}.jsx`, exists => { 11 | if(exists) { 12 | logFunctions.fileExistErrorLog(`${name}.jsx`); 13 | } else { 14 | mkdir('-p',`${path}/`); 15 | cd(path); 16 | var writeStream = fs.createWriteStream(`${name}.jsx`); 17 | 18 | switch(name) { 19 | case 'app': 20 | writeStream.write(baseTemplate.appJSX()); 21 | break; 22 | case 'root': 23 | writeStream.write(baseTemplate.rootJSX()); 24 | break; 25 | default: 26 | writeStream.write(baseTemplate.entryJSX()); 27 | } 28 | writeStream.end(); 29 | 30 | logFunctions.createFileLog(`${path}/${name}.jsx`); 31 | 32 | if (path.split('/').length === 2) { 33 | cd('../..'); 34 | } else { 35 | cd('..'); 36 | } 37 | } 38 | }); 39 | }; 40 | 41 | const generateBase = (name = 'sample_app') => { 42 | name = caseConverter.convert(name, caseConverter.toSnakeCase); 43 | 44 | generateOne('frontend/components', 'app'); 45 | generateOne('frontend/components', 'root'); 46 | generateOne('frontend', `${name}`); 47 | }; 48 | 49 | module.exports = generateBase; 50 | -------------------------------------------------------------------------------- /file_types/component.js: -------------------------------------------------------------------------------- 1 | /* global cd mkdir */ 2 | require('shelljs/global'); 3 | const fs = require('fs'); 4 | 5 | const caseConverter = require('../helpers/case_converter.js'); 6 | const logFunctions = require('../helpers/logs.js'); 7 | const componentTemplate = require('../templates/component.js'); 8 | 9 | const generateComponent = (name, flags) => { 10 | let nameSC = caseConverter.convert(name, caseConverter.toSnakeCase); 11 | let nameUCC = caseConverter.convert(name, caseConverter.toPascalCase); 12 | flags = flags.join(''); 13 | 14 | fs.exists(`frontend/components/${nameSC}/${nameSC}.jsx`, exists => { 15 | if(exists) { 16 | logFunctions.fileExistErrorLog(`${nameSC}.jsx`); 17 | } else { 18 | 19 | mkdir('-p', `frontend/components/${nameSC}`); 20 | cd(`frontend/components/${nameSC}`); 21 | 22 | // write functional component 23 | if(flags.includes('-f') || flags.includes('--functional')) { 24 | let writeStreamFunctional = fs.createWriteStream(`${nameSC}.jsx`); 25 | let functionalData = componentTemplate.functional(nameUCC); 26 | writeStreamFunctional.write(functionalData); 27 | writeStreamFunctional.close(); 28 | logFunctions.createFileLog(`frontend/components/${nameSC}/${nameSC}.jsx`); 29 | 30 | // write full class component 31 | } else { 32 | 33 | let writeStreamPresentation = fs.createWriteStream(`${nameSC}.jsx`); 34 | let presentationData = componentTemplate.presentational(nameUCC); 35 | writeStreamPresentation.write(presentationData); 36 | writeStreamPresentation.close(); 37 | 38 | logFunctions.createFileLog(`frontend/components/${nameSC}/${nameSC}.jsx`); 39 | } 40 | // write container file 41 | if(!flags.includes('-nc') && !flags.includes('--no-container')) { 42 | let writeStreamContainer = fs.createWriteStream(`index.jsx`); 43 | let containerData = componentTemplate.container(nameUCC, nameSC); 44 | writeStreamContainer.write(containerData); 45 | writeStreamContainer.close(); 46 | 47 | logFunctions.createFileLog(`frontend/components/${nameSC}/index.jsx`); 48 | } 49 | 50 | cd('../../..'); 51 | } 52 | }); 53 | }; 54 | 55 | module.exports = generateComponent; 56 | -------------------------------------------------------------------------------- /file_types/reducer.js: -------------------------------------------------------------------------------- 1 | /* global cd mkdir */ 2 | require('shelljs/global'); 3 | const fs = require('fs'); 4 | 5 | const caseConverter = require('../helpers/case_converter.js'); 6 | const logFunctions = require('../helpers/logs.js'); 7 | const reducerTemplate = require('../templates/reducer.js'); 8 | 9 | const createRootReducerImports = reducerNameArray => { 10 | return reducerNameArray.map( reducer => { 11 | let name = reducer.split('.')[0]; 12 | let nameLCC = caseConverter.convert(name, caseConverter.toCamelCase); 13 | 14 | return `import ${nameLCC} from './${reducer}';`; 15 | 16 | }).join('\n'); 17 | }; 18 | 19 | const createRootReducerKeyPairs = reducerNameArray => { 20 | return reducerNameArray.map( reducer => { 21 | let name = reducer.split('.')[0]; 22 | let key = name.split('_')[0]; 23 | let nameLCC = caseConverter.convert(name, caseConverter.toCamelCase); 24 | 25 | return ` ${key}: ${nameLCC}`; 26 | 27 | }).join(',\n'); 28 | }; 29 | 30 | // add import statement and key-value pairs to root_reducer. 31 | const updateRootReducer = (nameLCC, nameSC) => { 32 | 33 | fs.exists(`root_reducer.js`, exists => { 34 | if(exists) { 35 | let root = fs.readFileSync('root_reducer.js', 'utf8'); 36 | let keyValStr = 'const rootReducer = combineReducers({\n'; 37 | let kvIdx = root.indexOf(keyValStr) + keyValStr.length; 38 | 39 | root = root.slice(0, kvIdx) + ` ${nameLCC}: ${nameLCC}Reducer,\n` + root.slice(kvIdx); 40 | 41 | let importStr = 'import { combineReducers } from \'redux\';\n'; 42 | let iIdx = root.indexOf(importStr) + importStr.length; 43 | root = root.slice(0, iIdx) + `import ${nameLCC}Reducer from './${nameSC}_reducer.js';\n` + root.slice(iIdx); 44 | 45 | fs.writeFileSync('root_reducer.js', root); 46 | } 47 | }); 48 | }; 49 | 50 | // create reducer file. Update root reducer if needed. 51 | const createReducer = (name, actions) => { 52 | let nameLCC = caseConverter.convert(name, caseConverter.toCamelCase); 53 | let nameSC = caseConverter.convert(name, caseConverter.toSnakeCase); 54 | let reducerFiles = []; 55 | let fileName = `${nameSC}_reducer.js`; 56 | 57 | if (fs.existsSync('frontend/reducers')) { 58 | reducerFiles = fs.readdirSync('frontend/reducers'); 59 | } 60 | 61 | fs.exists(`frontend/reducers/${fileName}`, exists => { 62 | if(exists) { 63 | logFunctions.fileExistErrorLog(fileName); 64 | } else { 65 | mkdir('-p',`frontend/reducers/`); 66 | cd('frontend/reducers'); 67 | 68 | var writeStream = fs.createWriteStream(fileName); 69 | if (name === 'root') { 70 | let importStatements = createRootReducerImports(reducerFiles); 71 | let keyPairStatements = createRootReducerKeyPairs(reducerFiles); 72 | writeStream.write(reducerTemplate.root(importStatements, keyPairStatements)); 73 | writeStream.end(); 74 | } else { 75 | writeStream.write(reducerTemplate.reducer(nameLCC, actions)); 76 | writeStream.end(); 77 | updateRootReducer(nameLCC, nameSC); 78 | } 79 | 80 | cd('../..'); 81 | 82 | logFunctions.createFileLog(`frontend/reducers/${fileName}`); 83 | } 84 | }); 85 | 86 | 87 | }; 88 | 89 | module.exports = createReducer; 90 | -------------------------------------------------------------------------------- /file_types/store.js: -------------------------------------------------------------------------------- 1 | /* global cd mkdir */ 2 | require('shelljs/global'); 3 | const fs = require('fs'); 4 | 5 | const logFunctions = require('../helpers/logs.js'); 6 | const storeTemplate = require('../templates/store.js'); 7 | 8 | const generateStore = () => { 9 | 10 | fs.exists(`frontend/store/store.js`, exists => { 11 | if(exists) { 12 | logFunctions.fileExistErrorLog('store.js'); 13 | } else { 14 | mkdir('-p',`frontend/store/`); 15 | cd('frontend/store'); 16 | let writeStream = fs.createWriteStream('store.js'); 17 | writeStream.write(storeTemplate()); 18 | writeStream.end(); 19 | cd('../..'); 20 | logFunctions.createFileLog(`frontend/store/store.js`); 21 | } 22 | }); 23 | }; 24 | 25 | module.exports = generateStore; 26 | -------------------------------------------------------------------------------- /file_types/util.js: -------------------------------------------------------------------------------- 1 | /* global cd mkdir */ 2 | require('shelljs/global'); 3 | const fs = require('fs'); 4 | 5 | const caseConverter = require('../helpers/case_converter.js'); 6 | const logFunctions = require('../helpers/logs.js'); 7 | 8 | const writeUtil = (utilName, constName) => { 9 | let arg = '()'; 10 | let data = '// your code here'; 11 | 12 | if(/fetch/.test(utilName)) { 13 | arg = utilName.replace('fetch', ''); 14 | arg = caseConverter.convert(arg, caseConverter.toCamelCase); 15 | data = `$.ajax({\n method: '',\n url: '',\n data: ''\n })`; 16 | } 17 | 18 | return ( 19 | `export const ${utilName} = ${arg} => ( 20 | ${data} 21 | ); 22 | ` 23 | ); 24 | }; 25 | 26 | const generateUtil = (name, utils = []) => { 27 | name = caseConverter.convert(name, caseConverter.toSnakeCase); 28 | 29 | let fileName = `${name}_util.js`; 30 | 31 | fs.exists(`frontend/util/${fileName}`, exists => { 32 | if(exists) { 33 | logFunctions.fileExistErrorLog(`${name}_util.js`); 34 | } else { 35 | 36 | mkdir('-p', 'frontend/util/'); 37 | cd('frontend/util'); 38 | 39 | let writeStream = fs.createWriteStream(fileName); 40 | 41 | let data = utils.map( util => { 42 | util = caseConverter.convert(util,caseConverter.toCamelCase); 43 | return writeUtil(util); 44 | }).join('\n'); 45 | 46 | writeStream.write(data); 47 | writeStream.close(); 48 | cd('../..'); 49 | logFunctions.createFileLog(`frontend/util/${fileName}`); 50 | } 51 | }); 52 | }; 53 | 54 | module.exports = generateUtil; 55 | -------------------------------------------------------------------------------- /helpers/case_converter.js: -------------------------------------------------------------------------------- 1 | const toSnakeCase = words => ( 2 | words.map( word => word.toLowerCase() ).join('_') 3 | ); 4 | 5 | const toPascalCase = words => ( 6 | words.map( word => ( 7 | word[0].toUpperCase() + word.slice(1).toLowerCase() 8 | )).join('') 9 | ); 10 | 11 | const toCamelCase = words => ( 12 | words.map( (word, index) => { 13 | if (index === 0) { 14 | return word.toLowerCase(); 15 | } else { 16 | return word[0].toUpperCase() + word.slice(1).toLowerCase(); 17 | } 18 | }).join('') 19 | ); 20 | 21 | const toScreamingSnakeCase = words => ( 22 | words.map( word => word.toUpperCase() ).join('_') 23 | ); 24 | 25 | const convert = (words, fs) => { 26 | let wordsArray = words.split('_'); 27 | if (wordsArray.length === 1 && words.toUpperCase() !== words) { 28 | wordsArray = words.split(/(?=[A-Z])/); 29 | } 30 | return fs(wordsArray); 31 | }; 32 | 33 | module.exports = { 34 | toSnakeCase, 35 | toPascalCase, 36 | toCamelCase, 37 | toScreamingSnakeCase, 38 | convert 39 | }; 40 | -------------------------------------------------------------------------------- /helpers/logs.js: -------------------------------------------------------------------------------- 1 | const colors = require('colors'); 2 | 3 | const fileExistErrorLog = fileName => { 4 | console.log(" error: ".red.bold, `${fileName} exists. Cannot overwrite.`); 5 | }; 6 | 7 | const noMethodErrorLog = () => { 8 | console.log(" error: ".red.bold, "I am not sure what you want to create..."); 9 | }; 10 | 11 | const noNameErrorLog = method => { 12 | console.log(" error: ".red.bold, `Please enter a name for the ${method}`); 13 | }; 14 | 15 | const createFileLog = fullPath => { 16 | console.log("created: ".green.bold, `${fullPath}`); 17 | }; 18 | 19 | const removedFileLog = fullPath => { 20 | console.log("deleted: ".yellow.bold, `${fullPath}`); 21 | }; 22 | 23 | const noExistFileErrorLog = fileName => { 24 | console.log(" error: ".red.bold, `${fileName} do not exist. Cannot remove.`); 25 | }; 26 | 27 | module.exports = { 28 | fileExistErrorLog, 29 | noMethodErrorLog, 30 | noNameErrorLog, 31 | createFileLog, 32 | removedFileLog, 33 | noExistFileErrorLog 34 | }; 35 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | require('shelljs/global'); 3 | 4 | const logFunctions = require('./helpers/logs.js'); 5 | const helpTemplates = require('./templates/help.js'); 6 | const generator = require('./commands/generator.js'); 7 | const remover = require('./commands/remover.js'); 8 | 9 | let possibleTypes = ['reducers', 'stores', 'actions', 'utils', 'components', 'bases', 'cycles'].join(''); 10 | let method = process.argv[2]; 11 | let type = process.argv[3]; 12 | let name = process.argv[4]; 13 | let actions = process.argv.slice(5); 14 | 15 | if (['help', '--help', '-h', undefined].includes(method)) { 16 | helpTemplates.helpTemplate(); 17 | 18 | } else if (['version', '-v', '--version'].includes(method)) { 19 | console.log('v' + require('./package.json').version); 20 | 21 | } else if (possibleTypes.includes(type) === false) { 22 | logFunctions.noMethodErrorLog(); 23 | 24 | } else if(name === undefined && type !== 'store') { 25 | logFunctions.noNameErrorLog(type); 26 | 27 | } else { 28 | if(method === 'g' || method === 'generate') { 29 | generator(type, name, actions); 30 | } else if (method === 'r' || method === 'remove') { 31 | remover(type, name); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "redux-file-gen", 3 | "version": "1.2.40", 4 | "description": "Generates necessary files for react-redux application", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha test", 8 | "patch": "npm version patch && npm publish" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/davidhu2000/react_redux_generator.git" 13 | }, 14 | "preferGlobal": "true", 15 | "bin": { 16 | "redux": "index.js" 17 | }, 18 | "keywords": [ 19 | "reactjs", 20 | "redux", 21 | "javascript", 22 | "react", 23 | "react-redux", 24 | "generator", 25 | "file generator", 26 | "redux-file-gem" 27 | ], 28 | "author": "David Hu ", 29 | "license": "ISC", 30 | "bugs": { 31 | "url": "https://github.com/davidhu2000/react_redux_generator/issues" 32 | }, 33 | "homepage": "https://github.com/davidhu2000/react_redux_generator#readme", 34 | "dependencies": { 35 | "colors": "^1.1.2", 36 | "shelljs": "^0.7.5" 37 | }, 38 | "devDependencies": { 39 | "blanket": "^1.2.3", 40 | "bluebird": "^3.5.0", 41 | "chai": "^3.5.0", 42 | "chai-fs": "^1.0.0", 43 | "coveralls": "^2.13.1", 44 | "mocha": "^3.3.0", 45 | "mocha-lcov-reporter": "^1.3.0", 46 | "mocha-sinon": "^2.0.0", 47 | "sinon": "^2.1.0" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /templates/base.js: -------------------------------------------------------------------------------- 1 | const appJSX = () => ( 2 | `import React from 'react'; 3 | 4 | const App = ({ store }) => ( 5 |
6 |

App

7 |
8 | ); 9 | 10 | export default App; 11 | ` 12 | ); 13 | 14 | const rootJSX = () => ( 15 | `import React from 'react'; 16 | import { Provider } from 'react-redux'; 17 | import App from './app.jsx'; 18 | 19 | const Root = ({ store }) => ( 20 | 21 | 22 | 23 | ); 24 | 25 | export default Root; 26 | ` 27 | ); 28 | 29 | const entryJSX = () => ( 30 | `import React from 'react'; 31 | import ReactDOM from 'react-dom'; 32 | import configureStore from './store/store.js'; 33 | import Root from './components/root'; 34 | 35 | document.addEventListener('DOMContentLoaded', () => { 36 | const root = document.getElementById('root'); 37 | const store = configureStore(); 38 | 39 | ReactDOM.render(, root); 40 | }); 41 | ` 42 | ); 43 | 44 | module.exports = { 45 | appJSX, 46 | entryJSX, 47 | rootJSX 48 | }; 49 | -------------------------------------------------------------------------------- /templates/component.js: -------------------------------------------------------------------------------- 1 | const functional = name => ( 2 | `import React from 'react'; 3 | 4 | const ${name} = (props) => ( 5 | 6 | ); 7 | 8 | export default ${name}; 9 | ` 10 | ); 11 | 12 | const presentational = name => ( 13 | `import React from 'react'; 14 | 15 | class ${name} extends React.Component { 16 | constructor(props) { 17 | super(props); 18 | } 19 | 20 | render() { 21 | return ( 22 |
23 | // your code here... 24 |
25 | ); 26 | } 27 | } 28 | 29 | export default ${name}; 30 | ` 31 | ); 32 | 33 | const container = (nameUCC, nameSC) => ( 34 | `import React from 'react'; 35 | import { connect } from 'react-redux'; 36 | import ${nameUCC} from './${nameSC}.jsx'; 37 | 38 | const mapStateToProps = (state, ownProps) => ({ 39 | // your code here... 40 | }); 41 | 42 | const mapDispatchToProps = dispatch => ({ 43 | // your code here... 44 | }); 45 | 46 | export default connect( 47 | mapStateToProps, 48 | mapDispatchToProps 49 | )(${nameUCC}); 50 | ` 51 | ); 52 | 53 | module.exports = { 54 | functional, 55 | presentational, 56 | container 57 | }; 58 | -------------------------------------------------------------------------------- /templates/help.js: -------------------------------------------------------------------------------- 1 | const helpTemplate = () => { 2 | basicHelp(); 3 | commandHelp(); 4 | fileTypeHelp(); 5 | optionsHelp(); 6 | }; 7 | 8 | const basicHelp = () => { 9 | console.log(` 10 | Usage: redux [command] [file] [options] 11 | 12 | `); 13 | }; 14 | 15 | const commandHelp = () => { 16 | console.log(` Commands: 17 | 18 | generate, g use the file generator 19 | remove, r remove files 20 | --help, -h show terminal command help 21 | --version, -v show current package version 22 | `); 23 | } 24 | 25 | const fileTypeHelp = () => { 26 | console.log(` 27 | Files: 28 | 29 | base generate app.jsx, root.jsx, .jsx, and store.js 30 | action [action1] [action2] ... generate _actions.js with the specified actions 31 | component [options] generate .jsx and _container.jsx 32 | reducer [action1] [action2] ... generate _reducer.js 33 | store generate store.js 34 | util [util1] [util2] ... generate _util.js with specified utils. 35 | `); 36 | } 37 | 38 | const optionsHelp = () => { 39 | console.log(` 40 | Options: 41 | 42 | --functional, -f create functional component 43 | --no-container, -nc do not create component container 44 | `); 45 | } 46 | 47 | module.exports = { 48 | helpTemplate 49 | }; 50 | -------------------------------------------------------------------------------- /templates/reducer.js: -------------------------------------------------------------------------------- 1 | const caseConverter = require('../helpers/case_converter.js'); 2 | 3 | const reducer = (nameLCC, actions) => { 4 | let nameSC = caseConverter.convert(nameLCC, caseConverter.toSnakeCase); 5 | let actionFilePath = `../actions/${nameSC}_actions.js`; 6 | 7 | let actionsSSC = actions.map( action => ( 8 | `${caseConverter.convert(action, caseConverter.toScreamingSnakeCase)}` 9 | )); 10 | 11 | let actionImport = `\nimport { 12 | ${actionsSSC.join(',\n ')} } from "${actionFilePath}";`; 13 | 14 | let actionCase = actionsSSC.map(action => ( 15 | ` case ${action}: 16 | // your code here` 17 | )).join('\n'); 18 | 19 | actionCase = '\n' + actionCase; 20 | 21 | if(actionsSSC.length === 0) { 22 | actionImport = ''; 23 | actionCase = ''; 24 | } 25 | 26 | return ( 27 | `import { merge } from 'lodash';${actionImport} 28 | 29 | let _defaultState = {}; 30 | 31 | const ${nameLCC}Reducer = (state = _defaultState, action) => { 32 | Object.freeze(state); 33 | switch(action.type) {${actionCase} 34 | default: 35 | return state; 36 | } 37 | }; 38 | 39 | export default ${nameLCC}Reducer;` 40 | ); 41 | }; 42 | 43 | const root = (imports, keyPairs) => ( 44 | `import { combineReducers } from 'redux'; 45 | ${imports} 46 | 47 | const rootReducer = combineReducers({ 48 | ${keyPairs} 49 | }); 50 | 51 | export default rootReducer;` 52 | ); 53 | 54 | module.exports = { 55 | reducer, 56 | root 57 | }; 58 | -------------------------------------------------------------------------------- /templates/store.js: -------------------------------------------------------------------------------- 1 | const storeFormat = () => ( 2 | `import { createStore, applyMiddleware } from 'redux'; 3 | import thunk from 'redux-thunk'; 4 | import rootReducer from '../reducers/root_reducer.js'; 5 | 6 | const _defaultState = {}; 7 | 8 | const configureStore = (preloadedState = _defaultState) => ( 9 | createStore( 10 | rootReducer, 11 | preloadedState, 12 | applyMiddleware(thunk) 13 | ) 14 | ); 15 | 16 | export default configureStore; 17 | ` 18 | ); 19 | 20 | module.exports = storeFormat; 21 | -------------------------------------------------------------------------------- /test/file_types-tests/action-test.js: -------------------------------------------------------------------------------- 1 | let chai = require('chai'); 2 | let assert = chai.assert; 3 | let expect = chai.expect; 4 | let sinon = require('sinon'); 5 | chai.use(require('chai-fs')); 6 | let Promise = require('bluebird'); 7 | let rm = Promise.promisify(require('shelljs').rm); 8 | 9 | let generateAction = Promise.promisify(require('../../file_types/action.js')); 10 | 11 | describe('Action Generator', () => { 12 | let actions; 13 | 14 | afterEach(() => { 15 | Promise.resolve(rm('-r', 'frontend')); 16 | }); 17 | 18 | it('should export a function', () => { 19 | expect(generateAction).to.exist; 20 | expect(generateAction).to.be.instanceOf(Function); 21 | }); 22 | 23 | it('should create an empty action file with empty actions array', () => { 24 | Promise.resolve(generateAction('test', [])).then(() => { 25 | expect('frontend/actions/test_actions.js').to.be.a.file().with.content('\n\n'); 26 | }); 27 | }); 28 | 29 | it('should create a file with action constants', () => { 30 | Promise.resolve(generateAction('another', ['actionUno', 'actionDos'])).then(() => { 31 | expect('frontend/actions/another_actions.js').to.be.a.file(); 32 | expect('frontend/actions/another_actions.js').to.be.a.file().with.contents.that.match(/export const ACTION_UNO = 'ACTION_UNO'/); 33 | expect('frontend/actions/another_actions.js').to.be.a.file().with.contents.that.match(/export const ACTION_DOS = 'ACTION_DOS'/); 34 | expect('frontend/actions/another_actions.js').to.be.a.file().with.contents.that.match(/export const actionUno/); 35 | expect('frontend/actions/another_actions.js').to.be.a.file().with.contents.that.match(/type: ACTION_UNO/); 36 | }); 37 | }); 38 | 39 | it('should not overwrite existing file', () => { 40 | Promise.resolve(generateAction('test', ['not'])).then(() => { 41 | expect('frontend/actions/test_actions.js').to.be.a.file().and.not.have.content('not'); 42 | }); 43 | }); 44 | }); -------------------------------------------------------------------------------- /test/file_types-tests/base-test.js: -------------------------------------------------------------------------------- 1 | let chai = require('chai'); 2 | let assert = chai.assert; 3 | let expect = chai.expect; 4 | let sinon = require('sinon'); 5 | chai.use(require('chai-fs')); 6 | let Promise = require('bluebird'); 7 | let rm = Promise.promisify(require('shelljs').rm); 8 | 9 | let generateBase = Promise.promisify(require('../../file_types/base.js')); 10 | 11 | describe('Base Files Generator', () => { 12 | before(() => { 13 | Promise.resolve(generateBase('mocha')); 14 | }); 15 | 16 | after(() => { 17 | Promise.resolve(rm('-r', 'frontend')); 18 | }); 19 | 20 | it('should export a function', () => { 21 | expect(generateBase).to.exist; 22 | expect(generateBase).to.be.instanceOf(Function); 23 | }); 24 | 25 | it('should create the app.jsx file', () => { 26 | expect('frontend/components/app.jsx').to.be.a.file(); 27 | }); 28 | 29 | it('should create the root.jsx file', () => { 30 | expect('frontend/components/root.jsx').to.be.a.file(); 31 | }); 32 | 33 | it('should create the entry entry', () => { 34 | expect('frontend/mocha.jsx').to.be.a.file(); 35 | }); 36 | 37 | }); -------------------------------------------------------------------------------- /test/file_types-tests/component-test.js: -------------------------------------------------------------------------------- 1 | let chai = require('chai'); 2 | let assert = chai.assert; 3 | let expect = chai.expect; 4 | let sinon = require('sinon'); 5 | chai.use(require('chai-fs')); 6 | 7 | let Promise = require('bluebird'); 8 | let rm = Promise.promisify(require('shelljs').rm); 9 | 10 | let generateComponent = Promise.promisify(require('../../file_types/component.js')); 11 | 12 | describe('Component Generator', () => { 13 | 14 | after(() => { 15 | Promise.resolve(rm('-r', 'frontend')); 16 | }); 17 | 18 | it('should generate a component file and a container file', () => { 19 | Promise.resolve(generateComponent('bothTypes', [])).then(() => { 20 | expect('frontend/components/both_types/index.jsx').to.be.a.file(); 21 | expect('frontend/components/both_types/both_types.jsx').to.be.a.file().with.contents.that.match(`class BothTypes`); 22 | expect('frontend/components/both_types/both_types.jsx').to.be.a.file().with.contents.that.match(`constructor(props)`); 23 | expect('frontend/components/both_types/both_types.jsx').to.be.a.file().with.contents.that.match(`render()`); 24 | }); 25 | }); 26 | 27 | it('should generate a function component file when receive -f flag', () => { 28 | Promise.resolve(generateComponent('functional', ['-f'])).then(() => { 29 | expect('frontend/components/functional/functional.jsx').to.be.a.file().with.contents.that.match(`const Functional = (props)`); 30 | expect('frontend/components/functional/functional.jsx').to.be.a.file().with.contents.that.match(`export default Functional;`); 31 | }); 32 | }); 33 | 34 | it('should not generate a container file when receive the -nc flag', () => { 35 | Promise.resolve(generateComponent('noContainer', ['-nc'])).then(() => { 36 | expect('frontend/components/no_container/no_container.jsx').to.be.a.file(); 37 | expect('frontend/components/no_container/index.jsx').to.not.be.a.file(); 38 | }); 39 | }); 40 | 41 | 42 | }); -------------------------------------------------------------------------------- /test/file_types-tests/reducer-test.js: -------------------------------------------------------------------------------- 1 | let chai = require('chai'); 2 | let assert = chai.assert; 3 | let expect = chai.expect; 4 | let sinon = require('sinon'); 5 | chai.use(require('chai-fs')); 6 | 7 | let Promise = require('bluebird'); 8 | let rm = Promise.promisify(require('shelljs').rm); 9 | 10 | let generateReducer = Promise.promisify(require('../../file_types/reducer.js')); 11 | 12 | describe('Reducer Generator', () => { 13 | afterEach(() => { 14 | Promise.resolve(rm('-r', 'frontend')); 15 | }); 16 | 17 | it('should create root_reducer.js if name is root', () => { 18 | Promise.resolve(generateReducer("root", [])).then(() => { 19 | expect('frontend/reducers/root_reducer.js').to.be.a.file(); 20 | expect('frontend/reducers/root_reducer.js').to.be.a.file().with.contents.that.match(/rootReducer/); 21 | expect('frontend/reducers/root_reducer.js').to.be.a.file().with.contents.that.match(/combineReducers/); 22 | }); 23 | }); 24 | 25 | it('should create a reducer if name is not root', () => { 26 | Promise.resolve(generateReducer("reduce", [])).then(() => { 27 | expect('frontend/reducers/reduce_reducer.js').to.be.a.file(); 28 | expect('frontend/reducers/reduce_reducer.js').to.be.a.file().with.contents.that.match(/reduceReducer/); 29 | expect('frontend/reducers/reduce_reducer.js').to.be.a.file().with.contents.that.match(/return state/); 30 | }); 31 | }); 32 | 33 | it('should create a reducer with action constants if passed in actions', () => { 34 | Promise.resolve(generateReducer("full", ['action', 'cut'])).then(() => { 35 | expect('frontend/reducers/full_reducer.js').to.be.a.file(); 36 | expect('frontend/reducers/full_reducer.js').to.be.a.file().with.contents.that.match(/ACTION/); 37 | expect('frontend/reducers/full_reducer.js').to.be.a.file().with.contents.that.match(/CUT/); 38 | }); 39 | }); 40 | 41 | it('should update root_reducer with correct import statements', () => { 42 | Promise.resolve(generateReducer("dos", [])).then(() => generateReducer('root', [])).then(() => { 43 | expect('frontend/reducers/tres_reducer.js').to.be.a.file(); 44 | 45 | expect('frontend/reducers/root_reducer.js').to.be.a.file(); 46 | expect('frontend/reducers/root_reducer.js').to.be.a.file().with.contents.that.match(/import dosReducer/); 47 | expect('frontend/reducers/root_reducer.js').to.be.a.file().with.contents.that.match(/dos: dosReducer/); 48 | }); 49 | }); 50 | 51 | it('should update root_reducer with correct import statements', () => { 52 | Promise.resolve(generateReducer("root", [])).then(() => generateReducer('tres', ['action', 'cut'])).then(() => { 53 | expect('frontend/reducers/root_reducer.js').to.be.a.file(); 54 | expect('frontend/reducers/root_reducer.js').to.be.a.file().with.contents.that.match(/import tresReducer/); 55 | expect('frontend/reducers/root_reducer.js').to.be.a.file().with.contents.that.match(/tres: tresReducer/); 56 | 57 | expect('frontend/reducers/tres_reducer.js').to.be.a.file(); 58 | expect('frontend/reducers/tres_reducer.js').to.be.a.file().with.contents.that.match(/ACTION/); 59 | expect('frontend/reducers/tres_reducer.js').to.be.a.file().with.contents.that.match(/CUT/); 60 | }); 61 | }); 62 | }); -------------------------------------------------------------------------------- /test/file_types-tests/store-test.js: -------------------------------------------------------------------------------- 1 | let chai = require('chai'); 2 | let assert = chai.assert; 3 | let expect = chai.expect; 4 | let sinon = require('sinon'); 5 | chai.use(require('chai-fs')); 6 | 7 | let Promise = require('bluebird'); 8 | let rm = Promise.promisify(require('shelljs').rm); 9 | 10 | let generateStore = Promise.promisify(require('../../file_types/store.js')); 11 | 12 | describe('Store Generator', () => { 13 | after(() => { 14 | Promise.resolve(rm('-r', 'frontend')); 15 | }); 16 | 17 | it('should generate a store file', () => { 18 | Promise.resolve(generateStore()).then(() => { 19 | expect('frontend/store/store.js').to.be.a.file(); 20 | expect('frontend/store/store.js').to.be.a.file().with.contents.that.match(/configureStore/); 21 | }); 22 | }); 23 | }); -------------------------------------------------------------------------------- /test/file_types-tests/util-test.js: -------------------------------------------------------------------------------- 1 | let chai = require('chai'); 2 | let assert = chai.assert; 3 | let expect = chai.expect; 4 | let sinon = require('sinon'); 5 | chai.use(require('chai-fs')); 6 | 7 | let Promise = require('bluebird'); 8 | let rm = Promise.promisify(require('shelljs').rm); 9 | 10 | let generateUtil = Promise.promisify(require('../../file_types/util.js')); 11 | 12 | describe('Util Generator', () => { 13 | after(() => { 14 | Promise.resolve(rm('-r', 'frontend')); 15 | }); 16 | 17 | it('should create an empty util file when given empty array', () => { 18 | Promise.resolve(generateUtil('empty', [])).then(() => { 19 | expect('frontend/util/empty_util.js').to.be.a.file(); 20 | expect('frontend/util/empty_util.js').to.be.a.file().and.not.have.contents.that.match(/export/); 21 | }); 22 | }); 23 | 24 | it('should create an util file with given utils', () => { 25 | Promise.resolve(generateUtil('test', ['doSomething'])).then(() => { 26 | expect('frontend/util/test_util.js').to.be.a.file(); 27 | expect('frontend/util/test_util.js').to.be.a.file().with.contents.that.match(/export const doSomething = ()/); 28 | }); 29 | }); 30 | 31 | it('should create a blank ajax request if util has fetch in name', () => { 32 | Promise.resolve(generateUtil('fetch', ['fetchPuppy'])).then(() => { 33 | expect('frontend/util/fetch_util.js').to.be.a.file(); 34 | expect('frontend/util/fetch_util.js').to.be.a.file().with.contents.that.match(/\$.ajax/); 35 | }); 36 | }); 37 | }); -------------------------------------------------------------------------------- /test/helpers-tests/case_converter-test.js: -------------------------------------------------------------------------------- 1 | let chai = require('chai'); 2 | let assert = chai.assert; 3 | let expect = chai.expect; 4 | let sinon = require('sinon'); 5 | 6 | let caseConverter = require('../../helpers/case_converter.js'); 7 | 8 | describe('Case Converter', () => { 9 | let snakeCase, pascalCase, camelCase, screamingSnakeCase; 10 | let snakeCaseWords, pascalCaseWords, camelCaseWords, screamingSnakeCaseWords; 11 | 12 | beforeEach(() => { 13 | snakeCase = 'mocha_chai_latte'; 14 | snakeCaseWords = ['mocha', 'chai', 'latte']; 15 | 16 | pascalCase = 'MochaChaiLatte'; 17 | pascalCaseWords = ['Mocha', 'Chai', 'Latte']; 18 | 19 | camelCase = 'mochaChaiLatte'; 20 | camelCaseWords = ['mocha', 'Chai', 'Latte']; 21 | 22 | screamingSnakeCase = 'MOCHA_CHAI_LATTE'; 23 | screamingSnakeCaseWords = ['MOCHA', 'CHAI', 'LATTE']; 24 | }); 25 | 26 | describe('#toSnakeCase', () => { 27 | it('should be a function', () => { 28 | expect(caseConverter.toSnakeCase).to.exist; 29 | expect(caseConverter.toSnakeCase).to.be.instanceOf(Function); 30 | }); 31 | 32 | it('should receive string in snake case and return string in snake case', () => { 33 | assert.equal(caseConverter.toSnakeCase(snakeCaseWords), snakeCase); 34 | }); 35 | 36 | it('should receive string in upper camel case and return string in snake case', () => { 37 | assert.equal(caseConverter.toSnakeCase(pascalCaseWords), snakeCase); 38 | }); 39 | 40 | it('should receive string in lower camel case and return string in snake case', () => { 41 | assert.equal(caseConverter.toSnakeCase(camelCaseWords), snakeCase); 42 | }); 43 | 44 | it('should receive string in screaming snake case and return string in snake case', () => { 45 | assert.equal(caseConverter.toSnakeCase(screamingSnakeCaseWords), snakeCase); 46 | }); 47 | }); 48 | 49 | describe('#toPascalCase', () => { 50 | it('should be a function', () => { 51 | expect(caseConverter.toPascalCase).to.exist; 52 | expect(caseConverter.toPascalCase).to.be.instanceOf(Function); 53 | }); 54 | 55 | it('should receive string in snake case and return string in upper camel case', () => { 56 | assert.equal(caseConverter.toPascalCase(snakeCaseWords), pascalCase); 57 | }); 58 | 59 | it('should receive string in upper camel case and return string in upper camel case', () => { 60 | assert.equal(caseConverter.toPascalCase(pascalCaseWords), pascalCase); 61 | }); 62 | 63 | it('should receive string in lower camel case and return string in upper camel case', () => { 64 | assert.equal(caseConverter.toPascalCase(camelCaseWords), pascalCase); 65 | }); 66 | 67 | it('should receive string in screaming snake case and return string in upper camel case', () => { 68 | assert.equal(caseConverter.toPascalCase(screamingSnakeCaseWords), pascalCase); 69 | }); 70 | }); 71 | 72 | describe('#toCamelCase', () => { 73 | it('should be a function', () => { 74 | expect(caseConverter.toCamelCase).to.exist; 75 | expect(caseConverter.toCamelCase).to.be.instanceOf(Function); 76 | }); 77 | 78 | it('should receive string in snake case and return string in lower camel case', () => { 79 | assert.equal(caseConverter.toCamelCase(snakeCaseWords), camelCase); 80 | }); 81 | 82 | it('should receive string in upper camel case and return string in lower camel case', () => { 83 | assert.equal(caseConverter.toCamelCase(pascalCaseWords), camelCase); 84 | }); 85 | 86 | it('should receive string in lower camel case and return string in lower camel case', () => { 87 | assert.equal(caseConverter.toCamelCase(camelCaseWords), camelCase); 88 | }); 89 | 90 | it('should receive string in screaming snake case and return string in lower camel case', () => { 91 | assert.equal(caseConverter.toCamelCase(screamingSnakeCaseWords), camelCase); 92 | }); 93 | }); 94 | 95 | describe('#toScreamingSnakeCase', () => { 96 | it('should be a function', () => { 97 | expect(caseConverter.toScreamingSnakeCase).to.exist; 98 | expect(caseConverter.toScreamingSnakeCase).to.be.instanceOf(Function); 99 | }); 100 | 101 | it('should receive string in snake case and return string in screaming snake case', () => { 102 | assert.equal(caseConverter.toScreamingSnakeCase(snakeCaseWords),screamingSnakeCase); 103 | }); 104 | 105 | it('should receive string in upper camel case and return string in screaming snake case', () => { 106 | assert.equal(caseConverter.toScreamingSnakeCase(pascalCaseWords), screamingSnakeCase); 107 | }); 108 | 109 | it('should receive string in lower camel case and return string in screaming snake case', () => { 110 | assert.equal(caseConverter.toScreamingSnakeCase(camelCaseWords), screamingSnakeCase); 111 | }); 112 | 113 | it('should receive string in screaming snake case and return string in screaming snake case', () => { 114 | assert.equal(caseConverter.toScreamingSnakeCase(screamingSnakeCaseWords), screamingSnakeCase); 115 | }); 116 | }); 117 | 118 | describe('#convert', () => { 119 | let func; 120 | 121 | beforeEach(() => { 122 | func = sinon.spy(); 123 | }); 124 | 125 | it('should be a function', () => { 126 | expect(caseConverter.convert).to.exist; 127 | expect(caseConverter.convert).to.be.instanceOf(Function); 128 | }); 129 | 130 | it('should run callback once', () => { 131 | caseConverter.convert('word', func); 132 | assert(func.calledOnce); 133 | }); 134 | }); 135 | }); 136 | -------------------------------------------------------------------------------- /test/helpers-tests/logs-test.js: -------------------------------------------------------------------------------- 1 | let chai = require('chai'); 2 | let assert = chai.assert; 3 | let expect = chai.expect; 4 | let sinon = require('sinon'); 5 | 6 | let logs = require('../../helpers/logs.js'); 7 | 8 | describe('Logs', () => { 9 | let spy; 10 | 11 | beforeEach(() => { 12 | spy = sinon.spy(console, 'log'); 13 | }); 14 | 15 | afterEach(() => { 16 | spy.restore(); 17 | }); 18 | 19 | describe('#fileExistErrorLog', () => { 20 | it('should export fileExistErrorLog function', () => { 21 | expect(logs.fileExistErrorLog).to.exist; 22 | expect(logs.fileExistErrorLog).to.be.instanceOf(Function); 23 | }); 24 | 25 | it('should call console log once', () => { 26 | logs.fileExistErrorLog('exist_file.js'); 27 | expect(spy.calledOnce).to.be.true; 28 | }); 29 | }); 30 | 31 | describe('#noMethodErrorLog', () => { 32 | it('should export noMethodErrorLog function', () => { 33 | expect(logs.noMethodErrorLog).to.exist; 34 | expect(logs.noMethodErrorLog).to.be.instanceOf(Function); 35 | }); 36 | 37 | it('should call console log', () => { 38 | logs.noMethodErrorLog(); 39 | expect(spy.called).to.be.true; 40 | }); 41 | }); 42 | 43 | describe('#noNameErrorLog', () => { 44 | it('should export noNameErrorLog function', () => { 45 | expect(logs.noNameErrorLog).to.exist; 46 | expect(logs.noNameErrorLog).to.be.instanceOf(Function); 47 | }); 48 | 49 | it('should call console log', () => { 50 | logs.noNameErrorLog('fileType'); 51 | expect(spy.called).to.be.true; 52 | }); 53 | }); 54 | 55 | describe('#createFileLog', () => { 56 | it('should export createFileLog function', () => { 57 | expect(logs.createFileLog).to.exist; 58 | expect(logs.createFileLog).to.be.instanceOf(Function); 59 | }); 60 | 61 | it('should call console log', () => { 62 | logs.createFileLog('some_random_file'); 63 | expect(spy.called).to.be.true; 64 | }); 65 | }); 66 | 67 | describe('#removedFileLog', () => { 68 | it('should export removedFileLog function', () => { 69 | expect(logs.removedFileLog).to.exist; 70 | expect(logs.removedFileLog).to.be.instanceOf(Function); 71 | }); 72 | 73 | it('should call console log', () => { 74 | logs.removedFileLog('good_bye'); 75 | expect(spy.called).to.be.true; 76 | }); 77 | }); 78 | 79 | describe('#noExistFileErrorLog', () => { 80 | it('should export noExistFileErrorLog function', () => { 81 | expect(logs.noExistFileErrorLog).to.exist; 82 | expect(logs.noExistFileErrorLog).to.be.instanceOf(Function); 83 | }); 84 | 85 | it('should call console log', () => { 86 | logs.noExistFileErrorLog('noexistant_file'); 87 | expect(spy.called).to.be.true; 88 | }); 89 | }); 90 | }); -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --recursive -------------------------------------------------------------------------------- /test/template-tests/base-test.js: -------------------------------------------------------------------------------- 1 | let chai = require('chai'); 2 | let assert = chai.assert; 3 | let expect = chai.expect; 4 | 5 | let baseTemplates = require('../../templates/base.js'); 6 | 7 | describe('Base Templates', () => { 8 | describe('#appJSX', () => { 9 | it('should export appJSX function', () => { 10 | expect(baseTemplates.appJSX).to.exist; 11 | expect(baseTemplates.appJSX).to.be.instanceOf(Function); 12 | }); 13 | 14 | it('should return correct template', () => { 15 | let appTest = ['import React from \'react\';', 16 | '', 17 | 'const App = ({ store }) => (', 18 | '
', 19 | '

App

', 20 | '
', 21 | ');', 22 | '', 23 | 'export default App;', 24 | '' 25 | ]; 26 | 27 | let appCode = baseTemplates.appJSX().split('\n'); 28 | 29 | assert.equal(appTest.length, appCode.length); 30 | 31 | for (let i = 0; i < appTest.length; i++) { 32 | assert.equal(appTest[i], appCode[i]); 33 | } 34 | }); 35 | }); 36 | 37 | describe('#rootJSX', () => { 38 | it('should export rootJSX function', () => { 39 | expect(baseTemplates.rootJSX).to.exist; 40 | expect(baseTemplates.rootJSX).to.be.instanceOf(Function); 41 | }); 42 | 43 | it('should return correct template', () => { 44 | let rootTest = ['import React from \'react\';', 45 | 'import { Provider } from \'react-redux\';', 46 | 'import App from \'./app.jsx\';', 47 | '', 48 | 'const Root = ({ store }) => (', 49 | ' ', 50 | ' ', 51 | ' ', 52 | ');', 53 | '', 54 | 'export default Root;', 55 | '' 56 | ]; 57 | 58 | let rootCode = baseTemplates.rootJSX().split('\n'); 59 | 60 | assert.equal(rootTest.length, rootCode.length); 61 | 62 | for (let i = 0; i < rootTest.length; i++) { 63 | assert.equal(rootTest[i], rootCode[i]); 64 | } 65 | }); 66 | }); 67 | 68 | describe('#entryJSX', () => { 69 | it('should export entryJSX function', () => { 70 | expect(baseTemplates.entryJSX).to.exist; 71 | expect(baseTemplates.entryJSX).to.be.instanceOf(Function); 72 | }); 73 | 74 | it('should return correct template', () => { 75 | let entryTest = ['import React from \'react\';', 76 | 'import ReactDOM from \'react-dom\';', 77 | 'import configureStore from \'./store/store.js\';', 78 | 'import Root from \'./components/root\';', 79 | '', 80 | 'document.addEventListener(\'DOMContentLoaded\', () => {', 81 | ' const root = document.getElementById(\'root\');', 82 | ' const store = configureStore();', 83 | '', 84 | ' ReactDOM.render(, root);', 85 | '});', 86 | '' 87 | ]; 88 | 89 | let entryCode = baseTemplates.entryJSX().split('\n'); 90 | 91 | assert.equal(entryTest.length, entryCode.length); 92 | 93 | for (let i = 0; i < entryTest.length; i++) { 94 | assert.equal(entryTest[i], entryCode[i]); 95 | } 96 | }); 97 | }); 98 | }); -------------------------------------------------------------------------------- /test/template-tests/component-test.js: -------------------------------------------------------------------------------- 1 | let chai = require('chai'); 2 | let assert = chai.assert; 3 | let expect = chai.expect; 4 | 5 | let componentTemplates = require('../../templates/component.js'); 6 | 7 | describe('Component Templates', () => { 8 | let componentName1; 9 | let componentName2; 10 | 11 | beforeEach(() => { 12 | componentName1 = 'testComponent'; 13 | componentName2 = 'someOtherName'; 14 | }); 15 | 16 | describe('#functional', () => { 17 | it('should export functional function', () => { 18 | expect(componentTemplates.functional).to.exist; 19 | expect(componentTemplates.functional).to.be.instanceOf(Function); 20 | }); 21 | 22 | it('should return correct template', () => { 23 | let functionalTest = [ 24 | 'import React from \'react\';', 25 | '', 26 | 'const testComponent = (props) => (', 27 | '', 28 | ');', 29 | '', 30 | 'export default testComponent;', 31 | '' 32 | ]; 33 | 34 | let functionalCode = componentTemplates.functional(componentName1).split('\n'); 35 | 36 | assert.equal(functionalTest.length, functionalCode.length); 37 | 38 | for (let i = 0; i < functionalTest.length; i++) { 39 | assert.equal(functionalTest[i], functionalCode[i]); 40 | } 41 | }); 42 | 43 | it('should use the passed in component name', () => { 44 | let functionalCode = componentTemplates.functional(componentName2); 45 | expect(functionalCode).to.include(componentName2); 46 | }); 47 | }); 48 | 49 | describe('#presentational', () => { 50 | it('should export presentational function', () => { 51 | expect(componentTemplates.presentational).to.exist; 52 | expect(componentTemplates.presentational).to.be.instanceOf(Function); 53 | }); 54 | 55 | it('should return correct template', () => { 56 | let presentationalTest = [ 57 | 'import React from \'react\';', 58 | '', 59 | 'class testComponent extends React.Component {', 60 | ' constructor(props) {', 61 | ' super(props);', 62 | ' }', 63 | '', 64 | ' render() {', 65 | ' return (', 66 | '
', 67 | ' // your code here...', 68 | '
', 69 | ' );', 70 | ' }', 71 | '}', 72 | '', 73 | 'export default testComponent;', 74 | '' 75 | ]; 76 | 77 | let presentationalCode = componentTemplates.presentational(componentName1).split('\n'); 78 | 79 | assert.equal(presentationalTest.length, presentationalCode.length); 80 | 81 | for (let i = 0; i < presentationalTest.length; i++) { 82 | assert.equal(presentationalTest[i], presentationalCode[i]); 83 | } 84 | }); 85 | 86 | it('should use the passed in component name', () => { 87 | let presentationalCode = componentTemplates.presentational(componentName2); 88 | expect(presentationalCode).to.include(componentName2); 89 | }); 90 | }); 91 | 92 | describe('#container', () => { 93 | it('should export container function', () => { 94 | expect(componentTemplates.container).to.exist; 95 | expect(componentTemplates.container).to.be.instanceOf(Function); 96 | }); 97 | 98 | it('should return correct template', () => { 99 | let containerTest = [ 100 | 'import React from \'react\';', 101 | 'import { connect } from \'react-redux\';', 102 | 'import TestComponent from \'./test_component.jsx\';', 103 | '', 104 | 'const mapStateToProps = (state, ownProps) => ({', 105 | ' // your code here...', 106 | '});', 107 | '', 108 | 'const mapDispatchToProps = dispatch => ({', 109 | ' // your code here...', 110 | '});', 111 | '', 112 | 'export default connect(', 113 | ' mapStateToProps,', 114 | ' mapDispatchToProps', 115 | ')(TestComponent);', 116 | '' 117 | ]; 118 | 119 | let containerCode = componentTemplates.container('TestComponent', 'test_component').split('\n'); 120 | 121 | assert.equal(containerTest.length, containerCode.length); 122 | 123 | for (let i = 0; i < containerTest.length; i++) { 124 | assert.equal(containerTest[i], containerCode[i]); 125 | } 126 | }); 127 | 128 | it('should use the passed in component name', () => { 129 | let nameUCC = 'AnotherComponent'; 130 | let nameSC = 'another_component'; 131 | let presentationalCode = componentTemplates.container(nameUCC, nameSC); 132 | expect(presentationalCode).to.include(nameUCC); 133 | expect(presentationalCode).to.include(nameSC); 134 | }); 135 | }); 136 | }); -------------------------------------------------------------------------------- /test/template-tests/help-test.js: -------------------------------------------------------------------------------- 1 | let chai = require('chai'); 2 | let assert = chai.assert; 3 | let expect = chai.expect; 4 | let sinon = require('sinon'); 5 | require('mocha-sinon'); 6 | 7 | let helpTemplates = require('../../templates/help.js'); 8 | 9 | describe('Help Templates', () => { 10 | let spy; 11 | 12 | beforeEach(() => { 13 | spy = sinon.spy(console, 'log'); 14 | }); 15 | 16 | afterEach(() => { 17 | spy.restore(); 18 | }); 19 | 20 | describe('#helpTemplate', () => { 21 | beforeEach(() => { 22 | helpTemplates.helpTemplate(); 23 | }); 24 | 25 | it('should export basicHelp function', () => { 26 | expect(helpTemplates.helpTemplate).to.exist; 27 | expect(helpTemplates.helpTemplate).to.be.instanceOf(Function); 28 | }); 29 | 30 | it('should call console logs', () => { 31 | expect(spy.called).to.be.true; 32 | }); 33 | }); 34 | }); -------------------------------------------------------------------------------- /test/template-tests/reducer-test.js: -------------------------------------------------------------------------------- 1 | let chai = require('chai'); 2 | let assert = chai.assert; 3 | let expect = chai.expect; 4 | 5 | let reducerTemplates = require('../../templates/reducer.js'); 6 | 7 | describe('Reducer Templates', () => { 8 | describe('#root', () => { 9 | it('should export the root function', () => { 10 | expect(reducerTemplates.root).to.exist; 11 | expect(reducerTemplates.root).to.be.instanceOf(Function); 12 | }); 13 | 14 | it('should render empty template when both arguments are empty', () => { 15 | let rootTest = [ 16 | 'import { combineReducers } from \'redux\';', 17 | '', 18 | '', 19 | 'const rootReducer = combineReducers({', 20 | '', 21 | '});', 22 | '', 23 | 'export default rootReducer;' 24 | ]; 25 | 26 | let rootCode = reducerTemplates.root('', '').split('\n'); 27 | 28 | assert.equal(rootTest.length, rootCode.length); 29 | 30 | for (let i = 0; i < rootTest.length; i++) { 31 | assert.equal(rootTest[i], rootCode[i]); 32 | } 33 | }); 34 | 35 | it('should render the with imports and key pairs', () => { 36 | let imports = "import userReducer from './user_reducer.js';"; 37 | let keyPairs = " user: userReducer"; 38 | 39 | let rootTest = [ 40 | 'import { combineReducers } from \'redux\';', 41 | 'import userReducer from \'./user_reducer.js\';', 42 | '', 43 | 'const rootReducer = combineReducers({', 44 | ' user: userReducer', 45 | '});', 46 | '', 47 | 'export default rootReducer;' 48 | ]; 49 | 50 | let rootCode = reducerTemplates.root(imports, keyPairs).split('\n'); 51 | 52 | assert.equal(rootTest.length, rootCode.length); 53 | 54 | for (let i = 0; i < rootTest.length; i++) { 55 | assert.equal(rootTest[i], rootCode[i]); 56 | } 57 | }); 58 | }); 59 | 60 | describe('#reducer', () => { 61 | it('should export the reducer function', () => { 62 | expect(reducerTemplates.reducer).to.exist; 63 | expect(reducerTemplates.reducer).to.be.instanceOf(Function); 64 | }); 65 | 66 | it('should render empty template when no argument is passed in', () => { 67 | let reducerTest = [ 68 | 'import { merge } from \'lodash\';', 69 | '', 70 | 'let _defaultState = {};', 71 | '', 72 | 'const userReducer = (state = _defaultState, action) => {', 73 | ' Object.freeze(state);', 74 | ' switch(action.type) {', 75 | ' default:', 76 | ' return state;', 77 | ' }', 78 | '};', 79 | '', 80 | 'export default userReducer;' 81 | ]; 82 | 83 | let reducerCode = reducerTemplates.reducer('user', []).split('\n'); 84 | 85 | assert.equal(reducerTest.length, reducerCode.length); 86 | 87 | for (let i = 0; i < reducerTest.length; i++) { 88 | assert.equal(reducerTest[i], reducerCode[i]); 89 | } 90 | }); 91 | 92 | it('should render the with imports and key pairs', () => { 93 | let name = 'user'; 94 | let actions = ['receiveUser', 'clearUser']; 95 | let reducerTest = [ 96 | 'import { merge } from \'lodash\';', 97 | 'import {', 98 | ' RECEIVE_USER,', 99 | ' CLEAR_USER } from "../actions/user_actions.js";', 100 | '', 101 | 'let _defaultState = {};', 102 | '', 103 | 'const userReducer = (state = _defaultState, action) => {', 104 | ' Object.freeze(state);', 105 | ' switch(action.type) {', 106 | ' case RECEIVE_USER:', 107 | ' // your code here', 108 | ' case CLEAR_USER:', 109 | ' // your code here', 110 | ' default:', 111 | ' return state;', 112 | ' }', 113 | '};', 114 | '', 115 | 'export default userReducer;' 116 | ]; 117 | 118 | let reducerCode = reducerTemplates.reducer(name, actions).split('\n'); 119 | 120 | assert.equal(reducerTest.length, reducerCode.length); 121 | 122 | for (let i = 0; i < reducerTest.length; i++) { 123 | assert.equal(reducerTest[i], reducerCode[i]); 124 | } 125 | }); 126 | }); 127 | }); -------------------------------------------------------------------------------- /test/template-tests/store-test.js: -------------------------------------------------------------------------------- 1 | let chai = require('chai'); 2 | let assert = chai.assert; 3 | let expect = chai.expect; 4 | 5 | let storeTemplates = require('../../templates/store.js'); 6 | 7 | describe('Store Template', () => { 8 | describe('#storeFormat', () => { 9 | it('should export a storeFormat function', () => { 10 | expect(storeTemplates).to.exist; 11 | expect(storeTemplates).to.be.instanceOf(Function); 12 | }); 13 | 14 | it('should render a store template', () => { 15 | let storeTest = [ 16 | 'import { createStore, applyMiddleware } from \'redux\';', 17 | 'import thunk from \'redux-thunk\';', 18 | 'import rootReducer from \'../reducers/root_reducer.js\';', 19 | '', 20 | 'const _defaultState = {};', 21 | '', 22 | 'const configureStore = (preloadedState = _defaultState) => (', 23 | ' createStore(', 24 | ' rootReducer,', 25 | ' preloadedState,', 26 | ' applyMiddleware(thunk)', 27 | ' )', 28 | ');', 29 | '', 30 | 'export default configureStore;', 31 | '' 32 | ]; 33 | 34 | let storeCode = storeTemplates().split('\n'); 35 | 36 | assert.equal(storeTest.length, storeCode.length); 37 | 38 | for (let i = 0; i < storeTest.length; i++) { 39 | assert.equal(storeTest[i], storeCode[i]); 40 | } 41 | }); 42 | }); 43 | }); -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | acorn@^1.0.3: 6 | version "1.2.2" 7 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-1.2.2.tgz#c8ce27de0acc76d896d2b1fad3df588d9e82f014" 8 | 9 | ansi-regex@^2.0.0: 10 | version "2.1.1" 11 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" 12 | 13 | ansi-styles@^2.2.1: 14 | version "2.2.1" 15 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" 16 | 17 | argparse@^1.0.7: 18 | version "1.0.9" 19 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" 20 | dependencies: 21 | sprintf-js "~1.0.2" 22 | 23 | asn1@~0.2.3: 24 | version "0.2.3" 25 | resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" 26 | 27 | assert-plus@1.0.0, assert-plus@^1.0.0: 28 | version "1.0.0" 29 | resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" 30 | 31 | assert-plus@^0.2.0: 32 | version "0.2.0" 33 | resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" 34 | 35 | assertion-error@^1.0.1: 36 | version "1.0.2" 37 | resolved "https://registry.yarnpkg.com/assertion-error/-/assertion-error-1.0.2.tgz#13ca515d86206da0bac66e834dd397d87581094c" 38 | 39 | asynckit@^0.4.0: 40 | version "0.4.0" 41 | resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" 42 | 43 | aws-sign2@~0.6.0: 44 | version "0.6.0" 45 | resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" 46 | 47 | aws4@^1.2.1: 48 | version "1.6.0" 49 | resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" 50 | 51 | balanced-match@^0.4.1: 52 | version "0.4.2" 53 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838" 54 | 55 | bcrypt-pbkdf@^1.0.0: 56 | version "1.0.1" 57 | resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d" 58 | dependencies: 59 | tweetnacl "^0.14.3" 60 | 61 | bit-mask@0.0.2-alpha: 62 | version "0.0.2-alpha" 63 | resolved "https://registry.yarnpkg.com/bit-mask/-/bit-mask-0.0.2-alpha.tgz#42880fa80055151165d5fa15b331bc0459c27372" 64 | dependencies: 65 | prime "0.0.5-alpha" 66 | 67 | blanket@^1.2.3: 68 | version "1.2.3" 69 | resolved "https://registry.yarnpkg.com/blanket/-/blanket-1.2.3.tgz#151b4987c3bd84552bb5f03b90ef5f7e5931e473" 70 | dependencies: 71 | acorn "^1.0.3" 72 | falafel "~1.2.0" 73 | foreach "^2.0.5" 74 | isarray "0.0.1" 75 | object-keys "^1.0.6" 76 | xtend "~4.0.0" 77 | 78 | bluebird@^3.5.0: 79 | version "3.5.0" 80 | resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c" 81 | 82 | boom@2.x.x: 83 | version "2.10.1" 84 | resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" 85 | dependencies: 86 | hoek "2.x.x" 87 | 88 | brace-expansion@^1.0.0: 89 | version "1.1.7" 90 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.7.tgz#3effc3c50e000531fb720eaff80f0ae8ef23cf59" 91 | dependencies: 92 | balanced-match "^0.4.1" 93 | concat-map "0.0.1" 94 | 95 | browser-stdout@1.3.0: 96 | version "1.3.0" 97 | resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.0.tgz#f351d32969d32fa5d7a5567154263d928ae3bd1f" 98 | 99 | call-me-maybe@^1.0.1: 100 | version "1.0.1" 101 | resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" 102 | 103 | caseless@~0.11.0: 104 | version "0.11.0" 105 | resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.11.0.tgz#715b96ea9841593cc33067923f5ec60ebda4f7d7" 106 | 107 | chai-fs@^1.0.0: 108 | version "1.0.0" 109 | resolved "https://registry.yarnpkg.com/chai-fs/-/chai-fs-1.0.0.tgz#8b242852748a54f1df91f1ea430bb39748cf7421" 110 | dependencies: 111 | bit-mask "0.0.2-alpha" 112 | readdir-enhanced "^1.4.0" 113 | 114 | chai@^3.5.0: 115 | version "3.5.0" 116 | resolved "https://registry.yarnpkg.com/chai/-/chai-3.5.0.tgz#4d02637b067fe958bdbfdd3a40ec56fef7373247" 117 | dependencies: 118 | assertion-error "^1.0.1" 119 | deep-eql "^0.1.3" 120 | type-detect "^1.0.0" 121 | 122 | chalk@^1.1.1: 123 | version "1.1.3" 124 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" 125 | dependencies: 126 | ansi-styles "^2.2.1" 127 | escape-string-regexp "^1.0.2" 128 | has-ansi "^2.0.0" 129 | strip-ansi "^3.0.0" 130 | supports-color "^2.0.0" 131 | 132 | colors@^1.1.2: 133 | version "1.1.2" 134 | resolved "https://registry.yarnpkg.com/colors/-/colors-1.1.2.tgz#168a4701756b6a7f51a12ce0c97bfa28c084ed63" 135 | 136 | combined-stream@^1.0.5, combined-stream@~1.0.5: 137 | version "1.0.5" 138 | resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" 139 | dependencies: 140 | delayed-stream "~1.0.0" 141 | 142 | commander@2.9.0, commander@^2.9.0: 143 | version "2.9.0" 144 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" 145 | dependencies: 146 | graceful-readlink ">= 1.0.0" 147 | 148 | concat-map@0.0.1: 149 | version "0.0.1" 150 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 151 | 152 | coveralls@^2.13.1: 153 | version "2.13.1" 154 | resolved "https://registry.yarnpkg.com/coveralls/-/coveralls-2.13.1.tgz#d70bb9acc1835ec4f063ff9dac5423c17b11f178" 155 | dependencies: 156 | js-yaml "3.6.1" 157 | lcov-parse "0.0.10" 158 | log-driver "1.2.5" 159 | minimist "1.2.0" 160 | request "2.79.0" 161 | 162 | cryptiles@2.x.x: 163 | version "2.0.5" 164 | resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" 165 | dependencies: 166 | boom "2.x.x" 167 | 168 | dashdash@^1.12.0: 169 | version "1.14.1" 170 | resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" 171 | dependencies: 172 | assert-plus "^1.0.0" 173 | 174 | debug@2.6.0: 175 | version "2.6.0" 176 | resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.0.tgz#bc596bcabe7617f11d9fa15361eded5608b8499b" 177 | dependencies: 178 | ms "0.7.2" 179 | 180 | deep-eql@^0.1.3: 181 | version "0.1.3" 182 | resolved "https://registry.yarnpkg.com/deep-eql/-/deep-eql-0.1.3.tgz#ef558acab8de25206cd713906d74e56930eb69f2" 183 | dependencies: 184 | type-detect "0.1.1" 185 | 186 | delayed-stream@~1.0.0: 187 | version "1.0.0" 188 | resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" 189 | 190 | diff@3.2.0, diff@^3.1.0: 191 | version "3.2.0" 192 | resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9" 193 | 194 | ecc-jsbn@~0.1.1: 195 | version "0.1.1" 196 | resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" 197 | dependencies: 198 | jsbn "~0.1.0" 199 | 200 | es6-promise@^4.1.0: 201 | version "4.1.0" 202 | resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.1.0.tgz#dda03ca8f9f89bc597e689842929de7ba8cebdf0" 203 | 204 | escape-string-regexp@1.0.5, escape-string-regexp@^1.0.2: 205 | version "1.0.5" 206 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 207 | 208 | esprima@^2.6.0: 209 | version "2.7.3" 210 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" 211 | 212 | extend@~3.0.0: 213 | version "3.0.1" 214 | resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" 215 | 216 | extsprintf@1.0.2: 217 | version "1.0.2" 218 | resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550" 219 | 220 | falafel@~1.2.0: 221 | version "1.2.0" 222 | resolved "https://registry.yarnpkg.com/falafel/-/falafel-1.2.0.tgz#c18d24ef5091174a497f318cd24b026a25cddab4" 223 | dependencies: 224 | acorn "^1.0.3" 225 | foreach "^2.0.5" 226 | isarray "0.0.1" 227 | object-keys "^1.0.6" 228 | 229 | foreach@^2.0.5: 230 | version "2.0.5" 231 | resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99" 232 | 233 | forever-agent@~0.6.1: 234 | version "0.6.1" 235 | resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" 236 | 237 | form-data@~2.1.1: 238 | version "2.1.4" 239 | resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1" 240 | dependencies: 241 | asynckit "^0.4.0" 242 | combined-stream "^1.0.5" 243 | mime-types "^2.1.12" 244 | 245 | formatio@1.2.0: 246 | version "1.2.0" 247 | resolved "https://registry.yarnpkg.com/formatio/-/formatio-1.2.0.tgz#f3b2167d9068c4698a8d51f4f760a39a54d818eb" 248 | dependencies: 249 | samsam "1.x" 250 | 251 | fs.realpath@^1.0.0: 252 | version "1.0.0" 253 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 254 | 255 | generate-function@^2.0.0: 256 | version "2.0.0" 257 | resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74" 258 | 259 | generate-object-property@^1.1.0: 260 | version "1.2.0" 261 | resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0" 262 | dependencies: 263 | is-property "^1.0.0" 264 | 265 | getpass@^0.1.1: 266 | version "0.1.7" 267 | resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" 268 | dependencies: 269 | assert-plus "^1.0.0" 270 | 271 | glob-to-regexp@^0.3.0: 272 | version "0.3.0" 273 | resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz#8c5a1494d2066c570cc3bfe4496175acc4d502ab" 274 | 275 | glob@7.1.1, glob@^7.0.0: 276 | version "7.1.1" 277 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.1.tgz#805211df04faaf1c63a3600306cdf5ade50b2ec8" 278 | dependencies: 279 | fs.realpath "^1.0.0" 280 | inflight "^1.0.4" 281 | inherits "2" 282 | minimatch "^3.0.2" 283 | once "^1.3.0" 284 | path-is-absolute "^1.0.0" 285 | 286 | "graceful-readlink@>= 1.0.0": 287 | version "1.0.1" 288 | resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" 289 | 290 | growl@1.9.2: 291 | version "1.9.2" 292 | resolved "https://registry.yarnpkg.com/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" 293 | 294 | har-validator@~2.0.6: 295 | version "2.0.6" 296 | resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-2.0.6.tgz#cdcbc08188265ad119b6a5a7c8ab70eecfb5d27d" 297 | dependencies: 298 | chalk "^1.1.1" 299 | commander "^2.9.0" 300 | is-my-json-valid "^2.12.4" 301 | pinkie-promise "^2.0.0" 302 | 303 | has-ansi@^2.0.0: 304 | version "2.0.0" 305 | resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" 306 | dependencies: 307 | ansi-regex "^2.0.0" 308 | 309 | has-flag@^1.0.0: 310 | version "1.0.0" 311 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" 312 | 313 | hawk@~3.1.3: 314 | version "3.1.3" 315 | resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" 316 | dependencies: 317 | boom "2.x.x" 318 | cryptiles "2.x.x" 319 | hoek "2.x.x" 320 | sntp "1.x.x" 321 | 322 | hoek@2.x.x: 323 | version "2.16.3" 324 | resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" 325 | 326 | http-signature@~1.1.0: 327 | version "1.1.1" 328 | resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" 329 | dependencies: 330 | assert-plus "^0.2.0" 331 | jsprim "^1.2.2" 332 | sshpk "^1.7.0" 333 | 334 | inflight@^1.0.4: 335 | version "1.0.6" 336 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 337 | dependencies: 338 | once "^1.3.0" 339 | wrappy "1" 340 | 341 | inherits@2: 342 | version "2.0.3" 343 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" 344 | 345 | interpret@^1.0.0: 346 | version "1.0.3" 347 | resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.3.tgz#cbc35c62eeee73f19ab7b10a801511401afc0f90" 348 | 349 | is-my-json-valid@^2.12.4: 350 | version "2.16.0" 351 | resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz#f079dd9bfdae65ee2038aae8acbc86ab109e3693" 352 | dependencies: 353 | generate-function "^2.0.0" 354 | generate-object-property "^1.1.0" 355 | jsonpointer "^4.0.0" 356 | xtend "^4.0.0" 357 | 358 | is-property@^1.0.0: 359 | version "1.0.2" 360 | resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" 361 | 362 | is-typedarray@~1.0.0: 363 | version "1.0.0" 364 | resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" 365 | 366 | isarray@0.0.1: 367 | version "0.0.1" 368 | resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" 369 | 370 | isstream@~0.1.2: 371 | version "0.1.2" 372 | resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" 373 | 374 | jodid25519@^1.0.0: 375 | version "1.0.2" 376 | resolved "https://registry.yarnpkg.com/jodid25519/-/jodid25519-1.0.2.tgz#06d4912255093419477d425633606e0e90782967" 377 | dependencies: 378 | jsbn "~0.1.0" 379 | 380 | js-yaml@3.6.1: 381 | version "3.6.1" 382 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.6.1.tgz#6e5fe67d8b205ce4d22fad05b7781e8dadcc4b30" 383 | dependencies: 384 | argparse "^1.0.7" 385 | esprima "^2.6.0" 386 | 387 | jsbn@~0.1.0: 388 | version "0.1.1" 389 | resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" 390 | 391 | json-schema@0.2.3: 392 | version "0.2.3" 393 | resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" 394 | 395 | json-stringify-safe@~5.0.1: 396 | version "5.0.1" 397 | resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" 398 | 399 | json3@3.3.2: 400 | version "3.3.2" 401 | resolved "https://registry.yarnpkg.com/json3/-/json3-3.3.2.tgz#3c0434743df93e2f5c42aee7b19bcb483575f4e1" 402 | 403 | jsonpointer@^4.0.0: 404 | version "4.0.1" 405 | resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" 406 | 407 | jsprim@^1.2.2: 408 | version "1.4.0" 409 | resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.0.tgz#a3b87e40298d8c380552d8cc7628a0bb95a22918" 410 | dependencies: 411 | assert-plus "1.0.0" 412 | extsprintf "1.0.2" 413 | json-schema "0.2.3" 414 | verror "1.3.6" 415 | 416 | lcov-parse@0.0.10: 417 | version "0.0.10" 418 | resolved "https://registry.yarnpkg.com/lcov-parse/-/lcov-parse-0.0.10.tgz#1b0b8ff9ac9c7889250582b70b71315d9da6d9a3" 419 | 420 | lodash._baseassign@^3.0.0: 421 | version "3.2.0" 422 | resolved "https://registry.yarnpkg.com/lodash._baseassign/-/lodash._baseassign-3.2.0.tgz#8c38a099500f215ad09e59f1722fd0c52bfe0a4e" 423 | dependencies: 424 | lodash._basecopy "^3.0.0" 425 | lodash.keys "^3.0.0" 426 | 427 | lodash._basecopy@^3.0.0: 428 | version "3.0.1" 429 | resolved "https://registry.yarnpkg.com/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz#8da0e6a876cf344c0ad8a54882111dd3c5c7ca36" 430 | 431 | lodash._basecreate@^3.0.0: 432 | version "3.0.3" 433 | resolved "https://registry.yarnpkg.com/lodash._basecreate/-/lodash._basecreate-3.0.3.tgz#1bc661614daa7fc311b7d03bf16806a0213cf821" 434 | 435 | lodash._getnative@^3.0.0: 436 | version "3.9.1" 437 | resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" 438 | 439 | lodash._isiterateecall@^3.0.0: 440 | version "3.0.9" 441 | resolved "https://registry.yarnpkg.com/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz#5203ad7ba425fae842460e696db9cf3e6aac057c" 442 | 443 | lodash.create@3.1.1: 444 | version "3.1.1" 445 | resolved "https://registry.yarnpkg.com/lodash.create/-/lodash.create-3.1.1.tgz#d7f2849f0dbda7e04682bb8cd72ab022461debe7" 446 | dependencies: 447 | lodash._baseassign "^3.0.0" 448 | lodash._basecreate "^3.0.0" 449 | lodash._isiterateecall "^3.0.0" 450 | 451 | lodash.isarguments@^3.0.0: 452 | version "3.1.0" 453 | resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" 454 | 455 | lodash.isarray@^3.0.0: 456 | version "3.0.4" 457 | resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" 458 | 459 | lodash.keys@^3.0.0: 460 | version "3.1.2" 461 | resolved "https://registry.yarnpkg.com/lodash.keys/-/lodash.keys-3.1.2.tgz#4dbc0472b156be50a0b286855d1bd0b0c656098a" 462 | dependencies: 463 | lodash._getnative "^3.0.0" 464 | lodash.isarguments "^3.0.0" 465 | lodash.isarray "^3.0.0" 466 | 467 | log-driver@1.2.5: 468 | version "1.2.5" 469 | resolved "https://registry.yarnpkg.com/log-driver/-/log-driver-1.2.5.tgz#7ae4ec257302fd790d557cb10c97100d857b0056" 470 | 471 | lolex@^1.6.0: 472 | version "1.6.0" 473 | resolved "https://registry.yarnpkg.com/lolex/-/lolex-1.6.0.tgz#3a9a0283452a47d7439e72731b9e07d7386e49f6" 474 | 475 | mime-db@~1.27.0: 476 | version "1.27.0" 477 | resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1" 478 | 479 | mime-types@^2.1.12, mime-types@~2.1.7: 480 | version "2.1.15" 481 | resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.15.tgz#a4ebf5064094569237b8cf70046776d09fc92aed" 482 | dependencies: 483 | mime-db "~1.27.0" 484 | 485 | minimatch@^3.0.2: 486 | version "3.0.3" 487 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.3.tgz#2a4e4090b96b2db06a9d7df01055a62a77c9b774" 488 | dependencies: 489 | brace-expansion "^1.0.0" 490 | 491 | minimist@0.0.8: 492 | version "0.0.8" 493 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" 494 | 495 | minimist@1.2.0: 496 | version "1.2.0" 497 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" 498 | 499 | mkdirp@0.5.1: 500 | version "0.5.1" 501 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" 502 | dependencies: 503 | minimist "0.0.8" 504 | 505 | mocha-lcov-reporter@^1.3.0: 506 | version "1.3.0" 507 | resolved "https://registry.yarnpkg.com/mocha-lcov-reporter/-/mocha-lcov-reporter-1.3.0.tgz#469bdef4f8afc9a116056f079df6182d0afb0384" 508 | 509 | mocha-sinon@^2.0.0: 510 | version "2.0.0" 511 | resolved "https://registry.yarnpkg.com/mocha-sinon/-/mocha-sinon-2.0.0.tgz#723a9310e7d737d7b77c7a66821237425b032d48" 512 | 513 | mocha@^3.3.0: 514 | version "3.3.0" 515 | resolved "https://registry.yarnpkg.com/mocha/-/mocha-3.3.0.tgz#d29b7428d3f52c82e2e65df1ecb7064e1aabbfb5" 516 | dependencies: 517 | browser-stdout "1.3.0" 518 | commander "2.9.0" 519 | debug "2.6.0" 520 | diff "3.2.0" 521 | escape-string-regexp "1.0.5" 522 | glob "7.1.1" 523 | growl "1.9.2" 524 | json3 "3.3.2" 525 | lodash.create "3.1.1" 526 | mkdirp "0.5.1" 527 | supports-color "3.1.2" 528 | 529 | ms@0.7.2: 530 | version "0.7.2" 531 | resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.2.tgz#ae25cf2512b3885a1d95d7f037868d8431124765" 532 | 533 | native-promise-only@^0.8.1: 534 | version "0.8.1" 535 | resolved "https://registry.yarnpkg.com/native-promise-only/-/native-promise-only-0.8.1.tgz#20a318c30cb45f71fe7adfbf7b21c99c1472ef11" 536 | 537 | oauth-sign@~0.8.1: 538 | version "0.8.2" 539 | resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" 540 | 541 | object-keys@^1.0.6: 542 | version "1.0.11" 543 | resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.11.tgz#c54601778ad560f1142ce0e01bcca8b56d13426d" 544 | 545 | once@^1.3.0: 546 | version "1.4.0" 547 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 548 | dependencies: 549 | wrappy "1" 550 | 551 | path-is-absolute@^1.0.0: 552 | version "1.0.1" 553 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 554 | 555 | path-parse@^1.0.5: 556 | version "1.0.5" 557 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" 558 | 559 | path-to-regexp@^1.7.0: 560 | version "1.7.0" 561 | resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.7.0.tgz#59fde0f435badacba103a84e9d3bc64e96b9937d" 562 | dependencies: 563 | isarray "0.0.1" 564 | 565 | pinkie-promise@^2.0.0: 566 | version "2.0.1" 567 | resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" 568 | dependencies: 569 | pinkie "^2.0.0" 570 | 571 | pinkie@^2.0.0: 572 | version "2.0.4" 573 | resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" 574 | 575 | prime@0.0.5-alpha: 576 | version "0.0.5-alpha" 577 | resolved "https://registry.yarnpkg.com/prime/-/prime-0.0.5-alpha.tgz#e4d49a657bed2eb30e513adc6e5dfb83622e90c1" 578 | 579 | punycode@^1.4.1: 580 | version "1.4.1" 581 | resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" 582 | 583 | qs@~6.3.0: 584 | version "6.3.2" 585 | resolved "https://registry.yarnpkg.com/qs/-/qs-6.3.2.tgz#e75bd5f6e268122a2a0e0bda630b2550c166502c" 586 | 587 | readdir-enhanced@^1.4.0: 588 | version "1.5.2" 589 | resolved "https://registry.yarnpkg.com/readdir-enhanced/-/readdir-enhanced-1.5.2.tgz#61463048690ac6a455b75b62fa78a88f8dc85e53" 590 | dependencies: 591 | call-me-maybe "^1.0.1" 592 | es6-promise "^4.1.0" 593 | glob-to-regexp "^0.3.0" 594 | 595 | rechoir@^0.6.2: 596 | version "0.6.2" 597 | resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" 598 | dependencies: 599 | resolve "^1.1.6" 600 | 601 | request@2.79.0: 602 | version "2.79.0" 603 | resolved "https://registry.yarnpkg.com/request/-/request-2.79.0.tgz#4dfe5bf6be8b8cdc37fcf93e04b65577722710de" 604 | dependencies: 605 | aws-sign2 "~0.6.0" 606 | aws4 "^1.2.1" 607 | caseless "~0.11.0" 608 | combined-stream "~1.0.5" 609 | extend "~3.0.0" 610 | forever-agent "~0.6.1" 611 | form-data "~2.1.1" 612 | har-validator "~2.0.6" 613 | hawk "~3.1.3" 614 | http-signature "~1.1.0" 615 | is-typedarray "~1.0.0" 616 | isstream "~0.1.2" 617 | json-stringify-safe "~5.0.1" 618 | mime-types "~2.1.7" 619 | oauth-sign "~0.8.1" 620 | qs "~6.3.0" 621 | stringstream "~0.0.4" 622 | tough-cookie "~2.3.0" 623 | tunnel-agent "~0.4.1" 624 | uuid "^3.0.0" 625 | 626 | resolve@^1.1.6: 627 | version "1.3.3" 628 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.3.3.tgz#655907c3469a8680dc2de3a275a8fdd69691f0e5" 629 | dependencies: 630 | path-parse "^1.0.5" 631 | 632 | samsam@1.x, samsam@^1.1.3: 633 | version "1.2.1" 634 | resolved "https://registry.yarnpkg.com/samsam/-/samsam-1.2.1.tgz#edd39093a3184370cb859243b2bdf255e7d8ea67" 635 | 636 | shelljs@^0.7.5: 637 | version "0.7.7" 638 | resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.7.7.tgz#b2f5c77ef97148f4b4f6e22682e10bba8667cff1" 639 | dependencies: 640 | glob "^7.0.0" 641 | interpret "^1.0.0" 642 | rechoir "^0.6.2" 643 | 644 | sinon@^2.1.0: 645 | version "2.1.0" 646 | resolved "https://registry.yarnpkg.com/sinon/-/sinon-2.1.0.tgz#e057a9d2bf1b32f5d6dd62628ca9ee3961b0cafb" 647 | dependencies: 648 | diff "^3.1.0" 649 | formatio "1.2.0" 650 | lolex "^1.6.0" 651 | native-promise-only "^0.8.1" 652 | path-to-regexp "^1.7.0" 653 | samsam "^1.1.3" 654 | text-encoding "0.6.4" 655 | type-detect "^4.0.0" 656 | 657 | sntp@1.x.x: 658 | version "1.0.9" 659 | resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" 660 | dependencies: 661 | hoek "2.x.x" 662 | 663 | sprintf-js@~1.0.2: 664 | version "1.0.3" 665 | resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" 666 | 667 | sshpk@^1.7.0: 668 | version "1.13.0" 669 | resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.0.tgz#ff2a3e4fd04497555fed97b39a0fd82fafb3a33c" 670 | dependencies: 671 | asn1 "~0.2.3" 672 | assert-plus "^1.0.0" 673 | dashdash "^1.12.0" 674 | getpass "^0.1.1" 675 | optionalDependencies: 676 | bcrypt-pbkdf "^1.0.0" 677 | ecc-jsbn "~0.1.1" 678 | jodid25519 "^1.0.0" 679 | jsbn "~0.1.0" 680 | tweetnacl "~0.14.0" 681 | 682 | stringstream@~0.0.4: 683 | version "0.0.5" 684 | resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" 685 | 686 | strip-ansi@^3.0.0: 687 | version "3.0.1" 688 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" 689 | dependencies: 690 | ansi-regex "^2.0.0" 691 | 692 | supports-color@3.1.2: 693 | version "3.1.2" 694 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5" 695 | dependencies: 696 | has-flag "^1.0.0" 697 | 698 | supports-color@^2.0.0: 699 | version "2.0.0" 700 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" 701 | 702 | text-encoding@0.6.4: 703 | version "0.6.4" 704 | resolved "https://registry.yarnpkg.com/text-encoding/-/text-encoding-0.6.4.tgz#e399a982257a276dae428bb92845cb71bdc26d19" 705 | 706 | tough-cookie@~2.3.0: 707 | version "2.3.2" 708 | resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.2.tgz#f081f76e4c85720e6c37a5faced737150d84072a" 709 | dependencies: 710 | punycode "^1.4.1" 711 | 712 | tunnel-agent@~0.4.1: 713 | version "0.4.3" 714 | resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.4.3.tgz#6373db76909fe570e08d73583365ed828a74eeeb" 715 | 716 | tweetnacl@^0.14.3, tweetnacl@~0.14.0: 717 | version "0.14.5" 718 | resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" 719 | 720 | type-detect@0.1.1: 721 | version "0.1.1" 722 | resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-0.1.1.tgz#0ba5ec2a885640e470ea4e8505971900dac58822" 723 | 724 | type-detect@^1.0.0: 725 | version "1.0.0" 726 | resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-1.0.0.tgz#762217cc06db258ec48908a1298e8b95121e8ea2" 727 | 728 | type-detect@^4.0.0: 729 | version "4.0.3" 730 | resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.3.tgz#0e3f2670b44099b0b46c284d136a7ef49c74c2ea" 731 | 732 | uuid@^3.0.0: 733 | version "3.0.1" 734 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.0.1.tgz#6544bba2dfda8c1cf17e629a3a305e2bb1fee6c1" 735 | 736 | verror@1.3.6: 737 | version "1.3.6" 738 | resolved "https://registry.yarnpkg.com/verror/-/verror-1.3.6.tgz#cff5df12946d297d2baaefaa2689e25be01c005c" 739 | dependencies: 740 | extsprintf "1.0.2" 741 | 742 | wrappy@1: 743 | version "1.0.2" 744 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 745 | 746 | xtend@^4.0.0, xtend@~4.0.0: 747 | version "4.0.1" 748 | resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" 749 | --------------------------------------------------------------------------------