diff --git a/readme.md b/readme.md
index 81810a0..2f392c4 100644
--- a/readme.md
+++ b/readme.md
@@ -2,7 +2,7 @@
[![License](https://img.shields.io/npm/l/express.svg)](https://github.com/arminbro/generate-react-cli/blob/master/LICENSE)
-
+
@@ -20,6 +20,7 @@ You can also watch an excellent [video](https://www.youtube.com/watch?v=NEvnt3MW
- [Generate components](#generate-components)
- [Custom component types](#custom-component-types)
- [Custom component templates](#custom-component-templates)
+- [Custom component directory](#custom-component-directory)
- [Custom component files](#custom-component-files)
- [OpenAi integration (Alpha release)](#openai-integration-alpha-release)
@@ -97,10 +98,11 @@ Otherwise, if you don't pass any options, it will just use the default values th
Value Type |
Default Value |
+
--path |
- Value of the path where you want the component to be generated in (e.g. src/components).
+ Value of the path where you want the component to be generated in (e.g. src/components).
|
String |
component.default.path
|
@@ -118,7 +120,7 @@ Otherwise, if you don't pass any options, it will just use the default values th
--withLazy |
- Creates a corresponding lazy file (a file that lazy-loads your component out of the box and enables code splitting) with this component.
+ Creates a corresponding lazy file (a file that lazy-loads your component out of the box and enables code splitting) with this component.
|
Boolean |
component.default.withLazy
|
@@ -127,7 +129,7 @@ Otherwise, if you don't pass any options, it will just use the default values th
--withStory |
- Creates a corresponding (storybook) story file with this component.
+ Creates a corresponding (storybook) story file with this component.
|
Boolean |
component.default.withStory
|
@@ -136,7 +138,7 @@ Otherwise, if you don't pass any options, it will just use the default values th
--withStyle |
- Creates a corresponding stylesheet file with this component.
+ Creates a corresponding stylesheet file with this component.
|
Boolean |
component.default.withStyle
|
@@ -145,11 +147,12 @@ Otherwise, if you don't pass any options, it will just use the default values th
--withTest |
- Creates a corresponding test file with this component.
+ Creates a corresponding test file with this component.
|
Boolean |
component.default.withTest
|
+
--dry-run |
@@ -158,15 +161,27 @@ Otherwise, if you don't pass any options, it will just use the default values th
| Boolean |
false
|
+
--flat |
- Generate the files in the mentioned path insted of creating new folder for it
+ Generate the files in the mentioned path instead of creating new folder for it
|
Boolean |
false
|
-
+
+
+ --customDirectory |
+
+ Template value that overrides the name of the directory of the component to be generated in.
+ See more under custom component directory.
+ |
+ String |
+ null |
+
+
+
--describe |
Describe the component you're trying to generate, and OpenAI will do its best to render it following your instructions.
@@ -176,7 +191,7 @@ Otherwise, if you don't pass any options, it will just use the default values th
|
-### Custom component types:
+### Custom component types
By default, GRC will use the `component.default` configuration rules when running the component command out of the box.
@@ -329,6 +344,93 @@ it('It should mount', () => {
});
```
+### Custom component directory
+
+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.
+
+The `customDirectory` directive allows all supported casings (see previous section) and can be overridden at the following levels in ascending specific of priority:
+
+- top
+- component.default
+- component._type_
+- CLI
+
+#### Example:
+
+For React Context Providers in a project, the decision has been made to separate Context generation from the visual components.
+
+In a typical configuration the configuration would look as following:
+
+```json
+{
+ "provider": {
+ "path": "src/components/providers",
+ "withLazy": false,
+ "withStory": true,
+ "withStyle": false,
+ "withTest": true,
+ "withTypes": true,
+ "withContext": true,
+ "customTemplates": {
+ "component": "src/components/templates/provider/TemplateName.tsx",
+ "context": "src/components/templates/provider/TemplateName.context.ts",
+ "story": "src/components/templates/provider/TemplateName.stories.tsx",
+ "test": "src/components/templates/provider/TemplateName.test.tsx",
+ "types": "src/components/templates/provider/TemplateName.types.ts"
+ }
+ }
+}
+```
+
+With the configuration above, the component would be required to either follow a full or a minimalistic naming convention.
+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.
+
+To work around this, the `customDirectory` option can be used to enforce a particular style.
+
+```json
+{
+ ...
+ "provider": {
+ "path": "src/components/providers",
+ "withLazy": false,
+ "withStory": true,
+ "withStyle": false,
+ "withTest": true,
+ "withTypes": true,
+ "withContext": true,
+ "customDirectory": "TemplateNameProvider",
+ "customTemplates": {
+ "component": "src/components/templates/provider/TemplateNameProvider.tsx",
+ "context": "src/components/templates/provider/TemplateName.context.ts",
+ "story": "src/components/templates/provider/TemplateNameProvider.stories.tsx",
+ "test": "src/components/templates/provider/TemplateNameProvider.test.tsx",
+ "types": "src/components/templates/provider/TemplateNameProvider.types.ts"
+ }
+ }
+ ...
+}
+```
+
+The above configuration would allow you to mix and match different template names and keep naming consistent.
+
+If we executed GRC with the above configuration (`npx generate-react-cli component Theme --type=provider`), the result would look like this:
+
+```fs
+src/components/providers/ThemeProvider/Theme.context.ts
+src/components/providers/ThemeProvider/ThemeProvider.tsx
+src/components/providers/ThemeProvider/ThemeProvider.stories.tsx
+src/components/providers/ThemeProvider/ThemeProvider.test.tsx
+src/components/providers/ThemeProvider/ThemeProvider.types.ts
+```
+
+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:
+
+```sh
+ npx generate-react-cli component Box --type=layout --customDir=TemplateNameLayout
+```
+
+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
+
### Custom component files
GRC comes with corresponding built-in files for a given component if you need them (i.e., `withStyle`, `withTest`, `withStory`, and `withLazy`).
diff --git a/src/commands/generateComponent.js b/src/commands/generateComponent.js
index 20d92aa..e438d39 100644
--- a/src/commands/generateComponent.js
+++ b/src/commands/generateComponent.js
@@ -29,7 +29,17 @@ export default function initGenerateComponentCommand(args, cliConfigFile, progra
'Generate the files in the mentioned path instead of creating new folder for it',
selectedComponentType.flat || false
)
- .option('-dr, --dry-run', 'Show what will be generated without writing to disk');
+ .option('-dr, --dry-run', 'Show what will be generated without writing to disk')
+ .option(
+ '--customDirectory ',
+ 'You can pass a cased path template that will be used as the component path for the component being generated.\n' +
+ 'E.g. this allows you to add a prefix or suffix to the component path, ' +
+ 'or change the case of the name of the directory holding the components to kebab-case.\n' +
+ 'Examples:\n' +
+ '- TemplateName\n' +
+ '- template-name\n' +
+ '- TemplateNameSuffix'
+ );
// Dynamic component command option defaults.
diff --git a/src/utils/generateComponentUtils.js b/src/utils/generateComponentUtils.js
index 80692d4..e41b315 100644
--- a/src/utils/generateComponentUtils.js
+++ b/src/utils/generateComponentUtils.js
@@ -19,6 +19,8 @@ import componentTestEnzymeTemplate from '../templates/component/componentTestEnz
import componentTestDefaultTemplate from '../templates/component/componentTestDefaultTemplate.js';
import componentTestTestingLibraryTemplate from '../templates/component/componentTestTestingLibraryTemplate.js';
+const templateNameRE = /.*(template[|_-]?name).*/i;
+
const { existsSync, outputFileSync, readFileSync } = fsExtra;
export function getComponentByType(args, cliConfigFile) {
@@ -71,7 +73,7 @@ function getCustomTemplate(componentName, templatePath) {
console.error(
chalk.red(
`
-ERROR: The custom template path of "${templatePath}" does not exist.
+ERROR: The custom template path of "${templatePath}" does not exist.
Please make sure you're pointing to the right custom template path in your generate-react-cli.json config file.
`
)
@@ -81,7 +83,52 @@ Please make sure you're pointing to the right custom template path in your gener
}
}
-function componentTemplateGenerator({ cmd, componentName, cliConfigFile }) {
+function componentDirectoryNameGenerator({ cmd, componentName, cliConfigFile, filename, convertors }) {
+ let componentPath = cmd.path;
+
+ if (cmd.flat !== true) {
+ let componentDirectory = componentName;
+
+ const customDirectoryConfigs = [
+ cliConfigFile.customDirectory,
+ cliConfigFile.component.default.customDirectory,
+ cliConfigFile.component[cmd.type].customDirectory,
+ cmd.customDirectory,
+ ].filter((e) => Boolean(e) && typeof e === 'string');
+
+ if (customDirectoryConfigs.length > 0) {
+ const customDirectory = customDirectoryConfigs.slice(-1).toString();
+
+ // Double check if the customDirectory is templatable
+ if (templateNameRE.exec(customDirectory) == null) {
+ console.error(
+ chalk.red(
+ `customDirectory [${customDirectory}] for ${componentName} does not contain a templatable value.\nPlease check your configuration!`
+ )
+ );
+
+ process.exit(-2);
+ }
+
+ for (const convertor in convertors) {
+ const re = new RegExp(`.*${convertor}.*`);
+
+ if (re.exec(customDirectory) !== null) {
+ componentDirectory = customDirectory.replace(convertor, convertors[convertor]);
+ }
+ }
+ }
+
+ componentPath += `/${componentDirectory}`;
+ }
+
+ componentPath += `/${filename}`;
+
+ return componentPath;
+}
+
+function componentTemplateGenerator({ cmd, componentName, cliConfigFile, convertors }) {
+ // @ts-ignore
const { usesStyledComponents, cssPreprocessor, testLibrary, usesCssModule, usesTypeScript } = cliConfigFile;
const { customTemplates } = cliConfigFile.component[cmd.type];
let template = null;
@@ -145,13 +192,13 @@ function componentTemplateGenerator({ cmd, componentName, cliConfigFile }) {
}
return {
- componentPath: `${cmd.path}${cmd.flat ? '' : `/${componentName}`}/${filename}`,
+ componentPath: componentDirectoryNameGenerator({ cmd, componentName, cliConfigFile, filename, convertors }),
filename,
template,
};
}
-function componentStyleTemplateGenerator({ cliConfigFile, cmd, componentName }) {
+function componentStyleTemplateGenerator({ cliConfigFile, cmd, componentName, convertors }) {
const { customTemplates } = cliConfigFile.component[cmd.type];
let template = null;
let filename = null;
@@ -185,13 +232,13 @@ function componentStyleTemplateGenerator({ cliConfigFile, cmd, componentName })
}
return {
- componentPath: `${cmd.path}${cmd.flat ? '' : `/${componentName}`}/${filename}`,
+ componentPath: componentDirectoryNameGenerator({ cmd, componentName, cliConfigFile, filename, convertors }),
filename,
template,
};
}
-function componentTestTemplateGenerator({ cliConfigFile, cmd, componentName }) {
+function componentTestTemplateGenerator({ cliConfigFile, cmd, componentName, convertors }) {
const { customTemplates } = cliConfigFile.component[cmd.type];
const { testLibrary, usesTypeScript } = cliConfigFile;
let template = null;
@@ -224,13 +271,13 @@ function componentTestTemplateGenerator({ cliConfigFile, cmd, componentName }) {
}
return {
- componentPath: `${cmd.path}${cmd.flat ? '' : `/${componentName}`}/${filename}`,
+ componentPath: componentDirectoryNameGenerator({ cmd, componentName, cliConfigFile, filename, convertors }),
filename,
template,
};
}
-function componentStoryTemplateGenerator({ cliConfigFile, cmd, componentName }) {
+function componentStoryTemplateGenerator({ cliConfigFile, cmd, componentName, convertors }) {
const { usesTypeScript } = cliConfigFile;
const { customTemplates } = cliConfigFile.component[cmd.type];
let template = null;
@@ -256,13 +303,13 @@ function componentStoryTemplateGenerator({ cliConfigFile, cmd, componentName })
}
return {
- componentPath: `${cmd.path}${cmd.flat ? '' : `/${componentName}`}/${filename}`,
+ componentPath: componentDirectoryNameGenerator({ cmd, componentName, cliConfigFile, filename, convertors }),
filename,
template,
};
}
-function componentLazyTemplateGenerator({ cmd, componentName, cliConfigFile }) {
+function componentLazyTemplateGenerator({ cmd, componentName, cliConfigFile, convertors }) {
const { usesTypeScript } = cliConfigFile;
const { customTemplates } = cliConfigFile.component[cmd.type];
let template = null;
@@ -288,13 +335,13 @@ function componentLazyTemplateGenerator({ cmd, componentName, cliConfigFile }) {
}
return {
- componentPath: `${cmd.path}${cmd.flat ? '' : `/${componentName}`}/${filename}`,
+ componentPath: componentDirectoryNameGenerator({ cmd, componentName, cliConfigFile, filename, convertors }),
filename,
template,
};
}
-function customFileTemplateGenerator({ componentName, cmd, cliConfigFile, componentFileType }) {
+function customFileTemplateGenerator({ componentName, cmd, cliConfigFile, componentFileType, convertors }) {
const { customTemplates } = cliConfigFile.component[cmd.type];
const fileType = camelCase(componentFileType.split('with')[1]);
let filename = null;
@@ -306,7 +353,7 @@ function customFileTemplateGenerator({ componentName, cmd, cliConfigFile, compon
console.error(
chalk.red(
`
-ERROR: Custom component files require a valid custom template.
+ERROR: Custom component files require a valid custom template.
Please make sure you're pointing to the right custom template path in your generate-react-cli.json config file.
`
)
@@ -326,7 +373,7 @@ Please make sure you're pointing to the right custom template path in your gener
filename = customTemplateFilename;
return {
- componentPath: `${cmd.path}${cmd.flat ? '' : `/${componentName}`}/${filename}`,
+ componentPath: componentDirectoryNameGenerator({ cmd, componentName, cliConfigFile, filename, convertors }),
filename,
template,
};
@@ -365,11 +412,21 @@ export function generateComponent(componentName, cmd, cliConfigFile) {
) {
const generateTemplate = componentTemplateGeneratorMap[componentFileType] || customFileTemplateGenerator;
+ const convertors = {
+ templatename: componentName,
+ TemplateName: startCase(camelCase(componentName)).replace(/ /g, ''),
+ templateName: camelCase(componentName),
+ 'template-name': kebabCase(componentName),
+ template_name: snakeCase(componentName),
+ TEMPLATE_NAME: snakeCase(componentName).toUpperCase(),
+ };
+
const { componentPath, filename, template } = generateTemplate({
cmd,
componentName,
cliConfigFile,
componentFileType,
+ convertors,
});
// --- Make sure the component does not already exist in the path directory.
@@ -384,7 +441,7 @@ export function generateComponent(componentName, cmd, cliConfigFile) {
// Will replace the templatename in whichever format the user typed the component name in the command.
replace({
regex: 'templatename',
- replacement: componentName,
+ replacement: convertors['templatename'],
paths: [componentPath],
recursive: false,
silent: true,
@@ -393,7 +450,7 @@ export function generateComponent(componentName, cmd, cliConfigFile) {
// Will replace the TemplateName in PascalCase
replace({
regex: 'TemplateName',
- replacement: startCase(camelCase(componentName)).replace(/ /g, ''),
+ replacement: convertors['TemplateName'],
paths: [componentPath],
recursive: false,
silent: true,
@@ -402,7 +459,7 @@ export function generateComponent(componentName, cmd, cliConfigFile) {
// Will replace the templateName in camelCase
replace({
regex: 'templateName',
- replacement: camelCase(componentName),
+ replacement: convertors['templateName'],
paths: [componentPath],
recursive: false,
silent: true,
@@ -411,7 +468,7 @@ export function generateComponent(componentName, cmd, cliConfigFile) {
// Will replace the template-name in kebab-case
replace({
regex: 'template-name',
- replacement: kebabCase(componentName),
+ replacement: convertors['template-name'],
paths: [componentPath],
recursive: false,
silent: true,
@@ -420,7 +477,7 @@ export function generateComponent(componentName, cmd, cliConfigFile) {
// Will replace the template_name in snake_case
replace({
regex: 'template_name',
- replacement: snakeCase(componentName),
+ replacement: convertors['template_name'],
paths: [componentPath],
recursive: false,
silent: true,
@@ -429,7 +486,7 @@ export function generateComponent(componentName, cmd, cliConfigFile) {
// Will replace the TEMPLATE_NAME in uppercase SNAKE_CASE
replace({
regex: 'TEMPLATE_NAME',
- replacement: snakeCase(componentName).toUpperCase(),
+ replacement: convertors['TEMPLATE_NAME'],
paths: [componentPath],
recursive: false,
silent: true,
@@ -441,6 +498,7 @@ export function generateComponent(componentName, cmd, cliConfigFile) {
if (cmd.describe && componentFileType === buildInComponentFileTypes.COMPONENT) {
aiComponentGenerator(template, cmd.describe)
.then((aiGeneratedComponent) => {
+ // @ts-ignore
outputFileSync(componentPath, aiGeneratedComponent.trim());
console.log(
chalk.green(`OpenAI Successfully created the ${filename} component with the provided description.`)