speedrun-cli 2.7.7 → 2.7.9

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/CHANGELOG.md CHANGED
@@ -5,6 +5,25 @@ All notable changes to create-nestjs-auth will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.8.0] - 2026-08-22
9
+
10
+ ### Added
11
+ - **Auto-Scaffold Missing Base Architecture (`ensureBaseArchitecture`)** — When generating modules via `speedrun-cli g [module]`, `src/common/base` is automatically scaffolded if missing from target project, resolving TS2307 & TS4112 compilation errors.
12
+ - **Custom Primary Key Selection** — Prompt to choose primary key format (`id`, `<singular_snake>_id`, `<singular_camel>Id`, or `Custom...`), applied dynamically across ORM schemas, DTOs, response decorators, and `@Param()` controller annotations.
13
+ - **ORM-Native Field Types** — Customized field type choices in the interactive builder according to detected ORM:
14
+ - **Prisma:** `[String, Int, Float, Decimal, Boolean, DateTime, Json]`
15
+ - **TypeORM:** `[varchar, text, int, float, decimal, boolean, timestamp, json]`
16
+ - **Mongoose:** `[String, Number, Boolean, Date, Array, Object]`
17
+ - **Drizzle:** `[varchar, text, integer, numeric, boolean, timestamp, json]`
18
+ - **Specific Field Editing Sub-menu** — Interactive sub-menu allows modifying specific field properties (Field Name, Field Type, Optional Status, or All) instead of forcing full re-entry.
19
+ - **Advanced Relationship Builder** — Interactively add relations (`Many-to-One`, `One-to-Many`) pointing to target modules and foreign keys, automatically injecting attributes into Prisma, TypeORM, Mongoose, and Drizzle schemas.
20
+ - **Auth & Roles Guard Protection Prompt** — Optional step to protect write operations (`POST`, `PUT`, `DELETE`) with `@UseGuards()` and role-based decorators (`ADMIN`, `USER`, `MANAGER`).
21
+
22
+ ### Changed
23
+ - **Standardized Import Statements** — Controller & Service templates now fetch `BaseController`, `BaseService`, `IBaseRepository`, `ApiResponseDto`, `ApiResponseSchema`, `PaginatedResponseDto`, `PaginatedResponseSchema`, `PaginationQueryDto` cleanly from the single barrel export `../../common/base`.
24
+
25
+ ---
26
+
8
27
  ## [2.7.1] - 2026-08-22
9
28
 
10
29
  ### Fixed
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.9",
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 };