generate-react-cli 8.3.0 → 8.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "generate-react-cli",
3
- "version": "8.3.0",
3
+ "version": "8.4.0",
4
4
  "description": "A simple React CLI to generate components instantly and more.",
5
5
  "repository": "https://github.com/arminbro/generate-react-cli",
6
6
  "bugs": "https://github.com/arminbro/generate-react-cli/issues",
package/readme.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![License](https://img.shields.io/npm/l/express.svg)](https://github.com/arminbro/generate-react-cli/blob/master/LICENSE)
4
4
 
5
- <p align="center">
5
+ <p align="center">
6
6
  <img src="https://raw.githubusercontent.com/arminbro/generate-react-cli/master/docs/assets/generate-react-cli.svg?raw=true"/>
7
7
  </p>
8
8
 
@@ -20,6 +20,7 @@ You can also watch an excellent [video](https://www.youtube.com/watch?v=NEvnt3MW
20
20
  - [Generate components](#generate-components)
21
21
  - [Custom component types](#custom-component-types)
22
22
  - [Custom component templates](#custom-component-templates)
23
+ - [Custom component directory](#custom-component-directory)
23
24
  - [Custom component files](#custom-component-files)
24
25
  - [OpenAi integration (Alpha release)](#openai-integration-alpha-release)
25
26
 
@@ -97,10 +98,11 @@ Otherwise, if you don't pass any options, it will just use the default values th
97
98
  <th>Value Type</th>
98
99
  <th>Default Value</th>
99
100
  </tr>
101
+
100
102
  <tr>
101
103
  <td width="20%"><b>--path</b></td>
102
104
  <td width="60%">
103
- Value of the path where you want the component to be generated in (e.g. <b>src/components</b>).
105
+ Value of the path where you want the component to be generated in (e.g. <b>src/components</b>).
104
106
  </td>
105
107
  <td width="20%">String</td>
106
108
  <td width="20%"><code>component.default.path<code></td>
@@ -118,7 +120,7 @@ Otherwise, if you don't pass any options, it will just use the default values th
118
120
  <tr>
119
121
  <td width="20%"><b>--withLazy</b></td>
120
122
  <td width="60%">
121
- Creates a corresponding lazy file (a file that lazy-loads your component out of the box and enables <a href="https://reactjs.org/docs/code-splitting.html#code-splitting">code splitting</a>) with this component.
123
+ Creates a corresponding lazy file (a file that lazy-loads your component out of the box and enables <a href="https://reactjs.org/docs/code-splitting.html#code-splitting">code splitting</a>) with this component.
122
124
  </td>
123
125
  <td width="20%">Boolean</td>
124
126
  <td width="20%"><code>component.default.withLazy<code></td>
@@ -127,7 +129,7 @@ Otherwise, if you don't pass any options, it will just use the default values th
127
129
  <tr>
128
130
  <td width="20%"><b>--withStory</b></td>
129
131
  <td width="60%">
130
- Creates a corresponding (<a href="https://storybook.js.org">storybook</a>) story file with this component.
132
+ Creates a corresponding (<a href="https://storybook.js.org">storybook</a>) story file with this component.
131
133
  </td>
132
134
  <td width="20%">Boolean</td>
133
135
  <td width="20%"><code>component.default.withStory<code></td>
@@ -136,7 +138,7 @@ Otherwise, if you don't pass any options, it will just use the default values th
136
138
  <tr>
137
139
  <td width="20%"><b>--withStyle</b></td>
138
140
  <td width="60%">
139
- Creates a corresponding stylesheet file with this component.
141
+ Creates a corresponding stylesheet file with this component.
140
142
  </td>
141
143
  <td width="20%">Boolean</td>
142
144
  <td width="20%"><code>component.default.withStyle<code></td>
@@ -145,11 +147,12 @@ Otherwise, if you don't pass any options, it will just use the default values th
145
147
  <tr>
146
148
  <td width="20%"><b>--withTest</b></td>
147
149
  <td width="60%">
148
- Creates a corresponding test file with this component.
150
+ Creates a corresponding test file with this component.
149
151
  </td>
150
152
  <td width="20%">Boolean</td>
151
153
  <td width="20%"><code>component.default.withTest<code></td>
152
154
  </tr>
155
+
153
156
  <tr>
154
157
  <td width="20%"><b>--dry-run</b></td>
155
158
  <td width="60%">
@@ -158,15 +161,27 @@ Otherwise, if you don't pass any options, it will just use the default values th
158
161
  <td width="20%">Boolean</td>
159
162
  <td width="20%"><code>false<code></td>
160
163
  </tr>
164
+
161
165
  <tr>
162
166
  <td width="20%"><b>--flat</b></td>
163
167
  <td width="60%">
164
- Generate the files in the mentioned path insted of creating new folder for it
168
+ Generate the files in the mentioned path instead of creating new folder for it
165
169
  </td>
166
170
  <td width="20%">Boolean</td>
167
171
  <td width="20%"><code>false<code></td>
168
172
  </tr>
169
- <tr>
173
+
174
+ <tr>
175
+ <td width="20%"><b>--customDirectory</b></td>
176
+ <td width="60%">
177
+ Template value that overrides the name of the directory of the component to be generated in.<br />
178
+ See more under <a href="#custom-component-directory">custom component directory</a>.
179
+ </td>
180
+ <td width="20%">String</td>
181
+ <td width="20%"><code>null</code></td>
182
+ </tr>
183
+
184
+ <tr>
170
185
  <td width="20%"><b>--describe</b></td>
171
186
  <td width="60%">
172
187
  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
176
191
  </tr>
177
192
  </table>
178
193
 
179
- ### Custom component types:
194
+ ### Custom component types
180
195
 
181
196
  By default, GRC will use the `component.default` configuration rules when running the component command out of the box.
182
197
 
@@ -329,6 +344,93 @@ it('It should mount', () => {
329
344
  });
330
345
  ```
331
346
 
347
+ ### Custom component directory
348
+
349
+ 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.
350
+
351
+ The `customDirectory` directive allows all supported casings (see previous section) and can be overridden at the following levels in ascending specific of priority:
352
+
353
+ - top
354
+ - component.default
355
+ - component._type_
356
+ - CLI
357
+
358
+ #### Example:
359
+
360
+ For React Context Providers in a project, the decision has been made to separate Context generation from the visual components.
361
+
362
+ In a typical configuration the configuration would look as following:
363
+
364
+ ```json
365
+ {
366
+ "provider": {
367
+ "path": "src/components/providers",
368
+ "withLazy": false,
369
+ "withStory": true,
370
+ "withStyle": false,
371
+ "withTest": true,
372
+ "withTypes": true,
373
+ "withContext": true,
374
+ "customTemplates": {
375
+ "component": "src/components/templates/provider/TemplateName.tsx",
376
+ "context": "src/components/templates/provider/TemplateName.context.ts",
377
+ "story": "src/components/templates/provider/TemplateName.stories.tsx",
378
+ "test": "src/components/templates/provider/TemplateName.test.tsx",
379
+ "types": "src/components/templates/provider/TemplateName.types.ts"
380
+ }
381
+ }
382
+ }
383
+ ```
384
+
385
+ With the configuration above, the component would be required to either follow a full or a minimalistic naming convention.
386
+ 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.
387
+
388
+ To work around this, the `customDirectory` option can be used to enforce a particular style.
389
+
390
+ ```json
391
+ {
392
+ ...
393
+ "provider": {
394
+ "path": "src/components/providers",
395
+ "withLazy": false,
396
+ "withStory": true,
397
+ "withStyle": false,
398
+ "withTest": true,
399
+ "withTypes": true,
400
+ "withContext": true,
401
+ "customDirectory": "TemplateNameProvider",
402
+ "customTemplates": {
403
+ "component": "src/components/templates/provider/TemplateNameProvider.tsx",
404
+ "context": "src/components/templates/provider/TemplateName.context.ts",
405
+ "story": "src/components/templates/provider/TemplateNameProvider.stories.tsx",
406
+ "test": "src/components/templates/provider/TemplateNameProvider.test.tsx",
407
+ "types": "src/components/templates/provider/TemplateNameProvider.types.ts"
408
+ }
409
+ }
410
+ ...
411
+ }
412
+ ```
413
+
414
+ The above configuration would allow you to mix and match different template names and keep naming consistent.
415
+
416
+ If we executed GRC with the above configuration (`npx generate-react-cli component Theme --type=provider`), the result would look like this:
417
+
418
+ ```fs
419
+ src/components/providers/ThemeProvider/Theme.context.ts
420
+ src/components/providers/ThemeProvider/ThemeProvider.tsx
421
+ src/components/providers/ThemeProvider/ThemeProvider.stories.tsx
422
+ src/components/providers/ThemeProvider/ThemeProvider.test.tsx
423
+ src/components/providers/ThemeProvider/ThemeProvider.types.ts
424
+ ```
425
+
426
+ 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:
427
+
428
+ ```sh
429
+ npx generate-react-cli component Box --type=layout --customDir=TemplateNameLayout
430
+ ```
431
+
432
+ 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
433
+
332
434
  ### Custom component files
333
435
 
334
436
  GRC comes with corresponding built-in files for a given component if you need them (i.e., `withStyle`, `withTest`, `withStory`, and `withLazy`).
@@ -29,7 +29,17 @@ export default function initGenerateComponentCommand(args, cliConfigFile, progra
29
29
  'Generate the files in the mentioned path instead of creating new folder for it',
30
30
  selectedComponentType.flat || false
31
31
  )
32
- .option('-dr, --dry-run', 'Show what will be generated without writing to disk');
32
+ .option('-dr, --dry-run', 'Show what will be generated without writing to disk')
33
+ .option(
34
+ '--customDirectory <customDirectory>',
35
+ 'You can pass a cased path template that will be used as the component path for the component being generated.\n' +
36
+ 'E.g. this allows you to add a prefix or suffix to the component path, ' +
37
+ 'or change the case of the name of the directory holding the components to kebab-case.\n' +
38
+ 'Examples:\n' +
39
+ '- TemplateName\n' +
40
+ '- template-name\n' +
41
+ '- TemplateNameSuffix'
42
+ );
33
43
 
34
44
  // Dynamic component command option defaults.
35
45
 
@@ -19,6 +19,8 @@ import componentTestEnzymeTemplate from '../templates/component/componentTestEnz
19
19
  import componentTestDefaultTemplate from '../templates/component/componentTestDefaultTemplate.js';
20
20
  import componentTestTestingLibraryTemplate from '../templates/component/componentTestTestingLibraryTemplate.js';
21
21
 
22
+ const templateNameRE = /.*(template[|_-]?name).*/i;
23
+
22
24
  const { existsSync, outputFileSync, readFileSync } = fsExtra;
23
25
 
24
26
  export function getComponentByType(args, cliConfigFile) {
@@ -71,7 +73,7 @@ function getCustomTemplate(componentName, templatePath) {
71
73
  console.error(
72
74
  chalk.red(
73
75
  `
74
- ERROR: The custom template path of "${templatePath}" does not exist.
76
+ ERROR: The custom template path of "${templatePath}" does not exist.
75
77
  Please make sure you're pointing to the right custom template path in your generate-react-cli.json config file.
76
78
  `
77
79
  )
@@ -81,7 +83,52 @@ Please make sure you're pointing to the right custom template path in your gener
81
83
  }
82
84
  }
83
85
 
84
- function componentTemplateGenerator({ cmd, componentName, cliConfigFile }) {
86
+ function componentDirectoryNameGenerator({ cmd, componentName, cliConfigFile, filename, convertors }) {
87
+ let componentPath = cmd.path;
88
+
89
+ if (cmd.flat !== true) {
90
+ let componentDirectory = componentName;
91
+
92
+ const customDirectoryConfigs = [
93
+ cliConfigFile.customDirectory,
94
+ cliConfigFile.component.default.customDirectory,
95
+ cliConfigFile.component[cmd.type].customDirectory,
96
+ cmd.customDirectory,
97
+ ].filter((e) => Boolean(e) && typeof e === 'string');
98
+
99
+ if (customDirectoryConfigs.length > 0) {
100
+ const customDirectory = customDirectoryConfigs.slice(-1).toString();
101
+
102
+ // Double check if the customDirectory is templatable
103
+ if (templateNameRE.exec(customDirectory) == null) {
104
+ console.error(
105
+ chalk.red(
106
+ `customDirectory [${customDirectory}] for ${componentName} does not contain a templatable value.\nPlease check your configuration!`
107
+ )
108
+ );
109
+
110
+ process.exit(-2);
111
+ }
112
+
113
+ for (const convertor in convertors) {
114
+ const re = new RegExp(`.*${convertor}.*`);
115
+
116
+ if (re.exec(customDirectory) !== null) {
117
+ componentDirectory = customDirectory.replace(convertor, convertors[convertor]);
118
+ }
119
+ }
120
+ }
121
+
122
+ componentPath += `/${componentDirectory}`;
123
+ }
124
+
125
+ componentPath += `/${filename}`;
126
+
127
+ return componentPath;
128
+ }
129
+
130
+ function componentTemplateGenerator({ cmd, componentName, cliConfigFile, convertors }) {
131
+ // @ts-ignore
85
132
  const { usesStyledComponents, cssPreprocessor, testLibrary, usesCssModule, usesTypeScript } = cliConfigFile;
86
133
  const { customTemplates } = cliConfigFile.component[cmd.type];
87
134
  let template = null;
@@ -145,13 +192,13 @@ function componentTemplateGenerator({ cmd, componentName, cliConfigFile }) {
145
192
  }
146
193
 
147
194
  return {
148
- componentPath: `${cmd.path}${cmd.flat ? '' : `/${componentName}`}/${filename}`,
195
+ componentPath: componentDirectoryNameGenerator({ cmd, componentName, cliConfigFile, filename, convertors }),
149
196
  filename,
150
197
  template,
151
198
  };
152
199
  }
153
200
 
154
- function componentStyleTemplateGenerator({ cliConfigFile, cmd, componentName }) {
201
+ function componentStyleTemplateGenerator({ cliConfigFile, cmd, componentName, convertors }) {
155
202
  const { customTemplates } = cliConfigFile.component[cmd.type];
156
203
  let template = null;
157
204
  let filename = null;
@@ -185,13 +232,13 @@ function componentStyleTemplateGenerator({ cliConfigFile, cmd, componentName })
185
232
  }
186
233
 
187
234
  return {
188
- componentPath: `${cmd.path}${cmd.flat ? '' : `/${componentName}`}/${filename}`,
235
+ componentPath: componentDirectoryNameGenerator({ cmd, componentName, cliConfigFile, filename, convertors }),
189
236
  filename,
190
237
  template,
191
238
  };
192
239
  }
193
240
 
194
- function componentTestTemplateGenerator({ cliConfigFile, cmd, componentName }) {
241
+ function componentTestTemplateGenerator({ cliConfigFile, cmd, componentName, convertors }) {
195
242
  const { customTemplates } = cliConfigFile.component[cmd.type];
196
243
  const { testLibrary, usesTypeScript } = cliConfigFile;
197
244
  let template = null;
@@ -224,13 +271,13 @@ function componentTestTemplateGenerator({ cliConfigFile, cmd, componentName }) {
224
271
  }
225
272
 
226
273
  return {
227
- componentPath: `${cmd.path}${cmd.flat ? '' : `/${componentName}`}/${filename}`,
274
+ componentPath: componentDirectoryNameGenerator({ cmd, componentName, cliConfigFile, filename, convertors }),
228
275
  filename,
229
276
  template,
230
277
  };
231
278
  }
232
279
 
233
- function componentStoryTemplateGenerator({ cliConfigFile, cmd, componentName }) {
280
+ function componentStoryTemplateGenerator({ cliConfigFile, cmd, componentName, convertors }) {
234
281
  const { usesTypeScript } = cliConfigFile;
235
282
  const { customTemplates } = cliConfigFile.component[cmd.type];
236
283
  let template = null;
@@ -256,13 +303,13 @@ function componentStoryTemplateGenerator({ cliConfigFile, cmd, componentName })
256
303
  }
257
304
 
258
305
  return {
259
- componentPath: `${cmd.path}${cmd.flat ? '' : `/${componentName}`}/${filename}`,
306
+ componentPath: componentDirectoryNameGenerator({ cmd, componentName, cliConfigFile, filename, convertors }),
260
307
  filename,
261
308
  template,
262
309
  };
263
310
  }
264
311
 
265
- function componentLazyTemplateGenerator({ cmd, componentName, cliConfigFile }) {
312
+ function componentLazyTemplateGenerator({ cmd, componentName, cliConfigFile, convertors }) {
266
313
  const { usesTypeScript } = cliConfigFile;
267
314
  const { customTemplates } = cliConfigFile.component[cmd.type];
268
315
  let template = null;
@@ -288,13 +335,13 @@ function componentLazyTemplateGenerator({ cmd, componentName, cliConfigFile }) {
288
335
  }
289
336
 
290
337
  return {
291
- componentPath: `${cmd.path}${cmd.flat ? '' : `/${componentName}`}/${filename}`,
338
+ componentPath: componentDirectoryNameGenerator({ cmd, componentName, cliConfigFile, filename, convertors }),
292
339
  filename,
293
340
  template,
294
341
  };
295
342
  }
296
343
 
297
- function customFileTemplateGenerator({ componentName, cmd, cliConfigFile, componentFileType }) {
344
+ function customFileTemplateGenerator({ componentName, cmd, cliConfigFile, componentFileType, convertors }) {
298
345
  const { customTemplates } = cliConfigFile.component[cmd.type];
299
346
  const fileType = camelCase(componentFileType.split('with')[1]);
300
347
  let filename = null;
@@ -306,7 +353,7 @@ function customFileTemplateGenerator({ componentName, cmd, cliConfigFile, compon
306
353
  console.error(
307
354
  chalk.red(
308
355
  `
309
- ERROR: Custom component files require a valid custom template.
356
+ ERROR: Custom component files require a valid custom template.
310
357
  Please make sure you're pointing to the right custom template path in your generate-react-cli.json config file.
311
358
  `
312
359
  )
@@ -326,7 +373,7 @@ Please make sure you're pointing to the right custom template path in your gener
326
373
  filename = customTemplateFilename;
327
374
 
328
375
  return {
329
- componentPath: `${cmd.path}${cmd.flat ? '' : `/${componentName}`}/${filename}`,
376
+ componentPath: componentDirectoryNameGenerator({ cmd, componentName, cliConfigFile, filename, convertors }),
330
377
  filename,
331
378
  template,
332
379
  };
@@ -365,11 +412,21 @@ export function generateComponent(componentName, cmd, cliConfigFile) {
365
412
  ) {
366
413
  const generateTemplate = componentTemplateGeneratorMap[componentFileType] || customFileTemplateGenerator;
367
414
 
415
+ const convertors = {
416
+ templatename: componentName,
417
+ TemplateName: startCase(camelCase(componentName)).replace(/ /g, ''),
418
+ templateName: camelCase(componentName),
419
+ 'template-name': kebabCase(componentName),
420
+ template_name: snakeCase(componentName),
421
+ TEMPLATE_NAME: snakeCase(componentName).toUpperCase(),
422
+ };
423
+
368
424
  const { componentPath, filename, template } = generateTemplate({
369
425
  cmd,
370
426
  componentName,
371
427
  cliConfigFile,
372
428
  componentFileType,
429
+ convertors,
373
430
  });
374
431
 
375
432
  // --- Make sure the component does not already exist in the path directory.
@@ -384,7 +441,7 @@ export function generateComponent(componentName, cmd, cliConfigFile) {
384
441
  // Will replace the templatename in whichever format the user typed the component name in the command.
385
442
  replace({
386
443
  regex: 'templatename',
387
- replacement: componentName,
444
+ replacement: convertors['templatename'],
388
445
  paths: [componentPath],
389
446
  recursive: false,
390
447
  silent: true,
@@ -393,7 +450,7 @@ export function generateComponent(componentName, cmd, cliConfigFile) {
393
450
  // Will replace the TemplateName in PascalCase
394
451
  replace({
395
452
  regex: 'TemplateName',
396
- replacement: startCase(camelCase(componentName)).replace(/ /g, ''),
453
+ replacement: convertors['TemplateName'],
397
454
  paths: [componentPath],
398
455
  recursive: false,
399
456
  silent: true,
@@ -402,7 +459,7 @@ export function generateComponent(componentName, cmd, cliConfigFile) {
402
459
  // Will replace the templateName in camelCase
403
460
  replace({
404
461
  regex: 'templateName',
405
- replacement: camelCase(componentName),
462
+ replacement: convertors['templateName'],
406
463
  paths: [componentPath],
407
464
  recursive: false,
408
465
  silent: true,
@@ -411,7 +468,7 @@ export function generateComponent(componentName, cmd, cliConfigFile) {
411
468
  // Will replace the template-name in kebab-case
412
469
  replace({
413
470
  regex: 'template-name',
414
- replacement: kebabCase(componentName),
471
+ replacement: convertors['template-name'],
415
472
  paths: [componentPath],
416
473
  recursive: false,
417
474
  silent: true,
@@ -420,7 +477,7 @@ export function generateComponent(componentName, cmd, cliConfigFile) {
420
477
  // Will replace the template_name in snake_case
421
478
  replace({
422
479
  regex: 'template_name',
423
- replacement: snakeCase(componentName),
480
+ replacement: convertors['template_name'],
424
481
  paths: [componentPath],
425
482
  recursive: false,
426
483
  silent: true,
@@ -429,7 +486,7 @@ export function generateComponent(componentName, cmd, cliConfigFile) {
429
486
  // Will replace the TEMPLATE_NAME in uppercase SNAKE_CASE
430
487
  replace({
431
488
  regex: 'TEMPLATE_NAME',
432
- replacement: snakeCase(componentName).toUpperCase(),
489
+ replacement: convertors['TEMPLATE_NAME'],
433
490
  paths: [componentPath],
434
491
  recursive: false,
435
492
  silent: true,
@@ -441,6 +498,7 @@ export function generateComponent(componentName, cmd, cliConfigFile) {
441
498
  if (cmd.describe && componentFileType === buildInComponentFileTypes.COMPONENT) {
442
499
  aiComponentGenerator(template, cmd.describe)
443
500
  .then((aiGeneratedComponent) => {
501
+ // @ts-ignore
444
502
  outputFileSync(componentPath, aiGeneratedComponent.trim());
445
503
  console.log(
446
504
  chalk.green(`OpenAI Successfully created the ${filename} component with the provided description.`)