├── .gitignore ├── README.md ├── bin └── index.js ├── command └── index.js ├── config ├── Component.js ├── Config.js └── index.js ├── lib ├── create │ ├── CreateFile.js │ ├── component.js │ └── index.js └── index.js ├── package.json ├── sources └── components │ ├── class-with-redux.js │ ├── class.js │ ├── function-with-redux.js │ └── function.js └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | ### Node ### 2 | 3 | # Logs 4 | logs 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | 9 | # Optional npm cache directory 10 | .npm 11 | 12 | # Dependency directories 13 | /node_modules 14 | /jspm_packages 15 | /bower_components 16 | 17 | # Yarn Integrity file 18 | .yarn-integrity 19 | 20 | # Optional eslint cache 21 | .eslintcache 22 | 23 | # dotenv environment variables file(s) 24 | .env 25 | .env.* 26 | 27 | #Build generated 28 | dist/ 29 | build/ 30 | 31 | # Serverless generated files 32 | .serverless/ 33 | 34 | ### SublimeText ### 35 | # cache files for sublime text 36 | *.tmlanguage.cache 37 | *.tmPreferences.cache 38 | *.stTheme.cache 39 | 40 | # workspace files are user-specific 41 | *.sublime-workspace 42 | 43 | # project files should be checked into the repository, unless a significant 44 | # proportion of contributors will probably not be using SublimeText 45 | # *.sublime-project 46 | 47 | 48 | ### VisualStudioCode ### 49 | .vscode/* 50 | !.vscode/settings.json 51 | !.vscode/tasks.json 52 | !.vscode/launch.json 53 | !.vscode/extensions.json 54 | 55 | ### Vim ### 56 | *.sw[a-p] 57 | 58 | ### WebStorm/IntelliJ ### 59 | /.idea 60 | modules.xml 61 | *.ipr 62 | *.iml 63 | 64 | 65 | ### System Files ### 66 | *.DS_Store 67 | 68 | # Windows thumbnail cache files 69 | Thumbs.db 70 | ehthumbs.db 71 | ehthumbs_vista.db 72 | 73 | # Folder config file 74 | Desktop.ini 75 | 76 | # Recycle Bin used on file shares 77 | $RECYCLE.BIN/ 78 | 79 | # Thumbnails 80 | ._* 81 | 82 | # Files that might appear in the root of a volume 83 | .DocumentRevisions-V100 84 | .fseventsd 85 | .Spotlight-V100 86 | .TemporaryItems 87 | .Trashes 88 | .VolumeIcon.icns 89 | .com.apple.timemachine.donotpresent 90 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React Artisan 2 | 3 | ## How to install 4 | ```bash 5 | $ yarn add -g react-artisan 6 | # or 7 | $ npm install -g react-artisan 8 | ``` 9 | 10 | ## How to use 11 | 12 | ```bash 13 | $ react-artisan COMMAND FILENANE [options] 14 | 15 | # e.g 16 | $ react-artisan create:component About --with-redux --function 17 | ``` 18 | 19 | ### List of Command 20 | 21 | ``` 22 | create:component 23 | ``` 24 | 25 | ### List of Option 26 | 27 | | Options | Description | 28 | | ------------- | ------------- | 29 | | --with-react-redux | create a component file that already connected with react-redux | 30 | | --functional / -f | create a functional component file | 31 | | --folder-file / -ff | create a component file inside folder, filename will be the folder name component file name would be index.js | 32 | | --here | create component file in current directory | 33 | 34 | ### Config File 35 | you can also create a config file to change default config. 36 | you can create file with name rean-config.js in project root folder 37 | 38 | ### e.g 39 | ```javascript 40 | module.exports = { 41 | component: { 42 | rootPath: '/component', // to change root folder also inside /src 43 | classComponent: true, // default value create class component 44 | withReactRedux: false, // create a component file without connect to react-redux 45 | folderFile: false // create component file without folder file name 46 | } 47 | } 48 | ``` 49 | 50 | ## Example Code 51 | ### Class Component 52 | ```javascript 53 | import React, { Component } from 'react'; 54 | 55 | class About extends Component { 56 | constructor(props) { 57 | super(props); 58 | this.state = { }; 59 | 60 | this.exampleMethod = this.exampleMethod.bind(this) //if you dont want to use arrow function 61 | } 62 | 63 | componentDidMount() { 64 | //will run after component rendered 65 | } 66 | 67 | //you can use arrow function if you want bind function 68 | exampleMethod() { 69 | 70 | } 71 | 72 | render() { 73 | return ( 74 | //Component Start Here 75 | ); 76 | } 77 | } 78 | 79 | export default About; 80 | ``` 81 | 82 | -------------------------------------------------------------------------------- /bin/index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const path = require('path'); 3 | const config = require(path.join(__dirname, '../config')) 4 | const commandList = require(path.join(__dirname, '../command')) 5 | const generate = require(path.join(__dirname, '../lib')) 6 | 7 | const args = process.argv.splice(2) 8 | const command = args[0] 9 | const filename = args[1] 10 | const optionCommand = args.splice(2) 11 | 12 | const doing = command.split(':')[0] 13 | const type = command.split(':')[1] 14 | 15 | const checkDoing = commandList.doing.indexOf(doing) 16 | const checkType = commandList.type.indexOf(type) 17 | 18 | if (checkDoing < 0 || checkType < 0) { 19 | console.log('===================================='); 20 | console.log('Unknown Command '+'"'+command+'"' ); 21 | console.log('===================================='); 22 | } else if (!filename) { 23 | console.log('===================================='); 24 | console.log('Filename must be exist' ); 25 | console.log('===================================='); 26 | } else { 27 | const getConfig = config(type, filename, optionCommand) 28 | generate(doing, getConfig); 29 | } 30 | -------------------------------------------------------------------------------- /command/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | doing: ['create'], 3 | type: ['component'] 4 | } -------------------------------------------------------------------------------- /config/Component.js: -------------------------------------------------------------------------------- 1 | let Config = require('./config.js') 2 | 3 | class Component extends Config { 4 | constructor(options) { 5 | super(options) 6 | 7 | this.rootPath = this.setDefault(options, 'rootPath', '/components'), 8 | this.classComponent = this.setDefault(options, 'classComponent', true) 9 | this.withReactRedux = this.setDefault(options, 'withReactRedux', false) 10 | this.folderFile = this.setDefault(options, 'folderFile', false) 11 | } 12 | } 13 | 14 | module.exports = Component -------------------------------------------------------------------------------- /config/Config.js: -------------------------------------------------------------------------------- 1 | class Config { 2 | constructor(options) { 3 | this.filename = options.filename 4 | this.type = options.type 5 | this.isHere = this.setDefault(options, 'isHere', false) 6 | } 7 | setDefault(options, property, defaultValue){ 8 | return options.hasOwnProperty(property) ? options[property] : defaultValue 9 | } 10 | } 11 | 12 | module.exports = Config -------------------------------------------------------------------------------- /config/index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const Component = require('./component') 3 | let fileConfig; 4 | try { 5 | fileConfig = require(process.cwd()+'/ryan.config.js') 6 | } 7 | catch(err){ 8 | fileConfig = null 9 | } 10 | 11 | module.exports = function(type, filename, options) { 12 | 13 | let objOptions = { 14 | filename: filename, 15 | type: type 16 | } 17 | 18 | if (fileConfig){ 19 | objOptions = { 20 | ...objOptions, 21 | ...fileConfig[type] 22 | } 23 | } 24 | 25 | options.forEach(option => { 26 | switch (option) { 27 | case "--functional": 28 | case "-f": 29 | objOptions.classComponent = false 30 | break; 31 | case "--here": 32 | objOptions.isHere = true 33 | break; 34 | case "--with-react-redux": 35 | objOptions.withReactRedux = true 36 | break; 37 | case "--folder-file": 38 | case "-ff": 39 | objOptions.folderFile = true 40 | break; 41 | } 42 | }); 43 | 44 | switch (type) { 45 | case "component": 46 | return new Component(objOptions) 47 | default: 48 | return new Component(objOptions) 49 | } 50 | } -------------------------------------------------------------------------------- /lib/create/CreateFile.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs') 2 | const path = require('path') 3 | const mkdirp = require('mkdirp') 4 | 5 | class CreateFile { 6 | 7 | constructor(config){ 8 | this.config = config 9 | } 10 | 11 | checkSrc() { 12 | if(this.config.isHere){ 13 | return (process.cwd().split('/').indexOf("src") > -1) 14 | } else { 15 | return fs.existsSync('./src') 16 | } 17 | } 18 | 19 | readFile(sourcefile) { 20 | 21 | const filepath = path.join(__dirname, `../../sources/${this.config.type}s/${sourcefile}`) 22 | 23 | let filenya = fs.readFileSync(filepath, 'utf8') 24 | let result = filenya.replace(/FILENAME/g, this.config.filename); 25 | 26 | return result 27 | 28 | } 29 | 30 | createFolder() { 31 | 32 | let folderPath = this.getFolderPath() 33 | 34 | let statusFolder = fs.existsSync(folderPath) 35 | 36 | if(!statusFolder){ 37 | try { 38 | mkdirp.sync(folderPath, { mode: parseInt('0755',8) & (~process.umask()) }) 39 | } 40 | catch(err){ 41 | if(err.code == 'EACCES'){ 42 | console.log('===================================='); 43 | console.log(`check your permission folder in ./src${this.config.rootPath}`); 44 | console.log('===================================='); 45 | } else { 46 | // console.log(err) 47 | console.log('===================================='); 48 | console.log('You must be at project root folder'); 49 | console.log('===================================='); 50 | } 51 | } 52 | } 53 | 54 | } 55 | 56 | writeFile(rawfile) { 57 | 58 | let filePath = this.getFilePath() 59 | 60 | let statusFile = fs.existsSync(filePath) 61 | 62 | if (!statusFile) { 63 | try { 64 | fs.writeFileSync(filePath, rawfile) 65 | } 66 | catch(err) { 67 | console.log(err) 68 | } 69 | console.log('===================================='); 70 | console.log('You success create '+ filePath); 71 | console.log('===================================='); 72 | } else { 73 | console.log('===================================='); 74 | console.log('file already exist in '+ filePath); 75 | console.log('===================================='); 76 | } 77 | 78 | } 79 | 80 | getFolderPath() { 81 | 82 | if(this.config.isHere) { 83 | return path.join(`./${this.config.filename}`) 84 | } else if(this.config.folderFile){ 85 | return path.join('./src', this.config.rootPath, this.config.filename) 86 | } else { 87 | return path.join('./src', this.config.rootPath) 88 | } 89 | 90 | } 91 | 92 | getFilePath() { 93 | 94 | let rootPath = this.config.isHere ? '' : path.join('./src/', this.config.rootPath) 95 | 96 | if(this.config.folderFile) { 97 | return path.join(rootPath, `./${this.config.filename}/index.js`) 98 | } else { 99 | return path.join(rootPath, `./${this.config.filename}.js`) 100 | } 101 | 102 | } 103 | 104 | } 105 | 106 | module.exports = CreateFile -------------------------------------------------------------------------------- /lib/create/component.js: -------------------------------------------------------------------------------- 1 | const CreateFile = require('./CreateFile') 2 | 3 | class Component extends CreateFile { 4 | 5 | constructor(config){ 6 | super(config) 7 | } 8 | 9 | getSourceFilename() { 10 | if(this.config.classComponent && this.config.withReactRedux) { 11 | return 'class-with-redux.js' 12 | } else if(!this.config.classComponent && this.config.withReactRedux) { 13 | return 'function-with-redux.js' 14 | } else if(this.config.classComponent && !this.config.withReactRedux) { 15 | return 'class.js' 16 | } else if(!this.config.classComponent && !this.config.withReactRedux) { 17 | return 'function.js' 18 | } 19 | } 20 | 21 | static create(config) { 22 | 23 | const component = new Component(config) 24 | 25 | let srcStatus = component.checkSrc() 26 | 27 | if(!srcStatus){ 28 | 29 | console.log('===================================='); 30 | console.log('You must be at project root folder'); 31 | console.log('===================================='); 32 | 33 | } else { 34 | 35 | let sourcefile = component.getSourceFilename() 36 | let rawfile = component.readFile(sourcefile) 37 | 38 | if(!config.isHere || config.folderFile){ 39 | 40 | component.createFolder() 41 | 42 | } 43 | 44 | component.writeFile(rawfile) 45 | 46 | } 47 | } 48 | } 49 | 50 | module.exports = Component -------------------------------------------------------------------------------- /lib/create/index.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | const Component = require(path.join(__dirname, './Component')) 3 | 4 | module.exports = function(config) { 5 | 6 | switch (config.type) { 7 | case "component": 8 | Component.create(config) 9 | break; 10 | } 11 | 12 | } -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | const create = require('./create') 2 | 3 | module.exports = function(doing, config) { 4 | switch (doing) { 5 | case "create": 6 | create(config) 7 | break; 8 | } 9 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ryan-react-cli", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "bin": { 10 | "ryan": "bin/index.js" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "mkdirp": "^0.5.1" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /sources/components/class-with-redux.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux' 3 | 4 | class FILENAME extends Component { 5 | constructor(props) { 6 | super(props); 7 | this.state = { }; 8 | 9 | this.exampleMethod = this.exampleMethod.bind(this) //if you dont want to use arrow function 10 | } 11 | 12 | componentDidMount() { 13 | //will run after component rendered 14 | } 15 | 16 | //you can use arrow function if you want bind function 17 | exampleMethod() { 18 | 19 | } 20 | 21 | render() { 22 | return ( 23 | //Component Start Here 24 | ); 25 | } 26 | } 27 | 28 | const mapStateToProps = state => { 29 | return { 30 | // example: state.stateFromReducer 31 | } 32 | } 33 | 34 | const mapDispatchToProps = dispatch => { 35 | return { 36 | // example: () => { dispatch({}) } 37 | } 38 | } 39 | 40 | export default connect(mapStateToProps, mapDispatchToProps)(FILENAME); -------------------------------------------------------------------------------- /sources/components/class.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | 3 | class FILENAME extends Component { 4 | constructor(props) { 5 | super(props); 6 | this.state = { }; 7 | 8 | this.exampleMethod = this.exampleMethod.bind(this) //if you dont want to use arrow function 9 | } 10 | 11 | componentDidMount() { 12 | //will run after component rendered 13 | } 14 | 15 | //you can use arrow function if you want bind function 16 | exampleMethod() { 17 | 18 | } 19 | 20 | render() { 21 | return ( 22 | //Component Start Here 23 | ); 24 | } 25 | } 26 | 27 | export default FILENAME; -------------------------------------------------------------------------------- /sources/components/function-with-redux.js: -------------------------------------------------------------------------------- 1 | //in this component you can't get state and react lifecycle only props that you have 2 | import React from 'react'; 3 | import { connect } from 'react-redux' 4 | 5 | const FILENAME = (props) => { 6 | return ( 7 | //component goes here 8 | ); 9 | } 10 | 11 | const mapStateToProps = state => { 12 | return { 13 | // example: state.stateFromReducer 14 | } 15 | } 16 | 17 | const mapDispatchToProps = dispatch => { 18 | return { 19 | // example: () => { dispatch({}) } 20 | } 21 | } 22 | 23 | export default connect(mapStateToProps, mapDispatchToProps)(FILENAME); -------------------------------------------------------------------------------- /sources/components/function.js: -------------------------------------------------------------------------------- 1 | //in this component you can't get state and react lifecycle only props that you have 2 | import React from 'react'; 3 | 4 | const FILENAME = (props) => { 5 | return ( 6 | //component goes here 7 | ); 8 | } 9 | 10 | export default FILENAME; -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | minimist@0.0.8: 6 | version "0.0.8" 7 | resolved "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" 8 | 9 | mkdirp@^0.5.1: 10 | version "0.5.1" 11 | resolved "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" 12 | dependencies: 13 | minimist "0.0.8" 14 | --------------------------------------------------------------------------------