generate-react-cli 10.0.0 → 11.0.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/bin/generate-react.js +10 -9
- package/package.json +15 -15
- package/readme.md +13 -8
- package/src/commands/generateComponent.js +12 -12
- package/src/templates/component/componentJsTemplate.js +1 -7
- package/src/templates/component/componentLazyTemplate.js +2 -2
- package/src/templates/component/componentStoryTemplate.js +4 -8
- package/src/templates/component/componentTestDefaultTemplate.js +6 -6
- package/src/templates/component/componentTestTestingLibraryTemplate.js +2 -3
- package/src/templates/component/componentTestVitestTemplate.js +15 -0
- package/src/templates/component/componentTsLazyTemplate.js +2 -2
- package/src/templates/component/componentTsTemplate.js +1 -1
- package/src/utils/generateComponentUtils.js +108 -164
- package/src/utils/grcConfigUtils.js +42 -99
- package/src/utils/messagesUtils.js +148 -0
- package/src/templates/component/componentTestEnzymeTemplate.js +0 -16
package/bin/generate-react.js
CHANGED
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import cli from '../src/cli.js';
|
|
3
|
+
import { error } from '../src/utils/messagesUtils.js';
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
function isNotValidNodeVersion() {
|
|
5
6
|
const currentNodeVersion = process.versions.node;
|
|
6
7
|
const semver = currentNodeVersion.split('.');
|
|
7
8
|
const major = semver[0];
|
|
8
9
|
|
|
9
|
-
if (major <
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
);
|
|
10
|
+
if (major < 22) {
|
|
11
|
+
error(`Node ${currentNodeVersion} is not supported`, {
|
|
12
|
+
details: 'Generate React CLI requires Node 22 or higher',
|
|
13
|
+
suggestions: [
|
|
14
|
+
'Update your Node.js version to 22 or higher',
|
|
15
|
+
],
|
|
16
|
+
});
|
|
16
17
|
|
|
17
18
|
return true;
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
return false;
|
|
21
|
-
}
|
|
22
|
+
}
|
|
22
23
|
|
|
23
24
|
// --- Check if user is running Node 12 or higher.
|
|
24
25
|
|
package/package.json
CHANGED
|
@@ -1,32 +1,32 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "generate-react-cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "11.0.0",
|
|
4
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
5
|
"author": "Armin Broubakarian",
|
|
8
6
|
"license": "MIT",
|
|
7
|
+
"repository": "https://github.com/arminbro/generate-react-cli",
|
|
8
|
+
"bugs": "https://github.com/arminbro/generate-react-cli/issues",
|
|
9
|
+
"keywords": [
|
|
10
|
+
"cli",
|
|
11
|
+
"react",
|
|
12
|
+
"build-tools",
|
|
13
|
+
"generate-react-cli"
|
|
14
|
+
],
|
|
9
15
|
"main": "bin/generate-react",
|
|
10
16
|
"bin": {
|
|
11
17
|
"generate-react": "bin/generate-react.js"
|
|
12
18
|
},
|
|
13
19
|
"type": "module",
|
|
14
20
|
"files": [
|
|
15
|
-
"bin/",
|
|
16
|
-
"src/",
|
|
17
|
-
"README.md",
|
|
18
21
|
"CHANGELOG.md",
|
|
19
|
-
"LICENSE"
|
|
22
|
+
"LICENSE",
|
|
23
|
+
"README.md",
|
|
24
|
+
"bin/",
|
|
25
|
+
"src/"
|
|
20
26
|
],
|
|
21
27
|
"publishConfig": {
|
|
22
28
|
"access": "public"
|
|
23
29
|
},
|
|
24
|
-
"keywords": [
|
|
25
|
-
"cli",
|
|
26
|
-
"react",
|
|
27
|
-
"build-tools",
|
|
28
|
-
"generate-react-cli"
|
|
29
|
-
],
|
|
30
30
|
"engines": {
|
|
31
31
|
"node": ">=22",
|
|
32
32
|
"npm": ">=10"
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"replace": "1.2.2"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@antfu/eslint-config": "
|
|
51
|
+
"@antfu/eslint-config": "7.0.1",
|
|
52
52
|
"@commitlint/cli": "20.3.1",
|
|
53
53
|
"@commitlint/config-conventional": "20.3.1",
|
|
54
54
|
"@semantic-release/commit-analyzer": "13.0.1",
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
"@semantic-release/release-notes-generator": "14.1.0",
|
|
59
59
|
"eslint": "9.39.2",
|
|
60
60
|
"husky": "9.1.7",
|
|
61
|
-
"lint-staged": "16.
|
|
61
|
+
"lint-staged": "16.2.7",
|
|
62
62
|
"semantic-release": "25.0.2"
|
|
63
63
|
},
|
|
64
64
|
"lint-staged": {
|
package/readme.md
CHANGED
|
@@ -55,6 +55,12 @@ When you run GRC within your project the first time, it will ask you a series of
|
|
|
55
55
|
}
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
+
#### Test library options:
|
|
59
|
+
|
|
60
|
+
- `Testing Library` - Generates tests using [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/)
|
|
61
|
+
- `Vitest` - Generates tests using [Vitest](https://vitest.dev/) with React Testing Library
|
|
62
|
+
- `None` - Generates basic tests using React's createRoot API
|
|
63
|
+
|
|
58
64
|
## Generate Components
|
|
59
65
|
|
|
60
66
|
```sh
|
|
@@ -119,7 +125,7 @@ Otherwise, if you don't pass any options, it will just use the default values th
|
|
|
119
125
|
<tr>
|
|
120
126
|
<td width="20%"><b>--withLazy</b></td>
|
|
121
127
|
<td width="60%">
|
|
122
|
-
Creates a corresponding lazy file (a file that lazy-loads your component out of the box and enables <a href="https://
|
|
128
|
+
Creates a corresponding lazy file (a file that lazy-loads your component out of the box and enables <a href="https://react.dev/reference/react/lazy">code splitting</a>) with this component.
|
|
123
129
|
</td>
|
|
124
130
|
<td width="20%">Boolean</td>
|
|
125
131
|
<td width="20%"><code>component.default.withLazy<code></td>
|
|
@@ -295,7 +301,6 @@ Notice in the `page.customTemplates` that we only specified the `test` custom te
|
|
|
295
301
|
```jsx
|
|
296
302
|
// templates/component/TemplateName.js
|
|
297
303
|
|
|
298
|
-
import React from 'react';
|
|
299
304
|
import styles from './TemplateName.module.css';
|
|
300
305
|
|
|
301
306
|
const TemplateName = () => (
|
|
@@ -324,14 +329,14 @@ export default TemplateName;
|
|
|
324
329
|
```jsx
|
|
325
330
|
// templates/component/TemplateName.test.js
|
|
326
331
|
|
|
327
|
-
import
|
|
328
|
-
import ReactDOM from 'react-dom';
|
|
332
|
+
import { createRoot } from 'react-dom/client';
|
|
329
333
|
import TemplateName from './TemplateName';
|
|
330
334
|
|
|
331
|
-
it('
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
335
|
+
it('should mount', () => {
|
|
336
|
+
const container = document.createElement('div');
|
|
337
|
+
const root = createRoot(container);
|
|
338
|
+
root.render(<TemplateName />);
|
|
339
|
+
root.unmount();
|
|
335
340
|
});
|
|
336
341
|
```
|
|
337
342
|
|
|
@@ -11,29 +11,29 @@ export default function initGenerateComponentCommand(args, cliConfigFile, progra
|
|
|
11
11
|
.command('component [names...]')
|
|
12
12
|
.alias('c')
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
// Static component command option defaults.
|
|
15
15
|
|
|
16
16
|
.option('-p, --path <path>', 'The path where the component will get generated in.', selectedComponentType.path)
|
|
17
17
|
.option(
|
|
18
18
|
'--type <type>',
|
|
19
19
|
'You can pass a component type that you have configured in your GRC config file.',
|
|
20
|
-
'default'
|
|
20
|
+
'default',
|
|
21
21
|
)
|
|
22
22
|
.option(
|
|
23
23
|
'-f, --flat',
|
|
24
24
|
'Generate the files in the mentioned path instead of creating new folder for it',
|
|
25
|
-
selectedComponentType.flat || false
|
|
25
|
+
selectedComponentType.flat || false,
|
|
26
26
|
)
|
|
27
27
|
.option('--dry-run', 'Show what will be generated without writing to disk')
|
|
28
28
|
.option(
|
|
29
29
|
'--customDirectory <customDirectory>',
|
|
30
|
-
'You can pass a cased path template that will be used as the component path for the component being generated.\n'
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
37
|
);
|
|
38
38
|
|
|
39
39
|
// Dynamic component command option defaults.
|
|
@@ -44,13 +44,13 @@ export default function initGenerateComponentCommand(args, cliConfigFile, progra
|
|
|
44
44
|
componentCommand.option(
|
|
45
45
|
`--${dynamicOption} <${dynamicOption}>`,
|
|
46
46
|
`With corresponding ${dynamicOption.split('with')[1]} file.`,
|
|
47
|
-
selectedComponentType[dynamicOption]
|
|
47
|
+
selectedComponentType[dynamicOption],
|
|
48
48
|
);
|
|
49
49
|
});
|
|
50
50
|
|
|
51
51
|
// Component command action.
|
|
52
52
|
|
|
53
53
|
componentCommand.action((componentNames, cmd) =>
|
|
54
|
-
componentNames.forEach(
|
|
54
|
+
componentNames.forEach(componentName => generateComponent(componentName, cmd, cliConfigFile)),
|
|
55
55
|
);
|
|
56
56
|
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
export default `import
|
|
2
|
-
import PropTypes from 'prop-types';
|
|
3
|
-
import styles from './templatename.module.css';
|
|
1
|
+
export default `import styles from './templatename.module.css';
|
|
4
2
|
|
|
5
3
|
const templatename = () => (
|
|
6
4
|
<div className={styles.templatename} data-testid="templatename">
|
|
@@ -8,9 +6,5 @@ const templatename = () => (
|
|
|
8
6
|
</div>
|
|
9
7
|
);
|
|
10
8
|
|
|
11
|
-
templatename.propTypes = {};
|
|
12
|
-
|
|
13
|
-
templatename.defaultProps = {};
|
|
14
|
-
|
|
15
9
|
export default templatename;
|
|
16
10
|
`;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export default `import
|
|
1
|
+
export default `import { lazy, Suspense } from 'react';
|
|
2
2
|
|
|
3
3
|
const Lazytemplatename = lazy(() => import('./templatename'));
|
|
4
4
|
|
|
5
|
-
const templatename = props => (
|
|
5
|
+
const templatename = (props) => (
|
|
6
6
|
<Suspense fallback={null}>
|
|
7
7
|
<Lazytemplatename {...props} />
|
|
8
8
|
</Suspense>
|
|
@@ -1,13 +1,9 @@
|
|
|
1
|
-
export default
|
|
2
|
-
import templatename from './templatename';
|
|
1
|
+
export default `import templatename from './templatename';
|
|
3
2
|
|
|
4
3
|
export default {
|
|
5
|
-
title:
|
|
4
|
+
title: 'templatename',
|
|
5
|
+
component: templatename,
|
|
6
6
|
};
|
|
7
7
|
|
|
8
|
-
export const Default =
|
|
9
|
-
|
|
10
|
-
Default.story = {
|
|
11
|
-
name: 'default',
|
|
12
|
-
};
|
|
8
|
+
export const Default = {};
|
|
13
9
|
`;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
export default `import
|
|
2
|
-
import ReactDOM from 'react-dom';
|
|
1
|
+
export default `import { createRoot } from 'react-dom/client';
|
|
3
2
|
import templatename from './templatename';
|
|
4
3
|
|
|
5
|
-
it('
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
it('should mount', () => {
|
|
5
|
+
const container = document.createElement('div');
|
|
6
|
+
const root = createRoot(container);
|
|
7
|
+
root.render(<templatename />);
|
|
8
|
+
root.unmount();
|
|
9
9
|
});`;
|
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
export default `import
|
|
2
|
-
import { render, screen } from '@testing-library/react';
|
|
1
|
+
export default `import { render, screen } from '@testing-library/react';
|
|
3
2
|
import '@testing-library/jest-dom';
|
|
4
3
|
import templatename from './templatename';
|
|
5
4
|
|
|
6
5
|
describe('<templatename />', () => {
|
|
7
|
-
test('
|
|
6
|
+
test('should mount', () => {
|
|
8
7
|
render(<templatename />);
|
|
9
8
|
|
|
10
9
|
const templateName = screen.getByTestId('templatename');
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export default `import { describe, test, expect } from 'vitest';
|
|
2
|
+
import { render, screen } from '@testing-library/react';
|
|
3
|
+
import '@testing-library/jest-dom/vitest';
|
|
4
|
+
import templatename from './templatename';
|
|
5
|
+
|
|
6
|
+
describe('<templatename />', () => {
|
|
7
|
+
test('should mount', () => {
|
|
8
|
+
render(<templatename />);
|
|
9
|
+
|
|
10
|
+
const templateName = screen.getByTestId('templatename');
|
|
11
|
+
|
|
12
|
+
expect(templateName).toBeInTheDocument();
|
|
13
|
+
});
|
|
14
|
+
});
|
|
15
|
+
`;
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export default `import
|
|
1
|
+
export default `import { lazy, Suspense, ComponentProps } from 'react';
|
|
2
2
|
|
|
3
3
|
const Lazytemplatename = lazy(() => import('./templatename'));
|
|
4
4
|
|
|
5
|
-
const templatename = (props:
|
|
5
|
+
const templatename = (props: ComponentProps<typeof Lazytemplatename>) => (
|
|
6
6
|
<Suspense fallback={null}>
|
|
7
7
|
<Lazytemplatename {...props} />
|
|
8
8
|
</Suspense>
|
|
@@ -1,29 +1,29 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import replace from 'replace';
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fsExtra from 'fs-extra';
|
|
4
3
|
import camelCase from 'lodash/camelCase.js';
|
|
5
4
|
import kebabCase from 'lodash/kebabCase.js';
|
|
6
5
|
import snakeCase from 'lodash/snakeCase.js';
|
|
7
6
|
import startCase from 'lodash/startCase.js';
|
|
8
|
-
import
|
|
7
|
+
import replace from 'replace';
|
|
8
|
+
import componentCssTemplate from '../templates/component/componentCssTemplate.js';
|
|
9
9
|
|
|
10
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
11
|
import componentLazyTemplate from '../templates/component/componentLazyTemplate.js';
|
|
15
|
-
import componentTsLazyTemplate from '../templates/component/componentTsLazyTemplate.js';
|
|
16
12
|
import componentStoryTemplate from '../templates/component/componentStoryTemplate.js';
|
|
17
|
-
import
|
|
13
|
+
import componentStyledTemplate from '../templates/component/componentStyledTemplate.js';
|
|
18
14
|
import componentTestDefaultTemplate from '../templates/component/componentTestDefaultTemplate.js';
|
|
19
15
|
import componentTestTestingLibraryTemplate from '../templates/component/componentTestTestingLibraryTemplate.js';
|
|
16
|
+
import componentTestVitestTemplate from '../templates/component/componentTestVitestTemplate.js';
|
|
17
|
+
import componentTsLazyTemplate from '../templates/component/componentTsLazyTemplate.js';
|
|
18
|
+
import componentTsTemplate from '../templates/component/componentTsTemplate.js';
|
|
19
|
+
import { error, exitWithError, fileSummary } from './messagesUtils.js';
|
|
20
20
|
|
|
21
|
-
const
|
|
21
|
+
const TEMPLATE_NAME_REGEX = /template[-_]?name/i;
|
|
22
22
|
|
|
23
23
|
const { existsSync, outputFileSync, readFileSync } = fsExtra;
|
|
24
24
|
|
|
25
25
|
export function getComponentByType(args, cliConfigFile) {
|
|
26
|
-
const hasComponentTypeOption = args.find(
|
|
26
|
+
const hasComponentTypeOption = args.find(arg => arg.includes('--type'));
|
|
27
27
|
|
|
28
28
|
// Check for component type option.
|
|
29
29
|
|
|
@@ -34,16 +34,14 @@ export function getComponentByType(args, cliConfigFile) {
|
|
|
34
34
|
// If the selected component type does not exists in the cliConfigFile under `component` throw an error
|
|
35
35
|
|
|
36
36
|
if (!selectedComponentType) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
);
|
|
45
|
-
|
|
46
|
-
process.exit(1);
|
|
37
|
+
const availableTypes = Object.keys(cliConfigFile.component).join(', ');
|
|
38
|
+
exitWithError(`Unknown component type "${componentType}"`, {
|
|
39
|
+
details: `Available types: ${availableTypes}`,
|
|
40
|
+
suggestions: [
|
|
41
|
+
`Use one of the available types: ${availableTypes}`,
|
|
42
|
+
'Add this component type to your generate-react-cli.json config',
|
|
43
|
+
],
|
|
44
|
+
});
|
|
47
45
|
}
|
|
48
46
|
|
|
49
47
|
// Otherwise return it.
|
|
@@ -57,28 +55,25 @@ export function getComponentByType(args, cliConfigFile) {
|
|
|
57
55
|
}
|
|
58
56
|
|
|
59
57
|
export function getCorrespondingComponentFileTypes(component) {
|
|
60
|
-
return Object.keys(component).filter(
|
|
58
|
+
return Object.keys(component).filter(key => key.split('with').length > 1);
|
|
61
59
|
}
|
|
62
60
|
|
|
63
61
|
function getCustomTemplate(componentName, templatePath) {
|
|
64
|
-
//
|
|
62
|
+
// Try loading custom template
|
|
65
63
|
|
|
66
64
|
try {
|
|
67
65
|
const template = readFileSync(templatePath, 'utf8');
|
|
68
66
|
const filename = path.basename(templatePath).replace(/template[_-]?name/i, componentName);
|
|
69
67
|
|
|
70
68
|
return { template, filename };
|
|
71
|
-
} catch
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
return process.exit(1);
|
|
69
|
+
} catch {
|
|
70
|
+
exitWithError(`Custom template not found: "${templatePath}"`, {
|
|
71
|
+
suggestions: [
|
|
72
|
+
'Verify the template path in your generate-react-cli.json config',
|
|
73
|
+
'Check that the file exists and is readable',
|
|
74
|
+
'Use an absolute path or a path relative to project root',
|
|
75
|
+
],
|
|
76
|
+
});
|
|
82
77
|
}
|
|
83
78
|
}
|
|
84
79
|
|
|
@@ -93,20 +88,20 @@ function componentDirectoryNameGenerator({ cmd, componentName, cliConfigFile, fi
|
|
|
93
88
|
cliConfigFile.component.default.customDirectory,
|
|
94
89
|
cliConfigFile.component[cmd.type].customDirectory,
|
|
95
90
|
cmd.customDirectory,
|
|
96
|
-
].filter(
|
|
91
|
+
].filter(e => Boolean(e) && typeof e === 'string');
|
|
97
92
|
|
|
98
93
|
if (customDirectoryConfigs.length > 0) {
|
|
99
94
|
const customDirectory = customDirectoryConfigs.slice(-1).toString();
|
|
100
95
|
|
|
101
|
-
//
|
|
102
|
-
if (
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
96
|
+
// Check if the customDirectory contains a template placeholder
|
|
97
|
+
if (!TEMPLATE_NAME_REGEX.test(customDirectory)) {
|
|
98
|
+
exitWithError(`Invalid customDirectory: "${customDirectory}"`, {
|
|
99
|
+
details: 'customDirectory must contain a template placeholder',
|
|
100
|
+
suggestions: [
|
|
101
|
+
'Use templatename, TemplateName, template-name, or template_name',
|
|
102
|
+
'Example: "{{templatename}}" or "TemplateName"',
|
|
103
|
+
],
|
|
104
|
+
});
|
|
110
105
|
}
|
|
111
106
|
|
|
112
107
|
for (const convertor in convertors) {
|
|
@@ -128,7 +123,7 @@ function componentDirectoryNameGenerator({ cmd, componentName, cliConfigFile, fi
|
|
|
128
123
|
|
|
129
124
|
function componentTemplateGenerator({ cmd, componentName, cliConfigFile, convertors }) {
|
|
130
125
|
// @ts-ignore
|
|
131
|
-
const {
|
|
126
|
+
const { cssPreprocessor, testLibrary, usesCssModule, usesTypeScript } = cliConfigFile;
|
|
132
127
|
const { customTemplates } = cliConfigFile.component[cmd.type];
|
|
133
128
|
let template = null;
|
|
134
129
|
let filename = null;
|
|
@@ -136,35 +131,36 @@ function componentTemplateGenerator({ cmd, componentName, cliConfigFile, convert
|
|
|
136
131
|
// Check for a custom component template.
|
|
137
132
|
|
|
138
133
|
if (customTemplates && customTemplates.component) {
|
|
139
|
-
//
|
|
134
|
+
// Load and use the custom component template
|
|
140
135
|
|
|
141
136
|
const { template: customTemplate, filename: customTemplateFilename } = getCustomTemplate(
|
|
142
137
|
componentName,
|
|
143
|
-
customTemplates.component
|
|
138
|
+
customTemplates.component,
|
|
144
139
|
);
|
|
145
140
|
|
|
146
141
|
template = customTemplate;
|
|
147
142
|
filename = customTemplateFilename;
|
|
148
143
|
} else {
|
|
149
|
-
//
|
|
144
|
+
// Else use GRC built-in component template
|
|
150
145
|
|
|
151
146
|
template = usesTypeScript ? componentTsTemplate : componentJsTemplate;
|
|
152
147
|
filename = usesTypeScript ? `${componentName}.tsx` : `${componentName}.js`;
|
|
153
148
|
|
|
154
|
-
//
|
|
149
|
+
// If test library doesn't use data-testid or if withTest is false. Remove data-testid from template
|
|
155
150
|
|
|
156
|
-
|
|
151
|
+
const usesTestId = testLibrary === 'Testing Library' || testLibrary === 'Vitest';
|
|
152
|
+
if (!usesTestId || !cmd.withTest) {
|
|
157
153
|
template = template.replace(` data-testid="templatename"`, '');
|
|
158
154
|
}
|
|
159
155
|
|
|
160
|
-
//
|
|
156
|
+
// If it has a corresponding stylesheet
|
|
161
157
|
|
|
162
158
|
if (cmd.withStyle) {
|
|
163
159
|
if (cliConfigFile.usesStyledComponents) {
|
|
164
160
|
const cssPath = `${componentName}.styled`;
|
|
165
161
|
template = template.replace(
|
|
166
162
|
`import styles from './templatename.module.css'`,
|
|
167
|
-
`import { templatenameWrapper } from './${cssPath}'
|
|
163
|
+
`import { templatenameWrapper } from './${cssPath}'`,
|
|
168
164
|
);
|
|
169
165
|
template = template.replace(` className={styles.templatename}`, '');
|
|
170
166
|
template = template.replace(` <div`, '<templatenameWrapper');
|
|
@@ -173,7 +169,7 @@ function componentTemplateGenerator({ cmd, componentName, cliConfigFile, convert
|
|
|
173
169
|
const module = usesCssModule ? '.module' : '';
|
|
174
170
|
const cssPath = `${componentName}${module}.${cssPreprocessor}`;
|
|
175
171
|
|
|
176
|
-
//
|
|
172
|
+
// If the css module is true make sure to update the template accordingly
|
|
177
173
|
|
|
178
174
|
if (module.length) {
|
|
179
175
|
template = template.replace(`'./templatename.module.css'`, `'./${cssPath}'`);
|
|
@@ -183,7 +179,7 @@ function componentTemplateGenerator({ cmd, componentName, cliConfigFile, convert
|
|
|
183
179
|
}
|
|
184
180
|
}
|
|
185
181
|
} else {
|
|
186
|
-
//
|
|
182
|
+
// If no stylesheet, remove className attribute and style import from jsTemplate
|
|
187
183
|
|
|
188
184
|
template = template.replace(` className={styles.templatename}`, '');
|
|
189
185
|
template = template.replace(`import styles from './templatename.module.css';`, '');
|
|
@@ -205,11 +201,11 @@ function componentStyleTemplateGenerator({ cliConfigFile, cmd, componentName, co
|
|
|
205
201
|
// Check for a custom style template.
|
|
206
202
|
|
|
207
203
|
if (customTemplates && customTemplates.style) {
|
|
208
|
-
//
|
|
204
|
+
// Load and use the custom style template
|
|
209
205
|
|
|
210
206
|
const { template: customTemplate, filename: customTemplateFilename } = getCustomTemplate(
|
|
211
207
|
componentName,
|
|
212
|
-
customTemplates.style
|
|
208
|
+
customTemplates.style,
|
|
213
209
|
);
|
|
214
210
|
|
|
215
211
|
template = customTemplate;
|
|
@@ -223,7 +219,7 @@ function componentStyleTemplateGenerator({ cliConfigFile, cmd, componentName, co
|
|
|
223
219
|
const module = usesCssModule ? '.module' : '';
|
|
224
220
|
const cssFilename = `${componentName}${module}.${cssPreprocessor}`;
|
|
225
221
|
|
|
226
|
-
//
|
|
222
|
+
// Else use GRC built-in style template
|
|
227
223
|
|
|
228
224
|
template = componentCssTemplate;
|
|
229
225
|
filename = cssFilename;
|
|
@@ -246,11 +242,11 @@ function componentTestTemplateGenerator({ cliConfigFile, cmd, componentName, con
|
|
|
246
242
|
// Check for a custom test template.
|
|
247
243
|
|
|
248
244
|
if (customTemplates && customTemplates.test) {
|
|
249
|
-
//
|
|
245
|
+
// Load and use the custom test template
|
|
250
246
|
|
|
251
247
|
const { template: customTemplate, filename: customTemplateFilename } = getCustomTemplate(
|
|
252
248
|
componentName,
|
|
253
|
-
customTemplates.test
|
|
249
|
+
customTemplates.test,
|
|
254
250
|
);
|
|
255
251
|
|
|
256
252
|
template = customTemplate;
|
|
@@ -258,12 +254,10 @@ function componentTestTemplateGenerator({ cliConfigFile, cmd, componentName, con
|
|
|
258
254
|
} else {
|
|
259
255
|
filename = usesTypeScript ? `${componentName}.test.tsx` : `${componentName}.test.js`;
|
|
260
256
|
|
|
261
|
-
if (testLibrary === '
|
|
262
|
-
// --- Else use GRC built-in test template based on test library type
|
|
263
|
-
|
|
264
|
-
template = componentTestEnzymeTemplate;
|
|
265
|
-
} else if (testLibrary === 'Testing Library') {
|
|
257
|
+
if (testLibrary === 'Testing Library') {
|
|
266
258
|
template = componentTestTestingLibraryTemplate;
|
|
259
|
+
} else if (testLibrary === 'Vitest') {
|
|
260
|
+
template = componentTestVitestTemplate;
|
|
267
261
|
} else {
|
|
268
262
|
template = componentTestDefaultTemplate;
|
|
269
263
|
}
|
|
@@ -285,17 +279,17 @@ function componentStoryTemplateGenerator({ cliConfigFile, cmd, componentName, co
|
|
|
285
279
|
// Check for a custom story template.
|
|
286
280
|
|
|
287
281
|
if (customTemplates && customTemplates.story) {
|
|
288
|
-
//
|
|
282
|
+
// Load and use the custom story template
|
|
289
283
|
|
|
290
284
|
const { template: customTemplate, filename: customTemplateFilename } = getCustomTemplate(
|
|
291
285
|
componentName,
|
|
292
|
-
customTemplates.story
|
|
286
|
+
customTemplates.story,
|
|
293
287
|
);
|
|
294
288
|
|
|
295
289
|
template = customTemplate;
|
|
296
290
|
filename = customTemplateFilename;
|
|
297
291
|
} else {
|
|
298
|
-
//
|
|
292
|
+
// Else use GRC built-in story template
|
|
299
293
|
|
|
300
294
|
template = componentStoryTemplate;
|
|
301
295
|
filename = usesTypeScript ? `${componentName}.stories.tsx` : `${componentName}.stories.js`;
|
|
@@ -317,17 +311,17 @@ function componentLazyTemplateGenerator({ cmd, componentName, cliConfigFile, con
|
|
|
317
311
|
// Check for a custom lazy template.
|
|
318
312
|
|
|
319
313
|
if (customTemplates && customTemplates.lazy) {
|
|
320
|
-
//
|
|
314
|
+
// Load and use the custom lazy template
|
|
321
315
|
|
|
322
316
|
const { template: customTemplate, filename: customTemplateFilename } = getCustomTemplate(
|
|
323
317
|
componentName,
|
|
324
|
-
customTemplates.lazy
|
|
318
|
+
customTemplates.lazy,
|
|
325
319
|
);
|
|
326
320
|
|
|
327
321
|
template = customTemplate;
|
|
328
322
|
filename = customTemplateFilename;
|
|
329
323
|
} else {
|
|
330
|
-
//
|
|
324
|
+
// Else use GRC built-in lazy template
|
|
331
325
|
|
|
332
326
|
template = usesTypeScript ? componentTsLazyTemplate : componentLazyTemplate;
|
|
333
327
|
filename = usesTypeScript ? `${componentName}.lazy.tsx` : `${componentName}.lazy.js`;
|
|
@@ -349,23 +343,20 @@ function customFileTemplateGenerator({ componentName, cmd, cliConfigFile, compon
|
|
|
349
343
|
// Check for a valid custom template for the corresponding custom component file.
|
|
350
344
|
|
|
351
345
|
if (!customTemplates || !customTemplates[fileType]) {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
);
|
|
360
|
-
|
|
361
|
-
return process.exit(1);
|
|
346
|
+
exitWithError(`Missing custom template for "${fileType}"`, {
|
|
347
|
+
details: 'Custom component files require a matching custom template',
|
|
348
|
+
suggestions: [
|
|
349
|
+
`Add a "${fileType}" template path to customTemplates in your config`,
|
|
350
|
+
'Check that the template file exists at the specified path',
|
|
351
|
+
],
|
|
352
|
+
});
|
|
362
353
|
}
|
|
363
354
|
|
|
364
|
-
//
|
|
355
|
+
// Load and use the custom component template.
|
|
365
356
|
|
|
366
357
|
const { template: customTemplate, filename: customTemplateFilename } = getCustomTemplate(
|
|
367
358
|
componentName,
|
|
368
|
-
customTemplates[fileType]
|
|
359
|
+
customTemplates[fileType],
|
|
369
360
|
);
|
|
370
361
|
|
|
371
362
|
template = customTemplate;
|
|
@@ -378,7 +369,7 @@ Please make sure you're pointing to the right custom template path in your gener
|
|
|
378
369
|
};
|
|
379
370
|
}
|
|
380
371
|
|
|
381
|
-
//
|
|
372
|
+
// Built in component file types
|
|
382
373
|
|
|
383
374
|
const buildInComponentFileTypes = {
|
|
384
375
|
COMPONENT: 'component',
|
|
@@ -388,7 +379,7 @@ const buildInComponentFileTypes = {
|
|
|
388
379
|
LAZY: 'withLazy',
|
|
389
380
|
};
|
|
390
381
|
|
|
391
|
-
//
|
|
382
|
+
// Generate component template map
|
|
392
383
|
|
|
393
384
|
const componentTemplateGeneratorMap = {
|
|
394
385
|
[buildInComponentFileTypes.COMPONENT]: componentTemplateGenerator,
|
|
@@ -400,25 +391,27 @@ const componentTemplateGeneratorMap = {
|
|
|
400
391
|
|
|
401
392
|
export function generateComponent(componentName, cmd, cliConfigFile) {
|
|
402
393
|
const componentFileTypes = ['component', ...getCorrespondingComponentFileTypes(cmd)];
|
|
394
|
+
const generatedFiles = [];
|
|
395
|
+
let basePath = '';
|
|
403
396
|
|
|
404
397
|
componentFileTypes.forEach((componentFileType) => {
|
|
405
|
-
//
|
|
398
|
+
// Generate templates only if the component options (withStyle, withTest, etc..) are true,
|
|
406
399
|
// or if the template type is "component"
|
|
407
400
|
|
|
408
401
|
if (
|
|
409
|
-
(cmd[componentFileType] && cmd[componentFileType].toString() === 'true')
|
|
410
|
-
componentFileType === buildInComponentFileTypes.COMPONENT
|
|
402
|
+
(cmd[componentFileType] && cmd[componentFileType].toString() === 'true')
|
|
403
|
+
|| componentFileType === buildInComponentFileTypes.COMPONENT
|
|
411
404
|
) {
|
|
412
405
|
const generateTemplate = componentTemplateGeneratorMap[componentFileType] || customFileTemplateGenerator;
|
|
413
406
|
|
|
414
407
|
const convertors = {
|
|
415
|
-
templatename: componentName,
|
|
416
|
-
TemplateName: startCase(camelCase(componentName)).replace(/ /g, ''),
|
|
417
|
-
templateName: camelCase(componentName),
|
|
408
|
+
'templatename': componentName,
|
|
409
|
+
'TemplateName': startCase(camelCase(componentName)).replace(/ /g, ''),
|
|
410
|
+
'templateName': camelCase(componentName),
|
|
418
411
|
'template-name': kebabCase(componentName),
|
|
419
|
-
template_name: snakeCase(componentName),
|
|
420
|
-
TEMPLATE_NAME: snakeCase(componentName).toUpperCase(),
|
|
421
|
-
TEMPLATENAME: componentName.toUpperCase(),
|
|
412
|
+
'template_name': snakeCase(componentName),
|
|
413
|
+
'TEMPLATE_NAME': snakeCase(componentName).toUpperCase(),
|
|
414
|
+
'TEMPLATENAME': componentName.toUpperCase(),
|
|
422
415
|
};
|
|
423
416
|
|
|
424
417
|
const { componentPath, filename, template } = generateTemplate({
|
|
@@ -429,90 +422,41 @@ export function generateComponent(componentName, cmd, cliConfigFile) {
|
|
|
429
422
|
convertors,
|
|
430
423
|
});
|
|
431
424
|
|
|
432
|
-
//
|
|
425
|
+
// Extract base path for summary
|
|
426
|
+
if (!basePath) {
|
|
427
|
+
basePath = path.dirname(componentPath);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Make sure the component does not already exist in the path directory.
|
|
433
431
|
|
|
434
432
|
if (existsSync(componentPath)) {
|
|
435
|
-
|
|
433
|
+
generatedFiles.push({ filename, status: 'skipped', path: componentPath });
|
|
436
434
|
} else {
|
|
437
435
|
try {
|
|
438
436
|
if (!cmd.dryRun) {
|
|
439
437
|
outputFileSync(componentPath, template);
|
|
440
438
|
|
|
441
|
-
//
|
|
442
|
-
|
|
443
|
-
regex:
|
|
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,
|
|
439
|
+
// Replace all template placeholders with their corresponding component name formats
|
|
440
|
+
Object.entries(convertors).forEach(([pattern, replacement]) => {
|
|
441
|
+
replace({ regex: pattern, replacement, paths: [componentPath], recursive: false, silent: true });
|
|
502
442
|
});
|
|
503
443
|
}
|
|
504
444
|
|
|
505
|
-
|
|
506
|
-
} catch (
|
|
507
|
-
|
|
508
|
-
|
|
445
|
+
generatedFiles.push({ filename, status: 'created', path: componentPath });
|
|
446
|
+
} catch (err) {
|
|
447
|
+
generatedFiles.push({ filename, status: 'failed', path: componentPath });
|
|
448
|
+
error(`Failed to create ${filename}`, {
|
|
449
|
+
details: err.message,
|
|
450
|
+
suggestions: [
|
|
451
|
+
'Check that you have write permissions to the target directory',
|
|
452
|
+
'Verify the path is valid',
|
|
453
|
+
],
|
|
454
|
+
});
|
|
509
455
|
}
|
|
510
456
|
}
|
|
511
457
|
}
|
|
512
458
|
});
|
|
513
459
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
console.log(chalk.yellow(`NOTE: The "dry-run" flag means no changes were made.`));
|
|
517
|
-
}
|
|
460
|
+
// Show summary
|
|
461
|
+
fileSummary(generatedFiles, basePath, { dryRun: cmd.dryRun });
|
|
518
462
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import chalk from 'chalk';
|
|
2
1
|
import fsExtra from 'fs-extra';
|
|
3
2
|
import inquirer from 'inquirer';
|
|
4
3
|
|
|
5
4
|
import merge from 'lodash/merge.js';
|
|
6
5
|
import deepKeys from './deepKeysUtils.js';
|
|
6
|
+
import { blank, error, exitWithError, header, outro, success } from './messagesUtils.js';
|
|
7
7
|
|
|
8
8
|
const { accessSync, constants, outputFileSync, readFileSync } = fsExtra;
|
|
9
9
|
const { prompt } = inquirer;
|
|
@@ -40,7 +40,7 @@ const projectLevelQuestions = [
|
|
|
40
40
|
type: 'select',
|
|
41
41
|
name: 'testLibrary',
|
|
42
42
|
message: 'What testing library does your project use?',
|
|
43
|
-
choices: ['Testing Library', '
|
|
43
|
+
choices: ['Testing Library', 'Vitest', 'None'],
|
|
44
44
|
},
|
|
45
45
|
];
|
|
46
46
|
|
|
@@ -76,7 +76,7 @@ export const componentLevelQuestions = [
|
|
|
76
76
|
type: 'confirm',
|
|
77
77
|
name: 'component.default.withLazy',
|
|
78
78
|
message:
|
|
79
|
-
'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://
|
|
79
|
+
'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://react.dev/reference/react/lazy) with each component you generate?',
|
|
80
80
|
},
|
|
81
81
|
];
|
|
82
82
|
|
|
@@ -89,85 +89,39 @@ const grcConfigQuestions = [
|
|
|
89
89
|
|
|
90
90
|
async function createCLIConfigFile() {
|
|
91
91
|
try {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
'--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------',
|
|
96
|
-
),
|
|
92
|
+
header(
|
|
93
|
+
'Welcome to Generate React CLI!',
|
|
94
|
+
'Answer a few questions to customize the CLI for your project.',
|
|
97
95
|
);
|
|
98
|
-
console.log(
|
|
99
|
-
chalk.cyan(
|
|
100
|
-
'It looks like this is the first time that you\'re running generate-react-cli within this project.',
|
|
101
|
-
),
|
|
102
|
-
);
|
|
103
|
-
console.log();
|
|
104
|
-
console.log(
|
|
105
|
-
chalk.cyan(
|
|
106
|
-
'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).',
|
|
107
|
-
),
|
|
108
|
-
);
|
|
109
|
-
console.log(
|
|
110
|
-
chalk.cyan(
|
|
111
|
-
'--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------',
|
|
112
|
-
),
|
|
113
|
-
);
|
|
114
|
-
console.log();
|
|
115
96
|
|
|
116
97
|
const answers = await prompt(grcConfigQuestions);
|
|
117
98
|
|
|
118
99
|
outputFileSync('generate-react-cli.json', JSON.stringify(answers, null, 2));
|
|
119
100
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
),
|
|
125
|
-
);
|
|
126
|
-
|
|
127
|
-
console.log('');
|
|
128
|
-
console.log(chalk.cyan('You can always go back and update it as needed.'));
|
|
129
|
-
console.log('');
|
|
130
|
-
console.log(chalk.cyan('Happy Hacking!'));
|
|
131
|
-
console.log('');
|
|
132
|
-
console.log('');
|
|
101
|
+
blank();
|
|
102
|
+
success('Created the generate-react-cli.json config file');
|
|
103
|
+
blank();
|
|
104
|
+
outro('You can always update the config file manually. Happy Hacking!');
|
|
133
105
|
|
|
134
106
|
return answers;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
'
|
|
140
|
-
|
|
141
|
-
|
|
107
|
+
} catch (e) {
|
|
108
|
+
error('Could not create config file', {
|
|
109
|
+
details: 'Failed to write generate-react-cli.json',
|
|
110
|
+
suggestions: [
|
|
111
|
+
'Check that you have write permissions in this directory',
|
|
112
|
+
'Make sure the directory is not read-only',
|
|
113
|
+
],
|
|
114
|
+
});
|
|
142
115
|
return e;
|
|
143
116
|
}
|
|
144
117
|
}
|
|
145
118
|
|
|
146
119
|
async function updateCLIConfigFile(missingConfigQuestions, currentConfigFile) {
|
|
147
120
|
try {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
'------------------------------------------------------------------------------------------------------------------------------',
|
|
152
|
-
),
|
|
153
|
-
);
|
|
154
|
-
console.log(
|
|
155
|
-
chalk.cyan(
|
|
156
|
-
'Generate React CLI has been updated and has a few new features from the last time you ran it within this project.',
|
|
157
|
-
),
|
|
158
|
-
);
|
|
159
|
-
console.log('');
|
|
160
|
-
console.log(
|
|
161
|
-
chalk.cyan(
|
|
162
|
-
'Please answer a few questions to update the "generate-react-cli.json" config file.',
|
|
163
|
-
),
|
|
121
|
+
header(
|
|
122
|
+
'Generate React CLI has new features!',
|
|
123
|
+
'Answer a few questions to update your config file.',
|
|
164
124
|
);
|
|
165
|
-
console.log(
|
|
166
|
-
chalk.cyan(
|
|
167
|
-
'------------------------------------------------------------------------------------------------------------------------------',
|
|
168
|
-
),
|
|
169
|
-
);
|
|
170
|
-
console.log('');
|
|
171
125
|
|
|
172
126
|
const answers = await prompt(missingConfigQuestions);
|
|
173
127
|
const updatedAnswers = merge({}, currentConfigFile, answers);
|
|
@@ -177,30 +131,20 @@ async function updateCLIConfigFile(missingConfigQuestions, currentConfigFile) {
|
|
|
177
131
|
JSON.stringify(updatedAnswers, null, 2),
|
|
178
132
|
);
|
|
179
133
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
),
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
console.log();
|
|
188
|
-
console.log(
|
|
189
|
-
chalk.cyan('You can always go back and manually update it as needed.'),
|
|
190
|
-
);
|
|
191
|
-
console.log();
|
|
192
|
-
console.log(chalk.cyan('Happy Hacking!'));
|
|
193
|
-
console.log();
|
|
194
|
-
console.log();
|
|
134
|
+
blank();
|
|
135
|
+
success('Updated the generate-react-cli.json config file');
|
|
136
|
+
blank();
|
|
137
|
+
outro('You can always update the config file manually. Happy Hacking!');
|
|
195
138
|
|
|
196
139
|
return updatedAnswers;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
'
|
|
202
|
-
|
|
203
|
-
|
|
140
|
+
} catch (e) {
|
|
141
|
+
error('Could not update config file', {
|
|
142
|
+
details: 'Failed to write generate-react-cli.json',
|
|
143
|
+
suggestions: [
|
|
144
|
+
'Check that the file is not locked or read-only',
|
|
145
|
+
'Verify you have write permissions',
|
|
146
|
+
],
|
|
147
|
+
});
|
|
204
148
|
return e;
|
|
205
149
|
}
|
|
206
150
|
}
|
|
@@ -238,17 +182,16 @@ export async function getCLIConfigFile() {
|
|
|
238
182
|
}
|
|
239
183
|
|
|
240
184
|
return currentConfigFile;
|
|
241
|
-
}
|
|
242
|
-
catch {
|
|
185
|
+
} catch {
|
|
243
186
|
return await createCLIConfigFile();
|
|
244
187
|
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
'
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
188
|
+
} catch {
|
|
189
|
+
exitWithError('Not in project root', {
|
|
190
|
+
details: 'Could not find package.json in current directory',
|
|
191
|
+
suggestions: [
|
|
192
|
+
'Run this command from your project root directory',
|
|
193
|
+
'Make sure package.json exists in the current directory',
|
|
194
|
+
],
|
|
195
|
+
});
|
|
253
196
|
}
|
|
254
197
|
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
const DEFAULT_TERMINAL_WIDTH = 80;
|
|
4
|
+
|
|
5
|
+
// Symbols for consistent visual feedback
|
|
6
|
+
const symbols = {
|
|
7
|
+
success: chalk.green('✓'),
|
|
8
|
+
error: chalk.red('✖'),
|
|
9
|
+
warning: chalk.yellow('⚠'),
|
|
10
|
+
info: chalk.blue('ℹ'),
|
|
11
|
+
arrow: chalk.cyan('→'),
|
|
12
|
+
bullet: chalk.dim('•'),
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// Create a responsive divider that adapts to terminal width
|
|
16
|
+
function divider(color = 'cyan') {
|
|
17
|
+
const width = Math.min(process.stdout.columns || DEFAULT_TERMINAL_WIDTH, DEFAULT_TERMINAL_WIDTH);
|
|
18
|
+
return chalk[color]('─'.repeat(width));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function pluralize(count, word) {
|
|
22
|
+
return count === 1 ? word : `${word}s`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Success message with optional file path
|
|
26
|
+
export function success(message, filePath) {
|
|
27
|
+
if (filePath) {
|
|
28
|
+
console.log(` ${symbols.success} ${chalk.green(message)}`);
|
|
29
|
+
console.log(` ${symbols.arrow} ${chalk.dim(filePath)}`);
|
|
30
|
+
} else {
|
|
31
|
+
console.log(`${symbols.success} ${chalk.green(message)}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Error message with optional details and suggestions
|
|
36
|
+
export function error(message, { details, suggestions } = {}) {
|
|
37
|
+
console.log();
|
|
38
|
+
console.log(`${symbols.error} ${chalk.red.bold('ERROR:')} ${chalk.red(message)}`);
|
|
39
|
+
|
|
40
|
+
if (details) {
|
|
41
|
+
console.log(` ${chalk.dim(details)}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (suggestions && suggestions.length > 0) {
|
|
45
|
+
console.log();
|
|
46
|
+
console.log(chalk.dim(' Try one of:'));
|
|
47
|
+
suggestions.forEach((suggestion) => {
|
|
48
|
+
console.log(` ${symbols.bullet} ${chalk.dim(suggestion)}`);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
console.log();
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Error message with exit
|
|
55
|
+
export function exitWithError(message, options = {}, exitCode = 1) {
|
|
56
|
+
error(message, options);
|
|
57
|
+
process.exit(exitCode);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Warning message
|
|
61
|
+
export function warning(message) {
|
|
62
|
+
console.log(`${symbols.warning} ${chalk.yellow(message)}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Info message
|
|
66
|
+
export function info(message) {
|
|
67
|
+
console.log(`${symbols.info} ${chalk.cyan(message)}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Header for sections (like config setup intro)
|
|
71
|
+
export function header(title, subtitle) {
|
|
72
|
+
console.log();
|
|
73
|
+
console.log(divider());
|
|
74
|
+
console.log(chalk.cyan.bold(title));
|
|
75
|
+
if (subtitle) {
|
|
76
|
+
console.log(chalk.dim(subtitle));
|
|
77
|
+
}
|
|
78
|
+
console.log(divider());
|
|
79
|
+
console.log();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Summary after file generation
|
|
83
|
+
export function fileSummary(files, basePath, { dryRun = false } = {}) {
|
|
84
|
+
console.log();
|
|
85
|
+
|
|
86
|
+
const createdFiles = files.filter(f => f.status === 'created');
|
|
87
|
+
const skippedFiles = files.filter(f => f.status === 'skipped');
|
|
88
|
+
|
|
89
|
+
if (dryRun) {
|
|
90
|
+
// Dry-run mode: show what would happen
|
|
91
|
+
console.log(`${symbols.info} ${chalk.cyan('Dry-run mode')} ${chalk.dim('- no files were created')}`);
|
|
92
|
+
console.log();
|
|
93
|
+
|
|
94
|
+
if (createdFiles.length > 0) {
|
|
95
|
+
console.log(chalk.dim(`Would create in ${basePath}:`));
|
|
96
|
+
createdFiles.forEach((file, index) => {
|
|
97
|
+
const isLast = index === createdFiles.length - 1 && skippedFiles.length === 0;
|
|
98
|
+
const prefix = isLast ? '└──' : '├──';
|
|
99
|
+
console.log(` ${chalk.dim(prefix)} ${file.filename}`);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (skippedFiles.length > 0) {
|
|
104
|
+
if (createdFiles.length > 0) {
|
|
105
|
+
console.log();
|
|
106
|
+
}
|
|
107
|
+
console.log(chalk.dim('Already exist (would be skipped):'));
|
|
108
|
+
skippedFiles.forEach((file, index) => {
|
|
109
|
+
const isLast = index === skippedFiles.length - 1;
|
|
110
|
+
const prefix = isLast ? '└──' : '├──';
|
|
111
|
+
console.log(` ${chalk.dim(prefix)} ${symbols.warning} ${chalk.dim(file.filename)}`);
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
// Actual run: show what happened
|
|
116
|
+
if (createdFiles.length > 0) {
|
|
117
|
+
console.log(`${symbols.success} ${chalk.green(`Created ${createdFiles.length} ${pluralize(createdFiles.length, 'file')} in ${basePath}`)}`);
|
|
118
|
+
}
|
|
119
|
+
if (skippedFiles.length > 0) {
|
|
120
|
+
console.log(`${symbols.warning} ${chalk.yellow(`Skipped ${skippedFiles.length} ${pluralize(skippedFiles.length, 'file')} (already exist)`)}`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Show file tree with status icons
|
|
124
|
+
files.forEach((file, index) => {
|
|
125
|
+
const isLast = index === files.length - 1;
|
|
126
|
+
const prefix = isLast ? '└──' : '├──';
|
|
127
|
+
const statusIcon = file.status === 'created'
|
|
128
|
+
? symbols.success
|
|
129
|
+
: file.status === 'skipped'
|
|
130
|
+
? symbols.warning
|
|
131
|
+
: symbols.error;
|
|
132
|
+
console.log(` ${chalk.dim(prefix)} ${statusIcon} ${file.filename}`);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
console.log();
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Closing message (Happy Hacking)
|
|
140
|
+
export function outro(message = 'Happy Hacking!') {
|
|
141
|
+
console.log(chalk.cyan(message));
|
|
142
|
+
console.log();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Blank line helper
|
|
146
|
+
export function blank() {
|
|
147
|
+
console.log();
|
|
148
|
+
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export default `import React from 'react';
|
|
2
|
-
import { shallow } from 'enzyme';
|
|
3
|
-
import templatename from './templatename';
|
|
4
|
-
|
|
5
|
-
describe('<templatename />', () => {
|
|
6
|
-
let component;
|
|
7
|
-
|
|
8
|
-
beforeEach(() => {
|
|
9
|
-
component = shallow(<templatename />);
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
test('It should mount', () => {
|
|
13
|
-
expect(component.length).toBe(1);
|
|
14
|
-
});
|
|
15
|
-
});
|
|
16
|
-
`;
|