├── src ├── templates │ └── component │ │ ├── componentCssTemplate.js │ │ ├── componentStyledTemplate.js │ │ ├── componentStoryTemplate.js │ │ ├── componentLazyTemplate.js │ │ ├── componentTestDefaultTemplate.js │ │ ├── componentTsTemplate.js │ │ ├── componentTsLazyTemplate.js │ │ ├── componentTestEnzymeTemplate.js │ │ ├── componentJsTemplate.js │ │ └── componentTestTestingLibraryTemplate.js ├── cli.test.js ├── cli.js ├── commands │ └── generateComponent.js └── utils │ ├── grcConfigUtils.js │ └── generateComponentUtils.js ├── .husky ├── commit-msg └── pre-commit ├── docs └── assets │ ├── generate-react-cli.png │ └── generate-react-cli.svg ├── renovate.json ├── .gitignore ├── .github ├── workflows │ ├── repo-pruner.yml │ └── publish.yml └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── bin └── generate-react.js ├── LICENSE ├── package.json ├── CHANGELOG.md └── readme.md /src/templates/component/componentCssTemplate.js: -------------------------------------------------------------------------------- 1 | export default `.templatename {}`; 2 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit $1 5 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install pretty-quick --staged 5 | -------------------------------------------------------------------------------- /docs/assets/generate-react-cli.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arminbro/generate-react-cli/HEAD/docs/assets/generate-react-cli.png -------------------------------------------------------------------------------- /src/cli.test.js: -------------------------------------------------------------------------------- 1 | import cli from './cli'; 2 | 3 | describe('cli', () => { 4 | it('should be defined.', () => { 5 | expect(cli).toBeDefined(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /src/templates/component/componentStyledTemplate.js: -------------------------------------------------------------------------------- 1 | export default `import styled from 'styled-components'; 2 | 3 | export const templatenameWrapper = styled.div\` 4 | \`; 5 | `; 6 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": ["config:base"], 4 | "baseBranches": ["dev"], 5 | "schedule": ["monthly"] 6 | } 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # build directory 7 | /dist 8 | 9 | # jest test coverage 10 | /coverage 11 | 12 | # ide 13 | .idea 14 | 15 | # Claude AI context file 16 | **/CLAUDE.md 17 | -------------------------------------------------------------------------------- /src/templates/component/componentStoryTemplate.js: -------------------------------------------------------------------------------- 1 | export default `/* eslint-disable */ 2 | import templatename from './templatename'; 3 | 4 | export default { 5 | title: "templatename", 6 | }; 7 | 8 | export const Default = () => ; 9 | 10 | Default.story = { 11 | name: 'default', 12 | }; 13 | `; 14 | -------------------------------------------------------------------------------- /src/templates/component/componentLazyTemplate.js: -------------------------------------------------------------------------------- 1 | export default `import React, { lazy, Suspense } from 'react'; 2 | 3 | const Lazytemplatename = lazy(() => import('./templatename')); 4 | 5 | const templatename = props => ( 6 | 7 | 8 | 9 | ); 10 | 11 | export default templatename; 12 | `; 13 | -------------------------------------------------------------------------------- /src/templates/component/componentTestDefaultTemplate.js: -------------------------------------------------------------------------------- 1 | export default `import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import templatename from './templatename'; 4 | 5 | it('It should mount', () => { 6 | const div = document.createElement('div'); 7 | ReactDOM.render(, div); 8 | ReactDOM.unmountComponentAtNode(div); 9 | });`; 10 | -------------------------------------------------------------------------------- /src/templates/component/componentTsTemplate.js: -------------------------------------------------------------------------------- 1 | export default `import React, { FC } from 'react'; 2 | import styles from './templatename.module.css'; 3 | 4 | interface templatenameProps {} 5 | 6 | const templatename: FC = () => ( 7 |
8 | templatename Component 9 |
10 | ); 11 | 12 | export default templatename; 13 | `; 14 | -------------------------------------------------------------------------------- /src/templates/component/componentTsLazyTemplate.js: -------------------------------------------------------------------------------- 1 | export default `import React, { lazy, Suspense } from 'react'; 2 | 3 | const Lazytemplatename = lazy(() => import('./templatename')); 4 | 5 | const templatename = (props: JSX.IntrinsicAttributes & { children?: React.ReactNode; }) => ( 6 | 7 | 8 | 9 | ); 10 | 11 | export default templatename; 12 | `; 13 | -------------------------------------------------------------------------------- /src/templates/component/componentTestEnzymeTemplate.js: -------------------------------------------------------------------------------- 1 | export default `import React from 'react'; 2 | import { shallow } from 'enzyme'; 3 | import templatename from './templatename'; 4 | 5 | describe('', () => { 6 | let component; 7 | 8 | beforeEach(() => { 9 | component = shallow(); 10 | }); 11 | 12 | test('It should mount', () => { 13 | expect(component.length).toBe(1); 14 | }); 15 | }); 16 | `; 17 | -------------------------------------------------------------------------------- /src/templates/component/componentJsTemplate.js: -------------------------------------------------------------------------------- 1 | export default `import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import styles from './templatename.module.css'; 4 | 5 | const templatename = () => ( 6 |
7 | templatename Component 8 |
9 | ); 10 | 11 | templatename.propTypes = {}; 12 | 13 | templatename.defaultProps = {}; 14 | 15 | export default templatename; 16 | `; 17 | -------------------------------------------------------------------------------- /.github/workflows/repo-pruner.yml: -------------------------------------------------------------------------------- 1 | name: 'Run Repo Pruner' 2 | on: 3 | schedule: 4 | - cron: '0 0 1 * *' # Runs once a month - At 00:00 on day-of-month 1. 5 | workflow_dispatch: 6 | 7 | jobs: 8 | repo-pruner: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Run Repo Pruner 12 | uses: arminbro/repo-pruner@v2.1.22 13 | env: 14 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 15 | with: 16 | inactive_days: 30 17 | -------------------------------------------------------------------------------- /src/templates/component/componentTestTestingLibraryTemplate.js: -------------------------------------------------------------------------------- 1 | export default `import React from 'react'; 2 | import { render, screen } from '@testing-library/react'; 3 | import '@testing-library/jest-dom'; 4 | import templatename from './templatename'; 5 | 6 | describe('', () => { 7 | test('it should mount', () => { 8 | render(); 9 | 10 | const templateName = screen.getByTestId('templatename'); 11 | 12 | expect(templateName).toBeInTheDocument(); 13 | }); 14 | });`; 15 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish 2 | 3 | on: 4 | push: 5 | branches: [alpha, beta, master] 6 | 7 | jobs: 8 | publish: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Check out repository 12 | uses: actions/checkout@v4 13 | with: 14 | fetch-depth: 0 15 | - name: Set up Node.js 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: '20.19.5' 19 | - run: npm ci 20 | - run: npx semantic-release 21 | env: 22 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 23 | NPM_TOKEN: ${{secrets.NPM_TOKEN}} 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /bin/generate-react.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import cli from '../src/cli.js'; 3 | 4 | const isNotValidNodeVersion = () => { 5 | const currentNodeVersion = process.versions.node; 6 | const semver = currentNodeVersion.split('.'); 7 | const major = semver[0]; 8 | 9 | if (major < 18) { 10 | console.error( 11 | // eslint-disable-next-line 12 | 'You are running Node ' + 13 | currentNodeVersion + 14 | ' Generate React CLI requires Node 18 or higher. Please update your version of Node.' 15 | ); 16 | 17 | return true; 18 | } 19 | 20 | return false; 21 | }; 22 | 23 | // --- Check if user is running Node 12 or higher. 24 | 25 | if (isNotValidNodeVersion()) { 26 | process.exit(1); 27 | } 28 | 29 | cli(process.argv); 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Machine (please complete the following information):** 24 | - OS: [e.g. macOS Mojave] 25 | - Node Version [e.g. v10.16.1] 26 | 27 | **Additional context** 28 | Add any other context about the problem here. 29 | 30 | **Screenshots** 31 | If applicable, add screenshots to help explain your problem. 32 | -------------------------------------------------------------------------------- /src/cli.js: -------------------------------------------------------------------------------- 1 | import { program } from 'commander'; 2 | import { createRequire } from 'module'; 3 | import { config as dotEnvConfig } from 'dotenv'; 4 | import path from 'path'; 5 | 6 | import initGenerateComponentCommand from './commands/generateComponent.js'; 7 | import { getCLIConfigFile } from './utils/grcConfigUtils.js'; 8 | 9 | export default async function cli(args) { 10 | const cliConfigFile = await getCLIConfigFile(); 11 | const localRequire = createRequire(import.meta.url); 12 | const pkg = localRequire('../package.json'); 13 | 14 | // init dotenv 15 | 16 | dotEnvConfig({ path: path.resolve(process.cwd(), '.env.local') }); 17 | 18 | // Initialize generate component command 19 | 20 | initGenerateComponentCommand(args, cliConfigFile, program); 21 | 22 | program.version(pkg.version); 23 | program.parse(args); 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Armin Broubakarian 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 | -------------------------------------------------------------------------------- /src/commands/generateComponent.js: -------------------------------------------------------------------------------- 1 | import { 2 | generateComponent, 3 | getComponentByType, 4 | getCorrespondingComponentFileTypes, 5 | } from '../utils/generateComponentUtils.js'; 6 | 7 | export default function initGenerateComponentCommand(args, cliConfigFile, program) { 8 | const selectedComponentType = getComponentByType(args, cliConfigFile); 9 | 10 | const componentCommand = program 11 | .command('component [names...]') 12 | .alias('c') 13 | 14 | // Static component command option defaults. 15 | 16 | .option('-p, --path ', 'The path where the component will get generated in.', selectedComponentType.path) 17 | .option( 18 | '--type ', 19 | 'You can pass a component type that you have configured in your GRC config file.', 20 | 'default' 21 | ) 22 | .option( 23 | '-f, --flat', 24 | 'Generate the files in the mentioned path instead of creating new folder for it', 25 | selectedComponentType.flat || false 26 | ) 27 | .option('--dry-run', 'Show what will be generated without writing to disk') 28 | .option( 29 | '--customDirectory ', 30 | 'You can pass a cased path template that will be used as the component path for the component being generated.\n' + 31 | 'E.g. this allows you to add a prefix or suffix to the component path, ' + 32 | 'or change the case of the name of the directory holding the components to kebab-case.\n' + 33 | 'Examples:\n' + 34 | '- TemplateName\n' + 35 | '- template-name\n' + 36 | '- TemplateNameSuffix' 37 | ); 38 | 39 | // Dynamic component command option defaults. 40 | 41 | const dynamicOptions = getCorrespondingComponentFileTypes(selectedComponentType); 42 | 43 | dynamicOptions.forEach((dynamicOption) => { 44 | componentCommand.option( 45 | `--${dynamicOption} <${dynamicOption}>`, 46 | `With corresponding ${dynamicOption.split('with')[1]} file.`, 47 | selectedComponentType[dynamicOption] 48 | ); 49 | }); 50 | 51 | // Component command action. 52 | 53 | componentCommand.action((componentNames, cmd) => 54 | componentNames.forEach((componentName) => generateComponent(componentName, cmd, cliConfigFile)) 55 | ); 56 | } 57 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "generate-react-cli", 3 | "version": "9.1.0", 4 | "description": "A simple React CLI to generate components instantly and more.", 5 | "repository": "https://github.com/arminbro/generate-react-cli", 6 | "bugs": "https://github.com/arminbro/generate-react-cli/issues", 7 | "author": "Armin Broubakarian", 8 | "license": "MIT", 9 | "main": "bin/generate-react", 10 | "bin": { 11 | "generate-react": "bin/generate-react.js" 12 | }, 13 | "type": "module", 14 | "files": [ 15 | "bin/", 16 | "src/", 17 | "README.md", 18 | "CHANGELOG.md", 19 | "LICENSE" 20 | ], 21 | "publishConfig": { 22 | "access": "public" 23 | }, 24 | "keywords": [ 25 | "cli", 26 | "react", 27 | "build-tools", 28 | "generate-react-cli" 29 | ], 30 | "engines": { 31 | "node": ">=10.x", 32 | "npm": ">= 6.x" 33 | }, 34 | "browserslist": [ 35 | "maintained node versions" 36 | ], 37 | "scripts": { 38 | "prepare": "husky install" 39 | }, 40 | "dependencies": { 41 | "chalk": "5.6.2", 42 | "commander": "14.0.0", 43 | "deep-keys": "0.5.0", 44 | "dotenv": "16.6.1", 45 | "fs-extra": "11.2.0", 46 | "inquirer": "12.9.4", 47 | "lodash": "4.17.21", 48 | "replace": "1.2.2" 49 | }, 50 | "devDependencies": { 51 | "@commitlint/cli": "19.8.1", 52 | "@commitlint/config-conventional": "19.8.1", 53 | "@semantic-release/commit-analyzer": "13.0.1", 54 | "@semantic-release/git": "10.0.1", 55 | "@semantic-release/github": "11.0.5", 56 | "@semantic-release/npm": "12.0.2", 57 | "@semantic-release/release-notes-generator": "14.0.3", 58 | "eslint": "8.57.1", 59 | "eslint-config-airbnb-base": "15.0.0", 60 | "eslint-config-prettier": "9.1.2", 61 | "eslint-plugin-prettier": "5.5.4", 62 | "husky": "9.1.7", 63 | "prettier": "3.6.2", 64 | "pretty-quick": "4.2.2", 65 | "semantic-release": "24.2.7" 66 | }, 67 | "prettier": { 68 | "singleQuote": true, 69 | "trailingComma": "es5", 70 | "printWidth": 120 71 | }, 72 | "release": { 73 | "plugins": [ 74 | "@semantic-release/commit-analyzer", 75 | "@semantic-release/release-notes-generator", 76 | "@semantic-release/npm", 77 | [ 78 | "@semantic-release/git", 79 | { 80 | "assets": [ 81 | "package.json" 82 | ], 83 | "message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}" 84 | } 85 | ], 86 | "@semantic-release/github" 87 | ] 88 | }, 89 | "commitlint": { 90 | "extends": [ 91 | "@commitlint/config-conventional" 92 | ], 93 | "rules": { 94 | "body-max-line-length": [ 95 | 0, 96 | "always", 97 | 200 98 | ] 99 | } 100 | }, 101 | "eslintConfig": { 102 | "extends": [ 103 | "airbnb-base", 104 | "plugin:prettier/recommended" 105 | ], 106 | "env": { 107 | "commonjs": false, 108 | "node": true 109 | }, 110 | "parserOptions": { 111 | "ecmaVersion": "latest" 112 | }, 113 | "rules": { 114 | "import/extensions": [ 115 | { 116 | "js": "always" 117 | } 118 | ] 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /src/utils/grcConfigUtils.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | import deepKeys from 'deep-keys'; 4 | import inquirer from 'inquirer'; 5 | import merge from 'lodash/merge.js'; 6 | import fsExtra from 'fs-extra'; 7 | 8 | const { accessSync, constants, outputFileSync, readFileSync } = fsExtra; 9 | const { prompt } = inquirer; 10 | 11 | // Generate React Config file questions. 12 | 13 | // --- project level questions. 14 | 15 | const projectLevelQuestions = [ 16 | { 17 | type: 'confirm', 18 | name: 'usesTypeScript', 19 | message: 'Does this project use TypeScript?', 20 | }, 21 | { 22 | type: 'confirm', 23 | name: 'usesStyledComponents', 24 | message: 'Does this project use styled-components?', 25 | }, 26 | { 27 | type: 'confirm', 28 | when: (answers) => !answers['usesStyledComponents'], 29 | name: 'usesCssModule', 30 | message: 'Does this project use CSS modules?', 31 | }, 32 | { 33 | type: 'list', 34 | name: 'cssPreprocessor', 35 | when: (answers) => !answers['usesStyledComponents'], 36 | message: 'Does this project use a CSS Preprocessor?', 37 | choices: ['css', 'scss', 'less', 'styl'], 38 | }, 39 | { 40 | type: 'list', 41 | name: 'testLibrary', 42 | message: 'What testing library does your project use?', 43 | choices: ['Testing Library', 'Enzyme', 'None'], 44 | }, 45 | ]; 46 | 47 | // --- component level questions. 48 | 49 | export const componentLevelQuestions = [ 50 | { 51 | type: 'input', 52 | name: 'component.default.path', 53 | message: 'Set the default path directory to where your components will be generated in?', 54 | default: () => 'src/components', 55 | }, 56 | { 57 | type: 'confirm', 58 | name: 'component.default.withStyle', 59 | message: 'Would you like to create a corresponding stylesheet file with each component you generate?', 60 | }, 61 | { 62 | type: 'confirm', 63 | name: 'component.default.withTest', 64 | message: 'Would you like to create a corresponding test file with each component you generate?', 65 | }, 66 | { 67 | type: 'confirm', 68 | name: 'component.default.withStory', 69 | message: 'Would you like to create a corresponding story with each component you generate?', 70 | }, 71 | { 72 | type: 'confirm', 73 | name: 'component.default.withLazy', 74 | message: 75 | 'Would you like to create a corresponding lazy file (a file that lazy-loads your component out of the box and enables code splitting: https://reactjs.org/docs/code-splitting.html#code-splitting) with each component you generate?', 76 | }, 77 | ]; 78 | 79 | // --- merge all questions together. 80 | 81 | const grcConfigQuestions = [...projectLevelQuestions, ...componentLevelQuestions]; 82 | 83 | async function createCLIConfigFile() { 84 | try { 85 | console.log(); 86 | console.log( 87 | chalk.cyan( 88 | '--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------' 89 | ) 90 | ); 91 | console.log( 92 | chalk.cyan("It looks like this is the first time that you're running generate-react-cli within this project.") 93 | ); 94 | console.log(); 95 | console.log( 96 | chalk.cyan( 97 | 'Answer a few questions to customize generate-react-cli for your project needs (this will create a "generate-react-cli.json" config file on the root level of this project).' 98 | ) 99 | ); 100 | console.log( 101 | chalk.cyan( 102 | '--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------' 103 | ) 104 | ); 105 | console.log(); 106 | 107 | const answers = await prompt(grcConfigQuestions); 108 | 109 | outputFileSync('generate-react-cli.json', JSON.stringify(answers, null, 2)); 110 | 111 | console.log(); 112 | console.log( 113 | chalk.cyan( 114 | 'The "generate-react-cli.json" config file has been successfully created on the root level of your project.' 115 | ) 116 | ); 117 | 118 | console.log(''); 119 | console.log(chalk.cyan('You can always go back and update it as needed.')); 120 | console.log(''); 121 | console.log(chalk.cyan('Happy Hacking!')); 122 | console.log(''); 123 | console.log(''); 124 | 125 | return answers; 126 | } catch (e) { 127 | console.error(chalk.red.bold('ERROR: Could not create a "generate-react-cli.json" config file.')); 128 | return e; 129 | } 130 | } 131 | 132 | async function updateCLIConfigFile(missingConfigQuestions, currentConfigFile) { 133 | try { 134 | console.log(''); 135 | console.log( 136 | chalk.cyan( 137 | '------------------------------------------------------------------------------------------------------------------------------' 138 | ) 139 | ); 140 | console.log( 141 | chalk.cyan( 142 | 'Generate React CLI has been updated and has a few new features from the last time you ran it within this project.' 143 | ) 144 | ); 145 | console.log(''); 146 | console.log(chalk.cyan('Please answer a few questions to update the "generate-react-cli.json" config file.')); 147 | console.log( 148 | chalk.cyan( 149 | '------------------------------------------------------------------------------------------------------------------------------' 150 | ) 151 | ); 152 | console.log(''); 153 | 154 | const answers = await prompt(missingConfigQuestions); 155 | const updatedAnswers = merge({}, currentConfigFile, answers); 156 | 157 | outputFileSync('generate-react-cli.json', JSON.stringify(updatedAnswers, null, 2)); 158 | 159 | console.log(); 160 | console.log(chalk.cyan('The ("generate-react-cli.json") has successfully updated for this project.')); 161 | 162 | console.log(); 163 | console.log(chalk.cyan('You can always go back and manually update it as needed.')); 164 | console.log(); 165 | console.log(chalk.cyan('Happy Hacking!')); 166 | console.log(); 167 | console.log(); 168 | 169 | return updatedAnswers; 170 | } catch (e) { 171 | console.error(chalk.red.bold('ERROR: Could not update the "generate-react-cli.json" config file.')); 172 | return e; 173 | } 174 | } 175 | 176 | export async function getCLIConfigFile() { 177 | // --- Make sure the cli commands are running from the root level of the project 178 | 179 | try { 180 | accessSync('./package.json', constants.R_OK); 181 | 182 | // --- Check to see if the config file exists 183 | 184 | try { 185 | accessSync('./generate-react-cli.json', constants.R_OK); 186 | const currentConfigFile = JSON.parse(readFileSync('./generate-react-cli.json')); 187 | 188 | /** 189 | * Check to see if there's a difference between grcConfigQuestions and the currentConfigFile. 190 | * If there is, update the currentConfigFile with the missingConfigQuestions. 191 | */ 192 | 193 | const missingConfigQuestions = grcConfigQuestions.filter( 194 | (question) => 195 | !deepKeys(currentConfigFile).includes(question.name) && 196 | (question.when ? question.when(currentConfigFile) : true) 197 | ); 198 | 199 | if (missingConfigQuestions.length) { 200 | return await updateCLIConfigFile(missingConfigQuestions, currentConfigFile); 201 | } 202 | 203 | return currentConfigFile; 204 | } catch (e) { 205 | return await createCLIConfigFile(); 206 | } 207 | } catch (error) { 208 | console.error( 209 | chalk.red.bold( 210 | "ERROR: Please make sure that you're running the generate-react-cli commands from the root level of your React project" 211 | ) 212 | ); 213 | return process.exit(1); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /docs/assets/generate-react-cli.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## [7.1.0](https://github.com/arminbro/generate-react-cli/compare/v7.0.6...v7.1.0) (2022-03-15) 6 | 7 | 8 | ### Features 9 | 10 | * adds dry-run flag to preview generated paths without writing files ([483aef2](https://github.com/arminbro/generate-react-cli/commit/483aef285a356bb02727d3c512d2e03e22b6493a)) 11 | 12 | ### [7.0.6](https://github.com/arminbro/generate-react-cli/compare/v7.0.5...v7.0.6) (2022-02-21) 13 | 14 | ### [7.0.5](https://github.com/arminbro/generate-react-cli/compare/v7.0.4...v7.0.5) (2022-01-17) 15 | 16 | ### [7.0.4](https://github.com/arminbro/generate-react-cli/compare/v7.0.3...v7.0.4) (2021-08-07) 17 | 18 | 19 | ### Bug Fixes 20 | 21 | * **story:** use storybook codemod's preferred way ([b694f76](https://github.com/arminbro/generate-react-cli/commit/b694f767d5619b2880f3d8aa143b7e9f2550ff11)) 22 | 23 | ### [7.0.3](https://github.com/arminbro/generate-react-cli/compare/v7.0.2...v7.0.3) (2021-06-12) 24 | 25 | ### [7.0.2](https://github.com/arminbro/generate-react-cli/compare/v7.0.1...v7.0.2) (2021-06-12) 26 | 27 | ### [7.0.1](https://github.com/arminbro/generate-react-cli/compare/v7.0.0...v7.0.1) (2021-06-12) 28 | 29 | 30 | ### Bug Fixes 31 | 32 | * remove camelCase from componentTestTemplateGenerator, it's handled in generateComponent ([7885d22](https://github.com/arminbro/generate-react-cli/commit/7885d22b816de7a60e2adcb3d977c1b541db1ae9)), closes [#43](https://github.com/arminbro/generate-react-cli/issues/43) 33 | 34 | ## [7.0.0](https://github.com/arminbro/generate-react-cli/compare/v6.0.2...v7.0.0) (2021-05-06) 35 | 36 | 37 | ### ⚠ BREAKING CHANGES 38 | 39 | * 🧨 Generate React CLI requires Node 12 or higher now. We no longer support 40 | Node 10. 41 | 42 | * 🤖 update dependencies ([331205f](https://github.com/arminbro/generate-react-cli/commit/331205f3afdc06ecccb9458dee873af9477b8e7f)) 43 | 44 | ### [6.0.2](https://github.com/arminbro/generate-react-cli/compare/v6.0.1...v6.0.2) (2021-02-24) 45 | 46 | ### [6.0.1](https://github.com/arminbro/generate-react-cli/compare/v6.0.0...v6.0.1) (2021-02-17) 47 | 48 | ## [6.0.0](https://github.com/arminbro/generate-react-cli/compare/v5.2.3...v6.0.0) (2021-02-17) 49 | 50 | 51 | ### ⚠ BREAKING CHANGES 52 | 53 | * You will need to use the "TemplateName" keyword as your custom template filename if 54 | you want the CLI to replace it with the component name. 55 | 56 | ### Features 57 | 58 | * custom component files ([6373a91](https://github.com/arminbro/generate-react-cli/commit/6373a912d725581571c2cdf01cf9062f3965c06f)), closes [#21](https://github.com/arminbro/generate-react-cli/issues/21) [#22](https://github.com/arminbro/generate-react-cli/issues/22) [#27](https://github.com/arminbro/generate-react-cli/issues/27) [#34](https://github.com/arminbro/generate-react-cli/issues/34) [#36](https://github.com/arminbro/generate-react-cli/issues/36) [#37](https://github.com/arminbro/generate-react-cli/issues/37) [#39](https://github.com/arminbro/generate-react-cli/issues/39) 59 | 60 | 61 | ### Bug Fixes 62 | 63 | * use "TemplateName" keyword for custom templates ([73a308f](https://github.com/arminbro/generate-react-cli/commit/73a308fe38e660f57cde102d3a5ded64c5339fc7)) 64 | * use lodash upperFirst to force component name start with uppercase ([e401caf](https://github.com/arminbro/generate-react-cli/commit/e401cafa0e35c85db8d4c26dad5f2425f4269980)) 65 | 66 | ### [5.2.3](https://github.com/arminbro/generate-react-cli/compare/v5.2.2...v5.2.3) (2021-01-05) 67 | 68 | ### [5.2.2](https://github.com/arminbro/generate-react-cli/compare/v5.2.1...v5.2.2) (2021-01-05) 69 | 70 | ## [5.2.1](https://github.com/arminbro/generate-react-cli/compare/v5.1.0...v5.2.0) (2021-01-05) 71 | 72 | ### Features 73 | 74 | - allow generation of multiple components at once ([18cd5f0](https://github.com/arminbro/generate-react-cli/commit/18cd5f070c3007947011699d7186b8e259e27b05)) 75 | 76 | ### Bug Fixes 77 | 78 | - 🐛 react components must start with a upper case letter. ([4c3bddd](https://github.com/arminbro/generate-react-cli/commit/4c3bdddf9e93c10905f28d6b4babe77fdbf10c4f)) 79 | 80 | ## [5.1.0](https://github.com/arminbro/generate-react-cli/compare/v5.0.1...v5.1.0) (2020-09-18) 81 | 82 | ### Features 83 | 84 | - 🎸 Support for custom extension via custom templates ([7f989a6](https://github.com/arminbro/generate-react-cli/commit/7f989a61702f8ff0e612845bafed79146e6a01ef)), closes [#18](https://github.com/arminbro/generate-react-cli/issues/18) [#19](https://github.com/arminbro/generate-react-cli/issues/19) [#25](https://github.com/arminbro/generate-react-cli/issues/25) 85 | 86 | ### [5.0.1](https://github.com/arminbro/generate-react-cli/compare/v5.0.0...v5.0.1) (2020-06-24) 87 | 88 | ## [5.0.0](https://github.com/arminbro/generate-react-cli/compare/v4.3.3...v5.0.0) (2020-05-25) 89 | 90 | ### ⚠ BREAKING CHANGES 91 | 92 | - 🧨 This new "type" option will replace the custom component commands that 93 | you run. Meaning you now can pass the custom component as type option 94 | (e.g npx generate-react-cli component HomePage --type=page ) that you 95 | have configured in your GRC config file. 96 | 97 | ### Features 98 | 99 | - 🎸 Add a new "type" option to the component command ([1a5ce6a](https://github.com/arminbro/generate-react-cli/commit/1a5ce6a3c9d8d19937b201ed8fb1bc5ec6c4fae9)) 100 | 101 | ### [4.3.3](https://github.com/arminbro/generate-react-cli/compare/v4.3.2...v4.3.3) (2020-05-10) 102 | 103 | ### [4.3.2](https://github.com/arminbro/generate-react-cli/compare/v4.3.1...v4.3.2) (2020-05-10) 104 | 105 | ### [4.3.1](https://github.com/arminbro/generate-react-cli/compare/v4.3.0...v4.3.1) (2020-05-10) 106 | 107 | ## [4.3.0](https://github.com/arminbro/generate-react-cli/compare/v4.2.2...v4.3.0) (2020-05-10) 108 | 109 | ### Features 110 | 111 | - 🎸 Make 'GRC' more configurable (multi component commands) ([59f1622](https://github.com/arminbro/generate-react-cli/commit/59f1622dc6c6ca5a2b42d870b02c265694bc10eb)), closes [#14](https://github.com/arminbro/generate-react-cli/issues/14) 112 | 113 | ### [4.2.2](https://github.com/arminbro/generate-react-cli/compare/v4.2.1...v4.2.2) (2020-05-03) 114 | 115 | ### [4.2.1](https://github.com/arminbro/generate-react-cli/compare/v4.2.0...v4.2.1) (2020-05-03) 116 | 117 | ## [4.2.0](https://github.com/arminbro/generate-react-cli/compare/v4.1.1...v4.2.0) (2020-05-02) 118 | 119 | ### Features 120 | 121 | - 🎸 Allow custom file templates ([6104241](https://github.com/arminbro/generate-react-cli/commit/610424136989b1f18de1e6fa9a04084114cde64b)), closes [#12](https://github.com/arminbro/generate-react-cli/issues/12) 122 | 123 | ### [4.1.1](https://github.com/arminbro/generate-react-cli/compare/v4.1.0...v4.1.1) (2020-04-23) 124 | 125 | ## [4.1.0](https://github.com/arminbro/generate-react-cli/compare/v4.0.2...v4.1.0) (2020-04-20) 126 | 127 | ### Features 128 | 129 | - 🎸 add new page command ([3a441de](https://github.com/arminbro/generate-react-cli/commit/3a441dede662bf6a3d65c67072b50900ece46879)), closes [#10](https://github.com/arminbro/generate-react-cli/issues/10) 130 | 131 | ### [4.0.2](https://github.com/arminbro/generate-react-cli/compare/v4.0.1...v4.0.2) (2020-04-05) 132 | 133 | ### Bug Fixes 134 | 135 | - 🐛 audit fix to resolve 1 low vulnerability ([0ac348e](https://github.com/arminbro/generate-react-cli/commit/0ac348ef6f6da6ecc4a72be153e22965894d796b)) 136 | 137 | ### [4.0.1](https://github.com/arminbro/generate-react-cli/compare/v4.0.0...v4.0.1) (2020-04-05) 138 | 139 | ## [4.0.0](https://github.com/arminbro/generate-react-cli/compare/v3.0.2...v4.0.0) (2020-03-21) 140 | 141 | ### ⚠ BREAKING CHANGES 142 | 143 | - 🧨 Generate React CLI requires Node 10 or higher 144 | 145 | - 🤖 Generate React CLI requires Node 10 or higher ([bd745f6](https://github.com/arminbro/generate-react-cli/commit/bd745f659d0e538e7abfb875cd1e160c5c6b064c)) 146 | 147 | ### [3.0.2](https://github.com/arminbro/generate-react-cli/compare/v3.0.1...v3.0.2) (2020-03-21) 148 | 149 | ### [3.0.1](https://github.com/arminbro/generate-react-cli/compare/v3.0.0...v3.0.1) (2020-03-14) 150 | 151 | ## [3.0.0](https://github.com/arminbro/generate-react-cli/compare/v2.0.2...v3.0.0) (2019-12-14) 152 | 153 | ### ⚠ BREAKING CHANGES 154 | 155 | - Update the way option values are passed in the component command. For 156 | example if you wanted or didn't want a corresponding test file, the old 157 | syntax looked like this: --withTest or --no-withTest. Now with the new 158 | syntax you just do this --withTest=true or --withTest=false this applies 159 | to all the other component options (withStyle, withStory, withLazy). 160 | 161 | - 💄 Update component command options ([c870c7c](https://github.com/arminbro/generate-react-cli/commit/c870c7c5544640e23848f4f22b883e2d0ee755e4)) 162 | 163 | ### [2.0.2](https://github.com/arminbro/generate-react-cli/compare/v2.0.1...v2.0.2) (2019-12-13) 164 | 165 | ### [2.0.1](https://github.com/arminbro/generate-react-cli/compare/v2.0.0...v2.0.1) (2019-12-13) 166 | 167 | ## [2.0.0](https://github.com/arminbro/generate-react-cli/compare/v1.8.0...v2.0.0) (2019-12-13) 168 | 169 | ### ⚠ BREAKING CHANGES 170 | 171 | - new command option parameters 172 | 173 | ### Features 174 | 175 | - add TypeScript support ([8d13018](https://github.com/arminbro/generate-react-cli/commit/8d13018fa22042b9ac058cc4b332583d4d8abf80)) 176 | 177 | - make stylesheets optional by adding “withStyle” option 178 | 179 | - improve developer experience when updating (“generate-react-cli.json”) the config file. The CLI will only inquire about the new missing properties in the config file the next time generate-react-cli is ran. 180 | 181 | - 🎸 make sure user is running Node 8 or higher ([fe5dba1](https://github.com/arminbro/generate-react-cli/commit/fe5dba19e68cb8914db4ee4fc1f93fbdd808e355)) 182 | 183 | * 💡 component command has a few option updates ([67579d3](https://github.com/arminbro/generate-react-cli/commit/67579d3724af1108932670b87dc7084f9b22cbe8)) 184 | 185 | ## 1.8.0 (2019-12-12) 186 | 187 | - testing standard-version 188 | 189 | ## 1.7.5 (2019-11-24) 190 | 191 | ### Chores 192 | 193 | - major dependency update (chalk 3.0.0) 194 | 195 | ## 1.7.4 (2019-11-24) 196 | 197 | ### Chores 198 | 199 | - update dependencies 200 | 201 | ## 1.7.3 (2019-11-07) 202 | 203 | ### Chores 204 | 205 | - update readme 206 | 207 | ## 1.7.2 (2019-11-06) 208 | 209 | ### Bug Fixes 210 | 211 | - remove unnecessary use of Fragment in Lazy template 212 | 213 | ## 1.7.1 (2019-11-06) 214 | 215 | ### Chores 216 | 217 | - update dependencies 218 | 219 | ### Bug Fixes 220 | 221 | - remove data-testid from jsTemplate if test library is not Testing Library 222 | - only import style object in jsTemplate if css module is true 223 | 224 | ## 1.7.0 (2019-10-17) 225 | 226 | ### Features 227 | 228 | - (#4) make getByTestId the default 229 | 230 | ### Bug Fixes 231 | 232 | - fix (#3) generated tests always use FollowBtn 233 | 234 | ## 1.6.2 (2019-10-02) 235 | 236 | ### Chores 237 | 238 | - update readme 239 | 240 | ## 1.6.1 (2019-10-02) 241 | 242 | ### Features 243 | 244 | - support different testing component libraries 245 | 246 | ### Chores 247 | 248 | - update dependencies 249 | - update readme 250 | 251 | ## 1.6.0 (2019-09-30) 252 | 253 | ### Chores 254 | 255 | - update dependencies 256 | 257 | ## 1.5.9 (2019-09-29) 258 | 259 | ### Chores 260 | 261 | - reorganize the file structure within the gr-cli 262 | 263 | ## 1.5.8 (2019-08-23) 264 | 265 | ### Chores 266 | 267 | - bump major version of inquirer 268 | 269 | ## 1.5.7 (2019-08-23) 270 | 271 | ### Chores 272 | 273 | - update dependencies 274 | 275 | ## 1.5.6 (2019-08-17) 276 | 277 | ### Chores 278 | 279 | - reorganize 280 | 281 | ## 1.5.5 (2019-08-17) 282 | 283 | ### Chores 284 | 285 | - update readme 286 | 287 | ## 1.5.4 (2019-08-17) 288 | 289 | ### Features 290 | 291 | - add shorthand "g-r" command 292 | 293 | ## 1.5.3 (2019-08-13) 294 | 295 | ### Chores 296 | 297 | - update issue templates for github 298 | - add component cmd gif for readme.md 299 | - update readme 300 | 301 | ## 1.5.2 (2019-08-12) 302 | 303 | ### Chores 304 | 305 | - update readme 306 | 307 | ## 1.5.1 (2019-8-12) 308 | 309 | ### Chores 310 | 311 | - update readme 312 | 313 | ## 1.5.0 (2019-08-12) 314 | 315 | ### Features 316 | 317 | - update GRC config file when needed. 318 | - add lazy template 319 | - add additional options to "component" command (withTest, withStory, withLazy) 320 | 321 | ### Chores 322 | 323 | - update readme 324 | 325 | ## 1.4.1 (2019-08-07) 326 | 327 | ### Bug Fixes 328 | 329 | - use correct preprocessor extension in component 330 | 331 | ## 1.4.0 (2019-08-07) 332 | 333 | ### Features 334 | 335 | - add question inquirer to create generate-react-cli config file 336 | 337 | ### Chores 338 | 339 | - create License 340 | 341 | ## 1.3.3 (2019-08-04) 342 | 343 | ### Chores 344 | 345 | - update readme 346 | 347 | ## 1.3.2 (2019-08-04) 348 | 349 | ### Chores 350 | 351 | - update package description 352 | 353 | ## 1.3.1 (2019-08-04) 354 | 355 | ### Chores 356 | 357 | - update readme 358 | 359 | ## 1.3.0 (2019-08-04) 360 | 361 | ### Chores 362 | 363 | - add templates to files in package.json 364 | 365 | ## 1.2.0 (2019-08-04) 366 | 367 | ### Chores 368 | 369 | - add repository to package.json 370 | 371 | ## 1.1.0 (2019-08-04) 372 | 373 | ### Features 374 | 375 | - initial base features of generate react cli 376 | 377 | ### Chores 378 | 379 | - add readme 380 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Generate React CLI 2 | 3 | [![License](https://img.shields.io/npm/l/express.svg)](https://github.com/arminbro/generate-react-cli/blob/master/LICENSE) 4 | 5 |

6 | 7 |

8 | 9 | ## Why? 10 | 11 | To help speed up productivity in React projects and stop copying, pasting, and renaming files each time you want to create a new component. 12 | 13 | A short [article](https://dev.to/arminbro/generate-react-cli-1ooh) goes deeper into why we created GRC if you have the time. 14 | 15 | You can also watch an excellent [video](https://www.youtube.com/watch?v=NEvnt3MWttY) tutorial on how to use GRC by [Eric Murphy](https://www.youtube.com/channel/UC5KDiSAFxrDWhmysBcNqtMA). 16 | 17 | ## Table of Contents: 18 | 19 | - [Config file](#config-file) 20 | - [Generate components](#generate-components) 21 | - [Custom component types](#custom-component-types) 22 | - [Custom component templates](#custom-component-templates) 23 | - [Custom component directory](#custom-component-directory) 24 | - [Custom component files](#custom-component-files) 25 | 26 | ## You can run it using npx like this: 27 | 28 | ``` 29 | npx generate-react-cli component Box 30 | ``` 31 | 32 | _([npx](https://medium.com/@maybekatz/introducing-npx-an-npm-package-runner-55f7d4bd282b) is a package runner tool that comes with npm 5.2+)_ 33 | 34 | ## Config File 35 | 36 | When you run GRC within your project the first time, it will ask you a series of questions to customize the cli for your project needs (this will create a "generate-react-cli.json" config file). 37 | 38 | #### Example of the **generate-react-cli.json** config file: 39 | 40 | ```json 41 | { 42 | "usesTypeScript": true, 43 | "usesCssModule": true, 44 | "cssPreprocessor": "scss", 45 | "testLibrary": "Testing Library", 46 | "component": { 47 | "default": { 48 | "path": "src/components", 49 | "withLazy": false, 50 | "withStory": false, 51 | "withStyle": true, 52 | "withTest": true 53 | } 54 | } 55 | } 56 | ``` 57 | 58 | ## Generate Components 59 | 60 | ```sh 61 | npx generate-react-cli component Box 62 | ``` 63 | 64 | This command will create a folder with your component name within your default (e.g. **src/components**) directory, and its corresponding files. 65 | 66 | #### Example of the component files structure: 67 | 68 | ``` 69 | |-- /src 70 | |-- /components 71 | |-- /Box 72 | |-- Box.js 73 | |-- Box.css 74 | |-- Box.test.js 75 | ``` 76 | 77 | ### Options 78 | 79 | You can also override some of the GRC component config rules using one-off commands. So for example, let's say you have set **withTest** to be `true` in the `component.default` property. You can override it like this: 80 | 81 | ```sh 82 | npx generate-react-cli component Box --withTest=false 83 | ``` 84 | 85 | Or vice versa, if you have set **withTest** to be `false` you can do this: 86 | 87 | ```sh 88 | npx generate-react-cli component Box --withTest=true 89 | ``` 90 | 91 | Otherwise, if you don't pass any options, it will just use the default values that you have set in the GRC config file under `component.default`. 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 179 | 180 | 181 | 182 |
OptionsDescriptionValue TypeDefault Value
--path 104 | Value of the path where you want the component to be generated in (e.g. src/components). 105 | Stringcomponent.default.path
--type 113 | You can pass a custom component type that you have configured in the GRC config file that has its own set of component config rules. Read more about custom component types. 114 | Stringcomponent.default
--withLazy 122 | Creates a corresponding lazy file (a file that lazy-loads your component out of the box and enables code splitting) with this component. 123 | Booleancomponent.default.withLazy
--withStory 131 | Creates a corresponding (storybook) story file with this component. 132 | Booleancomponent.default.withStory
--withStyle 140 | Creates a corresponding stylesheet file with this component. 141 | Booleancomponent.default.withStyle
--withTest 149 | Creates a corresponding test file with this component. 150 | Booleancomponent.default.withTest
--dry-run 158 | Show what will be generated without writing to disk 159 | Booleanfalse
--flat 167 | Generate the files in the mentioned path instead of creating new folder for it 168 | Booleanfalse
--customDirectory 176 | Template value that overrides the name of the directory of the component to be generated in.
177 | See more under custom component directory. 178 |
Stringnull
183 | 184 | ### Custom component types 185 | 186 | By default, GRC will use the `component.default` configuration rules when running the component command out of the box. 187 | 188 | What if you wanted to generate other types of components that have their own set of config rules (e.g., **page** or **layout**)? 189 | 190 | You can do so by extending the **generate-react-cli.json** config file like this. 191 | 192 | ```json 193 | { 194 | "usesTypeScript": false, 195 | "usesCssModule": true, 196 | "cssPreprocessor": "scss", 197 | "testLibrary": "Testing Library", 198 | "component": { 199 | "default": { 200 | "path": "src/components", 201 | "withLazy": false, 202 | "withStory": false, 203 | "withStyle": true, 204 | "withTest": true 205 | }, 206 | "page": { 207 | "path": "src/pages", 208 | "withLazy": true, 209 | "withStory": false, 210 | "withStyle": true, 211 | "withTest": true 212 | }, 213 | "layout": { 214 | "path": "src/layout", 215 | "withLazy": false, 216 | "withStory": false, 217 | "withStyle": false, 218 | "withTest": true 219 | } 220 | } 221 | } 222 | ``` 223 | 224 | Now you can generate a component with your custom component types like this: 225 | 226 | ```sh 227 | npx generate-react-cli component HomePage --type=page 228 | ``` 229 | 230 | ```sh 231 | npx generate-react-cli component BoxLayout --type=layout 232 | ``` 233 | 234 | You can also pass the same [options](#options) to your custom component types as you would for the default component type. 235 | 236 | ### Custom component templates 237 | 238 | You can also create your own custom templates that GRC can use instead of the built-in templates that come with it. We hope this will provide more flexibility for your components that you want to generate. 239 | 240 | There is an optional `customTemplates` object that you can pass to the `component.default` or any of your custom component types within your **generate-react-cli.json** config file. 241 | 242 | #### Example of the `customTemplates` object: 243 | 244 | ```json 245 | "customTemplates": { 246 | "component": "templates/TemplateName.js", 247 | "lazy": "templates/TemplateName.lazy.js", 248 | "story": "templates/TemplateName.story.js", 249 | "style": "templates/TemplateName.style.scss", 250 | "test": "templates/TemplateName.test.js" 251 | }, 252 | ``` 253 | 254 | The keys represent the type of file, and the values are the paths that point to where your custom template lives in your project/system. Please note the `TemplateName` keyword in the template filename. GRC will use this keyword and replace it with your component name (in whichever format you typed the component name in the command) as the filename. 255 | 256 | #### Example of using the `customTemplates` object within your generate-react-cli.json config file: 257 | 258 | ```json 259 | { 260 | "usesTypeScript": false, 261 | "usesCssModule": true, 262 | "cssPreprocessor": "scss", 263 | "testLibrary": "Testing Library", 264 | "component": { 265 | "default": { 266 | "customTemplates": { 267 | "component": "templates/component/TemplateName.js", 268 | "style": "templates/component/TemplateName.style.scss", 269 | "test": "templates/component/TemplateName.test.js" 270 | }, 271 | "path": "src/components", 272 | "withStyle": true, 273 | "withTest": true, 274 | "withStory": true, 275 | "withLazy": false 276 | }, 277 | "page": { 278 | "customTemplates": { 279 | "test": "templates/page/TemplateName.test.js" 280 | }, 281 | "path": "src/pages", 282 | "withLazy": true, 283 | "withStory": false, 284 | "withStyle": true, 285 | "withTest": true 286 | } 287 | } 288 | } 289 | ``` 290 | 291 | Notice in the `page.customTemplates` that we only specified the `test` custom template type. That's because all the custom template types are optional. If you don't set the other types, GRC will default to using the built-in templates it comes with. 292 | 293 | #### Example of a custom component template file: 294 | 295 | ```jsx 296 | // templates/component/TemplateName.js 297 | 298 | import React from 'react'; 299 | import styles from './TemplateName.module.css'; 300 | 301 | const TemplateName = () => ( 302 |
303 |

TemplateName component

304 |
305 | ); 306 | 307 | export default TemplateName; 308 | ``` 309 | 310 | **Important:** You can use the following keywords within your custom templates to format the component name. Note that the built-in GRC templates use `templatename` casing by default: 311 | 312 | | Keyword | Replacement | 313 | | --------------- | ---------------------------------------------------------------------------------------------- | 314 | | `templatename` | component name in raw case (whichever format the user typed the component name in the command) | 315 | | `TemplateName` | component name in PascalCase | 316 | | `templateName` | component name in camelCase | 317 | | `template-name` | component name in kebab-case | 318 | | `template_name` | component name in snake_case | 319 | | `TEMPLATE_NAME` | component name in uppercase SNAKE_CASE | 320 | | `TEMPLATENAME` | component name in full UPPERCASE | 321 | 322 | #### Example of a custom test template file: 323 | 324 | ```jsx 325 | // templates/component/TemplateName.test.js 326 | 327 | import React from 'react'; 328 | import ReactDOM from 'react-dom'; 329 | import TemplateName from './TemplateName'; 330 | 331 | it('It should mount', () => { 332 | const div = document.createElement('div'); 333 | ReactDOM.render(, div); 334 | ReactDOM.unmountComponentAtNode(div); 335 | }); 336 | ``` 337 | 338 | ### Custom component directory 339 | 340 | Using the `customDirectory` you can easily override the directory name for the component generated. For instance, if prefixes are required for particular components or if template names will be mixed, the `customDirectory` option will allow you to override the way that GRC generates the name of the directory where the component files will live. 341 | 342 | The `customDirectory` directive allows all supported casings (see previous section) and can be overridden at the following levels in ascending specific of priority: 343 | 344 | - top 345 | - component.default 346 | - component._type_ 347 | - CLI 348 | 349 | #### Example: 350 | 351 | For React Context Providers in a project, the decision has been made to separate Context generation from the visual components. 352 | 353 | In a typical configuration the configuration would look as following: 354 | 355 | ```json 356 | { 357 | "provider": { 358 | "path": "src/components/providers", 359 | "withLazy": false, 360 | "withStory": true, 361 | "withStyle": false, 362 | "withTest": true, 363 | "withTypes": true, 364 | "withContext": true, 365 | "customTemplates": { 366 | "component": "src/components/templates/provider/TemplateName.tsx", 367 | "context": "src/components/templates/provider/TemplateName.context.ts", 368 | "story": "src/components/templates/provider/TemplateName.stories.tsx", 369 | "test": "src/components/templates/provider/TemplateName.test.tsx", 370 | "types": "src/components/templates/provider/TemplateName.types.ts" 371 | } 372 | } 373 | } 374 | ``` 375 | 376 | With the configuration above, the component would be required to either follow a full or a minimalistic naming convention. 377 | I.e. the component would either need to be generated as `ThemeProvider` and consequently the context name would be generated as `ThemeProviderContext`, or by renaming the files and templates as `TemplateNameProvider` but with the downside of the component path being generated as `src/components/providers/Theme`. This creates inconsistent naming in the directory containg the component files. 378 | 379 | To work around this, the `customDirectory` option can be used to enforce a particular style. 380 | 381 | ```json 382 | { 383 | ... 384 | "provider": { 385 | "path": "src/components/providers", 386 | "withLazy": false, 387 | "withStory": true, 388 | "withStyle": false, 389 | "withTest": true, 390 | "withTypes": true, 391 | "withContext": true, 392 | "customDirectory": "TemplateNameProvider", 393 | "customTemplates": { 394 | "component": "src/components/templates/provider/TemplateNameProvider.tsx", 395 | "context": "src/components/templates/provider/TemplateName.context.ts", 396 | "story": "src/components/templates/provider/TemplateNameProvider.stories.tsx", 397 | "test": "src/components/templates/provider/TemplateNameProvider.test.tsx", 398 | "types": "src/components/templates/provider/TemplateNameProvider.types.ts" 399 | } 400 | } 401 | ... 402 | } 403 | ``` 404 | 405 | The above configuration would allow you to mix and match different template names and keep naming consistent. 406 | 407 | If we executed GRC with the above configuration (`npx generate-react-cli component Theme --type=provider`), the result would look like this: 408 | 409 | ```fs 410 | src/components/providers/ThemeProvider/Theme.context.ts 411 | src/components/providers/ThemeProvider/ThemeProvider.tsx 412 | src/components/providers/ThemeProvider/ThemeProvider.stories.tsx 413 | src/components/providers/ThemeProvider/ThemeProvider.test.tsx 414 | src/components/providers/ThemeProvider/ThemeProvider.types.ts 415 | ``` 416 | 417 | Similarly, this construct could be used as a shortcut for generating other named components, like the `BoxLayout` example above, depending on that could be shortened to: 418 | 419 | ```sh 420 | npx generate-react-cli component Box --type=layout --customDir=TemplateNameLayout 421 | ``` 422 | 423 | Or it could be used to generate files with a naming convention with `Test`, `Lazy`, `Context`, `Theme`, or `Provider` suffixes. Or even combined with skeleton CSS 424 | 425 | ### Custom component files 426 | 427 | GRC comes with corresponding built-in files for a given component if you need them (i.e., `withStyle`, `withTest`, `withStory`, and `withLazy`). 428 | 429 | What if you wanted to add custom files of your own? 430 | 431 | For example, let's say you wanted to add an `index.js` file for each component, so you don't have to add the additional component name with each import (i.e., `import Box from './components/Box'` instead of `import Box from './components/Box/Box'`). 432 | 433 | Or maybe you need an additional style file for your component stories. 434 | 435 | You can do so by editing your **generate-react-cli.json** config file like so. 436 | 437 | ```json 438 | { 439 | "usesTypeScript": false, 440 | "usesCssModule": false, 441 | "cssPreprocessor": "css", 442 | "testLibrary": "Testing Library", 443 | "component": { 444 | "default": { 445 | "path": "src/components", 446 | "withStyle": true, 447 | "withTest": true, 448 | "withStory": true, 449 | "withLazy": false, 450 | "withIndex": true, 451 | "withStoryStyle": true, 452 | "customTemplates": { 453 | "index": "templates/default/index.js", 454 | "storyStyle": "templates/default/TemplateName.stories.css" 455 | } 456 | } 457 | } 458 | } 459 | ``` 460 | 461 | ```jsx 462 | // templates/default/index.js 463 | 464 | export { default } from './TemplateName'; 465 | ``` 466 | 467 | ```css 468 | /* templates/default/TemplateName.stories.css */ 469 | 470 | .TemplateName { 471 | } 472 | ``` 473 | 474 | In this case, we added a `withIndex` & `withStoryStyle` to the `component.default`. Note: You can add custom files to any of your custom component types. 475 | 476 | You should also see that we added `index` and `storyStyle` to our `customTemplates` object. That's because custom files require custom templates. Otherwise, you will get an error when you generate a component. 477 | 478 | Also, we used the `TemplateName` keyword for the `storyStyle` custom file. GRC will generate this corresponding file and replace `TemplateName` with the component name. 479 | 480 | ## License 481 | 482 | Generate React CLI is an open source software [licensed as MIT](https://github.com/arminbro/generate-react-cli/blob/master/LICENSE). 483 | -------------------------------------------------------------------------------- /src/utils/generateComponentUtils.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import path from 'path'; 3 | import replace from 'replace'; 4 | import camelCase from 'lodash/camelCase.js'; 5 | import kebabCase from 'lodash/kebabCase.js'; 6 | import snakeCase from 'lodash/snakeCase.js'; 7 | import startCase from 'lodash/startCase.js'; 8 | import fsExtra from 'fs-extra'; 9 | 10 | import componentJsTemplate from '../templates/component/componentJsTemplate.js'; 11 | import componentTsTemplate from '../templates/component/componentTsTemplate.js'; 12 | import componentCssTemplate from '../templates/component/componentCssTemplate.js'; 13 | import componentStyledTemplate from '../templates/component/componentStyledTemplate.js'; 14 | import componentLazyTemplate from '../templates/component/componentLazyTemplate.js'; 15 | import componentTsLazyTemplate from '../templates/component/componentTsLazyTemplate.js'; 16 | import componentStoryTemplate from '../templates/component/componentStoryTemplate.js'; 17 | import componentTestEnzymeTemplate from '../templates/component/componentTestEnzymeTemplate.js'; 18 | import componentTestDefaultTemplate from '../templates/component/componentTestDefaultTemplate.js'; 19 | import componentTestTestingLibraryTemplate from '../templates/component/componentTestTestingLibraryTemplate.js'; 20 | 21 | const templateNameRE = /.*(template[|_-]?name).*/i; 22 | 23 | const { existsSync, outputFileSync, readFileSync } = fsExtra; 24 | 25 | export function getComponentByType(args, cliConfigFile) { 26 | const hasComponentTypeOption = args.find((arg) => arg.includes('--type')); 27 | 28 | // Check for component type option. 29 | 30 | if (hasComponentTypeOption) { 31 | const componentType = hasComponentTypeOption.split('=')[1]; // get the component type value 32 | const selectedComponentType = cliConfigFile.component[componentType]; 33 | 34 | // If the selected component type does not exists in the cliConfigFile under `component` throw an error 35 | 36 | if (!selectedComponentType) { 37 | console.error( 38 | chalk.red( 39 | ` 40 | ERROR: Please make sure the component type you're trying to use exists in the 41 | ${chalk.bold('generate-react-cli.json')} config file under the ${chalk.bold('component')} object. 42 | ` 43 | ) 44 | ); 45 | 46 | process.exit(1); 47 | } 48 | 49 | // Otherwise return it. 50 | 51 | return selectedComponentType; 52 | } 53 | 54 | // Otherwise return the default component type. 55 | 56 | return cliConfigFile.component.default; 57 | } 58 | 59 | export function getCorrespondingComponentFileTypes(component) { 60 | return Object.keys(component).filter((key) => key.split('with').length > 1); 61 | } 62 | 63 | function getCustomTemplate(componentName, templatePath) { 64 | // --- Try loading custom template 65 | 66 | try { 67 | const template = readFileSync(templatePath, 'utf8'); 68 | const filename = path.basename(templatePath).replace(/template[_-]?name/i, componentName); 69 | 70 | return { template, filename }; 71 | } catch (e) { 72 | console.error( 73 | chalk.red( 74 | ` 75 | ERROR: The custom template path of "${templatePath}" does not exist. 76 | Please make sure you're pointing to the right custom template path in your generate-react-cli.json config file. 77 | ` 78 | ) 79 | ); 80 | 81 | return process.exit(1); 82 | } 83 | } 84 | 85 | function componentDirectoryNameGenerator({ cmd, componentName, cliConfigFile, filename, convertors }) { 86 | let componentPath = cmd.path; 87 | 88 | if (cmd.flat !== true) { 89 | let componentDirectory = componentName; 90 | 91 | const customDirectoryConfigs = [ 92 | cliConfigFile.customDirectory, 93 | cliConfigFile.component.default.customDirectory, 94 | cliConfigFile.component[cmd.type].customDirectory, 95 | cmd.customDirectory, 96 | ].filter((e) => Boolean(e) && typeof e === 'string'); 97 | 98 | if (customDirectoryConfigs.length > 0) { 99 | const customDirectory = customDirectoryConfigs.slice(-1).toString(); 100 | 101 | // Double check if the customDirectory is templatable 102 | if (templateNameRE.exec(customDirectory) == null) { 103 | console.error( 104 | chalk.red( 105 | `customDirectory [${customDirectory}] for ${componentName} does not contain a templatable value.\nPlease check your configuration!` 106 | ) 107 | ); 108 | 109 | process.exit(-2); 110 | } 111 | 112 | for (const convertor in convertors) { 113 | const re = new RegExp(`.*${convertor}.*`); 114 | 115 | if (re.exec(customDirectory) !== null) { 116 | componentDirectory = customDirectory.replace(convertor, convertors[convertor]); 117 | } 118 | } 119 | } 120 | 121 | componentPath += `/${componentDirectory}`; 122 | } 123 | 124 | componentPath += `/${filename}`; 125 | 126 | return componentPath; 127 | } 128 | 129 | function componentTemplateGenerator({ cmd, componentName, cliConfigFile, convertors }) { 130 | // @ts-ignore 131 | const { usesStyledComponents, cssPreprocessor, testLibrary, usesCssModule, usesTypeScript } = cliConfigFile; 132 | const { customTemplates } = cliConfigFile.component[cmd.type]; 133 | let template = null; 134 | let filename = null; 135 | 136 | // Check for a custom component template. 137 | 138 | if (customTemplates && customTemplates.component) { 139 | // --- Load and use the custom component template 140 | 141 | const { template: customTemplate, filename: customTemplateFilename } = getCustomTemplate( 142 | componentName, 143 | customTemplates.component 144 | ); 145 | 146 | template = customTemplate; 147 | filename = customTemplateFilename; 148 | } else { 149 | // --- Else use GRC built-in component template 150 | 151 | template = usesTypeScript ? componentTsTemplate : componentJsTemplate; 152 | filename = usesTypeScript ? `${componentName}.tsx` : `${componentName}.js`; 153 | 154 | // --- If test library is not Testing Library or if withTest is false. Remove data-testid from template 155 | 156 | if (testLibrary !== 'Testing Library' || !cmd.withTest) { 157 | template = template.replace(` data-testid="templatename"`, ''); 158 | } 159 | 160 | // --- If it has a corresponding stylesheet 161 | 162 | if (cmd.withStyle) { 163 | if (cliConfigFile.usesStyledComponents) { 164 | const cssPath = `${componentName}.styled`; 165 | template = template.replace( 166 | `import styles from './templatename.module.css'`, 167 | `import { templatenameWrapper } from './${cssPath}'` 168 | ); 169 | template = template.replace(` className={styles.templatename}`, ''); 170 | template = template.replace(` `, ''); 172 | } else { 173 | const module = usesCssModule ? '.module' : ''; 174 | const cssPath = `${componentName}${module}.${cssPreprocessor}`; 175 | 176 | // --- If the css module is true make sure to update the template accordingly 177 | 178 | if (module.length) { 179 | template = template.replace(`'./templatename.module.css'`, `'./${cssPath}'`); 180 | } else { 181 | template = template.replace(`{styles.templatename}`, `"${componentName}"`); 182 | template = template.replace(`styles from './templatename.module.css'`, `'./${cssPath}'`); 183 | } 184 | } 185 | } else { 186 | // --- If no stylesheet, remove className attribute and style import from jsTemplate 187 | 188 | template = template.replace(` className={styles.templatename}`, ''); 189 | template = template.replace(`import styles from './templatename.module.css';`, ''); 190 | } 191 | } 192 | 193 | return { 194 | componentPath: componentDirectoryNameGenerator({ cmd, componentName, cliConfigFile, filename, convertors }), 195 | filename, 196 | template, 197 | }; 198 | } 199 | 200 | function componentStyleTemplateGenerator({ cliConfigFile, cmd, componentName, convertors }) { 201 | const { customTemplates } = cliConfigFile.component[cmd.type]; 202 | let template = null; 203 | let filename = null; 204 | 205 | // Check for a custom style template. 206 | 207 | if (customTemplates && customTemplates.style) { 208 | // --- Load and use the custom style template 209 | 210 | const { template: customTemplate, filename: customTemplateFilename } = getCustomTemplate( 211 | componentName, 212 | customTemplates.style 213 | ); 214 | 215 | template = customTemplate; 216 | filename = customTemplateFilename; 217 | } else { 218 | const { usesTypeScript, usesStyledComponents, cssPreprocessor, usesCssModule } = cliConfigFile; 219 | if (usesStyledComponents) { 220 | filename = usesTypeScript ? `${componentName}.styled.ts` : `${componentName}.styled.js`; 221 | template = componentStyledTemplate; 222 | } else { 223 | const module = usesCssModule ? '.module' : ''; 224 | const cssFilename = `${componentName}${module}.${cssPreprocessor}`; 225 | 226 | // --- Else use GRC built-in style template 227 | 228 | template = componentCssTemplate; 229 | filename = cssFilename; 230 | } 231 | } 232 | 233 | return { 234 | componentPath: componentDirectoryNameGenerator({ cmd, componentName, cliConfigFile, filename, convertors }), 235 | filename, 236 | template, 237 | }; 238 | } 239 | 240 | function componentTestTemplateGenerator({ cliConfigFile, cmd, componentName, convertors }) { 241 | const { customTemplates } = cliConfigFile.component[cmd.type]; 242 | const { testLibrary, usesTypeScript } = cliConfigFile; 243 | let template = null; 244 | let filename = null; 245 | 246 | // Check for a custom test template. 247 | 248 | if (customTemplates && customTemplates.test) { 249 | // --- Load and use the custom test template 250 | 251 | const { template: customTemplate, filename: customTemplateFilename } = getCustomTemplate( 252 | componentName, 253 | customTemplates.test 254 | ); 255 | 256 | template = customTemplate; 257 | filename = customTemplateFilename; 258 | } else { 259 | filename = usesTypeScript ? `${componentName}.test.tsx` : `${componentName}.test.js`; 260 | 261 | if (testLibrary === 'Enzyme') { 262 | // --- Else use GRC built-in test template based on test library type 263 | 264 | template = componentTestEnzymeTemplate; 265 | } else if (testLibrary === 'Testing Library') { 266 | template = componentTestTestingLibraryTemplate; 267 | } else { 268 | template = componentTestDefaultTemplate; 269 | } 270 | } 271 | 272 | return { 273 | componentPath: componentDirectoryNameGenerator({ cmd, componentName, cliConfigFile, filename, convertors }), 274 | filename, 275 | template, 276 | }; 277 | } 278 | 279 | function componentStoryTemplateGenerator({ cliConfigFile, cmd, componentName, convertors }) { 280 | const { usesTypeScript } = cliConfigFile; 281 | const { customTemplates } = cliConfigFile.component[cmd.type]; 282 | let template = null; 283 | let filename = null; 284 | 285 | // Check for a custom story template. 286 | 287 | if (customTemplates && customTemplates.story) { 288 | // --- Load and use the custom story template 289 | 290 | const { template: customTemplate, filename: customTemplateFilename } = getCustomTemplate( 291 | componentName, 292 | customTemplates.story 293 | ); 294 | 295 | template = customTemplate; 296 | filename = customTemplateFilename; 297 | } else { 298 | // --- Else use GRC built-in story template 299 | 300 | template = componentStoryTemplate; 301 | filename = usesTypeScript ? `${componentName}.stories.tsx` : `${componentName}.stories.js`; 302 | } 303 | 304 | return { 305 | componentPath: componentDirectoryNameGenerator({ cmd, componentName, cliConfigFile, filename, convertors }), 306 | filename, 307 | template, 308 | }; 309 | } 310 | 311 | function componentLazyTemplateGenerator({ cmd, componentName, cliConfigFile, convertors }) { 312 | const { usesTypeScript } = cliConfigFile; 313 | const { customTemplates } = cliConfigFile.component[cmd.type]; 314 | let template = null; 315 | let filename = null; 316 | 317 | // Check for a custom lazy template. 318 | 319 | if (customTemplates && customTemplates.lazy) { 320 | // --- Load and use the custom lazy template 321 | 322 | const { template: customTemplate, filename: customTemplateFilename } = getCustomTemplate( 323 | componentName, 324 | customTemplates.lazy 325 | ); 326 | 327 | template = customTemplate; 328 | filename = customTemplateFilename; 329 | } else { 330 | // --- Else use GRC built-in lazy template 331 | 332 | template = usesTypeScript ? componentTsLazyTemplate : componentLazyTemplate; 333 | filename = usesTypeScript ? `${componentName}.lazy.tsx` : `${componentName}.lazy.js`; 334 | } 335 | 336 | return { 337 | componentPath: componentDirectoryNameGenerator({ cmd, componentName, cliConfigFile, filename, convertors }), 338 | filename, 339 | template, 340 | }; 341 | } 342 | 343 | function customFileTemplateGenerator({ componentName, cmd, cliConfigFile, componentFileType, convertors }) { 344 | const { customTemplates } = cliConfigFile.component[cmd.type]; 345 | const fileType = camelCase(componentFileType.split('with')[1]); 346 | let filename = null; 347 | let template = null; 348 | 349 | // Check for a valid custom template for the corresponding custom component file. 350 | 351 | if (!customTemplates || !customTemplates[fileType]) { 352 | console.error( 353 | chalk.red( 354 | ` 355 | ERROR: Custom component files require a valid custom template. 356 | Please make sure you're pointing to the right custom template path in your generate-react-cli.json config file. 357 | ` 358 | ) 359 | ); 360 | 361 | return process.exit(1); 362 | } 363 | 364 | // --- Load and use the custom component template. 365 | 366 | const { template: customTemplate, filename: customTemplateFilename } = getCustomTemplate( 367 | componentName, 368 | customTemplates[fileType] 369 | ); 370 | 371 | template = customTemplate; 372 | filename = customTemplateFilename; 373 | 374 | return { 375 | componentPath: componentDirectoryNameGenerator({ cmd, componentName, cliConfigFile, filename, convertors }), 376 | filename, 377 | template, 378 | }; 379 | } 380 | 381 | // --- Built in component file types 382 | 383 | const buildInComponentFileTypes = { 384 | COMPONENT: 'component', 385 | STYLE: 'withStyle', 386 | TEST: 'withTest', 387 | STORY: 'withStory', 388 | LAZY: 'withLazy', 389 | }; 390 | 391 | // --- Generate component template map 392 | 393 | const componentTemplateGeneratorMap = { 394 | [buildInComponentFileTypes.COMPONENT]: componentTemplateGenerator, 395 | [buildInComponentFileTypes.STYLE]: componentStyleTemplateGenerator, 396 | [buildInComponentFileTypes.TEST]: componentTestTemplateGenerator, 397 | [buildInComponentFileTypes.STORY]: componentStoryTemplateGenerator, 398 | [buildInComponentFileTypes.LAZY]: componentLazyTemplateGenerator, 399 | }; 400 | 401 | export function generateComponent(componentName, cmd, cliConfigFile) { 402 | const componentFileTypes = ['component', ...getCorrespondingComponentFileTypes(cmd)]; 403 | 404 | componentFileTypes.forEach((componentFileType) => { 405 | // --- Generate templates only if the component options (withStyle, withTest, etc..) are true, 406 | // or if the template type is "component" 407 | 408 | if ( 409 | (cmd[componentFileType] && cmd[componentFileType].toString() === 'true') || 410 | componentFileType === buildInComponentFileTypes.COMPONENT 411 | ) { 412 | const generateTemplate = componentTemplateGeneratorMap[componentFileType] || customFileTemplateGenerator; 413 | 414 | const convertors = { 415 | templatename: componentName, 416 | TemplateName: startCase(camelCase(componentName)).replace(/ /g, ''), 417 | templateName: camelCase(componentName), 418 | 'template-name': kebabCase(componentName), 419 | template_name: snakeCase(componentName), 420 | TEMPLATE_NAME: snakeCase(componentName).toUpperCase(), 421 | TEMPLATENAME: componentName.toUpperCase(), 422 | }; 423 | 424 | const { componentPath, filename, template } = generateTemplate({ 425 | cmd, 426 | componentName, 427 | cliConfigFile, 428 | componentFileType, 429 | convertors, 430 | }); 431 | 432 | // --- Make sure the component does not already exist in the path directory. 433 | 434 | if (existsSync(componentPath)) { 435 | console.error(chalk.red(`${filename} already exists in this path "${componentPath}".`)); 436 | } else { 437 | try { 438 | if (!cmd.dryRun) { 439 | outputFileSync(componentPath, template); 440 | 441 | // Will replace the templatename in whichever format the user typed the component name in the command. 442 | replace({ 443 | regex: 'templatename', 444 | replacement: convertors['templatename'], 445 | paths: [componentPath], 446 | recursive: false, 447 | silent: true, 448 | }); 449 | 450 | // Will replace the TemplateName in PascalCase 451 | replace({ 452 | regex: 'TemplateName', 453 | replacement: convertors['TemplateName'], 454 | paths: [componentPath], 455 | recursive: false, 456 | silent: true, 457 | }); 458 | 459 | // Will replace the templateName in camelCase 460 | replace({ 461 | regex: 'templateName', 462 | replacement: convertors['templateName'], 463 | paths: [componentPath], 464 | recursive: false, 465 | silent: true, 466 | }); 467 | 468 | // Will replace the template-name in kebab-case 469 | replace({ 470 | regex: 'template-name', 471 | replacement: convertors['template-name'], 472 | paths: [componentPath], 473 | recursive: false, 474 | silent: true, 475 | }); 476 | 477 | // Will replace the template_name in snake_case 478 | replace({ 479 | regex: 'template_name', 480 | replacement: convertors['template_name'], 481 | paths: [componentPath], 482 | recursive: false, 483 | silent: true, 484 | }); 485 | 486 | // Will replace the TEMPLATE_NAME in uppercase SNAKE_CASE 487 | replace({ 488 | regex: 'TEMPLATE_NAME', 489 | replacement: convertors['TEMPLATE_NAME'], 490 | paths: [componentPath], 491 | recursive: false, 492 | silent: true, 493 | }); 494 | 495 | // Will replace the TEMPLATENAME in uppercase SNAKE_CASE 496 | replace({ 497 | regex: 'TEMPLATENAME', 498 | replacement: convertors['TEMPLATENAME'], 499 | paths: [componentPath], 500 | recursive: false, 501 | silent: true, 502 | }); 503 | } 504 | 505 | console.log(chalk.green(`${filename} was successfully created at ${componentPath}`)); 506 | } catch (error) { 507 | console.error(chalk.red(`${filename} failed and was not created.`)); 508 | console.error(error); 509 | } 510 | } 511 | } 512 | }); 513 | 514 | if (cmd.dryRun) { 515 | console.log(); 516 | console.log(chalk.yellow(`NOTE: The "dry-run" flag means no changes were made.`)); 517 | } 518 | } 519 | --------------------------------------------------------------------------------