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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "speedrun-cli",
3
- "version": "2.7.7",
3
+ "version": "2.7.8",
4
4
  "description": "CLI tool to scaffold a production-ready NestJS authentication system with JWT, refresh tokens, and RBAC",
5
5
  "keywords": [
6
6
  "nestjs",
@@ -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 };