speedrun-cli 2.7.7 β 2.7.8
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/cli.js +16 -0
- package/package.json +1 -1
- package/src/fieldManager.js +192 -0
package/bin/cli.js
CHANGED
|
@@ -28,6 +28,7 @@ const {
|
|
|
28
28
|
handlePostSetup,
|
|
29
29
|
printManualInstructions,
|
|
30
30
|
generateModule,
|
|
31
|
+
manageFields,
|
|
31
32
|
} = require(path.join(packageRoot, 'src'));
|
|
32
33
|
|
|
33
34
|
program
|
|
@@ -50,6 +51,21 @@ program
|
|
|
50
51
|
}
|
|
51
52
|
});
|
|
52
53
|
|
|
54
|
+
// ==================== FIELD MANAGEMENT COMMAND ====================
|
|
55
|
+
program
|
|
56
|
+
.command('field [module-name]')
|
|
57
|
+
.alias('f')
|
|
58
|
+
.description('Manage (add, edit, delete) fields of an existing module')
|
|
59
|
+
.action(async (moduleName) => {
|
|
60
|
+
try {
|
|
61
|
+
console.log(chalk.cyan(`\nπ±π€―π€― speedrun-cli v${CLI_VERSION} field manager π€§π₯Άπ₯Άπ₯Ά (real)\n`));
|
|
62
|
+
await manageFields(moduleName, process.cwd());
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error(chalk.red('\nβ Field management failed:'));
|
|
65
|
+
console.error(error);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
53
69
|
// ==================== MAIN SCAFFOLD COMMAND (DEFAULT) ====================
|
|
54
70
|
program
|
|
55
71
|
.command('create [app-name]', { isDefault: true })
|
package/package.json
CHANGED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interactive Field Manager for existing modules
|
|
3
|
+
* @module fieldManager
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const inquirer = require('inquirer');
|
|
7
|
+
const fs = require('fs-extra');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
|
|
11
|
+
// Helper untuk membaca field dari file DTO response yang sudah ada
|
|
12
|
+
async function parseExistingFields(moduleDir, kebabName) {
|
|
13
|
+
const dtoPath = path.join(moduleDir, 'dto', `${kebabName}.dto.ts`);
|
|
14
|
+
if (!(await fs.pathExists(dtoPath))) return [];
|
|
15
|
+
|
|
16
|
+
const content = await fs.readFile(dtoPath, 'utf8');
|
|
17
|
+
const fields = [];
|
|
18
|
+
|
|
19
|
+
// RegEx untuk mengekstrak property DTO NestJS (namaField?: type)
|
|
20
|
+
const regex = /^\s*([a-zA-Z0-9_]+)(\?)?:\s*([a-zA-Z]+);/gm;
|
|
21
|
+
let match;
|
|
22
|
+
|
|
23
|
+
const ignoredFields = ['id', 'status', 'createdAt', 'updatedAt', 'deletedAt'];
|
|
24
|
+
|
|
25
|
+
while ((match = regex.exec(content)) !== null) {
|
|
26
|
+
const [, name, optional, tsType] = match;
|
|
27
|
+
if (!ignoredFields.includes(name)) {
|
|
28
|
+
let type = 'String';
|
|
29
|
+
if (tsType === 'number') type = 'Number';
|
|
30
|
+
if (tsType === 'boolean') type = 'Boolean';
|
|
31
|
+
if (tsType === 'Date') type = 'Date';
|
|
32
|
+
|
|
33
|
+
fields.push({
|
|
34
|
+
name,
|
|
35
|
+
type,
|
|
36
|
+
isOptional: !!optional,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return fields;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Main Interactive Field Manager Entry Point
|
|
46
|
+
*/
|
|
47
|
+
async function manageFields(providedModuleName, targetDir = process.cwd()) {
|
|
48
|
+
try {
|
|
49
|
+
let moduleName = providedModuleName;
|
|
50
|
+
|
|
51
|
+
if (!moduleName) {
|
|
52
|
+
const nameAnswer = await inquirer.prompt([{
|
|
53
|
+
type: 'input',
|
|
54
|
+
name: 'moduleName',
|
|
55
|
+
message: 'Which module do you want to manage fields for? (e.g., orders, products)',
|
|
56
|
+
validate: (input) => (input && input.trim() ? true : 'Module name is required'),
|
|
57
|
+
}]);
|
|
58
|
+
moduleName = nameAnswer.moduleName.trim();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const kebabName = moduleName.toLowerCase();
|
|
62
|
+
const moduleDir = path.join(targetDir, 'src', 'modules', kebabName);
|
|
63
|
+
|
|
64
|
+
if (!(await fs.pathExists(moduleDir))) {
|
|
65
|
+
console.error(chalk.red(`\nβ Module "${kebabName}" not found at src/modules/${kebabName}`));
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Load existing fields
|
|
70
|
+
let fields = await parseExistingFields(moduleDir, kebabName);
|
|
71
|
+
console.log(chalk.cyan(`\nπ¦ Managing fields for module: ${chalk.bold(kebabName)}`));
|
|
72
|
+
|
|
73
|
+
let managing = true;
|
|
74
|
+
|
|
75
|
+
const promptSingleField = async (initialValues = {}) => {
|
|
76
|
+
return await inquirer.prompt([
|
|
77
|
+
{
|
|
78
|
+
type: 'input',
|
|
79
|
+
name: 'fieldName',
|
|
80
|
+
message: 'Enter field name:',
|
|
81
|
+
default: initialValues.name,
|
|
82
|
+
validate: (input) => {
|
|
83
|
+
if (!input || !input.trim()) return 'Field name is required';
|
|
84
|
+
if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(input.trim())) {
|
|
85
|
+
return 'Field name must be a valid identifier';
|
|
86
|
+
}
|
|
87
|
+
return true;
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
type: 'list',
|
|
92
|
+
name: 'fieldType',
|
|
93
|
+
message: (answers) => `Select field type for '${answers.fieldName}':`,
|
|
94
|
+
choices: ['String', 'Number', 'Boolean', 'Date'],
|
|
95
|
+
default: initialValues.type || 'String',
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
type: 'confirm',
|
|
99
|
+
name: 'isOptional',
|
|
100
|
+
message: (answers) => `Is '${answers.fieldName}' optional?`,
|
|
101
|
+
default: initialValues.isOptional !== undefined ? initialValues.isOptional : false,
|
|
102
|
+
},
|
|
103
|
+
]);
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
while (managing) {
|
|
107
|
+
console.log(
|
|
108
|
+
chalk.gray(`\nCurrent Fields (${fields.length}): `) +
|
|
109
|
+
(fields.length > 0
|
|
110
|
+
? fields.map(f => chalk.yellow(`${f.name} (${f.type}${f.isOptional ? '?' : ''})`)).join(', ')
|
|
111
|
+
: chalk.gray('None'))
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
const { action } = await inquirer.prompt([{
|
|
115
|
+
type: 'list',
|
|
116
|
+
name: 'action',
|
|
117
|
+
message: 'What do you want to do?',
|
|
118
|
+
choices: [
|
|
119
|
+
{ name: 'β Add new field', value: 'add' },
|
|
120
|
+
{ name: 'βοΈ Edit an existing field', value: 'edit' },
|
|
121
|
+
{ name: 'ποΈ Delete a field', value: 'delete' },
|
|
122
|
+
{ name: 'πΎ Save changes and update files', value: 'save' },
|
|
123
|
+
{ name: 'β Cancel', value: 'cancel' },
|
|
124
|
+
],
|
|
125
|
+
}]);
|
|
126
|
+
|
|
127
|
+
if (action === 'add') {
|
|
128
|
+
const newField = await promptSingleField();
|
|
129
|
+
fields.push({
|
|
130
|
+
name: newField.fieldName.trim(),
|
|
131
|
+
type: newField.fieldType,
|
|
132
|
+
isOptional: newField.isOptional,
|
|
133
|
+
});
|
|
134
|
+
} else if (action === 'edit') {
|
|
135
|
+
if (fields.length === 0) {
|
|
136
|
+
console.log(chalk.yellow('β οΈ No fields to edit.'));
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const { fieldToEditIndex } = await inquirer.prompt([{
|
|
141
|
+
type: 'list',
|
|
142
|
+
name: 'fieldToEditIndex',
|
|
143
|
+
message: 'Select field to edit:',
|
|
144
|
+
choices: fields.map((f, index) => ({
|
|
145
|
+
name: `${f.name} (${f.type}${f.isOptional ? '?' : ''})`,
|
|
146
|
+
value: index,
|
|
147
|
+
})),
|
|
148
|
+
}]);
|
|
149
|
+
|
|
150
|
+
const editedField = await promptSingleField(fields[fieldToEditIndex]);
|
|
151
|
+
fields[fieldToEditIndex] = {
|
|
152
|
+
name: editedField.fieldName.trim(),
|
|
153
|
+
type: editedField.fieldType,
|
|
154
|
+
isOptional: editedField.isOptional,
|
|
155
|
+
};
|
|
156
|
+
console.log(chalk.green(`β Field updated.`));
|
|
157
|
+
} else if (action === 'delete') {
|
|
158
|
+
if (fields.length === 0) {
|
|
159
|
+
console.log(chalk.yellow('β οΈ No fields to delete.'));
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const { fieldToDeleteIndex } = await inquirer.prompt([{
|
|
164
|
+
type: 'list',
|
|
165
|
+
name: 'fieldToDeleteIndex',
|
|
166
|
+
message: 'Select field to delete:',
|
|
167
|
+
choices: fields.map((f, index) => ({
|
|
168
|
+
name: `${f.name} (${f.type}${f.isOptional ? '?' : ''})`,
|
|
169
|
+
value: index,
|
|
170
|
+
})),
|
|
171
|
+
}]);
|
|
172
|
+
|
|
173
|
+
const deletedName = fields[fieldToDeleteIndex].name;
|
|
174
|
+
fields.splice(fieldToDeleteIndex, 1);
|
|
175
|
+
console.log(chalk.red(`ποΈ Field '${deletedName}' removed.`));
|
|
176
|
+
} else if (action === 'save') {
|
|
177
|
+
managing = false;
|
|
178
|
+
// Panggil fungsi regenerasi DTO & Sync ORM dari moduleGenerator
|
|
179
|
+
const { regenerateModuleComponents } = require('./moduleGenerator');
|
|
180
|
+
await regenerateModuleComponents(moduleName, fields, targetDir);
|
|
181
|
+
console.log(chalk.green(`\nβ
Successfully updated fields for module "${kebabName}"!`));
|
|
182
|
+
} else if (action === 'cancel') {
|
|
183
|
+
console.log(chalk.gray('Cancelled field management. No files were modified.'));
|
|
184
|
+
managing = false;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.error(chalk.red('\nβ Field management failed:'), error.message);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
module.exports = { manageFields };
|