go-duck-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.
Files changed (49) hide show
  1. package/README.md +130 -0
  2. package/generators/cache.js +107 -0
  3. package/generators/config.js +173 -0
  4. package/generators/devops.js +212 -0
  5. package/generators/docs.js +74 -0
  6. package/generators/graphql.js +38 -0
  7. package/generators/kratos.js +157 -0
  8. package/generators/logger.js +68 -0
  9. package/generators/metering.js +143 -0
  10. package/generators/migrations.js +240 -0
  11. package/generators/mqtt.js +87 -0
  12. package/generators/multitenancy.js +130 -0
  13. package/generators/postgrest.js +115 -0
  14. package/generators/repository.js +28 -0
  15. package/generators/resilience.js +69 -0
  16. package/generators/security.js +168 -0
  17. package/generators/swagger.js +145 -0
  18. package/generators/telemetry.js +121 -0
  19. package/generators/websocket.js +162 -0
  20. package/index.js +592 -0
  21. package/package.json +23 -0
  22. package/parser/gdl.js +162 -0
  23. package/templates/application.yml.hbs +18 -0
  24. package/templates/docs/gin_bottle.png +0 -0
  25. package/templates/docs/index.html.hbs +226 -0
  26. package/templates/docs/intro.mp4 +0 -0
  27. package/templates/docs/kratos_mark.png +0 -0
  28. package/templates/docs/layout.hbs +106 -0
  29. package/templates/docs/logo.png +0 -0
  30. package/templates/docs/pages/audit.hbs +39 -0
  31. package/templates/docs/pages/cli.hbs +83 -0
  32. package/templates/docs/pages/gdl.hbs +223 -0
  33. package/templates/docs/pages/graphql.hbs +51 -0
  34. package/templates/docs/pages/grpc.hbs +100 -0
  35. package/templates/docs/pages/index.hbs +181 -0
  36. package/templates/docs/pages/integrations.hbs +83 -0
  37. package/templates/docs/pages/observability.hbs +34 -0
  38. package/templates/docs/pages/realtime.hbs +43 -0
  39. package/templates/docs/pages/rest.hbs +149 -0
  40. package/templates/docs/pages/security.hbs +31 -0
  41. package/templates/go/controller.go.hbs +236 -0
  42. package/templates/go/entity.go.hbs +34 -0
  43. package/templates/go/enum.go.hbs +7 -0
  44. package/templates/go/main.go.hbs +186 -0
  45. package/templates/graphql/resolver.go.hbs +50 -0
  46. package/templates/graphql/schema.graphql.hbs +64 -0
  47. package/templates/kratos/service.go.hbs +104 -0
  48. package/templates/proto/entity.proto.hbs +95 -0
  49. package/test_parser.js +9 -0
package/index.js ADDED
@@ -0,0 +1,592 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * GO-DUCK-CLI: A powerful Go code generator for microservices.
5
+ * Supports full project creation and incremental GDL imports.
6
+ */
7
+
8
+ import { Command } from 'commander';
9
+ import fs from 'fs-extra';
10
+ import path from 'path';
11
+ import yaml from 'js-yaml';
12
+ import Handlebars from 'handlebars';
13
+ import chalk from 'chalk';
14
+ import { parseGDL } from './parser/gdl.js';
15
+ import { generateMultitenancy } from './generators/multitenancy.js';
16
+ import { generateLiquibaseChangelogs } from './generators/migrations.js';
17
+ import { generateMeteringCode } from './generators/metering.js';
18
+ import { generateGraphQLCode } from './generators/graphql.js';
19
+ import { generatePostgRESTCode } from './generators/postgrest.js';
20
+ import { generateSwaggerDocs } from './generators/swagger.js';
21
+ import { generateSecurityMiddleware } from './generators/security.js';
22
+ import { generateWebSocketCode } from './generators/websocket.js';
23
+ import { generateConfigLoader } from './generators/config.js';
24
+ import { generateLoggerCode } from './generators/logger.js';
25
+ import { generateMQTTCode } from './generators/mqtt.js';
26
+ import { generateCacheCode } from './generators/cache.js';
27
+ import { generateResilienceCode } from './generators/resilience.js';
28
+ import { generateTelemetryCode } from './generators/telemetry.js';
29
+ import { generateDeploymentArtifacts } from './generators/devops.js';
30
+ import { generateKratosCode } from './generators/kratos.js';
31
+ import { generateRepositoryCode } from './generators/repository.js';
32
+ import { generateDocumentation } from './generators/docs.js';
33
+
34
+ export const generateAuditCode = async (config, outputDir) => {
35
+ const middlewareDir = path.join(outputDir, 'middleware');
36
+ const modelsDir = path.join(outputDir, 'models');
37
+ const controllersDir = path.join(outputDir, 'controllers');
38
+
39
+ await fs.ensureDir(middlewareDir);
40
+ await fs.ensureDir(modelsDir);
41
+ await fs.ensureDir(controllersDir);
42
+
43
+ const auditModel = `
44
+ package models
45
+
46
+ import (
47
+ "time"
48
+ )
49
+
50
+ type AuditLog struct {
51
+ ID uint \`gorm:"primaryKey" json:"id"\`
52
+ EntityName string \`json:"entityName"\`
53
+ EntityID string \`json:"entityId"\`
54
+ Action string \`json:"action"\` // CREATE, UPDATE, DELETE
55
+ PreviousValue string \`json:"previousValue" gorm:"type:text"\`
56
+ NewValue string \`json:"newValue" gorm:"type:text"\`
57
+ ModifiedBy string \`json:"modifiedBy"\`
58
+ KeycloakID string \`json:"keycloakId"\`
59
+ ModifiedAt time.Time \`json:"modifiedAt"\`
60
+ ClientIP string \`json:"clientIp"\`
61
+ }
62
+ `;
63
+
64
+ const auditMiddleware = `
65
+ package middleware
66
+
67
+ import (
68
+ "bytes"
69
+ "io/ioutil"
70
+ "net/http"
71
+ "time"
72
+
73
+ "github.com/gin-gonic/gin"
74
+ "gorm.io/gorm"
75
+ "{{app_name}}/models"
76
+ )
77
+
78
+ func AuditMiddleware(db *gorm.DB) gin.HandlerFunc {
79
+ return func(c *gin.Context) {
80
+ if c.Request.Method == http.MethodGet {
81
+ c.Next()
82
+ return
83
+ }
84
+
85
+ // Simplified auditing logic
86
+ method := c.Request.Method
87
+ path := c.Request.URL.Path
88
+
89
+ // Map method to action
90
+ action := "UPDATE"
91
+ if method == http.MethodPost { action = "CREATE" }
92
+ if method == http.MethodDelete { action = "DELETE" }
93
+
94
+ // Mock user and IP
95
+ userEmail := c.GetHeader("User-Email")
96
+ if userEmail == "" { userEmail = "anonymous" }
97
+
98
+ keycloakId := c.GetHeader("X-Keycloak-Id")
99
+ clientIP := c.ClientIP()
100
+
101
+ // Call next handlers
102
+ c.Next()
103
+
104
+ // Logic to capture entity ID and snapshot values would go here...
105
+ // For now, track the action
106
+ auditEntry := models.AuditLog{
107
+ EntityName: path,
108
+ Action: action,
109
+ ModifiedBy: userEmail,
110
+ KeycloakID: keycloakId,
111
+ ModifiedAt: time.Now(),
112
+ ClientIP: clientIP,
113
+ }
114
+ db.Create(&auditEntry)
115
+ }
116
+ }
117
+ `;
118
+
119
+ const auditController = `
120
+ package controllers
121
+
122
+ import (
123
+ "net/http"
124
+ "{{app_name}}/models"
125
+
126
+ "github.com/gin-gonic/gin"
127
+ "gorm.io/gorm"
128
+ )
129
+
130
+ type AuditController struct {
131
+ DB *gorm.DB
132
+ }
133
+
134
+ func (ac *AuditController) GetLogs(c *gin.Context) {
135
+ var logs []models.AuditLog
136
+ ac.DB.Order("modified_at desc").Find(&logs)
137
+ c.JSON(http.StatusOK, logs)
138
+ }
139
+ `;
140
+
141
+ await fs.writeFile(path.join(modelsDir, 'audit_log.go'), auditModel);
142
+ await fs.writeFile(path.join(middlewareDir, 'audit_middleware.go'), auditMiddleware.replace('{{app_name}}', config.name));
143
+ await fs.writeFile(path.join(controllersDir, 'audit_controller.go'), auditController.replace('{{app_name}}', config.name));
144
+ };
145
+
146
+ const program = new Command();
147
+
148
+ // Handlebars Helpers
149
+ Handlebars.registerHelper('capitalize', (str) => {
150
+ if (typeof str !== 'string') return '';
151
+ return str.charAt(0).toUpperCase() + str.slice(1);
152
+ });
153
+
154
+ Handlebars.registerHelper('hasJson', (fields) => {
155
+ if (!fields || !Array.isArray(fields)) return false;
156
+ return fields.some(f => f.type === 'JSON' || f.type === 'JSONB');
157
+ });
158
+
159
+ Handlebars.registerHelper('isJson', (type) => type === 'JSON' || type === 'JSONB');
160
+
161
+ Handlebars.registerHelper('toLowerCase', (str) => {
162
+ if (typeof str !== 'string') return '';
163
+ return str.toLowerCase();
164
+ });
165
+
166
+ Handlebars.registerHelper('toGoType', (type, options) => {
167
+ const enums = options.data.root.enums || [];
168
+ const isEnum = enums.some(e => e.name === type);
169
+ if (isEnum) return type;
170
+
171
+ const types = {
172
+ 'String': 'string',
173
+ 'Text': 'string',
174
+ 'Integer': 'int',
175
+ 'Float': 'float64',
176
+ 'Boolean': 'bool',
177
+ 'Long': 'int64',
178
+ 'BigDecimal': 'float64',
179
+ 'LocalDate': 'time.Time',
180
+ 'Instant': 'time.Time',
181
+ 'JSON': 'datatypes.JSON',
182
+ 'JSONB': 'datatypes.JSON'
183
+ };
184
+ return types[type] || 'interface{}';
185
+ });
186
+
187
+ Handlebars.registerHelper('gql_type', (type, options) => {
188
+ const enums = options.data.root.enums || [];
189
+ const isEnum = enums.some(e => e.name === type);
190
+ if (isEnum) return type;
191
+
192
+ const types = {
193
+ 'String': 'String',
194
+ 'Integer': 'Int',
195
+ 'Float': 'Float',
196
+ 'Boolean': 'Boolean',
197
+ 'Long': 'ID',
198
+ 'BigDecimal': 'Float',
199
+ 'LocalDate': 'String',
200
+ 'Instant': 'String',
201
+ 'JSON': 'String',
202
+ 'JSONB': 'String'
203
+ };
204
+ return types[type] || 'String';
205
+ });
206
+
207
+ Handlebars.registerHelper('eq', (a, b) => a === b);
208
+
209
+ program
210
+ .name('go-duck-cli')
211
+ .description('A powerful Go code generator for microservices')
212
+ .version('1.0.0');
213
+
214
+ // Helper to load configuration
215
+ const loadConfig = async (configPath) => {
216
+ try {
217
+ const fileContents = await fs.readFile(configPath, 'utf8');
218
+ const config = yaml.load(fileContents);
219
+ const appConfig = config.app || {};
220
+ return appConfig;
221
+ } catch (error) {
222
+ console.error(chalk.red(`Error loading config from ${configPath}:`), error.message);
223
+ process.exit(1);
224
+ }
225
+ };
226
+
227
+ const saveEntitySnapshot = async (outputDir, entity) => {
228
+ const goDuckDir = path.join(outputDir, '.go-duck');
229
+ await fs.ensureDir(goDuckDir);
230
+ await fs.writeJson(path.join(goDuckDir, `${entity.name.toLowerCase()}.json`), entity, { spaces: 2 });
231
+ };
232
+
233
+ const getPreviousEntities = async (outputDir) => {
234
+ const goDuckDir = path.join(outputDir, '.go-duck');
235
+ if (!await fs.pathExists(goDuckDir)) return [];
236
+
237
+ const files = await fs.readdir(goDuckDir);
238
+ const entities = [];
239
+ for (const file of files) {
240
+ if (file.endsWith('.json')) {
241
+ const content = await fs.readJson(path.join(goDuckDir, file));
242
+ entities.push(content);
243
+ }
244
+ }
245
+ return entities;
246
+ };
247
+
248
+ const generateEntities = async (gdlFilePath, outputDir, config) => {
249
+ if (!await fs.pathExists(gdlFilePath)) {
250
+ console.error(chalk.red(`❌ GDL file not found: ${gdlFilePath}`));
251
+ return null;
252
+ }
253
+
254
+ const { entities, relationships, enums } = await parseGDL(gdlFilePath);
255
+ console.log(chalk.green(`✅ Parsed ${entities.length} entities, ${relationships.length} relationships, and ${enums.length} enums`));
256
+
257
+ const previousEntities = await getPreviousEntities(outputDir);
258
+ const delta = {
259
+ newEntities: [],
260
+ newFields: {},
261
+ newRelationships: []
262
+ };
263
+
264
+ // Calculate Delta for Incremental Migrations
265
+ for (const entity of entities) {
266
+ const prev = previousEntities.find(e => e.name === entity.name);
267
+ if (!prev) {
268
+ delta.newEntities.push(entity);
269
+ } else {
270
+ // Check for new fields
271
+ const newFields = entity.fields.filter(f => !prev.fields.some(pf => pf.name === f.name));
272
+ if (newFields.length > 0) {
273
+ delta.newFields[entity.name] = newFields;
274
+ }
275
+ }
276
+ }
277
+
278
+ // New relationships
279
+ delta.newRelationships = relationships.filter(rel => {
280
+ const fromEntityCreated = delta.newEntities.some(e => e.name === rel.from.entity);
281
+ const toEntityCreated = delta.newEntities.some(e => e.name === rel.to.entity);
282
+ return fromEntityCreated || toEntityCreated;
283
+ });
284
+
285
+ const entityTemplatePath = path.resolve(path.dirname(import.meta.url.replace('file://', '')), 'templates/go/entity.go.hbs');
286
+ const entityTemplateSource = await fs.readFile(entityTemplatePath, 'utf8');
287
+ const entityTemplate = Handlebars.compile(entityTemplateSource);
288
+
289
+ const controllerTemplatePath = path.resolve(path.dirname(import.meta.url.replace('file://', '')), 'templates/go/controller.go.hbs');
290
+ const controllerTemplateSource = await fs.readFile(controllerTemplatePath, 'utf8');
291
+ const controllerTemplate = Handlebars.compile(controllerTemplateSource);
292
+
293
+ const enumTemplatePath = path.resolve(path.dirname(import.meta.url.replace('file://', '')), 'templates/go/enum.go.hbs');
294
+ const enumTemplateSource = await fs.readFile(enumTemplatePath, 'utf8');
295
+ const enumTemplate = Handlebars.compile(enumTemplateSource);
296
+
297
+ await fs.ensureDir(path.join(outputDir, 'models'));
298
+ await fs.ensureDir(path.join(outputDir, 'controllers'));
299
+
300
+ // Generate Enums
301
+ if (enums.length > 0) {
302
+ let enumContent = 'package models\n\n';
303
+ for (const en of enums) {
304
+ enumContent += enumTemplate(en).trim() + '\n\n';
305
+ }
306
+ await fs.writeFile(path.join(outputDir, 'models', 'enums.go'), enumContent);
307
+ console.log(chalk.gray(` - Generated Enums: models/enums.go`));
308
+ }
309
+
310
+ for (const entity of entities) {
311
+ entity.relationships = relationships.filter(r => r.from.entity === entity.name || r.to.entity === entity.name);
312
+ entity.app_name = config.name;
313
+ entity.enums = enums; // Pass enums context for helpers
314
+
315
+ // Generate Model
316
+ const entityContent = entityTemplate(entity);
317
+ await fs.writeFile(path.join(outputDir, 'models', `${entity.name.toLowerCase()}.go`), entityContent);
318
+
319
+ // Generate Controller
320
+ const controllerContent = controllerTemplate(entity);
321
+ await fs.writeFile(path.join(outputDir, 'controllers', `${entity.name.toLowerCase()}_controller.go`), controllerContent);
322
+
323
+ console.log(chalk.gray(` - Updated Entity & Controller: ${entity.name}`));
324
+
325
+ // Save Snapshot for next comparison
326
+ await saveEntitySnapshot(outputDir, entity);
327
+ }
328
+
329
+ // Generate Incremental Changelogs!
330
+ await generateLiquibaseChangelogs(entities, relationships, outputDir, delta, enums);
331
+ console.log(chalk.green('✅ Liquibase incremental migrations updated!'));
332
+
333
+ return { entities, relationships, enums };
334
+ };
335
+
336
+ program
337
+ .command('create')
338
+ .description('Create a new base Go app from config.yaml and GDL')
339
+ .option('-c, --config <path>', 'Path to config.yaml', '../CONFIG/config.yaml')
340
+ .option('-o, --output <path>', 'Path to generate project', '.')
341
+ .option('-g, --gdl <path>', 'Path to GDL files directory', '../GDL')
342
+ .action(async (options) => {
343
+ const { config: configPath, output: outputDir, gdl: gdlDir } = options;
344
+ console.log(chalk.blue('🚀 Starting Go-Duck project generation...'));
345
+
346
+ const absoluteOutputDir = path.resolve(process.cwd(), outputDir);
347
+ await fs.ensureDir(absoluteOutputDir);
348
+
349
+ const config = await loadConfig(path.resolve(process.cwd(), configPath));
350
+ console.log(chalk.green(`✅ Config loaded for app: ${config.name}`));
351
+
352
+ await generateConfigLoader(absoluteOutputDir);
353
+ await generateLoggerCode(config, absoluteOutputDir);
354
+ await generateMQTTCode(config, absoluteOutputDir);
355
+ await generateCacheCode(config, absoluteOutputDir);
356
+ await generateResilienceCode(config, absoluteOutputDir);
357
+ await generateTelemetryCode(config, absoluteOutputDir);
358
+ await generateDeploymentArtifacts(config, absoluteOutputDir);
359
+ await generateYAMLConfigs(config, absoluteOutputDir);
360
+ const { entities, relationships, enums } = await generateEntities(path.join(path.resolve(process.cwd(), gdlDir), 'app.gdl'), absoluteOutputDir, config);
361
+ await generateKratosCode(entities, absoluteOutputDir, config.name, enums);
362
+
363
+ await generateRepositoryCode(absoluteOutputDir);
364
+
365
+ await generateGraphQLCode(config, entities, relationships, absoluteOutputDir, enums);
366
+ if (config.multitenancy?.enabled) await generateMultitenancy(config, absoluteOutputDir);
367
+ await generateAuditCode(config, absoluteOutputDir);
368
+ await generateMeteringCode(config, absoluteOutputDir);
369
+ await generateSecurityMiddleware(config, absoluteOutputDir);
370
+ await generateWebSocketCode(config, entities, absoluteOutputDir);
371
+ await generatePostgRESTCode(config, absoluteOutputDir);
372
+ console.log(chalk.green('✅ PostgREST-like search layer created!'));
373
+
374
+ // 8. Generate Swagger Docs
375
+ await generateSwaggerDocs(config, entities, absoluteOutputDir);
376
+ console.log(chalk.green('✅ Swagger API documentation generated!'));
377
+
378
+ // 8.5 Generate Web Docs App
379
+ await generateDocumentation(config, entities, absoluteOutputDir, enums);
380
+ console.log(chalk.green('✅ Web Documentation App generated!'));
381
+
382
+ // 9. Generate main.go
383
+ const mainTemplatePath = path.resolve(path.dirname(import.meta.url.replace('file://', '')), 'templates/go/main.go.hbs');
384
+ if (await fs.pathExists(mainTemplatePath)) {
385
+ const mainTemplateSource = await fs.readFile(mainTemplatePath, 'utf8');
386
+ const mainTemplate = Handlebars.compile(mainTemplateSource);
387
+ await fs.writeFile(path.join(absoluteOutputDir, 'main.go'), mainTemplate({ app_name: config.name, entities }));
388
+ console.log(chalk.green('✅ main.go entry point created!'));
389
+ }
390
+ console.log(chalk.bold.magenta('\n✨ Project created successfully!'));
391
+ });
392
+
393
+ program
394
+ .command('import-gdl <file>')
395
+ .description('Import entities from a GDL file into an existing app')
396
+ .option('-o, --output <path>', 'Path to the existing app root', '.')
397
+ .action(async (file, options) => {
398
+ const absoluteOutputDir = path.resolve(process.cwd(), options.output);
399
+ console.log(chalk.blue(`📥 Importing GDL from ${file}...`));
400
+
401
+ const config = await loadConfig(path.resolve(process.cwd(), '../CONFIG/config.yaml'));
402
+ await generateConfigLoader(absoluteOutputDir);
403
+ await generateLoggerCode(config, absoluteOutputDir);
404
+ await generateMQTTCode(config, absoluteOutputDir);
405
+ await generateCacheCode(config, absoluteOutputDir);
406
+ await generateResilienceCode(config, absoluteOutputDir);
407
+ await generateTelemetryCode(config, absoluteOutputDir);
408
+ await generateDeploymentArtifacts(config, absoluteOutputDir);
409
+ const { entities, relationships, enums } = await generateEntities(path.resolve(process.cwd(), file), absoluteOutputDir, config);
410
+ await generateKratosCode(entities, absoluteOutputDir, config.name, enums);
411
+
412
+ await generateRepositoryCode(absoluteOutputDir);
413
+
414
+ await generateGraphQLCode(config, entities, relationships, absoluteOutputDir, enums);
415
+ // Sync PostgREST search as well
416
+ await generatePostgRESTCode(config, absoluteOutputDir);
417
+
418
+ // Sync Security
419
+ await generateSecurityMiddleware(config, absoluteOutputDir);
420
+
421
+ // Sync WebSocket
422
+ await generateWebSocketCode(config, entities, absoluteOutputDir);
423
+
424
+ // Sync Swagger Docs
425
+ await generateSwaggerDocs(config, entities, absoluteOutputDir);
426
+
427
+ // Sync Web Docs App
428
+ await generateDocumentation(config, entities, absoluteOutputDir, enums);
429
+
430
+ // Regenerate main.go to include new routes if any (or just entities)
431
+ const mainTemplatePath = path.resolve(path.dirname(import.meta.url.replace('file://', '')), 'templates/go/main.go.hbs');
432
+ if (await fs.pathExists(mainTemplatePath)) {
433
+ const mainTemplate = Handlebars.compile(await fs.readFile(mainTemplatePath, 'utf8'));
434
+ await fs.writeFile(path.join(absoluteOutputDir, 'main.go'), mainTemplate({ app_name: config.name, entities }));
435
+ console.log(chalk.green('✅ Updated main.go to register new entity routes.'));
436
+ }
437
+ console.log(chalk.bold.magenta('\n✨ GDL Import Completed with Incremental Migrations! ✨'));
438
+ });
439
+
440
+ const generateYAMLConfigs = async (config, outputDir) => {
441
+ // Clean up input config - remove the static multitenancy-databases list if it exists
442
+ const cleanConfig = { ...config };
443
+ if (cleanConfig.datasource && cleanConfig.datasource['multitenancy-databases']) {
444
+ delete cleanConfig.datasource['multitenancy-databases'];
445
+ }
446
+
447
+ const extendedConfig = {
448
+ ...cleanConfig,
449
+ server: {
450
+ port: 8080,
451
+ 'read-timeout': '30s',
452
+ 'write-timeout': '30s',
453
+ grpc: {
454
+ addr: ':9000',
455
+ network: 'tcp',
456
+ timeout: '1s'
457
+ },
458
+ cors: {
459
+ 'allow-origins': ['*'],
460
+ 'allow-methods': ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
461
+ 'allow-headers': ['Origin', 'Content-Type', 'Accept', 'Authorization']
462
+ }
463
+ },
464
+ datasource: {
465
+ ...cleanConfig.datasource,
466
+ 'max-open-conns': 25,
467
+ 'max-idle-conns': 10,
468
+ 'conn-max-lifetime': '5m'
469
+ },
470
+ security: {
471
+ ...cleanConfig.security,
472
+ 'rate-limit': {
473
+ rps: 100,
474
+ burst: 200
475
+ }
476
+ },
477
+ logging: {
478
+ datadog: {
479
+ enabled: false,
480
+ 'api-key': 'YOUR_DATADOG_API_KEY',
481
+ site: 'datadoghq.com',
482
+ service: cleanConfig.name || 'go-duck-service'
483
+ }
484
+ },
485
+ messaging: {
486
+ mqtt: {
487
+ enabled: false,
488
+ broker: 'tcp://localhost:1883',
489
+ 'client-id': (cleanConfig.name || 'go-duck') + '-dev',
490
+ username: 'dev_user',
491
+ password: 'dev_password',
492
+ 'topic-prefix': 'go-duck/events'
493
+ }
494
+ },
495
+ cache: {
496
+ redis: {
497
+ enabled: false,
498
+ host: 'localhost:6379',
499
+ password: '',
500
+ db: 0,
501
+ ttl: '10m'
502
+ }
503
+ },
504
+ resilience: {
505
+ 'circuit-breaker': {
506
+ enabled: true,
507
+ 'failure-threshold': 5,
508
+ 'success-threshold': 2,
509
+ timeout: '60s'
510
+ }
511
+ },
512
+ telemetry: {
513
+ otel: {
514
+ enabled: false,
515
+ endpoint: 'localhost:4317',
516
+ 'sampler-ratio': 1.0
517
+ }
518
+ }
519
+ };
520
+
521
+ const baseConfig = { 'go-duck': extendedConfig };
522
+ await fs.writeFile(path.join(outputDir, 'application.yml'), yaml.dump(baseConfig));
523
+
524
+ const devConfig = {
525
+ 'go-duck': extendedConfig,
526
+ environment: { active_profile: 'dev' }
527
+ };
528
+ await fs.writeFile(path.join(outputDir, 'application-dev.yml'), yaml.dump(devConfig));
529
+
530
+ const prodConfig = {
531
+ 'go-duck': {
532
+ ...extendedConfig,
533
+ server: {
534
+ ...extendedConfig.server,
535
+ cors: {
536
+ ...extendedConfig.server.cors,
537
+ 'allow-origins': ['https://your-domain.com']
538
+ }
539
+ },
540
+ logging: {
541
+ ...extendedConfig.logging,
542
+ datadog: {
543
+ ...extendedConfig.logging.datadog,
544
+ enabled: true
545
+ }
546
+ },
547
+ messaging: {
548
+ mqtt: {
549
+ enabled: true,
550
+ broker: 'tcp://mqtt.production.svc:1883',
551
+ 'client-id': cleanConfig.name + '-prod',
552
+ username: 'prod_user',
553
+ password: 'prod_password',
554
+ 'topic-prefix': 'go-duck/events'
555
+ }
556
+ },
557
+ cache: {
558
+ redis: {
559
+ enabled: true,
560
+ host: 'redis.production.svc:6379',
561
+ password: 'prod_redis_password',
562
+ db: 0,
563
+ ttl: '1h'
564
+ }
565
+ },
566
+ resilience: {
567
+ 'circuit-breaker': {
568
+ enabled: true,
569
+ 'failure-threshold': 3,
570
+ 'success-threshold': 5,
571
+ timeout: '30s'
572
+ }
573
+ },
574
+ telemetry: {
575
+ otel: {
576
+ enabled: true,
577
+ endpoint: 'otel-collector.monitoring.svc:4317',
578
+ 'sampler-ratio': 0.1
579
+ }
580
+ },
581
+ datasource: {
582
+ ...extendedConfig.datasource,
583
+ 'max-open-conns': 100,
584
+ 'max-idle-conns': 50
585
+ }
586
+ },
587
+ environment: { active_profile: 'prod' }
588
+ };
589
+ await fs.writeFile(path.join(outputDir, 'application-prod.yml'), yaml.dump(prodConfig));
590
+ };
591
+
592
+ program.parse(process.argv);
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "go-duck-cli",
3
+ "version": "1.0.0",
4
+ "description": "go function generator",
5
+ "main": "index.js",
6
+ "type": "module",
7
+ "bin": {
8
+ "go-duck": "./index.js"
9
+ },
10
+ "scripts": {
11
+ "test": "echo \"Error: no test specified\" && exit 1"
12
+ },
13
+ "author": "heavenscode",
14
+ "license": "ISC",
15
+ "dependencies": {
16
+ "chalk": "^4.1.2",
17
+ "commander": "^14.0.3",
18
+ "fs-extra": "^11.3.4",
19
+ "handlebars": "^4.7.8",
20
+ "inquirer": "^8.2.7",
21
+ "js-yaml": "^4.1.1"
22
+ }
23
+ }