go-gin-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/README.md +287 -0
- package/git-manager.sh +495 -0
- package/package.json +36 -0
- package/prompt.md +6 -0
- package/src/bin/index.js +410 -0
- package/src/lib/constants/index.js +24 -0
- package/src/lib/shares/createDir.js +22 -0
- package/src/lib/shares/createFile.js +23 -0
- package/src/lib/utils/add-auth-to-resource.js +225 -0
- package/src/lib/utils/create-auth.js +937 -0
- package/src/lib/utils/create-resource.js +1426 -0
- package/src/lib/utils/create-service.js +456 -0
- package/src/lib/utils/display.js +19 -0
- package/src/lib/utils/help.js +93 -0
- package/src/lib/utils/remove-module.js +146 -0
- package/src/lib/utils/setup.js +1626 -0
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs").promises;
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const prompts = require("prompts");
|
|
6
|
+
const createDir = require("../shares/createDir");
|
|
7
|
+
const createFile = require("../shares/createFile");
|
|
8
|
+
const { COLORS } = require("../constants/index");
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* ServiceGenerator - Adds custom services to existing resources
|
|
12
|
+
* Following Clean Architecture pattern with action folders
|
|
13
|
+
*
|
|
14
|
+
* Creates:
|
|
15
|
+
* src/infrastructure/repositories/{module}/{serviceName}/
|
|
16
|
+
* ├── action.go
|
|
17
|
+
* └── validation.go
|
|
18
|
+
*/
|
|
19
|
+
class ServiceGenerator {
|
|
20
|
+
constructor() {
|
|
21
|
+
this.moduleName = "";
|
|
22
|
+
this.serviceName = "";
|
|
23
|
+
this.serviceNamePascal = "";
|
|
24
|
+
this.serviceNameCamel = "";
|
|
25
|
+
this.goModule = "";
|
|
26
|
+
|
|
27
|
+
// Paths matching Clean Architecture structure
|
|
28
|
+
this.srcDir = "src";
|
|
29
|
+
this.domainDir = path.join(this.srcDir, "domain");
|
|
30
|
+
this.infrastructureDir = path.join(this.srcDir, "infrastructure");
|
|
31
|
+
this.usecasesDir = path.join(this.srcDir, "usecases");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
getGoModuleName() {
|
|
35
|
+
try {
|
|
36
|
+
const goModPath = path.join(process.cwd(), "go.mod");
|
|
37
|
+
const content = require("fs").readFileSync(goModPath, "utf8");
|
|
38
|
+
const match = content.match(/module\s+(.+)/);
|
|
39
|
+
return match ? match[1].trim() : "github.com/example/project";
|
|
40
|
+
} catch {
|
|
41
|
+
return "github.com/example/project";
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
getRepositoryFolders() {
|
|
46
|
+
try {
|
|
47
|
+
const repoPath = path.join(this.infrastructureDir, "repositories");
|
|
48
|
+
|
|
49
|
+
if (!require("fs").existsSync(repoPath)) {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return require("fs").readdirSync(repoPath, { withFileTypes: true })
|
|
54
|
+
.filter(dirent => dirent.isDirectory())
|
|
55
|
+
.map(dirent => dirent.name)
|
|
56
|
+
.filter(folder => folder !== "base");
|
|
57
|
+
} catch (error) {
|
|
58
|
+
console.error(`${COLORS.RED}❌ Error listing modules: ${error.message}${COLORS.NC}`);
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
validateServiceName(name) {
|
|
64
|
+
const isValid = /^[a-zA-Z][a-zA-Z0-9]*$/.test(name);
|
|
65
|
+
if (!isValid) {
|
|
66
|
+
console.error(`${COLORS.RED}Service name must start with a letter and contain only letters and numbers${COLORS.NC}`);
|
|
67
|
+
}
|
|
68
|
+
return isValid;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
toPascalCase(str) {
|
|
72
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
toCamelCase(str) {
|
|
76
|
+
return str.charAt(0).toLowerCase() + str.slice(1);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async buildParams() {
|
|
80
|
+
// Check if we're in the right directory
|
|
81
|
+
if (!require("fs").existsSync(path.join(this.infrastructureDir, "repositories"))) {
|
|
82
|
+
throw new Error("This command must be run from within a Go Clean Architecture project root directory.");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const modules = this.getRepositoryFolders();
|
|
86
|
+
|
|
87
|
+
if (modules.length === 0) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
"No modules found. Please create a resource first using: gcg g res <resource-name>",
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const { selectedModule } = await prompts({
|
|
94
|
+
type: "select",
|
|
95
|
+
name: "selectedModule",
|
|
96
|
+
message: "Select a module for the service:",
|
|
97
|
+
choices: modules.map(module => ({
|
|
98
|
+
title: module,
|
|
99
|
+
value: module
|
|
100
|
+
})),
|
|
101
|
+
initial: 0,
|
|
102
|
+
onState: (state) => {
|
|
103
|
+
if (state.aborted) {
|
|
104
|
+
console.log(`${COLORS.CYAN}Service generation cancelled${COLORS.NC}`);
|
|
105
|
+
process.exit(0);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
if (!selectedModule) {
|
|
111
|
+
throw new Error("Module selection is required");
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const { serviceName } = await prompts({
|
|
115
|
+
type: "text",
|
|
116
|
+
name: "serviceName",
|
|
117
|
+
message: "Enter service name (e.g., getByEmail, activate):",
|
|
118
|
+
validate: this.validateServiceName.bind(this),
|
|
119
|
+
hint: "Use camelCase format",
|
|
120
|
+
onState: (state) => {
|
|
121
|
+
if (state.aborted) {
|
|
122
|
+
console.log(`${COLORS.CYAN}Service generation cancelled${COLORS.NC}`);
|
|
123
|
+
process.exit(0);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
if (!serviceName) {
|
|
129
|
+
throw new Error("Service name is required");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
this.moduleName = selectedModule;
|
|
133
|
+
this.serviceName = serviceName;
|
|
134
|
+
this.serviceNamePascal = this.toPascalCase(serviceName);
|
|
135
|
+
this.serviceNameCamel = this.toCamelCase(serviceName);
|
|
136
|
+
this.goModule = this.getGoModuleName();
|
|
137
|
+
|
|
138
|
+
// Get module pascal case
|
|
139
|
+
this.moduleNamePascal = selectedModule
|
|
140
|
+
.split("-")
|
|
141
|
+
.map(part => part.charAt(0).toUpperCase() + part.slice(1))
|
|
142
|
+
.join("");
|
|
143
|
+
this.moduleNameCamel = selectedModule
|
|
144
|
+
.split("-")
|
|
145
|
+
.map((part, i) => i === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1))
|
|
146
|
+
.join("");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async execute() {
|
|
150
|
+
try {
|
|
151
|
+
console.log(`${COLORS.CYAN}🚀 Starting service generation...${COLORS.NC}`);
|
|
152
|
+
|
|
153
|
+
await this.buildParams();
|
|
154
|
+
|
|
155
|
+
console.log(`
|
|
156
|
+
${COLORS.MAGENTA}📋 Service Details:${COLORS.NC}
|
|
157
|
+
${COLORS.YELLOW}Module:${COLORS.NC} ${this.moduleName}
|
|
158
|
+
${COLORS.YELLOW}Service:${COLORS.NC} ${this.serviceName}
|
|
159
|
+
`);
|
|
160
|
+
|
|
161
|
+
// Create service action folder
|
|
162
|
+
await this.createServiceFolder();
|
|
163
|
+
|
|
164
|
+
// Update files
|
|
165
|
+
await this.updateRepositoryInterface();
|
|
166
|
+
await this.updateMainRepository();
|
|
167
|
+
await this.updateUsecase();
|
|
168
|
+
await this.updateController();
|
|
169
|
+
await this.updateRouter();
|
|
170
|
+
|
|
171
|
+
console.log(`${COLORS.GREEN}🎉 Service generation completed successfully!${COLORS.NC}`);
|
|
172
|
+
this.printSummary();
|
|
173
|
+
} catch (error) {
|
|
174
|
+
console.error(`${COLORS.RED}❌ Service generation failed: ${error.message}${COLORS.NC}`);
|
|
175
|
+
throw error;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
async createServiceFolder() {
|
|
180
|
+
const serviceDir = path.join(
|
|
181
|
+
this.infrastructureDir,
|
|
182
|
+
"repositories",
|
|
183
|
+
this.moduleName,
|
|
184
|
+
`${this.serviceNameCamel}${this.moduleNamePascal}`
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
await createDir(serviceDir);
|
|
188
|
+
|
|
189
|
+
// Create action.go
|
|
190
|
+
await createFile(
|
|
191
|
+
path.join(serviceDir, "action.go"),
|
|
192
|
+
this.getServiceActionContent()
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
// Create validation.go
|
|
196
|
+
await createFile(
|
|
197
|
+
path.join(serviceDir, "validation.go"),
|
|
198
|
+
this.getServiceValidationContent()
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
getServiceActionContent() {
|
|
203
|
+
return `package ${this.serviceNameCamel}${this.moduleNamePascal}
|
|
204
|
+
|
|
205
|
+
import (
|
|
206
|
+
"context"
|
|
207
|
+
|
|
208
|
+
"${this.goModule}/src/domain/models"
|
|
209
|
+
"${this.goModule}/src/infrastructure/entities"
|
|
210
|
+
|
|
211
|
+
"github.com/google/uuid"
|
|
212
|
+
"gorm.io/gorm"
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
// Action performs the ${this.serviceNameCamel} operation
|
|
216
|
+
func Action(ctx context.Context, db *gorm.DB, id uuid.UUID) (*models.${this.moduleNamePascal}Model, error) {
|
|
217
|
+
var entity entities.${this.moduleNamePascal}
|
|
218
|
+
err := db.WithContext(ctx).Where("id = ? AND is_active = ?", id, true).First(&entity).Error
|
|
219
|
+
if err != nil {
|
|
220
|
+
return nil, err
|
|
221
|
+
}
|
|
222
|
+
return entity.ToModel(), nil
|
|
223
|
+
}
|
|
224
|
+
`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
getServiceValidationContent() {
|
|
228
|
+
return `package ${this.serviceNameCamel}${this.moduleNamePascal}
|
|
229
|
+
|
|
230
|
+
import (
|
|
231
|
+
"errors"
|
|
232
|
+
|
|
233
|
+
"github.com/google/uuid"
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
// Validate validates the ${this.serviceNameCamel} request
|
|
237
|
+
func Validate(id uuid.UUID) error {
|
|
238
|
+
if id == uuid.Nil {
|
|
239
|
+
return errors.New("id is required")
|
|
240
|
+
}
|
|
241
|
+
return nil
|
|
242
|
+
}
|
|
243
|
+
`;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async updateRepositoryInterface() {
|
|
247
|
+
const filePath = path.join(this.domainDir, "repositories", `${this.moduleName}.go`);
|
|
248
|
+
|
|
249
|
+
try {
|
|
250
|
+
let content = await fs.readFile(filePath, "utf8");
|
|
251
|
+
|
|
252
|
+
// Check if method already exists
|
|
253
|
+
if (content.includes(`${this.serviceNamePascal}(`)) {
|
|
254
|
+
console.log(`${COLORS.YELLOW}⚠ Repository interface already contains ${this.serviceNamePascal}${COLORS.NC}`);
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Add new method to interface
|
|
259
|
+
const methodSignature = `\t${this.serviceNamePascal}(ctx context.Context, id uuid.UUID) (*models.${this.moduleNamePascal}Model, error)`;
|
|
260
|
+
|
|
261
|
+
// Find the interface closing brace and add method before it
|
|
262
|
+
const interfaceMatch = content.match(/(type\s+\w+Repository\s+interface\s*\{[\s\S]*?)(^\})/m);
|
|
263
|
+
if (interfaceMatch) {
|
|
264
|
+
const newContent = content.replace(
|
|
265
|
+
interfaceMatch[0],
|
|
266
|
+
`${interfaceMatch[1]}${methodSignature}\n}`
|
|
267
|
+
);
|
|
268
|
+
await fs.writeFile(filePath, newContent, "utf8");
|
|
269
|
+
console.log(`${COLORS.GREEN}✔ Updated ${filePath}${COLORS.NC}`);
|
|
270
|
+
}
|
|
271
|
+
} catch (error) {
|
|
272
|
+
console.error(`${COLORS.RED}❌ Failed to update repository interface: ${error.message}${COLORS.NC}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
async updateMainRepository() {
|
|
277
|
+
const filePath = path.join(this.infrastructureDir, "repositories", this.moduleName, `${this.moduleName}.repository.go`);
|
|
278
|
+
|
|
279
|
+
try {
|
|
280
|
+
let content = await fs.readFile(filePath, "utf8");
|
|
281
|
+
|
|
282
|
+
// Check if method already exists
|
|
283
|
+
if (content.includes(`func (r *Repository) ${this.serviceNamePascal}`)) {
|
|
284
|
+
console.log(`${COLORS.YELLOW}⚠ Repository already contains ${this.serviceNamePascal}${COLORS.NC}`);
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Add import for the new service package
|
|
289
|
+
const importLine = `\t"${this.goModule}/src/infrastructure/repositories/${this.moduleName}/${this.serviceNameCamel}${this.moduleNamePascal}"`;
|
|
290
|
+
content = content.replace(
|
|
291
|
+
/import \(([\s\S]*?)\)/,
|
|
292
|
+
(match, imports) => `import (${imports}${importLine}\n)`
|
|
293
|
+
);
|
|
294
|
+
|
|
295
|
+
// Add new method implementation
|
|
296
|
+
const methodImpl = `
|
|
297
|
+
func (r *Repository) ${this.serviceNamePascal}(ctx context.Context, id uuid.UUID) (*models.${this.moduleNamePascal}Model, error) {
|
|
298
|
+
if err := ${this.serviceNameCamel}${this.moduleNamePascal}.Validate(id); err != nil {
|
|
299
|
+
return nil, err
|
|
300
|
+
}
|
|
301
|
+
return ${this.serviceNameCamel}${this.moduleNamePascal}.Action(ctx, r.db, id)
|
|
302
|
+
}
|
|
303
|
+
`;
|
|
304
|
+
|
|
305
|
+
content += methodImpl;
|
|
306
|
+
await fs.writeFile(filePath, content, "utf8");
|
|
307
|
+
console.log(`${COLORS.GREEN}✔ Updated ${filePath}${COLORS.NC}`);
|
|
308
|
+
} catch (error) {
|
|
309
|
+
console.error(`${COLORS.RED}❌ Failed to update main repository: ${error.message}${COLORS.NC}`);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
async updateUsecase() {
|
|
314
|
+
const filePath = path.join(this.usecasesDir, this.moduleName, `${this.moduleName}.usecase.go`);
|
|
315
|
+
|
|
316
|
+
try {
|
|
317
|
+
let content = await fs.readFile(filePath, "utf8");
|
|
318
|
+
|
|
319
|
+
// Check if method already exists
|
|
320
|
+
if (content.includes(`func (u *Usecase) ${this.serviceNamePascal}`)) {
|
|
321
|
+
console.log(`${COLORS.YELLOW}⚠ Usecase already contains ${this.serviceNamePascal}${COLORS.NC}`);
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Add new method implementation
|
|
326
|
+
const methodImpl = `
|
|
327
|
+
// ${this.serviceNamePascal} performs ${this.serviceNameCamel} operation
|
|
328
|
+
func (u *Usecase) ${this.serviceNamePascal}(ctx context.Context, id uuid.UUID) (*models.${this.moduleNamePascal}Model, error) {
|
|
329
|
+
model, err := u.repo.${this.serviceNamePascal}(ctx, id)
|
|
330
|
+
if err != nil {
|
|
331
|
+
u.logger.Error("${this.moduleNamePascal}Usecase", "Failed to ${this.serviceNameCamel}", err)
|
|
332
|
+
return nil, err
|
|
333
|
+
}
|
|
334
|
+
return model, nil
|
|
335
|
+
}
|
|
336
|
+
`;
|
|
337
|
+
|
|
338
|
+
content += methodImpl;
|
|
339
|
+
await fs.writeFile(filePath, content, "utf8");
|
|
340
|
+
console.log(`${COLORS.GREEN}✔ Updated ${filePath}${COLORS.NC}`);
|
|
341
|
+
} catch (error) {
|
|
342
|
+
console.error(`${COLORS.RED}❌ Failed to update usecase: ${error.message}${COLORS.NC}`);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
async updateController() {
|
|
347
|
+
const filePath = path.join(this.infrastructureDir, "controllers", this.moduleName, `${this.moduleName}.controller.go`);
|
|
348
|
+
|
|
349
|
+
try {
|
|
350
|
+
let content = await fs.readFile(filePath, "utf8");
|
|
351
|
+
|
|
352
|
+
// Check if method already exists
|
|
353
|
+
if (content.includes(`func (c *Controller) ${this.serviceNamePascal}`)) {
|
|
354
|
+
console.log(`${COLORS.YELLOW}⚠ Controller already contains ${this.serviceNamePascal}${COLORS.NC}`);
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Add new controller method
|
|
359
|
+
const controllerMethod = `
|
|
360
|
+
// ${this.serviceNamePascal} performs ${this.serviceNameCamel} operation
|
|
361
|
+
// @Summary ${this.serviceNamePascal} for ${this.moduleNamePascal}
|
|
362
|
+
// @Tags ${this.moduleNamePascal}
|
|
363
|
+
// @Produce json
|
|
364
|
+
// @Param id path string true "${this.moduleNamePascal} ID"
|
|
365
|
+
// @Success 200 {object} response.Response{data=dtos.${this.moduleNamePascal}Response}
|
|
366
|
+
// @Failure 400 {object} response.Response
|
|
367
|
+
// @Failure 404 {object} response.Response
|
|
368
|
+
// @Router /api/v1/${this.moduleName}s/{id}/${this.serviceNameCamel} [get]
|
|
369
|
+
func (c *Controller) ${this.serviceNamePascal}(ctx *gin.Context) {
|
|
370
|
+
id, err := uuid.Parse(ctx.Param("id"))
|
|
371
|
+
if err != nil {
|
|
372
|
+
response.Error(ctx, http.StatusBadRequest, "Invalid ID format")
|
|
373
|
+
return
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
model, err := c.usecase.${this.serviceNamePascal}(ctx.Request.Context(), id)
|
|
377
|
+
if err != nil {
|
|
378
|
+
if appErr, ok := utils.IsAppError(err); ok {
|
|
379
|
+
response.Error(ctx, appErr.Code, appErr.Message)
|
|
380
|
+
return
|
|
381
|
+
}
|
|
382
|
+
response.Error(ctx, http.StatusNotFound, "${this.moduleNamePascal} not found")
|
|
383
|
+
return
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
response.Success(ctx, http.StatusOK, modelToResponse(model))
|
|
387
|
+
}
|
|
388
|
+
`;
|
|
389
|
+
|
|
390
|
+
content += controllerMethod;
|
|
391
|
+
await fs.writeFile(filePath, content, "utf8");
|
|
392
|
+
console.log(`${COLORS.GREEN}✔ Updated ${filePath}${COLORS.NC}`);
|
|
393
|
+
} catch (error) {
|
|
394
|
+
console.error(`${COLORS.RED}❌ Failed to update controller: ${error.message}${COLORS.NC}`);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
async updateRouter() {
|
|
399
|
+
const routerPath = path.join(this.infrastructureDir, "controllers", "router.go");
|
|
400
|
+
|
|
401
|
+
try {
|
|
402
|
+
let content = await fs.readFile(routerPath, "utf8");
|
|
403
|
+
|
|
404
|
+
// Check if route already exists
|
|
405
|
+
const routePattern = `${this.moduleName}s/{id}/${this.serviceNameCamel}`;
|
|
406
|
+
if (content.includes(routePattern)) {
|
|
407
|
+
console.log(`${COLORS.YELLOW}⚠ Router already contains route for ${this.serviceNamePascal}${COLORS.NC}`);
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Find the module's route group and add the new route
|
|
412
|
+
// Look for pattern like: moduleName := v1.Group("/moduleNames")
|
|
413
|
+
const groupPattern = new RegExp(
|
|
414
|
+
`(${this.moduleName}s?\\s*:=\\s*v1\\.Group\\(["\']\/${this.moduleName}s?["\']\\)[\\s\\S]*?\\{[\\s\\S]*?)(\\})`,
|
|
415
|
+
'm'
|
|
416
|
+
);
|
|
417
|
+
|
|
418
|
+
const match = content.match(groupPattern);
|
|
419
|
+
if (match) {
|
|
420
|
+
const newRoute = `\t\t${this.moduleName}s.GET("/:id/${this.serviceNameCamel}", ${this.moduleName}Ctrl.${this.serviceNamePascal})\n\t`;
|
|
421
|
+
content = content.replace(
|
|
422
|
+
groupPattern,
|
|
423
|
+
`$1${newRoute}$2`
|
|
424
|
+
);
|
|
425
|
+
await fs.writeFile(routerPath, content, "utf8");
|
|
426
|
+
console.log(`${COLORS.GREEN}✔ Updated ${routerPath}${COLORS.NC}`);
|
|
427
|
+
} else {
|
|
428
|
+
console.log(`${COLORS.YELLOW}⚠ Could not find ${this.moduleName} route group in router.go. Please add route manually.${COLORS.NC}`);
|
|
429
|
+
}
|
|
430
|
+
} catch (error) {
|
|
431
|
+
console.error(`${COLORS.YELLOW}⚠ Could not update router: ${error.message}${COLORS.NC}`);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
printSummary() {
|
|
436
|
+
console.log(`
|
|
437
|
+
${COLORS.CYAN}📁 Created files:${COLORS.NC}
|
|
438
|
+
${COLORS.GREEN}✓${COLORS.NC} src/infrastructure/repositories/${this.moduleName}/${this.serviceNameCamel}${this.moduleNamePascal}/
|
|
439
|
+
├── action.go
|
|
440
|
+
└── validation.go
|
|
441
|
+
|
|
442
|
+
${COLORS.CYAN}📝 Updated files:${COLORS.NC}
|
|
443
|
+
${COLORS.GREEN}✓${COLORS.NC} src/domain/repositories/${this.moduleName}.go
|
|
444
|
+
${COLORS.GREEN}✓${COLORS.NC} src/infrastructure/repositories/${this.moduleName}/${this.moduleName}.repository.go
|
|
445
|
+
${COLORS.GREEN}✓${COLORS.NC} src/usecases/${this.moduleName}/${this.moduleName}.usecase.go
|
|
446
|
+
${COLORS.GREEN}✓${COLORS.NC} src/infrastructure/controllers/${this.moduleName}/${this.moduleName}.controller.go
|
|
447
|
+
${COLORS.GREEN}✓${COLORS.NC} src/infrastructure/controllers/router.go
|
|
448
|
+
|
|
449
|
+
${COLORS.YELLOW}⚠ Don't forget to:${COLORS.NC}
|
|
450
|
+
1. Customize the service logic in action.go as needed
|
|
451
|
+
2. Run ${COLORS.CYAN}go mod tidy${COLORS.NC} if you added new dependencies
|
|
452
|
+
`);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
module.exports = ServiceGenerator;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const packageJson = require("../../../package.json");
|
|
4
|
+
|
|
5
|
+
const COLORS = {
|
|
6
|
+
GREEN: "\x1b[32m",
|
|
7
|
+
YELLOW: "\x1b[33m",
|
|
8
|
+
CYAN: "\x1b[36m",
|
|
9
|
+
NC: "\x1b[0m",
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
function displayVersion() {
|
|
13
|
+
console.log(`${COLORS.CYAN}╔══════════════════════════════════════════╗${COLORS.NC}`);
|
|
14
|
+
console.log(` ${COLORS.GREEN}Go Clean Architecture CLI${COLORS.NC}`);
|
|
15
|
+
console.log(` ${COLORS.YELLOW}Version: ${packageJson.version}${COLORS.NC}`);
|
|
16
|
+
console.log(`${COLORS.CYAN}╚══════════════════════════════════════════╝${COLORS.NC}`);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = { displayVersion };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Colors for output
|
|
4
|
+
const GREEN = "\x1b[32m";
|
|
5
|
+
const YELLOW = "\x1b[33m";
|
|
6
|
+
const RED = "\x1b[31m";
|
|
7
|
+
const CYAN = "\x1b[36m";
|
|
8
|
+
const BLUE = "\x1b[34m";
|
|
9
|
+
const MAGENTA = "\x1b[35m";
|
|
10
|
+
const NC = "\x1b[0m";
|
|
11
|
+
|
|
12
|
+
// Header
|
|
13
|
+
console.log(`${CYAN}╔══════════════════════════════════════════╗${NC}`);
|
|
14
|
+
console.log(` ${BLUE}✨ ${GREEN}Go Clean Architecture CLI ${BLUE}✨${NC} `);
|
|
15
|
+
console.log(`${CYAN}╚══════════════════════════════════════════╝${NC}`);
|
|
16
|
+
console.log("");
|
|
17
|
+
console.log(
|
|
18
|
+
`${YELLOW}📚 This CLI follows Clean Architecture principles${NC}`
|
|
19
|
+
);
|
|
20
|
+
console.log(`${YELLOW} and helps scaffold your Go/Gin-Gonic application${NC}`);
|
|
21
|
+
console.log("");
|
|
22
|
+
|
|
23
|
+
// Commands section
|
|
24
|
+
console.log(`${MAGENTA}🌈 Available Commands:${NC}`);
|
|
25
|
+
console.log("");
|
|
26
|
+
|
|
27
|
+
console.log(`${BLUE}⚙️ Create New Project:${NC}`);
|
|
28
|
+
console.log(
|
|
29
|
+
` ${GREEN}➜${NC} ${CYAN}gcg n${NC} - Initialize project structure`,
|
|
30
|
+
);
|
|
31
|
+
console.log(` ${GREEN}➜${NC} ${CYAN}gcg new${NC} `);
|
|
32
|
+
console.log("");
|
|
33
|
+
|
|
34
|
+
console.log(`${GREEN}🛠 Create Resources:${NC}`);
|
|
35
|
+
console.log(
|
|
36
|
+
` ${GREEN}➜${NC} ${CYAN}gcg g res${NC} - Generate a new resource`,
|
|
37
|
+
);
|
|
38
|
+
console.log(` ${GREEN}➜${NC} ${CYAN}gcg g resource${NC}`);
|
|
39
|
+
console.log("");
|
|
40
|
+
|
|
41
|
+
console.log(`${BLUE}⚡ Create Services:${NC}`);
|
|
42
|
+
console.log(
|
|
43
|
+
` ${GREEN}➜${NC} ${CYAN}gcg g s${NC} - Generate a new service`,
|
|
44
|
+
);
|
|
45
|
+
console.log(` ${GREEN}➜${NC} ${CYAN}gcg g service${NC}`);
|
|
46
|
+
console.log("");
|
|
47
|
+
|
|
48
|
+
console.log(`${MAGENTA}🔐 Create Authentication:${NC}`);
|
|
49
|
+
console.log(
|
|
50
|
+
` ${GREEN}➜${NC} ${CYAN}gcg g au${NC} - Generate JWT & Passport auth`,
|
|
51
|
+
);
|
|
52
|
+
console.log(` ${GREEN}➜${NC} ${CYAN}gcg g auth${NC}`);
|
|
53
|
+
console.log("");
|
|
54
|
+
|
|
55
|
+
console.log(`${YELLOW}🔒 Add Auth to Resource:${NC}`);
|
|
56
|
+
console.log(
|
|
57
|
+
` ${GREEN}➜${NC} ${CYAN}gcg add au${NC} - Add auth guard to a resource`,
|
|
58
|
+
);
|
|
59
|
+
console.log(` ${GREEN}➜${NC} ${CYAN}gcg add auth${NC}`);
|
|
60
|
+
console.log("");
|
|
61
|
+
|
|
62
|
+
console.log(`${RED}🗑 Remove Resources:${NC}`);
|
|
63
|
+
console.log(
|
|
64
|
+
` ${GREEN}➜${NC} ${CYAN}gcg rm res${NC} - Remove an existing resource`,
|
|
65
|
+
);
|
|
66
|
+
console.log(` ${GREEN}➜${NC} ${CYAN}gcg rm resource${NC}`);
|
|
67
|
+
console.log("");
|
|
68
|
+
|
|
69
|
+
console.log(`${YELLOW}ℹ️ Other Commands:${NC}`);
|
|
70
|
+
console.log(
|
|
71
|
+
` ${GREEN}➜${NC} ${CYAN}gcg --help${NC} - Show help message`,
|
|
72
|
+
);
|
|
73
|
+
console.log(
|
|
74
|
+
` ${GREEN}➜${NC} ${CYAN}gcg --version${NC} - Display version`,
|
|
75
|
+
);
|
|
76
|
+
console.log("");
|
|
77
|
+
|
|
78
|
+
// Tech Stack
|
|
79
|
+
console.log(`${MAGENTA}🔧 Tech Stack:${NC}`);
|
|
80
|
+
console.log(` ${GREEN}➜${NC} Framework: ${CYAN}Gin-Gonic${NC}`);
|
|
81
|
+
console.log(` ${GREEN}➜${NC} ORM: ${CYAN}GORM${NC}`);
|
|
82
|
+
console.log(` ${GREEN}➜${NC} Database: ${CYAN}PostgreSQL${NC}`);
|
|
83
|
+
console.log(` ${GREEN}➜${NC} API: ${CYAN}RESTful${NC}`);
|
|
84
|
+
console.log(` ${GREEN}➜${NC} Logging: ${CYAN}Zap${NC}`);
|
|
85
|
+
console.log(` ${GREEN}➜${NC} Config: ${CYAN}Viper${NC}`);
|
|
86
|
+
console.log("");
|
|
87
|
+
|
|
88
|
+
// Footer
|
|
89
|
+
console.log(`${CYAN}╔══════════════════════════════════════════╗${NC}`);
|
|
90
|
+
console.log(
|
|
91
|
+
`${BLUE}🔹 ${GREEN}Happy Coding with Clean Architecture! ${BLUE} 🔹${NC}`
|
|
92
|
+
);
|
|
93
|
+
console.log(`${CYAN}╚══════════════════════════════════════════╝${NC}`);
|