generate-react-cli 9.1.0 → 10.1.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 +11 -9
- package/package.json +32 -57
- package/src/cli.js +1 -7
- package/src/commands/generateComponent.js +12 -12
- package/src/utils/deepKeysUtils.js +23 -0
- package/src/utils/generateComponentUtils.js +84 -73
- package/src/utils/grcConfigUtils.js +71 -88
- package/src/utils/messagesUtils.js +137 -0
package/bin/generate-react.js
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
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
|
+
'Use nvm to manage multiple Node versions: nvm install 22',
|
|
16
|
+
],
|
|
17
|
+
});
|
|
16
18
|
|
|
17
19
|
return true;
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
return false;
|
|
21
|
-
}
|
|
23
|
+
}
|
|
22
24
|
|
|
23
25
|
// --- Check if user is running Node 12 or higher.
|
|
24
26
|
|
package/package.json
CHANGED
|
@@ -1,73 +1,68 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "generate-react-cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "10.1.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
|
-
"node": ">=
|
|
32
|
-
"npm": ">=
|
|
31
|
+
"node": ">=22",
|
|
32
|
+
"npm": ">=10"
|
|
33
33
|
},
|
|
34
34
|
"browserslist": [
|
|
35
35
|
"maintained node versions"
|
|
36
36
|
],
|
|
37
37
|
"scripts": {
|
|
38
|
-
"prepare": "husky install"
|
|
38
|
+
"prepare": "husky install",
|
|
39
|
+
"lint": "eslint .",
|
|
40
|
+
"lint:fix": "eslint . --fix"
|
|
39
41
|
},
|
|
40
42
|
"dependencies": {
|
|
41
43
|
"chalk": "5.6.2",
|
|
42
|
-
"commander": "14.0.
|
|
43
|
-
"
|
|
44
|
-
"
|
|
45
|
-
"fs-extra": "11.2.0",
|
|
46
|
-
"inquirer": "12.9.4",
|
|
44
|
+
"commander": "14.0.2",
|
|
45
|
+
"fs-extra": "11.3.3",
|
|
46
|
+
"inquirer": "13.2.0",
|
|
47
47
|
"lodash": "4.17.21",
|
|
48
48
|
"replace": "1.2.2"
|
|
49
49
|
},
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"@
|
|
52
|
-
"@commitlint/
|
|
51
|
+
"@antfu/eslint-config": "7.0.1",
|
|
52
|
+
"@commitlint/cli": "20.3.1",
|
|
53
|
+
"@commitlint/config-conventional": "20.3.1",
|
|
53
54
|
"@semantic-release/commit-analyzer": "13.0.1",
|
|
54
55
|
"@semantic-release/git": "10.0.1",
|
|
55
|
-
"@semantic-release/github": "
|
|
56
|
-
"@semantic-release/npm": "
|
|
57
|
-
"@semantic-release/release-notes-generator": "14.0
|
|
58
|
-
"eslint": "
|
|
59
|
-
"eslint-config-airbnb-base": "15.0.0",
|
|
60
|
-
"eslint-config-prettier": "9.1.2",
|
|
61
|
-
"eslint-plugin-prettier": "5.5.4",
|
|
56
|
+
"@semantic-release/github": "12.0.2",
|
|
57
|
+
"@semantic-release/npm": "13.1.3",
|
|
58
|
+
"@semantic-release/release-notes-generator": "14.1.0",
|
|
59
|
+
"eslint": "9.39.2",
|
|
62
60
|
"husky": "9.1.7",
|
|
63
|
-
"
|
|
64
|
-
"
|
|
65
|
-
"semantic-release": "24.2.7"
|
|
61
|
+
"lint-staged": "16.2.7",
|
|
62
|
+
"semantic-release": "25.0.2"
|
|
66
63
|
},
|
|
67
|
-
"
|
|
68
|
-
"
|
|
69
|
-
"trailingComma": "es5",
|
|
70
|
-
"printWidth": 120
|
|
64
|
+
"lint-staged": {
|
|
65
|
+
"*.js": "eslint --fix"
|
|
71
66
|
},
|
|
72
67
|
"release": {
|
|
73
68
|
"plugins": [
|
|
@@ -97,25 +92,5 @@
|
|
|
97
92
|
200
|
|
98
93
|
]
|
|
99
94
|
}
|
|
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
95
|
}
|
|
121
96
|
}
|
package/src/cli.js
CHANGED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
1
2
|
import { program } from 'commander';
|
|
2
|
-
import { createRequire } from 'module';
|
|
3
|
-
import { config as dotEnvConfig } from 'dotenv';
|
|
4
|
-
import path from 'path';
|
|
5
3
|
|
|
6
4
|
import initGenerateComponentCommand from './commands/generateComponent.js';
|
|
7
5
|
import { getCLIConfigFile } from './utils/grcConfigUtils.js';
|
|
@@ -11,10 +9,6 @@ export default async function cli(args) {
|
|
|
11
9
|
const localRequire = createRequire(import.meta.url);
|
|
12
10
|
const pkg = localRequire('../package.json');
|
|
13
11
|
|
|
14
|
-
// init dotenv
|
|
15
|
-
|
|
16
|
-
dotEnvConfig({ path: path.resolve(process.cwd(), '.env.local') });
|
|
17
|
-
|
|
18
12
|
// Initialize generate component command
|
|
19
13
|
|
|
20
14
|
initGenerateComponentCommand(args, cliConfigFile, program);
|
|
@@ -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
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recursively extracts all keys from a nested object as dot-notation paths.
|
|
3
|
+
*
|
|
4
|
+
* @param {object} obj - The object to extract keys from
|
|
5
|
+
* @param {string} [prefix] - Internal prefix for recursion
|
|
6
|
+
* @returns {string[]} Array of dot-notation key paths
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* deepKeys({ a: 1, b: { c: 2 } })
|
|
10
|
+
* // Returns: ['a', 'b.c']
|
|
11
|
+
*/
|
|
12
|
+
export default function deepKeys(obj, prefix = '') {
|
|
13
|
+
return Object.keys(obj).reduce((keys, key) => {
|
|
14
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
15
|
+
const value = obj[key];
|
|
16
|
+
|
|
17
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
18
|
+
return [...keys, ...deepKeys(value, path)];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return [...keys, path];
|
|
22
|
+
}, []);
|
|
23
|
+
}
|
|
@@ -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';
|
|
15
|
+
import componentTestEnzymeTemplate from '../templates/component/componentTestEnzymeTemplate.js';
|
|
19
16
|
import componentTestTestingLibraryTemplate from '../templates/component/componentTestTestingLibraryTemplate.js';
|
|
17
|
+
import componentTsLazyTemplate from '../templates/component/componentTsLazyTemplate.js';
|
|
18
|
+
import componentTsTemplate from '../templates/component/componentTsTemplate.js';
|
|
19
|
+
import { error, fileSummary } from './messagesUtils.js';
|
|
20
20
|
|
|
21
21
|
const templateNameRE = /.*(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,14 +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
|
-
);
|
|
37
|
+
const availableTypes = Object.keys(cliConfigFile.component).join(', ');
|
|
38
|
+
error(`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
|
+
});
|
|
45
45
|
|
|
46
46
|
process.exit(1);
|
|
47
47
|
}
|
|
@@ -57,7 +57,7 @@ export function getComponentByType(args, cliConfigFile) {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
export function getCorrespondingComponentFileTypes(component) {
|
|
60
|
-
return Object.keys(component).filter(
|
|
60
|
+
return Object.keys(component).filter(key => key.split('with').length > 1);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
function getCustomTemplate(componentName, templatePath) {
|
|
@@ -68,15 +68,14 @@ function getCustomTemplate(componentName, templatePath) {
|
|
|
68
68
|
const filename = path.basename(templatePath).replace(/template[_-]?name/i, componentName);
|
|
69
69
|
|
|
70
70
|
return { template, filename };
|
|
71
|
-
} catch
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
);
|
|
71
|
+
} catch {
|
|
72
|
+
error(`Custom template not found: "${templatePath}"`, {
|
|
73
|
+
suggestions: [
|
|
74
|
+
'Verify the template path in your generate-react-cli.json config',
|
|
75
|
+
'Check that the file exists and is readable',
|
|
76
|
+
'Use an absolute path or a path relative to project root',
|
|
77
|
+
],
|
|
78
|
+
});
|
|
80
79
|
|
|
81
80
|
return process.exit(1);
|
|
82
81
|
}
|
|
@@ -93,18 +92,20 @@ function componentDirectoryNameGenerator({ cmd, componentName, cliConfigFile, fi
|
|
|
93
92
|
cliConfigFile.component.default.customDirectory,
|
|
94
93
|
cliConfigFile.component[cmd.type].customDirectory,
|
|
95
94
|
cmd.customDirectory,
|
|
96
|
-
].filter(
|
|
95
|
+
].filter(e => Boolean(e) && typeof e === 'string');
|
|
97
96
|
|
|
98
97
|
if (customDirectoryConfigs.length > 0) {
|
|
99
98
|
const customDirectory = customDirectoryConfigs.slice(-1).toString();
|
|
100
99
|
|
|
101
100
|
// Double check if the customDirectory is templatable
|
|
102
101
|
if (templateNameRE.exec(customDirectory) == null) {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
102
|
+
error(`Invalid customDirectory: "${customDirectory}"`, {
|
|
103
|
+
details: 'customDirectory must contain a template placeholder',
|
|
104
|
+
suggestions: [
|
|
105
|
+
'Use templatename, TemplateName, template-name, or template_name',
|
|
106
|
+
'Example: "{{templatename}}" or "TemplateName"',
|
|
107
|
+
],
|
|
108
|
+
});
|
|
108
109
|
|
|
109
110
|
process.exit(-2);
|
|
110
111
|
}
|
|
@@ -128,7 +129,7 @@ function componentDirectoryNameGenerator({ cmd, componentName, cliConfigFile, fi
|
|
|
128
129
|
|
|
129
130
|
function componentTemplateGenerator({ cmd, componentName, cliConfigFile, convertors }) {
|
|
130
131
|
// @ts-ignore
|
|
131
|
-
const {
|
|
132
|
+
const { cssPreprocessor, testLibrary, usesCssModule, usesTypeScript } = cliConfigFile;
|
|
132
133
|
const { customTemplates } = cliConfigFile.component[cmd.type];
|
|
133
134
|
let template = null;
|
|
134
135
|
let filename = null;
|
|
@@ -140,7 +141,7 @@ function componentTemplateGenerator({ cmd, componentName, cliConfigFile, convert
|
|
|
140
141
|
|
|
141
142
|
const { template: customTemplate, filename: customTemplateFilename } = getCustomTemplate(
|
|
142
143
|
componentName,
|
|
143
|
-
customTemplates.component
|
|
144
|
+
customTemplates.component,
|
|
144
145
|
);
|
|
145
146
|
|
|
146
147
|
template = customTemplate;
|
|
@@ -164,7 +165,7 @@ function componentTemplateGenerator({ cmd, componentName, cliConfigFile, convert
|
|
|
164
165
|
const cssPath = `${componentName}.styled`;
|
|
165
166
|
template = template.replace(
|
|
166
167
|
`import styles from './templatename.module.css'`,
|
|
167
|
-
`import { templatenameWrapper } from './${cssPath}'
|
|
168
|
+
`import { templatenameWrapper } from './${cssPath}'`,
|
|
168
169
|
);
|
|
169
170
|
template = template.replace(` className={styles.templatename}`, '');
|
|
170
171
|
template = template.replace(` <div`, '<templatenameWrapper');
|
|
@@ -209,7 +210,7 @@ function componentStyleTemplateGenerator({ cliConfigFile, cmd, componentName, co
|
|
|
209
210
|
|
|
210
211
|
const { template: customTemplate, filename: customTemplateFilename } = getCustomTemplate(
|
|
211
212
|
componentName,
|
|
212
|
-
customTemplates.style
|
|
213
|
+
customTemplates.style,
|
|
213
214
|
);
|
|
214
215
|
|
|
215
216
|
template = customTemplate;
|
|
@@ -250,7 +251,7 @@ function componentTestTemplateGenerator({ cliConfigFile, cmd, componentName, con
|
|
|
250
251
|
|
|
251
252
|
const { template: customTemplate, filename: customTemplateFilename } = getCustomTemplate(
|
|
252
253
|
componentName,
|
|
253
|
-
customTemplates.test
|
|
254
|
+
customTemplates.test,
|
|
254
255
|
);
|
|
255
256
|
|
|
256
257
|
template = customTemplate;
|
|
@@ -289,7 +290,7 @@ function componentStoryTemplateGenerator({ cliConfigFile, cmd, componentName, co
|
|
|
289
290
|
|
|
290
291
|
const { template: customTemplate, filename: customTemplateFilename } = getCustomTemplate(
|
|
291
292
|
componentName,
|
|
292
|
-
customTemplates.story
|
|
293
|
+
customTemplates.story,
|
|
293
294
|
);
|
|
294
295
|
|
|
295
296
|
template = customTemplate;
|
|
@@ -321,7 +322,7 @@ function componentLazyTemplateGenerator({ cmd, componentName, cliConfigFile, con
|
|
|
321
322
|
|
|
322
323
|
const { template: customTemplate, filename: customTemplateFilename } = getCustomTemplate(
|
|
323
324
|
componentName,
|
|
324
|
-
customTemplates.lazy
|
|
325
|
+
customTemplates.lazy,
|
|
325
326
|
);
|
|
326
327
|
|
|
327
328
|
template = customTemplate;
|
|
@@ -349,14 +350,13 @@ function customFileTemplateGenerator({ componentName, cmd, cliConfigFile, compon
|
|
|
349
350
|
// Check for a valid custom template for the corresponding custom component file.
|
|
350
351
|
|
|
351
352
|
if (!customTemplates || !customTemplates[fileType]) {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
);
|
|
353
|
+
error(`Missing custom template for "${fileType}"`, {
|
|
354
|
+
details: 'Custom component files require a matching custom template',
|
|
355
|
+
suggestions: [
|
|
356
|
+
`Add a "${fileType}" template path to customTemplates in your config`,
|
|
357
|
+
'Check that the template file exists at the specified path',
|
|
358
|
+
],
|
|
359
|
+
});
|
|
360
360
|
|
|
361
361
|
return process.exit(1);
|
|
362
362
|
}
|
|
@@ -365,7 +365,7 @@ Please make sure you're pointing to the right custom template path in your gener
|
|
|
365
365
|
|
|
366
366
|
const { template: customTemplate, filename: customTemplateFilename } = getCustomTemplate(
|
|
367
367
|
componentName,
|
|
368
|
-
customTemplates[fileType]
|
|
368
|
+
customTemplates[fileType],
|
|
369
369
|
);
|
|
370
370
|
|
|
371
371
|
template = customTemplate;
|
|
@@ -400,25 +400,27 @@ const componentTemplateGeneratorMap = {
|
|
|
400
400
|
|
|
401
401
|
export function generateComponent(componentName, cmd, cliConfigFile) {
|
|
402
402
|
const componentFileTypes = ['component', ...getCorrespondingComponentFileTypes(cmd)];
|
|
403
|
+
const generatedFiles = [];
|
|
404
|
+
let basePath = '';
|
|
403
405
|
|
|
404
406
|
componentFileTypes.forEach((componentFileType) => {
|
|
405
407
|
// --- Generate templates only if the component options (withStyle, withTest, etc..) are true,
|
|
406
408
|
// or if the template type is "component"
|
|
407
409
|
|
|
408
410
|
if (
|
|
409
|
-
(cmd[componentFileType] && cmd[componentFileType].toString() === 'true')
|
|
410
|
-
componentFileType === buildInComponentFileTypes.COMPONENT
|
|
411
|
+
(cmd[componentFileType] && cmd[componentFileType].toString() === 'true')
|
|
412
|
+
|| componentFileType === buildInComponentFileTypes.COMPONENT
|
|
411
413
|
) {
|
|
412
414
|
const generateTemplate = componentTemplateGeneratorMap[componentFileType] || customFileTemplateGenerator;
|
|
413
415
|
|
|
414
416
|
const convertors = {
|
|
415
|
-
templatename: componentName,
|
|
416
|
-
TemplateName: startCase(camelCase(componentName)).replace(/ /g, ''),
|
|
417
|
-
templateName: camelCase(componentName),
|
|
417
|
+
'templatename': componentName,
|
|
418
|
+
'TemplateName': startCase(camelCase(componentName)).replace(/ /g, ''),
|
|
419
|
+
'templateName': camelCase(componentName),
|
|
418
420
|
'template-name': kebabCase(componentName),
|
|
419
|
-
template_name: snakeCase(componentName),
|
|
420
|
-
TEMPLATE_NAME: snakeCase(componentName).toUpperCase(),
|
|
421
|
-
TEMPLATENAME: componentName.toUpperCase(),
|
|
421
|
+
'template_name': snakeCase(componentName),
|
|
422
|
+
'TEMPLATE_NAME': snakeCase(componentName).toUpperCase(),
|
|
423
|
+
'TEMPLATENAME': componentName.toUpperCase(),
|
|
422
424
|
};
|
|
423
425
|
|
|
424
426
|
const { componentPath, filename, template } = generateTemplate({
|
|
@@ -429,10 +431,15 @@ export function generateComponent(componentName, cmd, cliConfigFile) {
|
|
|
429
431
|
convertors,
|
|
430
432
|
});
|
|
431
433
|
|
|
434
|
+
// Extract base path for summary
|
|
435
|
+
if (!basePath) {
|
|
436
|
+
basePath = path.dirname(componentPath);
|
|
437
|
+
}
|
|
438
|
+
|
|
432
439
|
// --- Make sure the component does not already exist in the path directory.
|
|
433
440
|
|
|
434
441
|
if (existsSync(componentPath)) {
|
|
435
|
-
|
|
442
|
+
generatedFiles.push({ filename, status: 'skipped', path: componentPath });
|
|
436
443
|
} else {
|
|
437
444
|
try {
|
|
438
445
|
if (!cmd.dryRun) {
|
|
@@ -441,7 +448,7 @@ export function generateComponent(componentName, cmd, cliConfigFile) {
|
|
|
441
448
|
// Will replace the templatename in whichever format the user typed the component name in the command.
|
|
442
449
|
replace({
|
|
443
450
|
regex: 'templatename',
|
|
444
|
-
replacement: convertors
|
|
451
|
+
replacement: convertors.templatename,
|
|
445
452
|
paths: [componentPath],
|
|
446
453
|
recursive: false,
|
|
447
454
|
silent: true,
|
|
@@ -450,7 +457,7 @@ export function generateComponent(componentName, cmd, cliConfigFile) {
|
|
|
450
457
|
// Will replace the TemplateName in PascalCase
|
|
451
458
|
replace({
|
|
452
459
|
regex: 'TemplateName',
|
|
453
|
-
replacement: convertors
|
|
460
|
+
replacement: convertors.TemplateName,
|
|
454
461
|
paths: [componentPath],
|
|
455
462
|
recursive: false,
|
|
456
463
|
silent: true,
|
|
@@ -459,7 +466,7 @@ export function generateComponent(componentName, cmd, cliConfigFile) {
|
|
|
459
466
|
// Will replace the templateName in camelCase
|
|
460
467
|
replace({
|
|
461
468
|
regex: 'templateName',
|
|
462
|
-
replacement: convertors
|
|
469
|
+
replacement: convertors.templateName,
|
|
463
470
|
paths: [componentPath],
|
|
464
471
|
recursive: false,
|
|
465
472
|
silent: true,
|
|
@@ -477,7 +484,7 @@ export function generateComponent(componentName, cmd, cliConfigFile) {
|
|
|
477
484
|
// Will replace the template_name in snake_case
|
|
478
485
|
replace({
|
|
479
486
|
regex: 'template_name',
|
|
480
|
-
replacement: convertors
|
|
487
|
+
replacement: convertors.template_name,
|
|
481
488
|
paths: [componentPath],
|
|
482
489
|
recursive: false,
|
|
483
490
|
silent: true,
|
|
@@ -486,7 +493,7 @@ export function generateComponent(componentName, cmd, cliConfigFile) {
|
|
|
486
493
|
// Will replace the TEMPLATE_NAME in uppercase SNAKE_CASE
|
|
487
494
|
replace({
|
|
488
495
|
regex: 'TEMPLATE_NAME',
|
|
489
|
-
replacement: convertors
|
|
496
|
+
replacement: convertors.TEMPLATE_NAME,
|
|
490
497
|
paths: [componentPath],
|
|
491
498
|
recursive: false,
|
|
492
499
|
silent: true,
|
|
@@ -495,24 +502,28 @@ export function generateComponent(componentName, cmd, cliConfigFile) {
|
|
|
495
502
|
// Will replace the TEMPLATENAME in uppercase SNAKE_CASE
|
|
496
503
|
replace({
|
|
497
504
|
regex: 'TEMPLATENAME',
|
|
498
|
-
replacement: convertors
|
|
505
|
+
replacement: convertors.TEMPLATENAME,
|
|
499
506
|
paths: [componentPath],
|
|
500
507
|
recursive: false,
|
|
501
508
|
silent: true,
|
|
502
509
|
});
|
|
503
510
|
}
|
|
504
511
|
|
|
505
|
-
|
|
506
|
-
} catch (
|
|
507
|
-
|
|
508
|
-
|
|
512
|
+
generatedFiles.push({ filename, status: 'created', path: componentPath });
|
|
513
|
+
} catch (err) {
|
|
514
|
+
generatedFiles.push({ filename, status: 'failed', path: componentPath });
|
|
515
|
+
error(`Failed to create ${filename}`, {
|
|
516
|
+
details: err.message,
|
|
517
|
+
suggestions: [
|
|
518
|
+
'Check that you have write permissions to the target directory',
|
|
519
|
+
'Verify the path is valid',
|
|
520
|
+
],
|
|
521
|
+
});
|
|
509
522
|
}
|
|
510
523
|
}
|
|
511
524
|
}
|
|
512
525
|
});
|
|
513
526
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
console.log(chalk.yellow(`NOTE: The "dry-run" flag means no changes were made.`));
|
|
517
|
-
}
|
|
527
|
+
// Show summary
|
|
528
|
+
fileSummary(generatedFiles, basePath, { dryRun: cmd.dryRun });
|
|
518
529
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import deepKeys from 'deep-keys';
|
|
1
|
+
import fsExtra from 'fs-extra';
|
|
4
2
|
import inquirer from 'inquirer';
|
|
3
|
+
|
|
5
4
|
import merge from 'lodash/merge.js';
|
|
6
|
-
import
|
|
5
|
+
import deepKeys from './deepKeysUtils.js';
|
|
6
|
+
import { blank, error, header, outro, success } from './messagesUtils.js';
|
|
7
7
|
|
|
8
8
|
const { accessSync, constants, outputFileSync, readFileSync } = fsExtra;
|
|
9
9
|
const { prompt } = inquirer;
|
|
@@ -25,19 +25,19 @@ const projectLevelQuestions = [
|
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
type: 'confirm',
|
|
28
|
-
when:
|
|
28
|
+
when: answers => !answers.usesStyledComponents,
|
|
29
29
|
name: 'usesCssModule',
|
|
30
30
|
message: 'Does this project use CSS modules?',
|
|
31
31
|
},
|
|
32
32
|
{
|
|
33
|
-
type: '
|
|
33
|
+
type: 'select',
|
|
34
34
|
name: 'cssPreprocessor',
|
|
35
|
-
when:
|
|
35
|
+
when: answers => !answers.usesStyledComponents,
|
|
36
36
|
message: 'Does this project use a CSS Preprocessor?',
|
|
37
37
|
choices: ['css', 'scss', 'less', 'styl'],
|
|
38
38
|
},
|
|
39
39
|
{
|
|
40
|
-
type: '
|
|
40
|
+
type: 'select',
|
|
41
41
|
name: 'testLibrary',
|
|
42
42
|
message: 'What testing library does your project use?',
|
|
43
43
|
choices: ['Testing Library', 'Enzyme', 'None'],
|
|
@@ -50,23 +50,27 @@ export const componentLevelQuestions = [
|
|
|
50
50
|
{
|
|
51
51
|
type: 'input',
|
|
52
52
|
name: 'component.default.path',
|
|
53
|
-
message:
|
|
53
|
+
message:
|
|
54
|
+
'Set the default path directory to where your components will be generated in?',
|
|
54
55
|
default: () => 'src/components',
|
|
55
56
|
},
|
|
56
57
|
{
|
|
57
58
|
type: 'confirm',
|
|
58
59
|
name: 'component.default.withStyle',
|
|
59
|
-
message:
|
|
60
|
+
message:
|
|
61
|
+
'Would you like to create a corresponding stylesheet file with each component you generate?',
|
|
60
62
|
},
|
|
61
63
|
{
|
|
62
64
|
type: 'confirm',
|
|
63
65
|
name: 'component.default.withTest',
|
|
64
|
-
message:
|
|
66
|
+
message:
|
|
67
|
+
'Would you like to create a corresponding test file with each component you generate?',
|
|
65
68
|
},
|
|
66
69
|
{
|
|
67
70
|
type: 'confirm',
|
|
68
71
|
name: 'component.default.withStory',
|
|
69
|
-
message:
|
|
72
|
+
message:
|
|
73
|
+
'Would you like to create a corresponding story with each component you generate?',
|
|
70
74
|
},
|
|
71
75
|
{
|
|
72
76
|
type: 'confirm',
|
|
@@ -78,97 +82,69 @@ export const componentLevelQuestions = [
|
|
|
78
82
|
|
|
79
83
|
// --- merge all questions together.
|
|
80
84
|
|
|
81
|
-
const grcConfigQuestions = [
|
|
85
|
+
const grcConfigQuestions = [
|
|
86
|
+
...projectLevelQuestions,
|
|
87
|
+
...componentLevelQuestions,
|
|
88
|
+
];
|
|
82
89
|
|
|
83
90
|
async function createCLIConfigFile() {
|
|
84
91
|
try {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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.")
|
|
92
|
+
header(
|
|
93
|
+
'Welcome to Generate React CLI!',
|
|
94
|
+
'Answer a few questions to customize the CLI for your project.',
|
|
93
95
|
);
|
|
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
96
|
|
|
107
97
|
const answers = await prompt(grcConfigQuestions);
|
|
108
98
|
|
|
109
99
|
outputFileSync('generate-react-cli.json', JSON.stringify(answers, null, 2));
|
|
110
100
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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('');
|
|
101
|
+
blank();
|
|
102
|
+
success('Config file created successfully');
|
|
103
|
+
blank();
|
|
104
|
+
outro('You can always update it manually. Happy Hacking!');
|
|
124
105
|
|
|
125
106
|
return answers;
|
|
126
107
|
} catch (e) {
|
|
127
|
-
|
|
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
|
+
});
|
|
128
115
|
return e;
|
|
129
116
|
}
|
|
130
117
|
}
|
|
131
118
|
|
|
132
119
|
async function updateCLIConfigFile(missingConfigQuestions, currentConfigFile) {
|
|
133
120
|
try {
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
)
|
|
121
|
+
header(
|
|
122
|
+
'Generate React CLI has new features!',
|
|
123
|
+
'Answer a few questions to update your config file.',
|
|
144
124
|
);
|
|
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
125
|
|
|
154
126
|
const answers = await prompt(missingConfigQuestions);
|
|
155
127
|
const updatedAnswers = merge({}, currentConfigFile, answers);
|
|
156
128
|
|
|
157
|
-
outputFileSync(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
129
|
+
outputFileSync(
|
|
130
|
+
'generate-react-cli.json',
|
|
131
|
+
JSON.stringify(updatedAnswers, null, 2),
|
|
132
|
+
);
|
|
161
133
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
console.log();
|
|
167
|
-
console.log();
|
|
134
|
+
blank();
|
|
135
|
+
success('Config file updated successfully');
|
|
136
|
+
blank();
|
|
137
|
+
outro('You can always update it manually. Happy Hacking!');
|
|
168
138
|
|
|
169
139
|
return updatedAnswers;
|
|
170
140
|
} catch (e) {
|
|
171
|
-
|
|
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
|
+
});
|
|
172
148
|
return e;
|
|
173
149
|
}
|
|
174
150
|
}
|
|
@@ -183,7 +159,9 @@ export async function getCLIConfigFile() {
|
|
|
183
159
|
|
|
184
160
|
try {
|
|
185
161
|
accessSync('./generate-react-cli.json', constants.R_OK);
|
|
186
|
-
const currentConfigFile = JSON.parse(
|
|
162
|
+
const currentConfigFile = JSON.parse(
|
|
163
|
+
readFileSync('./generate-react-cli.json'),
|
|
164
|
+
);
|
|
187
165
|
|
|
188
166
|
/**
|
|
189
167
|
* Check to see if there's a difference between grcConfigQuestions and the currentConfigFile.
|
|
@@ -191,25 +169,30 @@ export async function getCLIConfigFile() {
|
|
|
191
169
|
*/
|
|
192
170
|
|
|
193
171
|
const missingConfigQuestions = grcConfigQuestions.filter(
|
|
194
|
-
|
|
195
|
-
!deepKeys(currentConfigFile).includes(question.name)
|
|
196
|
-
(question.when ? question.when(currentConfigFile) : true)
|
|
172
|
+
question =>
|
|
173
|
+
!deepKeys(currentConfigFile).includes(question.name)
|
|
174
|
+
&& (question.when ? question.when(currentConfigFile) : true),
|
|
197
175
|
);
|
|
198
176
|
|
|
199
177
|
if (missingConfigQuestions.length) {
|
|
200
|
-
return await updateCLIConfigFile(
|
|
178
|
+
return await updateCLIConfigFile(
|
|
179
|
+
missingConfigQuestions,
|
|
180
|
+
currentConfigFile,
|
|
181
|
+
);
|
|
201
182
|
}
|
|
202
183
|
|
|
203
184
|
return currentConfigFile;
|
|
204
|
-
} catch
|
|
185
|
+
} catch {
|
|
205
186
|
return await createCLIConfigFile();
|
|
206
187
|
}
|
|
207
|
-
} catch
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
188
|
+
} catch {
|
|
189
|
+
error('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
|
+
});
|
|
213
196
|
return process.exit(1);
|
|
214
197
|
}
|
|
215
198
|
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
|
|
3
|
+
// Symbols for consistent visual feedback
|
|
4
|
+
const symbols = {
|
|
5
|
+
success: chalk.green('✓'),
|
|
6
|
+
error: chalk.red('✖'),
|
|
7
|
+
warning: chalk.yellow('⚠'),
|
|
8
|
+
info: chalk.blue('ℹ'),
|
|
9
|
+
arrow: chalk.cyan('→'),
|
|
10
|
+
bullet: chalk.dim('•'),
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// Create a responsive divider that adapts to terminal width
|
|
14
|
+
function divider(color = 'cyan') {
|
|
15
|
+
const width = Math.min(process.stdout.columns || 80, 80);
|
|
16
|
+
return chalk[color]('─'.repeat(width));
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Success message with optional file path
|
|
20
|
+
export function success(message, filePath) {
|
|
21
|
+
if (filePath) {
|
|
22
|
+
console.log(` ${symbols.success} ${chalk.green(message)}`);
|
|
23
|
+
console.log(` ${symbols.arrow} ${chalk.dim(filePath)}`);
|
|
24
|
+
} else {
|
|
25
|
+
console.log(`${symbols.success} ${chalk.green(message)}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Error message with optional details and suggestions
|
|
30
|
+
export function error(message, { details, suggestions } = {}) {
|
|
31
|
+
console.log();
|
|
32
|
+
console.log(`${symbols.error} ${chalk.red.bold('ERROR:')} ${chalk.red(message)}`);
|
|
33
|
+
|
|
34
|
+
if (details) {
|
|
35
|
+
console.log(` ${chalk.dim(details)}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (suggestions && suggestions.length > 0) {
|
|
39
|
+
console.log();
|
|
40
|
+
console.log(chalk.dim(' Try one of:'));
|
|
41
|
+
suggestions.forEach((suggestion) => {
|
|
42
|
+
console.log(` ${symbols.bullet} ${chalk.dim(suggestion)}`);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
console.log();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Warning message
|
|
49
|
+
export function warning(message) {
|
|
50
|
+
console.log(`${symbols.warning} ${chalk.yellow(message)}`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Info message
|
|
54
|
+
export function info(message) {
|
|
55
|
+
console.log(`${symbols.info} ${chalk.cyan(message)}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Header for sections (like config setup intro)
|
|
59
|
+
export function header(title, subtitle) {
|
|
60
|
+
console.log();
|
|
61
|
+
console.log(divider());
|
|
62
|
+
console.log(chalk.cyan.bold(title));
|
|
63
|
+
if (subtitle) {
|
|
64
|
+
console.log(chalk.dim(subtitle));
|
|
65
|
+
}
|
|
66
|
+
console.log(divider());
|
|
67
|
+
console.log();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Summary after file generation
|
|
71
|
+
export function fileSummary(files, basePath, { dryRun = false } = {}) {
|
|
72
|
+
console.log();
|
|
73
|
+
|
|
74
|
+
const createdFiles = files.filter(f => f.status === 'created');
|
|
75
|
+
const skippedFiles = files.filter(f => f.status === 'skipped');
|
|
76
|
+
|
|
77
|
+
if (dryRun) {
|
|
78
|
+
// Dry-run mode: show what would happen
|
|
79
|
+
console.log(`${symbols.info} ${chalk.cyan('Dry-run mode')} ${chalk.dim('- no files were created')}`);
|
|
80
|
+
console.log();
|
|
81
|
+
|
|
82
|
+
if (createdFiles.length > 0) {
|
|
83
|
+
console.log(chalk.dim(`Would create in ${basePath}:`));
|
|
84
|
+
createdFiles.forEach((file, index) => {
|
|
85
|
+
const isLast = index === createdFiles.length - 1 && skippedFiles.length === 0;
|
|
86
|
+
const prefix = isLast ? '└──' : '├──';
|
|
87
|
+
console.log(` ${chalk.dim(prefix)} ${file.filename}`);
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (skippedFiles.length > 0) {
|
|
92
|
+
if (createdFiles.length > 0) {
|
|
93
|
+
console.log();
|
|
94
|
+
}
|
|
95
|
+
console.log(chalk.dim('Already exist (would be skipped):'));
|
|
96
|
+
skippedFiles.forEach((file, index) => {
|
|
97
|
+
const isLast = index === skippedFiles.length - 1;
|
|
98
|
+
const prefix = isLast ? '└──' : '├──';
|
|
99
|
+
console.log(` ${chalk.dim(prefix)} ${symbols.warning} ${chalk.dim(file.filename)}`);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
} else {
|
|
103
|
+
// Actual run: show what happened
|
|
104
|
+
if (createdFiles.length > 0) {
|
|
105
|
+
console.log(`${symbols.success} ${chalk.green(`Created ${createdFiles.length} file${createdFiles.length === 1 ? '' : 's'} in ${basePath}`)}`);
|
|
106
|
+
}
|
|
107
|
+
if (skippedFiles.length > 0) {
|
|
108
|
+
console.log(`${symbols.warning} ${chalk.yellow(`Skipped ${skippedFiles.length} file${skippedFiles.length === 1 ? '' : 's'} (already exist)`)}`);
|
|
109
|
+
}
|
|
110
|
+
console.log();
|
|
111
|
+
|
|
112
|
+
// Show file tree with status icons
|
|
113
|
+
files.forEach((file, index) => {
|
|
114
|
+
const isLast = index === files.length - 1;
|
|
115
|
+
const prefix = isLast ? '└──' : '├──';
|
|
116
|
+
const statusIcon = file.status === 'created'
|
|
117
|
+
? symbols.success
|
|
118
|
+
: file.status === 'skipped'
|
|
119
|
+
? symbols.warning
|
|
120
|
+
: symbols.error;
|
|
121
|
+
console.log(` ${chalk.dim(prefix)} ${statusIcon} ${file.filename}`);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
console.log();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Closing message (Happy Hacking)
|
|
129
|
+
export function outro(message = 'Happy Hacking!') {
|
|
130
|
+
console.log(chalk.cyan(message));
|
|
131
|
+
console.log();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Blank line helper
|
|
135
|
+
export function blank() {
|
|
136
|
+
console.log();
|
|
137
|
+
}
|