@moamc/rn-cli 1.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/LICENSE +21 -0
- package/README.md +154 -0
- package/generators/api.js +155 -0
- package/generators/feature.js +155 -0
- package/generators/hook.js +115 -0
- package/generators/screen.js +351 -0
- package/index.js +100 -0
- package/package.json +43 -0
- package/templates/apiTemplate.js +217 -0
- package/templates/constantsTemplate.js +33 -0
- package/templates/constantsTemplate.js.backup +18 -0
- package/templates/fieldGeneratorTemplate.js +165 -0
- package/templates/helpersTemplate.js +10 -0
- package/templates/hookTemplate.js +132 -0
- package/templates/hookTemplate.js.backup +113 -0
- package/templates/queryTemplate.js +23 -0
- package/templates/screenTemplate.js +165 -0
- package/templates/screenTemplate.js.backup +147 -0
- package/templates/stylesTemplate.js +52 -0
- package/templates/stylesTemplate.js.backup +88 -0
- package/templates/validationTemplate.js +122 -0
- package/templates/validationTemplate.js.backup +37 -0
- package/utils/fileUtils.js +116 -0
- package/utils/navigationUtils.js +74 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 MOAMC Engineering Team
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# @moamc/rn-cli
|
|
2
|
+
|
|
3
|
+
Enterprise-grade Code Generation CLI for React Native Applications
|
|
4
|
+
|
|
5
|
+
## š Features
|
|
6
|
+
|
|
7
|
+
- ā
Generate complete feature modules with screens, hooks, and API integration
|
|
8
|
+
- ā
Add screens to existing features with form support
|
|
9
|
+
- ā
Create API services with GET/POST methods
|
|
10
|
+
- ā
Generate React Query hooks with validation
|
|
11
|
+
- ā
Support for multiple field types (text, number, email, date, dropdown, etc.)
|
|
12
|
+
- ā
Automatic validation generation
|
|
13
|
+
- ā
TypeScript-ready templates
|
|
14
|
+
|
|
15
|
+
## š¦ Installation
|
|
16
|
+
|
|
17
|
+
### Global Installation (Recommended)
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install -g @moamc/rn-cli
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Project-specific Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm install --save-dev @moamc/rn-cli
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## šÆ Usage
|
|
30
|
+
|
|
31
|
+
After installation, run the CLI from your React Native project root:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
rn-cli
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### Available Commands
|
|
38
|
+
|
|
39
|
+
#### Generate Feature
|
|
40
|
+
```bash
|
|
41
|
+
rn-cli generate:feature
|
|
42
|
+
# or
|
|
43
|
+
rn-cli g:f
|
|
44
|
+
```
|
|
45
|
+
Creates a complete feature module with screen, API service, and hooks.
|
|
46
|
+
|
|
47
|
+
#### Generate Screen
|
|
48
|
+
```bash
|
|
49
|
+
rn-cli generate:screen
|
|
50
|
+
# or
|
|
51
|
+
rn-cli g:s
|
|
52
|
+
```
|
|
53
|
+
Adds a new screen to an existing feature with optional form support.
|
|
54
|
+
|
|
55
|
+
#### Generate API
|
|
56
|
+
```bash
|
|
57
|
+
rn-cli generate:api
|
|
58
|
+
# or
|
|
59
|
+
rn-cli g:a
|
|
60
|
+
```
|
|
61
|
+
Creates API service methods with React Query integration.
|
|
62
|
+
|
|
63
|
+
#### Generate Hook
|
|
64
|
+
```bash
|
|
65
|
+
rn-cli generate:hook
|
|
66
|
+
# or
|
|
67
|
+
rn-cli g:h
|
|
68
|
+
```
|
|
69
|
+
Generates data-fetching hooks with validation.
|
|
70
|
+
|
|
71
|
+
## š Project Structure Requirements
|
|
72
|
+
|
|
73
|
+
Your React Native project should follow this structure:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
project-root/
|
|
77
|
+
āāā screens/
|
|
78
|
+
ā āāā FeatureName/
|
|
79
|
+
ā āāā ScreenName/
|
|
80
|
+
ā āāā ScreenName.js
|
|
81
|
+
ā āāā hooks/
|
|
82
|
+
ā āāā components/
|
|
83
|
+
ā āāā validation.js
|
|
84
|
+
ā āāā constants.js
|
|
85
|
+
ā āāā helpers.js
|
|
86
|
+
āāā services/
|
|
87
|
+
āāā queries/
|
|
88
|
+
āāā navigations/
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## šØ Form Field Types
|
|
92
|
+
|
|
93
|
+
- **Text** - Standard text input
|
|
94
|
+
- **Number** - Numeric input
|
|
95
|
+
- **Email** - Email address with validation
|
|
96
|
+
- **Date** - Date picker
|
|
97
|
+
- **Dropdown** - Select from options
|
|
98
|
+
- **Radio** - Single choice
|
|
99
|
+
- **Checkbox** - Multiple choices
|
|
100
|
+
- **File** - File upload
|
|
101
|
+
|
|
102
|
+
## š API Integration
|
|
103
|
+
|
|
104
|
+
The CLI supports both GET and POST APIs:
|
|
105
|
+
|
|
106
|
+
1. **GET API** - Fetch data with React Query
|
|
107
|
+
2. **POST API** - Submit forms with useMutation
|
|
108
|
+
3. **Automatic cache invalidation** after successful POST
|
|
109
|
+
|
|
110
|
+
## ⨠Features
|
|
111
|
+
|
|
112
|
+
### Automatic Validation
|
|
113
|
+
- Real-time field validation
|
|
114
|
+
- Submit-time validation
|
|
115
|
+
- Custom validation rules per field type
|
|
116
|
+
|
|
117
|
+
### React Query Integration
|
|
118
|
+
- Automatic query key generation
|
|
119
|
+
- Cache management
|
|
120
|
+
- Error handling
|
|
121
|
+
- Loading states
|
|
122
|
+
|
|
123
|
+
### Form Support
|
|
124
|
+
- Multiple field types
|
|
125
|
+
- Field width control (full/half)
|
|
126
|
+
- Required field validation
|
|
127
|
+
- Dropdown options
|
|
128
|
+
|
|
129
|
+
## š ļø Development
|
|
130
|
+
|
|
131
|
+
To use in development mode:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
cd cli-tool
|
|
135
|
+
npm link
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Then in your project:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
rn-cli
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## š License
|
|
145
|
+
|
|
146
|
+
MIT
|
|
147
|
+
|
|
148
|
+
## š„ Author
|
|
149
|
+
|
|
150
|
+
MOAMC Engineering Team
|
|
151
|
+
|
|
152
|
+
## š¤ Contributing
|
|
153
|
+
|
|
154
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
const inquirer = require('inquirer');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fs = require('fs-extra');
|
|
5
|
+
const {
|
|
6
|
+
getProjectPath,
|
|
7
|
+
readFile,
|
|
8
|
+
writeFile,
|
|
9
|
+
logSuccess,
|
|
10
|
+
logInfo,
|
|
11
|
+
logWarning,
|
|
12
|
+
toCamelCase,
|
|
13
|
+
} = require('../utils/fileUtils');
|
|
14
|
+
|
|
15
|
+
const generateAPI = async () => {
|
|
16
|
+
console.log(chalk.cyan.bold('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā®'));
|
|
17
|
+
console.log(chalk.cyan.bold('ā') + chalk.white.bold(' š API METHOD GENERATOR ') + chalk.cyan.bold('ā'));
|
|
18
|
+
console.log(chalk.cyan.bold('ā°āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāÆ'));
|
|
19
|
+
console.log(chalk.gray(' Create a new API service method\n'));
|
|
20
|
+
|
|
21
|
+
const servicesPath = getProjectPath('services');
|
|
22
|
+
const services = fs.readdirSync(servicesPath).filter(file => {
|
|
23
|
+
const fullPath = path.join(servicesPath, file);
|
|
24
|
+
return fs.statSync(fullPath).isDirectory() && !['common'].includes(file);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const answers = await inquirer.prompt([
|
|
28
|
+
{
|
|
29
|
+
type: 'list',
|
|
30
|
+
name: 'serviceName',
|
|
31
|
+
message: chalk.yellow('ā Select the target service:'),
|
|
32
|
+
choices: [...services, chalk.green('+ Create new service')],
|
|
33
|
+
prefix: chalk.green('ā'),
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
type: 'input',
|
|
37
|
+
name: 'newServiceName',
|
|
38
|
+
message: chalk.yellow('ā Enter new service name:'),
|
|
39
|
+
when: (answers) => answers.serviceName.includes('Create new service'),
|
|
40
|
+
validate: (input) => input.trim() !== '' || chalk.red('ā ļø Service name is required'),
|
|
41
|
+
prefix: chalk.green('ā'),
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
type: 'input',
|
|
45
|
+
name: 'methodName',
|
|
46
|
+
message: chalk.yellow('ā Enter method name') + chalk.gray(' (e.g., getUserProfile, saveBankDetails):'),
|
|
47
|
+
validate: (input) => input.trim() !== '' || chalk.red('ā ļø Method name is required'),
|
|
48
|
+
prefix: chalk.green('ā'),
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
type: 'input',
|
|
52
|
+
name: 'endpoint',
|
|
53
|
+
message: chalk.yellow('ā Enter API endpoint') + chalk.gray(' (e.g., /api/user/profile):'),
|
|
54
|
+
validate: (input) => input.trim() !== '' || chalk.red('ā ļø Endpoint is required'),
|
|
55
|
+
prefix: chalk.green('ā'),
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
type: 'list',
|
|
59
|
+
name: 'methodType',
|
|
60
|
+
message: chalk.yellow('ā Select HTTP method:'),
|
|
61
|
+
choices: [
|
|
62
|
+
{ name: chalk.green('POST') + chalk.gray(' - Create or update data'), value: 'POST' },
|
|
63
|
+
{ name: chalk.blue('GET') + chalk.gray(' - Retrieve data'), value: 'GET' },
|
|
64
|
+
{ name: chalk.yellow('PUT') + chalk.gray(' - Update existing data'), value: 'PUT' },
|
|
65
|
+
{ name: chalk.red('DELETE') + chalk.gray(' - Remove data'), value: 'DELETE' },
|
|
66
|
+
],
|
|
67
|
+
default: 'POST',
|
|
68
|
+
prefix: chalk.green('ā'),
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
type: 'confirm',
|
|
72
|
+
name: 'requiresAuth',
|
|
73
|
+
message: chalk.yellow('ā Requires authentication?'),
|
|
74
|
+
default: true,
|
|
75
|
+
prefix: chalk.green('ā'),
|
|
76
|
+
},
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
const serviceName = answers.serviceName.includes('Create new service')
|
|
80
|
+
? answers.newServiceName.toLowerCase()
|
|
81
|
+
: answers.serviceName;
|
|
82
|
+
|
|
83
|
+
const serviceFile = getProjectPath('services', serviceName, `${serviceName}.js`);
|
|
84
|
+
const methodName = toCamelCase(answers.methodName);
|
|
85
|
+
|
|
86
|
+
let newMethod = '';
|
|
87
|
+
if (answers.methodType === 'GET') {
|
|
88
|
+
newMethod = `
|
|
89
|
+
function ${methodName}(data) {
|
|
90
|
+
\tconst response = coreAPI.makeAuthenticatedGetCall(
|
|
91
|
+
\t\t'${answers.endpoint}',
|
|
92
|
+
\t\t${answers.requiresAuth}
|
|
93
|
+
\t);
|
|
94
|
+
\treturn handleResponse(response);
|
|
95
|
+
}`;
|
|
96
|
+
} else {
|
|
97
|
+
newMethod = `
|
|
98
|
+
function ${methodName}(data) {
|
|
99
|
+
\tconst response = coreAPI.makeAuthenticatedPostCall(
|
|
100
|
+
\t\t'${answers.endpoint}',
|
|
101
|
+
\t\tdata,
|
|
102
|
+
\t\t${answers.requiresAuth}
|
|
103
|
+
\t);
|
|
104
|
+
\treturn handleResponse(response);
|
|
105
|
+
}`;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (fs.existsSync(serviceFile)) {
|
|
109
|
+
let content = readFile(serviceFile);
|
|
110
|
+
|
|
111
|
+
// Find the export statement
|
|
112
|
+
const exportMatch = content.match(/export const \w+Service = \{([^}]*)\};/s);
|
|
113
|
+
|
|
114
|
+
if (exportMatch) {
|
|
115
|
+
const exportContent = exportMatch[1].trim();
|
|
116
|
+
const methods = exportContent.split(',').map(m => m.trim()).filter(m => m);
|
|
117
|
+
|
|
118
|
+
// Add new method before the export
|
|
119
|
+
const insertPosition = content.lastIndexOf('export const');
|
|
120
|
+
content = content.slice(0, insertPosition) + newMethod + '\n\n' + content.slice(insertPosition);
|
|
121
|
+
|
|
122
|
+
// Update export
|
|
123
|
+
methods.push(methodName);
|
|
124
|
+
const newExport = `export const ${serviceName}Service = {\n\t${methods.join(',\n\t')}\n};`;
|
|
125
|
+
content = content.replace(/export const \w+Service = \{[^}]*\};/s, newExport);
|
|
126
|
+
|
|
127
|
+
writeFile(serviceFile, content);
|
|
128
|
+
logSuccess(`Added method "${methodName}" to ${serviceName} service`);
|
|
129
|
+
} else {
|
|
130
|
+
logWarning('Could not find export statement. Please add the method manually.');
|
|
131
|
+
}
|
|
132
|
+
} else {
|
|
133
|
+
// Create new service file
|
|
134
|
+
const content = `import { coreAPI } from '../common/baseConfig';
|
|
135
|
+
import handleResponse from '../handler';
|
|
136
|
+
${newMethod}
|
|
137
|
+
|
|
138
|
+
export const ${serviceName}Service = {
|
|
139
|
+
\t${methodName}
|
|
140
|
+
};
|
|
141
|
+
`;
|
|
142
|
+
writeFile(serviceFile, content);
|
|
143
|
+
logSuccess(`Created new service: ${serviceName}`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
logSuccess(`\n⨠API method "${methodName}" added successfully!`);
|
|
147
|
+
console.log(chalk.cyan('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā®'));
|
|
148
|
+
console.log(chalk.cyan('ā') + chalk.white.bold(' š NEXT STEPS ') + chalk.cyan('ā'));
|
|
149
|
+
console.log(chalk.cyan('ā°āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāÆ'));
|
|
150
|
+
console.log(chalk.white(' 1. ') + chalk.green('ā') + chalk.gray(' Create query options in /queries if using React Query'));
|
|
151
|
+
console.log(chalk.white(' 2. ') + chalk.green('ā') + chalk.gray(' Use the method in your custom hook'));
|
|
152
|
+
console.log(chalk.cyan('\n' + 'ā'.repeat(65) + '\n'));
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
module.exports = { generateAPI };
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
const inquirer = require('inquirer');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const {
|
|
5
|
+
getProjectPath,
|
|
6
|
+
writeFile,
|
|
7
|
+
logSuccess,
|
|
8
|
+
logInfo,
|
|
9
|
+
toPascalCase,
|
|
10
|
+
confirmOverwrite,
|
|
11
|
+
fileExists,
|
|
12
|
+
} = require('../utils/fileUtils');
|
|
13
|
+
const { generateScreenTemplate } = require('../templates/screenTemplate');
|
|
14
|
+
const { generateHookTemplate } = require('../templates/hookTemplate');
|
|
15
|
+
const { generateValidationTemplate } = require('../templates/validationTemplate');
|
|
16
|
+
const { generateStylesTemplate } = require('../templates/stylesTemplate');
|
|
17
|
+
const { generateConstantsTemplate } = require('../templates/constantsTemplate');
|
|
18
|
+
const { generateHelpersTemplate } = require('../templates/helpersTemplate');
|
|
19
|
+
const { generateAPIServiceTemplate } = require('../templates/apiTemplate');
|
|
20
|
+
|
|
21
|
+
const generateFeature = async () => {
|
|
22
|
+
console.log(chalk.cyan.bold('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā®'));
|
|
23
|
+
console.log(chalk.cyan.bold('ā') + chalk.white.bold(' šÆ FEATURE CREATION WIZARD ') + chalk.cyan.bold('ā'));
|
|
24
|
+
console.log(chalk.cyan.bold('ā°āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāÆ'));
|
|
25
|
+
console.log(chalk.gray(' Let\'s build a new feature module for your application\n'));
|
|
26
|
+
|
|
27
|
+
const answers = await inquirer.prompt([
|
|
28
|
+
{
|
|
29
|
+
type: 'input',
|
|
30
|
+
name: 'featureName',
|
|
31
|
+
message: chalk.yellow('ā Enter feature name') + chalk.gray(' (e.g., UserProfile, BankManagement):'),
|
|
32
|
+
validate: (input) => input.trim() !== '' || chalk.red('ā ļø Feature name is required'),
|
|
33
|
+
prefix: chalk.green('ā'),
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
type: 'input',
|
|
37
|
+
name: 'screenName',
|
|
38
|
+
message: chalk.yellow('ā Enter main screen name') + chalk.gray(' (e.g., ProfileDetails, AddBank):'),
|
|
39
|
+
validate: (input) => input.trim() !== '' || chalk.red('ā ļø Screen name is required'),
|
|
40
|
+
prefix: chalk.green('ā'),
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
type: 'confirm',
|
|
44
|
+
name: 'hasForm',
|
|
45
|
+
message: chalk.yellow('ā Does this screen include a form?'),
|
|
46
|
+
default: false,
|
|
47
|
+
prefix: chalk.green('ā'),
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
type: 'confirm',
|
|
51
|
+
name: 'createAPI',
|
|
52
|
+
message: chalk.yellow('ā Would you like to create an API service?'),
|
|
53
|
+
default: true,
|
|
54
|
+
prefix: chalk.green('ā'),
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
type: 'input',
|
|
58
|
+
name: 'apiMethodName',
|
|
59
|
+
message: chalk.yellow('ā Enter API method name') + chalk.gray(' (e.g., getUserProfile, saveBankDetails):'),
|
|
60
|
+
when: (answers) => answers.createAPI,
|
|
61
|
+
validate: (input) => input.trim() !== '' || chalk.red('ā ļø API method name is required'),
|
|
62
|
+
prefix: chalk.green('ā'),
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
type: 'input',
|
|
66
|
+
name: 'apiEndpoint',
|
|
67
|
+
message: chalk.yellow('ā Enter API endpoint') + chalk.gray(' (e.g., /api/user/profile):'),
|
|
68
|
+
when: (answers) => answers.createAPI,
|
|
69
|
+
validate: (input) => input.trim() !== '' || chalk.red('ā ļø API endpoint is required'),
|
|
70
|
+
prefix: chalk.green('ā'),
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
type: 'list',
|
|
74
|
+
name: 'apiType',
|
|
75
|
+
message: chalk.yellow('ā Select API method type:'),
|
|
76
|
+
choices: [
|
|
77
|
+
{ name: chalk.green('POST') + chalk.gray(' - Create or update data'), value: 'POST' },
|
|
78
|
+
{ name: chalk.blue('GET') + chalk.gray(' - Retrieve data'), value: 'GET' },
|
|
79
|
+
],
|
|
80
|
+
when: (answers) => answers.createAPI,
|
|
81
|
+
default: 'POST',
|
|
82
|
+
prefix: chalk.green('ā'),
|
|
83
|
+
},
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
const featureName = toPascalCase(answers.featureName);
|
|
87
|
+
const screenName = toPascalCase(answers.screenName);
|
|
88
|
+
const screenPath = getProjectPath('screens', featureName, screenName);
|
|
89
|
+
|
|
90
|
+
// Create screen files
|
|
91
|
+
const screenFile = path.join(screenPath, `${screenName}.js`);
|
|
92
|
+
const hookFile = path.join(screenPath, `use${screenName}.js`);
|
|
93
|
+
const stylesFile = path.join(screenPath, 'styles.js');
|
|
94
|
+
const constantsFile = path.join(screenPath, 'constants.js');
|
|
95
|
+
const helpersFile = path.join(screenPath, 'helpers.js');
|
|
96
|
+
|
|
97
|
+
const files = [
|
|
98
|
+
{ path: screenFile, content: generateScreenTemplate(screenName, featureName, answers.hasForm, formFields) },
|
|
99
|
+
{ path: hookFile, content: generateHookTemplate(screenName, answers.hasForm, formFields) },
|
|
100
|
+
{ path: stylesFile, content: generateStylesTemplate() },
|
|
101
|
+
{ path: constantsFile, content: generateConstantsTemplate(formFields) },
|
|
102
|
+
{ path: helpersFile, content: generateHelpersTemplate() },
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
if (answers.hasForm) {
|
|
106
|
+
const validationFile = path.join(screenPath, 'validation.js');
|
|
107
|
+
files.push({ path: validationFile, content: generateValidationTemplate(formFields) });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
for (const file of files) {
|
|
111
|
+
if (fileExists(file.path)) {
|
|
112
|
+
const shouldOverwrite = await confirmOverwrite(inquirer, file.path);
|
|
113
|
+
if (!shouldOverwrite) {
|
|
114
|
+
logInfo(`Skipped: ${path.basename(file.path)}`);
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
writeFile(file.path, file.content);
|
|
119
|
+
logSuccess(`Created: ${path.relative(getProjectPath(), file.path)}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Create API service if requested
|
|
123
|
+
if (answers.createAPI) {
|
|
124
|
+
const serviceName = featureName.toLowerCase();
|
|
125
|
+
const servicePath = getProjectPath('services', serviceName);
|
|
126
|
+
const serviceFile = path.join(servicePath, `${serviceName}.js`);
|
|
127
|
+
|
|
128
|
+
const methods = [{
|
|
129
|
+
name: answers.apiMethodName,
|
|
130
|
+
endpoint: answers.apiEndpoint,
|
|
131
|
+
type: answers.apiType,
|
|
132
|
+
}];
|
|
133
|
+
|
|
134
|
+
const serviceContent = generateAPIServiceTemplate(featureName, methods);
|
|
135
|
+
|
|
136
|
+
if (fileExists(serviceFile)) {
|
|
137
|
+
logInfo(`Service file already exists: ${serviceFile}`);
|
|
138
|
+
logInfo('Please manually add the new method to the existing service file.');
|
|
139
|
+
} else {
|
|
140
|
+
writeFile(serviceFile, serviceContent);
|
|
141
|
+
logSuccess(`Created: ${path.relative(getProjectPath(), serviceFile)}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
logSuccess(`\n⨠Feature "${featureName}" created successfully!`);
|
|
146
|
+
console.log(chalk.cyan('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā®'));
|
|
147
|
+
console.log(chalk.cyan('ā') + chalk.white.bold(' š NEXT STEPS ') + chalk.cyan('ā'));
|
|
148
|
+
console.log(chalk.cyan('ā°āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāÆ'));
|
|
149
|
+
console.log(chalk.white(' 1. ') + chalk.green('ā') + chalk.gray(' Add the screen to Navigation.js'));
|
|
150
|
+
console.log(chalk.white(' 2. ') + chalk.green('ā') + chalk.gray(' Update the API service if needed'));
|
|
151
|
+
console.log(chalk.white(' 3. ') + chalk.green('ā') + chalk.gray(' Create query options in /queries if using React Query'));
|
|
152
|
+
console.log(chalk.cyan('\n' + 'ā'.repeat(65) + '\n'));
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
module.exports = { generateFeature };
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
const inquirer = require('inquirer');
|
|
2
|
+
const chalk = require('chalk');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fs = require('fs-extra');
|
|
5
|
+
const {
|
|
6
|
+
getProjectPath,
|
|
7
|
+
writeFile,
|
|
8
|
+
logSuccess,
|
|
9
|
+
logInfo,
|
|
10
|
+
toPascalCase,
|
|
11
|
+
confirmOverwrite,
|
|
12
|
+
fileExists,
|
|
13
|
+
} = require('../utils/fileUtils');
|
|
14
|
+
const { generateHookTemplate } = require('../templates/hookTemplate');
|
|
15
|
+
|
|
16
|
+
const generateHook = async () => {
|
|
17
|
+
console.log(chalk.cyan.bold('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā®'));
|
|
18
|
+
console.log(chalk.cyan.bold('ā') + chalk.white.bold(' šŖ CUSTOM HOOK GENERATOR ') + chalk.cyan.bold('ā'));
|
|
19
|
+
console.log(chalk.cyan.bold('ā°āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāÆ'));
|
|
20
|
+
console.log(chalk.gray(' Generate a reusable React hook\n'));
|
|
21
|
+
|
|
22
|
+
const screensPath = getProjectPath('screens');
|
|
23
|
+
const features = fs.readdirSync(screensPath).filter(file => {
|
|
24
|
+
const fullPath = path.join(screensPath, file);
|
|
25
|
+
return fs.statSync(fullPath).isDirectory();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const answers = await inquirer.prompt([
|
|
29
|
+
{
|
|
30
|
+
type: 'list',
|
|
31
|
+
name: 'location',
|
|
32
|
+
message: chalk.yellow('ā Where should the hook be created?'),
|
|
33
|
+
choices: [
|
|
34
|
+
{ name: chalk.blue('š± In a screen folder'), value: 'In a screen folder' },
|
|
35
|
+
{ name: chalk.blue('š¦ In helpers folder'), value: 'In helpers folder' },
|
|
36
|
+
{ name: chalk.blue('š Custom path'), value: 'Custom path' },
|
|
37
|
+
],
|
|
38
|
+
prefix: chalk.green('ā'),
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
type: 'list',
|
|
42
|
+
name: 'featureName',
|
|
43
|
+
message: chalk.yellow('ā Select the feature:'),
|
|
44
|
+
choices: features,
|
|
45
|
+
when: (answers) => answers.location === 'In a screen folder',
|
|
46
|
+
prefix: chalk.green('ā'),
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
type: 'input',
|
|
50
|
+
name: 'customPath',
|
|
51
|
+
message: chalk.yellow('ā Enter custom path') + chalk.gray(' (relative to project root):'),
|
|
52
|
+
when: (answers) => answers.location === 'Custom path',
|
|
53
|
+
validate: (input) => input.trim() !== '' || chalk.red('ā ļø Path is required'),
|
|
54
|
+
prefix: chalk.green('ā'),
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
type: 'input',
|
|
58
|
+
name: 'hookName',
|
|
59
|
+
message: chalk.yellow('ā Enter hook name') + chalk.gray(' (e.g., useFetchUserData, useFormSubmit):'),
|
|
60
|
+
validate: (input) => {
|
|
61
|
+
if (input.trim() === '') return chalk.red('ā ļø Hook name is required');
|
|
62
|
+
if (!input.startsWith('use')) return chalk.red('ā ļø Hook name should start with "use"');
|
|
63
|
+
return true;
|
|
64
|
+
},
|
|
65
|
+
prefix: chalk.green('ā'),
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
type: 'list',
|
|
69
|
+
name: 'hookType',
|
|
70
|
+
message: chalk.yellow('ā Select hook type:'),
|
|
71
|
+
choices: [
|
|
72
|
+
{ name: chalk.blue('š Data fetching (useQuery)'), value: 'query' },
|
|
73
|
+
{ name: chalk.blue('š Form handling (Formik)'), value: 'form' },
|
|
74
|
+
{ name: chalk.blue('āļø Custom logic'), value: 'custom' },
|
|
75
|
+
],
|
|
76
|
+
prefix: chalk.green('ā'),
|
|
77
|
+
},
|
|
78
|
+
]);
|
|
79
|
+
|
|
80
|
+
let hookPath;
|
|
81
|
+
if (answers.location === 'In a screen folder') {
|
|
82
|
+
hookPath = getProjectPath('screens', answers.featureName);
|
|
83
|
+
} else if (answers.location === 'In helpers folder') {
|
|
84
|
+
hookPath = getProjectPath('helpers');
|
|
85
|
+
} else {
|
|
86
|
+
hookPath = getProjectPath(answers.customPath);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const hookName = toPascalCase(answers.hookName.replace(/^use/, ''));
|
|
90
|
+
const hookFile = path.join(hookPath, `use${hookName}.js`);
|
|
91
|
+
|
|
92
|
+
if (fileExists(hookFile)) {
|
|
93
|
+
const shouldOverwrite = await confirmOverwrite(inquirer, hookFile);
|
|
94
|
+
if (!shouldOverwrite) {
|
|
95
|
+
logInfo('Operation cancelled');
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const hasForm = answers.hookType === 'form';
|
|
101
|
+
const content = generateHookTemplate(hookName, hasForm);
|
|
102
|
+
|
|
103
|
+
writeFile(hookFile, content);
|
|
104
|
+
logSuccess(`Created: ${path.relative(getProjectPath(), hookFile)}`);
|
|
105
|
+
|
|
106
|
+
logSuccess(`\n⨠Hook "use${hookName}" created successfully!`);
|
|
107
|
+
console.log(chalk.cyan('\nāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā®'));
|
|
108
|
+
console.log(chalk.cyan('ā') + chalk.white.bold(' š NEXT STEPS ') + chalk.cyan('ā'));
|
|
109
|
+
console.log(chalk.cyan('ā°āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāÆ'));
|
|
110
|
+
console.log(chalk.white(' 1. ') + chalk.green('ā') + chalk.gray(' Implement the hook logic'));
|
|
111
|
+
console.log(chalk.white(' 2. ') + chalk.green('ā') + chalk.gray(' Import and use in your component'));
|
|
112
|
+
console.log(chalk.cyan('\n' + 'ā'.repeat(65) + '\n'));
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
module.exports = { generateHook };
|