go-duck-cli 1.0.8 ā 1.1.1
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 +30 -15
- package/generators/ai_docs.js +130 -0
- package/generators/broker.js +63 -0
- package/generators/config.js +149 -7
- package/generators/devops.js +210 -43
- package/generators/docs.js +23 -4
- package/generators/elasticsearch.js +263 -0
- package/generators/kratos.js +229 -41
- package/generators/metering.js +280 -48
- package/generators/migrations.js +92 -198
- package/generators/mqtt.js +2 -39
- package/generators/multitenancy.js +274 -71
- package/generators/nats.js +39 -0
- package/generators/outbox.js +171 -0
- package/generators/postgrest.js +7 -3
- package/generators/postman.js +405 -0
- package/generators/repository.js +27 -0
- package/generators/router.js +27 -0
- package/generators/security.js +95 -14
- package/generators/serverless.js +147 -0
- package/generators/storage.js +589 -0
- package/generators/swagger.js +84 -60
- package/generators/telemetry.js +23 -32
- package/generators/websocket.js +55 -21
- package/index.js +481 -116
- package/package.json +6 -4
- package/parser/gdl.js +163 -24
- package/templates/docs/index.html.hbs +5 -5
- package/templates/docs/layout.hbs +221 -62
- package/templates/docs/pages/audit.hbs +83 -35
- package/templates/docs/pages/cli.hbs +18 -0
- package/templates/docs/pages/configuration.hbs +241 -0
- package/templates/docs/pages/datadog.hbs +46 -0
- package/templates/docs/pages/elasticsearch.hbs +121 -0
- package/templates/docs/pages/federation.hbs +241 -0
- package/templates/docs/pages/gdl-advanced.hbs +91 -0
- package/templates/docs/pages/gdl-annotations.hbs +137 -0
- package/templates/docs/pages/gdl-entities.hbs +134 -0
- package/templates/docs/pages/gdl-relationships.hbs +80 -0
- package/templates/docs/pages/gdl.hbs +60 -204
- package/templates/docs/pages/graphql.hbs +58 -44
- package/templates/docs/pages/grpc.hbs +53 -90
- package/templates/docs/pages/hybrid-store.hbs +127 -0
- package/templates/docs/pages/index.hbs +418 -149
- package/templates/docs/pages/keycloak.hbs +43 -0
- package/templates/docs/pages/legend.hbs +116 -0
- package/templates/docs/pages/mosquitto.hbs +39 -0
- package/templates/docs/pages/multitenancy.hbs +139 -71
- package/templates/docs/pages/otel.hbs +40 -0
- package/templates/docs/pages/realtime.hbs +38 -12
- package/templates/docs/pages/redis.hbs +40 -0
- package/templates/docs/pages/rest.hbs +120 -202
- package/templates/docs/pages/saga.hbs +94 -0
- package/templates/docs/pages/security.hbs +150 -44
- package/templates/docs/pages/serverless.hbs +157 -0
- package/templates/docs/pages/storage.hbs +127 -0
- package/templates/docs/pages/wizard.hbs +683 -0
- package/templates/docs/triple_identity_registry.png +0 -0
- package/templates/go/controller.go.hbs +287 -283
- package/templates/go/entity.go.hbs +17 -15
- package/templates/go/main.go.hbs +47 -180
- package/templates/go/migrator.go.hbs +65 -0
- package/templates/go/router.go.hbs +272 -0
- package/templates/graphql/resolver.go.hbs +53 -34
- package/templates/graphql/schema.graphql.hbs +17 -5
- package/templates/kratos/service.go.hbs +169 -34
- package/templates/proto/entity.proto.hbs +10 -14
- package/test_nested.gdl +21 -0
- package/templates/docs/intro.mp4 +0 -0
- package/test_parser.js +0 -9
package/index.js
CHANGED
|
@@ -11,6 +11,9 @@ import path from 'path';
|
|
|
11
11
|
import yaml from 'js-yaml';
|
|
12
12
|
import Handlebars from 'handlebars';
|
|
13
13
|
import chalk from 'chalk';
|
|
14
|
+
import { execSync } from 'child_process';
|
|
15
|
+
import express from 'express';
|
|
16
|
+
import open from 'open';
|
|
14
17
|
import { parseGDL } from './parser/gdl.js';
|
|
15
18
|
import { generateMultitenancy } from './generators/multitenancy.js';
|
|
16
19
|
import { generateLiquibaseChangelogs } from './generators/migrations.js';
|
|
@@ -18,6 +21,7 @@ import { generateMeteringCode } from './generators/metering.js';
|
|
|
18
21
|
import { generateGraphQLCode } from './generators/graphql.js';
|
|
19
22
|
import { generatePostgRESTCode } from './generators/postgrest.js';
|
|
20
23
|
import { generateSwaggerDocs } from './generators/swagger.js';
|
|
24
|
+
import { generatePostmanCollection } from './generators/postman.js';
|
|
21
25
|
import { generateSecurityMiddleware } from './generators/security.js';
|
|
22
26
|
import { generateWebSocketCode } from './generators/websocket.js';
|
|
23
27
|
import { generateConfigLoader } from './generators/config.js';
|
|
@@ -30,6 +34,14 @@ import { generateDeploymentArtifacts } from './generators/devops.js';
|
|
|
30
34
|
import { generateKratosCode } from './generators/kratos.js';
|
|
31
35
|
import { generateRepositoryCode } from './generators/repository.js';
|
|
32
36
|
import { generateDocumentation } from './generators/docs.js';
|
|
37
|
+
import { generateOutbox } from './generators/outbox.js';
|
|
38
|
+
import { generateNATSCode } from './generators/nats.js';
|
|
39
|
+
import { generateBrokerCode } from './generators/broker.js';
|
|
40
|
+
import { generateStorageCode } from './generators/storage.js';
|
|
41
|
+
import { generateElasticsearchLayer } from './generators/elasticsearch.js';
|
|
42
|
+
import { generateServerlessHandler } from './generators/serverless.js';
|
|
43
|
+
import { generateRouterCode } from './generators/router.js';
|
|
44
|
+
import { generateAIDocs } from './generators/ai_docs.js';
|
|
33
45
|
|
|
34
46
|
export const generateAuditCode = async (config, outputDir) => {
|
|
35
47
|
const middlewareDir = path.join(outputDir, 'middleware');
|
|
@@ -50,6 +62,7 @@ import (
|
|
|
50
62
|
type AuditLog struct {
|
|
51
63
|
ID uint \`gorm:"primaryKey" json:"id"\`
|
|
52
64
|
EntityName string \`json:"entityName"\`
|
|
65
|
+
TenantDB string \`json:"tenantDb"\`
|
|
53
66
|
EntityID string \`json:"entityId"\`
|
|
54
67
|
Action string \`json:"action"\` // CREATE, UPDATE, DELETE
|
|
55
68
|
PreviousValue string \`json:"previousValue" gorm:"type:text"\`
|
|
@@ -65,8 +78,7 @@ type AuditLog struct {
|
|
|
65
78
|
package middleware
|
|
66
79
|
|
|
67
80
|
import (
|
|
68
|
-
"
|
|
69
|
-
"io/ioutil"
|
|
81
|
+
"fmt"
|
|
70
82
|
"net/http"
|
|
71
83
|
"time"
|
|
72
84
|
|
|
@@ -77,7 +89,7 @@ import (
|
|
|
77
89
|
|
|
78
90
|
func AuditMiddleware(db *gorm.DB) gin.HandlerFunc {
|
|
79
91
|
return func(c *gin.Context) {
|
|
80
|
-
if c.Request.Method == http.MethodGet {
|
|
92
|
+
if db == nil || c.Request.Method == http.MethodGet {
|
|
81
93
|
c.Next()
|
|
82
94
|
return
|
|
83
95
|
}
|
|
@@ -91,23 +103,30 @@ func AuditMiddleware(db *gorm.DB) gin.HandlerFunc {
|
|
|
91
103
|
if method == http.MethodPost { action = "CREATE" }
|
|
92
104
|
if method == http.MethodDelete { action = "DELETE" }
|
|
93
105
|
|
|
94
|
-
//
|
|
95
|
-
userEmail := c.
|
|
96
|
-
|
|
106
|
+
// Extract Identity from context
|
|
107
|
+
userEmail, _ := c.Get("UserEmail")
|
|
108
|
+
emailStr := "anonymous"
|
|
109
|
+
if email, ok := userEmail.(string); ok { emailStr = email }
|
|
110
|
+
|
|
111
|
+
keycloakId, _ := c.Get("KeycloakID")
|
|
112
|
+
kidStr := ""
|
|
113
|
+
if kid, ok := keycloakId.(string); ok { kidStr = kid }
|
|
97
114
|
|
|
98
|
-
keycloakId := c.GetHeader("X-Keycloak-Id")
|
|
99
115
|
clientIP := c.ClientIP()
|
|
100
116
|
|
|
117
|
+
tenant, _ := c.Get("tenantDB")
|
|
118
|
+
tenantStr := fmt.Sprintf("%v", tenant)
|
|
119
|
+
|
|
101
120
|
// Call next handlers
|
|
102
121
|
c.Next()
|
|
103
122
|
|
|
104
123
|
// Logic to capture entity ID and snapshot values would go here...
|
|
105
|
-
// For now, track the action
|
|
106
124
|
auditEntry := models.AuditLog{
|
|
107
125
|
EntityName: path,
|
|
126
|
+
TenantDB: tenantStr,
|
|
108
127
|
Action: action,
|
|
109
|
-
ModifiedBy:
|
|
110
|
-
KeycloakID:
|
|
128
|
+
ModifiedBy: emailStr,
|
|
129
|
+
KeycloakID: kidStr,
|
|
111
130
|
ModifiedAt: time.Now(),
|
|
112
131
|
ClientIP: clientIP,
|
|
113
132
|
}
|
|
@@ -148,15 +167,26 @@ const program = new Command();
|
|
|
148
167
|
// Handlebars Helpers
|
|
149
168
|
Handlebars.registerHelper('capitalize', (str) => {
|
|
150
169
|
if (typeof str !== 'string') return '';
|
|
151
|
-
|
|
170
|
+
// Handle snake_case by splitting and joining title-cased parts
|
|
171
|
+
return str.replace(/(?:^|_| )(\w)/g, (match, p1) => p1.toUpperCase());
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
Handlebars.registerHelper('toProtoFieldName', (str) => {
|
|
175
|
+
if (typeof str !== 'string') return '';
|
|
176
|
+
// Protoc-gen-go converts snake_case to TitleCase (e.g. site_id -> SiteId)
|
|
177
|
+
// But it ALSO removes underscores. So we split and capitalize.
|
|
178
|
+
return str.split('_').map(s => s.charAt(0).toUpperCase() + s.slice(1)).join('');
|
|
152
179
|
});
|
|
153
180
|
|
|
154
181
|
Handlebars.registerHelper('hasJson', (fields) => {
|
|
155
182
|
if (!fields || !Array.isArray(fields)) return false;
|
|
156
|
-
return fields.some(f => f.type === '
|
|
183
|
+
return fields.some(f => (f.type || '').toLowerCase() === 'json' || (f.type || '').toLowerCase() === 'jsonb');
|
|
157
184
|
});
|
|
158
185
|
|
|
159
|
-
Handlebars.registerHelper('isJson', (type) =>
|
|
186
|
+
Handlebars.registerHelper('isJson', (type) => {
|
|
187
|
+
const t = (type || '').toLowerCase();
|
|
188
|
+
return t === 'json' || t === 'jsonb';
|
|
189
|
+
});
|
|
160
190
|
|
|
161
191
|
Handlebars.registerHelper('toLowerCase', (str) => {
|
|
162
192
|
if (typeof str !== 'string') return '';
|
|
@@ -165,23 +195,30 @@ Handlebars.registerHelper('toLowerCase', (str) => {
|
|
|
165
195
|
|
|
166
196
|
Handlebars.registerHelper('toGoType', (type, options) => {
|
|
167
197
|
const enums = options.data.root.enums || [];
|
|
168
|
-
const isEnum = enums.some(e => e.name === type);
|
|
169
|
-
if (isEnum)
|
|
198
|
+
const isEnum = enums.some(e => e.name.toLowerCase() === type.toLowerCase());
|
|
199
|
+
if (isEnum) {
|
|
200
|
+
const en = enums.find(e => e.name.toLowerCase() === type.toLowerCase());
|
|
201
|
+
return en.name;
|
|
202
|
+
}
|
|
170
203
|
|
|
204
|
+
const t = type.toLowerCase();
|
|
171
205
|
const types = {
|
|
172
|
-
'
|
|
173
|
-
'
|
|
174
|
-
'
|
|
175
|
-
'
|
|
176
|
-
'
|
|
177
|
-
'
|
|
178
|
-
'
|
|
179
|
-
'
|
|
180
|
-
'
|
|
181
|
-
'
|
|
182
|
-
'
|
|
206
|
+
'string': 'string',
|
|
207
|
+
'text': 'string',
|
|
208
|
+
'integer': 'int',
|
|
209
|
+
'int': 'int',
|
|
210
|
+
'float': 'float64',
|
|
211
|
+
'boolean': 'bool',
|
|
212
|
+
'bool': 'bool',
|
|
213
|
+
'long': 'int64',
|
|
214
|
+
'bigdecimal': 'float64',
|
|
215
|
+
'localdate': 'time.Time',
|
|
216
|
+
'instant': 'time.Time',
|
|
217
|
+
'datetime': 'time.Time',
|
|
218
|
+
'json': 'datatypes.JSON',
|
|
219
|
+
'jsonb': 'datatypes.JSON'
|
|
183
220
|
};
|
|
184
|
-
return types[
|
|
221
|
+
return types[t] || 'interface{}';
|
|
185
222
|
});
|
|
186
223
|
|
|
187
224
|
Handlebars.registerHelper('gql_type', (type, options) => {
|
|
@@ -204,8 +241,53 @@ Handlebars.registerHelper('gql_type', (type, options) => {
|
|
|
204
241
|
return types[type] || 'String';
|
|
205
242
|
});
|
|
206
243
|
|
|
244
|
+
Handlebars.registerHelper('isAnyOperationOpen', (entityName, openEntities) => {
|
|
245
|
+
if (!openEntities || !Array.isArray(openEntities)) return false;
|
|
246
|
+
const wildcard = openEntities.find(e => e.name === '*');
|
|
247
|
+
if (wildcard && wildcard.actions && wildcard.actions.some(a => a !== 'none' && a !== '')) return true;
|
|
248
|
+
const entry = openEntities.find(e => e.name.toLowerCase() === entityName.toLowerCase());
|
|
249
|
+
return !!(entry && entry.actions && entry.actions.some(a => a !== 'none' && a !== ''));
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
Handlebars.registerHelper('isOpen', (entityName, openEntities, action) => {
|
|
253
|
+
if (!openEntities || !Array.isArray(openEntities)) return false;
|
|
254
|
+
|
|
255
|
+
// Check wildcard first
|
|
256
|
+
const wildcard = openEntities.find(e => e.name === '*');
|
|
257
|
+
if (wildcard) {
|
|
258
|
+
if (typeof action !== 'string') return true;
|
|
259
|
+
if (wildcard.actions.includes(action.toLowerCase())) return true;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const entry = openEntities.find(e => e.name.toLowerCase() === entityName.toLowerCase());
|
|
263
|
+
if (entry) {
|
|
264
|
+
if (typeof action !== 'string') return true;
|
|
265
|
+
if (entry.actions.includes(action.toLowerCase())) return true;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return false;
|
|
269
|
+
});
|
|
270
|
+
|
|
207
271
|
Handlebars.registerHelper('eq', (a, b) => a === b);
|
|
208
272
|
|
|
273
|
+
Handlebars.registerHelper('or', function() {
|
|
274
|
+
const args = Array.prototype.slice.call(arguments, 0, -1);
|
|
275
|
+
return args.some(Boolean);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// Recursive field rendering for multi-level JSON
|
|
279
|
+
Handlebars.registerPartial('renderFields', `
|
|
280
|
+
{{#each fields}}
|
|
281
|
+
{{#if isNested}}
|
|
282
|
+
{{capitalize name}} struct {
|
|
283
|
+
{{> renderFields fields=children isDocument=../isDocument}}
|
|
284
|
+
} \`json:"{{name}}"{{#if ../isDocument}} bson:"{{name}}"{{/if}}\`
|
|
285
|
+
{{else}}
|
|
286
|
+
{{capitalize name}} {{toGoType type}} \`{{#if ../isDocument}}bson:"{{#if (eq name "id")}}_id{{else}}{{name}}{{/if}}{{#if (eq name "id")}},omitempty{{/if}}"{{else}}{{#if (eq name "id")}}gorm:"primaryKey"{{else}}{{#if unique}}gorm:"uniqueIndex"{{/if}}{{/if}}{{/if}} {{#if (isJson type)}}gorm:"type:{{toLowerCase type}};serializer:json"{{/if}} json:"{{name}}"{{#if required}} binding:"required"{{/if}}\`
|
|
287
|
+
{{/if}}
|
|
288
|
+
{{/each}}
|
|
289
|
+
`);
|
|
290
|
+
|
|
209
291
|
program
|
|
210
292
|
.name('go-duck-cli')
|
|
211
293
|
.description('A powerful Go code generator for microservices')
|
|
@@ -216,7 +298,7 @@ const loadConfig = async (configPath) => {
|
|
|
216
298
|
try {
|
|
217
299
|
const fileContents = await fs.readFile(configPath, 'utf8');
|
|
218
300
|
const config = yaml.load(fileContents);
|
|
219
|
-
const appConfig = config.app || {};
|
|
301
|
+
const appConfig = config['go-duck'] || config.app || config || {};
|
|
220
302
|
return appConfig;
|
|
221
303
|
} catch (error) {
|
|
222
304
|
console.error(chalk.red(`Error loading config from ${configPath}:`), error.message);
|
|
@@ -245,14 +327,46 @@ const getPreviousEntities = async (outputDir) => {
|
|
|
245
327
|
return entities;
|
|
246
328
|
};
|
|
247
329
|
|
|
248
|
-
const generateEntities = async (
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
330
|
+
const generateEntities = async (gdlPath, outputDir, config) => {
|
|
331
|
+
let entities = [];
|
|
332
|
+
let relationships = [];
|
|
333
|
+
let enums = [];
|
|
334
|
+
let openEntities = [];
|
|
335
|
+
|
|
336
|
+
if (!await fs.pathExists(gdlPath)) {
|
|
337
|
+
console.log(chalk.yellow(`ā ļø GDL path not found: ${gdlPath} - Executing Zero-Entity Infrastructure Scaffold.`));
|
|
338
|
+
const delta = { newEntities: [], newFields: {}, newRelationships: [] };
|
|
339
|
+
await generateLiquibaseChangelogs(entities, relationships, outputDir, delta, enums);
|
|
340
|
+
return { entities, relationships, enums, openEntities };
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const stats = await fs.stat(gdlPath);
|
|
344
|
+
const files = stats.isDirectory()
|
|
345
|
+
? (await fs.readdir(gdlPath)).filter(f => f.endsWith('.gdl')).map(f => path.join(gdlPath, f))
|
|
346
|
+
: [gdlPath];
|
|
347
|
+
|
|
348
|
+
if (files.length === 0) {
|
|
349
|
+
console.log(chalk.yellow(`ā ļø No .gdl files found in: ${gdlPath} - Executing Zero-Entity Infrastructure Scaffold.`));
|
|
350
|
+
const delta = { newEntities: [], newFields: {}, newRelationships: [] };
|
|
351
|
+
await generateLiquibaseChangelogs(entities, relationships, outputDir, delta, enums);
|
|
352
|
+
return { entities, relationships, enums, openEntities };
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
console.log(chalk.blue(`š Loading ${files.length} GDL file(s)...`));
|
|
356
|
+
|
|
357
|
+
for (const file of files) {
|
|
358
|
+
const result = await parseGDL(file);
|
|
359
|
+
entities = [...entities, ...result.entities];
|
|
360
|
+
relationships = [...relationships, ...result.relationships];
|
|
361
|
+
enums = [...enums, ...result.enums];
|
|
362
|
+
openEntities = [...openEntities, ...result.openEntities];
|
|
252
363
|
}
|
|
253
364
|
|
|
254
|
-
|
|
255
|
-
|
|
365
|
+
// Deduplicate entities and enums by name
|
|
366
|
+
entities = Array.from(new Map(entities.map(e => [e.name, e])).values());
|
|
367
|
+
enums = Array.from(new Map(enums.map(e => [e.name, e])).values());
|
|
368
|
+
|
|
369
|
+
console.log(chalk.green(`ā
Parsed ${entities.length} entities, ${relationships.length} relationships, ${enums.length} enums, and detected ${openEntities.length} open rules`));
|
|
256
370
|
|
|
257
371
|
const previousEntities = await getPreviousEntities(outputDir);
|
|
258
372
|
const delta = {
|
|
@@ -308,9 +422,20 @@ const generateEntities = async (gdlFilePath, outputDir, config) => {
|
|
|
308
422
|
}
|
|
309
423
|
|
|
310
424
|
for (const entity of entities) {
|
|
311
|
-
entity.relationships = relationships.filter(r => r.from.entity === entity.name || r.to.entity === entity.name)
|
|
425
|
+
entity.relationships = relationships.filter(r => r.from.entity === entity.name || r.to.entity === entity.name).map(r => {
|
|
426
|
+
const fromEnt = entities.find(e => e.name === r.from.entity);
|
|
427
|
+
const toEnt = entities.find(e => e.name === r.to.entity);
|
|
428
|
+
return {
|
|
429
|
+
...r,
|
|
430
|
+
fromIsDocument: fromEnt ? fromEnt.isDocument : false,
|
|
431
|
+
toIsDocument: toEnt ? toEnt.isDocument : false
|
|
432
|
+
};
|
|
433
|
+
});
|
|
312
434
|
entity.app_name = config.name;
|
|
313
|
-
entity.enums = enums;
|
|
435
|
+
entity.enums = enums;
|
|
436
|
+
entity.openEntities = openEntities; // Pass open entities to template
|
|
437
|
+
// Use already parsed flags from gdl.js
|
|
438
|
+
// entity.isSearchable, entity.isFederated, entity.isAudited, entity.isDocument are already set correctly.
|
|
314
439
|
|
|
315
440
|
// Generate Model
|
|
316
441
|
const entityContent = entityTemplate(entity);
|
|
@@ -320,7 +445,7 @@ const generateEntities = async (gdlFilePath, outputDir, config) => {
|
|
|
320
445
|
const controllerContent = controllerTemplate(entity);
|
|
321
446
|
await fs.writeFile(path.join(outputDir, 'controllers', `${entity.name.toLowerCase()}_controller.go`), controllerContent);
|
|
322
447
|
|
|
323
|
-
console.log(chalk.gray(` - Updated Entity & Controller: ${entity.name}`));
|
|
448
|
+
console.log(chalk.gray(` - Updated Entity & Controller: ${entity.name} (Mongo: ${entity.isDocument || false})`));
|
|
324
449
|
|
|
325
450
|
// Save Snapshot for next comparison
|
|
326
451
|
await saveEntitySnapshot(outputDir, entity);
|
|
@@ -328,9 +453,9 @@ const generateEntities = async (gdlFilePath, outputDir, config) => {
|
|
|
328
453
|
|
|
329
454
|
// Generate Incremental Changelogs!
|
|
330
455
|
await generateLiquibaseChangelogs(entities, relationships, outputDir, delta, enums);
|
|
331
|
-
console.log(chalk.green('ā
|
|
456
|
+
console.log(chalk.green('ā
Goose SQL incremental migrations updated!'));
|
|
332
457
|
|
|
333
|
-
return { entities, relationships, enums };
|
|
458
|
+
return { entities, relationships, enums, openEntities };
|
|
334
459
|
};
|
|
335
460
|
|
|
336
461
|
program
|
|
@@ -349,6 +474,18 @@ program
|
|
|
349
474
|
const config = await loadConfig(path.resolve(process.cwd(), configPath));
|
|
350
475
|
console.log(chalk.green(`ā
Config loaded for app: ${config.name}`));
|
|
351
476
|
|
|
477
|
+
// Cleanup legacy files if they exist in the root
|
|
478
|
+
const legacyFiles = ['Dockerfile', 'docker-compose.yml'];
|
|
479
|
+
const legacyDirs = ['k8s', 'realm-config'];
|
|
480
|
+
for (const f of legacyFiles) {
|
|
481
|
+
const p = path.join(absoluteOutputDir, f);
|
|
482
|
+
if (await fs.pathExists(p)) await fs.remove(p);
|
|
483
|
+
}
|
|
484
|
+
for (const d of legacyDirs) {
|
|
485
|
+
const p = path.join(absoluteOutputDir, d);
|
|
486
|
+
if (await fs.pathExists(p)) await fs.remove(p);
|
|
487
|
+
}
|
|
488
|
+
|
|
352
489
|
await generateConfigLoader(absoluteOutputDir);
|
|
353
490
|
await generateLoggerCode(config, absoluteOutputDir);
|
|
354
491
|
await generateMQTTCode(config, absoluteOutputDir);
|
|
@@ -357,48 +494,135 @@ program
|
|
|
357
494
|
await generateTelemetryCode(config, absoluteOutputDir);
|
|
358
495
|
await generateDeploymentArtifacts(config, absoluteOutputDir);
|
|
359
496
|
await generateYAMLConfigs(config, absoluteOutputDir);
|
|
360
|
-
const { entities, relationships, enums } = await generateEntities(path.
|
|
497
|
+
const { entities, relationships, enums, openEntities } = await generateEntities(path.resolve(process.cwd(), gdlDir), absoluteOutputDir, config);
|
|
361
498
|
await generateKratosCode(entities, absoluteOutputDir, config.name, enums);
|
|
362
499
|
|
|
363
500
|
await generateRepositoryCode(absoluteOutputDir);
|
|
501
|
+
|
|
502
|
+
{
|
|
503
|
+
const migratorTemplatePath = path.resolve(path.dirname(import.meta.url.replace('file://', '')), 'templates/go/migrator.go.hbs');
|
|
504
|
+
if (await fs.pathExists(migratorTemplatePath)) {
|
|
505
|
+
const migratorTemplate = Handlebars.compile(await fs.readFile(migratorTemplatePath, 'utf8'));
|
|
506
|
+
await fs.writeFile(path.join(absoluteOutputDir, 'migrations/migrations.go'), migratorTemplate({ app_name: config.name }));
|
|
507
|
+
}
|
|
508
|
+
}
|
|
364
509
|
|
|
365
510
|
await generateGraphQLCode(config, entities, relationships, absoluteOutputDir, enums);
|
|
366
|
-
|
|
511
|
+
await generatePostgRESTCode(config, absoluteOutputDir);
|
|
512
|
+
if (config.multitenancy?.enabled) await generateMultitenancy(config, absoluteOutputDir, entities);
|
|
367
513
|
await generateAuditCode(config, absoluteOutputDir);
|
|
368
514
|
await generateMeteringCode(config, absoluteOutputDir);
|
|
369
515
|
await generateSecurityMiddleware(config, absoluteOutputDir);
|
|
370
516
|
await generateWebSocketCode(config, entities, absoluteOutputDir);
|
|
371
|
-
await
|
|
372
|
-
|
|
517
|
+
await generateOutbox(absoluteOutputDir, config);
|
|
518
|
+
await generateNATSCode(config, absoluteOutputDir);
|
|
519
|
+
await generateBrokerCode(config, absoluteOutputDir);
|
|
520
|
+
await generateStorageCode(config, absoluteOutputDir, path.dirname(path.resolve(process.cwd(), configPath)));
|
|
521
|
+
await generateRouterCode(absoluteOutputDir, config, entities, openEntities);
|
|
522
|
+
await generateElasticsearchLayer(config, entities, absoluteOutputDir);
|
|
523
|
+
await generateServerlessHandler(absoluteOutputDir, config);
|
|
524
|
+
|
|
525
|
+
console.log(chalk.green('\nš Go-Duck project generated successfully!'));
|
|
373
526
|
|
|
374
527
|
// 8. Generate Swagger Docs
|
|
375
|
-
await generateSwaggerDocs(config, entities, absoluteOutputDir);
|
|
376
|
-
|
|
528
|
+
await generateSwaggerDocs(config, entities, absoluteOutputDir, openEntities);
|
|
529
|
+
await generatePostmanCollection(config, entities, absoluteOutputDir, openEntities);
|
|
530
|
+
console.log(chalk.green('ā
Swagger API and Postman Collection generated!'));
|
|
377
531
|
|
|
378
532
|
// 8.5 Generate Web Docs App
|
|
379
|
-
await generateDocumentation(config, entities, absoluteOutputDir, enums);
|
|
533
|
+
await generateDocumentation(config, entities, absoluteOutputDir, enums, openEntities);
|
|
380
534
|
console.log(chalk.green('ā
Web Documentation App generated!'));
|
|
381
535
|
|
|
536
|
+
// 8.6 Generate AI/LLM Blueprint Documentation
|
|
537
|
+
await generateAIDocs(config, entities, absoluteOutputDir, enums, openEntities);
|
|
538
|
+
console.log(chalk.green('š¤ System-level AI Blueprint Documentation generated!'));
|
|
539
|
+
|
|
382
540
|
// 9. Generate main.go
|
|
383
541
|
const mainTemplatePath = path.resolve(path.dirname(import.meta.url.replace('file://', '')), 'templates/go/main.go.hbs');
|
|
384
542
|
if (await fs.pathExists(mainTemplatePath)) {
|
|
385
543
|
const mainTemplateSource = await fs.readFile(mainTemplatePath, 'utf8');
|
|
386
544
|
const mainTemplate = Handlebars.compile(mainTemplateSource);
|
|
387
|
-
await fs.writeFile(path.join(absoluteOutputDir, 'main.go'), mainTemplate({ app_name: config.name, entities }));
|
|
545
|
+
await fs.writeFile(path.join(absoluteOutputDir, 'main.go'), mainTemplate({ app_name: config.name, entities, openEntities }));
|
|
388
546
|
console.log(chalk.green('ā
main.go entry point created!'));
|
|
389
547
|
}
|
|
548
|
+
|
|
549
|
+
// 10. Generate go.mod
|
|
550
|
+
const goModContent = `module ${config.name}
|
|
551
|
+
|
|
552
|
+
go 1.24
|
|
553
|
+
|
|
554
|
+
require (
|
|
555
|
+
github.com/gin-gonic/gin v1.10.0
|
|
556
|
+
github.com/go-kratos/kratos/v2 v2.8.2
|
|
557
|
+
github.com/google/uuid v1.6.0
|
|
558
|
+
github.com/spf13/viper v1.19.0
|
|
559
|
+
go.opentelemetry.io/otel v1.26.0
|
|
560
|
+
go.opentelemetry.io/otel/trace v1.26.0
|
|
561
|
+
go.opentelemetry.io/otel/sdk v1.26.0
|
|
562
|
+
go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin v0.51.0
|
|
563
|
+
google.golang.org/grpc v1.64.0
|
|
564
|
+
gorm.io/datatypes v1.2.0
|
|
565
|
+
gorm.io/driver/postgres v1.5.7
|
|
566
|
+
gorm.io/gorm v1.25.10
|
|
567
|
+
gorm.io/plugin/opentelemetry v0.1.5
|
|
568
|
+
github.com/golang-jwt/jwt/v5 v5.2.1
|
|
569
|
+
github.com/eclipse/paho.mqtt.golang v1.4.3
|
|
570
|
+
github.com/sony/gobreaker v0.5.0
|
|
571
|
+
github.com/redis/go-redis/v9 v9.5.1
|
|
572
|
+
github.com/gorilla/websocket v1.5.1
|
|
573
|
+
github.com/99designs/gqlgen v0.17.44
|
|
574
|
+
github.com/vektah/gqlparser/v2 v2.5.11
|
|
575
|
+
github.com/aws/aws-lambda-go v1.47.0
|
|
576
|
+
github.com/awslabs/aws-lambda-go-api-proxy v0.16.1
|
|
577
|
+
github.com/GoogleCloudPlatform/functions-framework-go v1.8.1
|
|
578
|
+
go.mongodb.org/mongo-driver v1.17.1
|
|
579
|
+
github.com/gin-contrib/cors v1.7.1
|
|
580
|
+
github.com/go-resty/resty/v2 v2.16.0
|
|
581
|
+
github.com/olivere/elastic/v7 v7.0.32
|
|
582
|
+
github.com/nats-io/nats.go v1.38.0
|
|
583
|
+
)
|
|
584
|
+
`;
|
|
585
|
+
await fs.writeFile(path.join(absoluteOutputDir, 'go.mod'), goModContent);
|
|
586
|
+
console.log(chalk.green('ā
go.mod file created!'));
|
|
587
|
+
|
|
588
|
+
// Post-generation: Run go mod tidy
|
|
589
|
+
try {
|
|
590
|
+
console.log(chalk.blue('Cleaning up Go modules (go mod tidy)...'));
|
|
591
|
+
execSync('go mod tidy', { cwd: absoluteOutputDir, stdio: 'inherit' });
|
|
592
|
+
console.log(chalk.green('ā
Go modules tidied!'));
|
|
593
|
+
} catch (error) {
|
|
594
|
+
console.warn(chalk.yellow('ā ļø Could not run go mod tidy. Please run it manually.'));
|
|
595
|
+
}
|
|
596
|
+
|
|
390
597
|
console.log(chalk.bold.magenta('\n⨠Project created successfully!'));
|
|
391
598
|
});
|
|
392
599
|
|
|
393
600
|
program
|
|
394
|
-
.command('import-gdl <
|
|
395
|
-
.description('Import entities from a GDL file into an existing app')
|
|
601
|
+
.command('import-gdl <path>')
|
|
602
|
+
.description('Import entities from a GDL file or directory into an existing app')
|
|
396
603
|
.option('-o, --output <path>', 'Path to the existing app root', '.')
|
|
397
|
-
.action(async (
|
|
604
|
+
.action(async (gdlPath, options) => {
|
|
398
605
|
const absoluteOutputDir = path.resolve(process.cwd(), options.output);
|
|
399
|
-
console.log(chalk.blue(`š„ Importing GDL from ${
|
|
606
|
+
console.log(chalk.blue(`š„ Importing GDL from ${gdlPath}...`));
|
|
607
|
+
|
|
608
|
+
let configPath = path.resolve(process.cwd(), './CONFIG/config.yaml');
|
|
609
|
+
if (!await fs.pathExists(configPath)) {
|
|
610
|
+
configPath = path.resolve(process.cwd(), '../CONFIG/config.yaml');
|
|
611
|
+
}
|
|
612
|
+
const config = await loadConfig(configPath);
|
|
613
|
+
|
|
614
|
+
// Cleanup legacy files if they exist in the root
|
|
615
|
+
const legacyFiles = ['Dockerfile', 'docker-compose.yml'];
|
|
616
|
+
const legacyDirs = ['k8s', 'realm-config'];
|
|
617
|
+
for (const f of legacyFiles) {
|
|
618
|
+
const p = path.join(absoluteOutputDir, f);
|
|
619
|
+
if (await fs.pathExists(p)) await fs.remove(p);
|
|
620
|
+
}
|
|
621
|
+
for (const d of legacyDirs) {
|
|
622
|
+
const p = path.join(absoluteOutputDir, d);
|
|
623
|
+
if (await fs.pathExists(p)) await fs.remove(p);
|
|
624
|
+
}
|
|
400
625
|
|
|
401
|
-
const config = await loadConfig(path.resolve(process.cwd(), '../CONFIG/config.yaml'));
|
|
402
626
|
await generateConfigLoader(absoluteOutputDir);
|
|
403
627
|
await generateLoggerCode(config, absoluteOutputDir);
|
|
404
628
|
await generateMQTTCode(config, absoluteOutputDir);
|
|
@@ -406,34 +630,52 @@ program
|
|
|
406
630
|
await generateResilienceCode(config, absoluteOutputDir);
|
|
407
631
|
await generateTelemetryCode(config, absoluteOutputDir);
|
|
408
632
|
await generateDeploymentArtifacts(config, absoluteOutputDir);
|
|
409
|
-
const { entities, relationships, enums } = await generateEntities(
|
|
633
|
+
const { entities, relationships, enums, openEntities } = await generateEntities(gdlPath, absoluteOutputDir, config);
|
|
410
634
|
await generateKratosCode(entities, absoluteOutputDir, config.name, enums);
|
|
411
635
|
|
|
412
636
|
await generateRepositoryCode(absoluteOutputDir);
|
|
413
637
|
|
|
414
638
|
await generateGraphQLCode(config, entities, relationships, absoluteOutputDir, enums);
|
|
415
|
-
// Sync PostgREST search as well
|
|
416
639
|
await generatePostgRESTCode(config, absoluteOutputDir);
|
|
417
640
|
|
|
418
|
-
// Sync Security
|
|
641
|
+
// Sync Search & Security
|
|
642
|
+
await generateElasticsearchLayer(config, entities, absoluteOutputDir);
|
|
419
643
|
await generateSecurityMiddleware(config, absoluteOutputDir);
|
|
420
644
|
|
|
421
645
|
// Sync WebSocket
|
|
422
646
|
await generateWebSocketCode(config, entities, absoluteOutputDir);
|
|
423
647
|
|
|
424
648
|
// Sync Swagger Docs
|
|
425
|
-
await generateSwaggerDocs(config, entities, absoluteOutputDir);
|
|
649
|
+
await generateSwaggerDocs(config, entities, absoluteOutputDir, openEntities);
|
|
650
|
+
await generatePostmanCollection(config, entities, absoluteOutputDir, openEntities);
|
|
426
651
|
|
|
427
652
|
// Sync Web Docs App
|
|
428
|
-
await generateDocumentation(config, entities, absoluteOutputDir, enums);
|
|
653
|
+
await generateDocumentation(config, entities, absoluteOutputDir, enums, openEntities);
|
|
654
|
+
|
|
655
|
+
// Sync AI Blueprints
|
|
656
|
+
await generateAIDocs(config, entities, absoluteOutputDir, enums, openEntities);
|
|
657
|
+
// Sync Serverless & Router
|
|
658
|
+
await generateRouterCode(absoluteOutputDir, config, entities, openEntities);
|
|
659
|
+
await generateServerlessHandler(absoluteOutputDir, config);
|
|
429
660
|
|
|
430
661
|
// Regenerate main.go to include new routes if any (or just entities)
|
|
662
|
+
const timestamp = new Date().toISOString().replace(/[-:T]/g, '').split('.')[0];
|
|
431
663
|
const mainTemplatePath = path.resolve(path.dirname(import.meta.url.replace('file://', '')), 'templates/go/main.go.hbs');
|
|
432
664
|
if (await fs.pathExists(mainTemplatePath)) {
|
|
433
665
|
const mainTemplate = Handlebars.compile(await fs.readFile(mainTemplatePath, 'utf8'));
|
|
434
|
-
await fs.writeFile(path.join(absoluteOutputDir, 'main.go'), mainTemplate({ app_name: config.name, entities }));
|
|
666
|
+
await fs.writeFile(path.join(absoluteOutputDir, 'main.go'), mainTemplate({ app_name: config.name, entities, openEntities, timestamp }));
|
|
435
667
|
console.log(chalk.green('ā
Updated main.go to register new entity routes.'));
|
|
436
668
|
}
|
|
669
|
+
|
|
670
|
+
// Run go mod tidy after import as well
|
|
671
|
+
try {
|
|
672
|
+
console.log(chalk.blue('Cleaning up Go modules (go mod tidy)...'));
|
|
673
|
+
execSync('go mod tidy', { cwd: absoluteOutputDir, stdio: 'inherit' });
|
|
674
|
+
console.log(chalk.green('ā
Go modules tidied!'));
|
|
675
|
+
} catch (error) {
|
|
676
|
+
console.warn(chalk.yellow('ā ļø Could not run go mod tidy. Please run it manually.'));
|
|
677
|
+
}
|
|
678
|
+
|
|
437
679
|
console.log(chalk.bold.magenta('\n⨠GDL Import Completed with Incremental Migrations! āØ'));
|
|
438
680
|
});
|
|
439
681
|
|
|
@@ -447,74 +689,159 @@ const generateYAMLConfigs = async (config, outputDir) => {
|
|
|
447
689
|
const extendedConfig = {
|
|
448
690
|
...cleanConfig,
|
|
449
691
|
server: {
|
|
450
|
-
port: 8080,
|
|
451
|
-
'read-timeout': '30s',
|
|
452
|
-
'write-timeout': '30s',
|
|
692
|
+
port: cleanConfig.server?.port || 8080,
|
|
693
|
+
'read-timeout': cleanConfig.server?.['read-timeout'] || '30s',
|
|
694
|
+
'write-timeout': cleanConfig.server?.['write-timeout'] || '30s',
|
|
453
695
|
grpc: {
|
|
454
|
-
addr: ':9000',
|
|
455
|
-
network: 'tcp',
|
|
456
|
-
timeout: '1s'
|
|
696
|
+
addr: cleanConfig.server?.grpc?.addr || ':9000',
|
|
697
|
+
network: cleanConfig.server?.grpc?.network || 'tcp',
|
|
698
|
+
timeout: cleanConfig.server?.grpc?.timeout || '1s'
|
|
457
699
|
},
|
|
458
700
|
cors: {
|
|
459
|
-
'allow-origins': ['*'],
|
|
460
|
-
'allow-methods': ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
|
|
461
|
-
'allow-headers': ['Origin', 'Content-Type', 'Accept', 'Authorization']
|
|
701
|
+
'allow-origins': cleanConfig.server?.cors?.['allow-origins'] || ['*'],
|
|
702
|
+
'allow-methods': cleanConfig.server?.cors?.['allow-methods'] || ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
|
|
703
|
+
'allow-headers': cleanConfig.server?.cors?.['allow-headers'] || ['Origin', 'Content-Type', 'Accept', 'Authorization', 'X-Tenant-ID']
|
|
462
704
|
}
|
|
463
705
|
},
|
|
464
706
|
datasource: {
|
|
465
707
|
...cleanConfig.datasource,
|
|
466
|
-
'
|
|
467
|
-
'max-
|
|
468
|
-
'
|
|
708
|
+
'ssl-mode': cleanConfig.datasource?.['ssl-mode'] || 'disable',
|
|
709
|
+
'max-open-conns': cleanConfig.datasource?.['max-open-conns'] || 25,
|
|
710
|
+
'max-idle-conns': cleanConfig.datasource?.['max-idle-conns'] || 10,
|
|
711
|
+
'conn-max-lifetime': cleanConfig.datasource?.['conn-max-lifetime'] || '5m',
|
|
712
|
+
mongodb: {
|
|
713
|
+
enabled: cleanConfig.datasource?.mongodb?.enabled || false,
|
|
714
|
+
uri: cleanConfig.datasource?.mongodb?.uri || 'mongodb://localhost:27017',
|
|
715
|
+
database: cleanConfig.datasource?.mongodb?.database || cleanConfig.name || 'go_duck_mongo'
|
|
716
|
+
}
|
|
469
717
|
},
|
|
470
718
|
security: {
|
|
471
719
|
...cleanConfig.security,
|
|
720
|
+
'keycloak-app-client-id': cleanConfig.security?.['keycloak-app-client-id'] || `${cleanConfig.name || 'go-duck'}-app`,
|
|
721
|
+
'keycloak-app-client-secret': cleanConfig.security?.['keycloak-app-client-secret'] || 'app-secret-123',
|
|
722
|
+
'keycloak-service-client-id': cleanConfig.security?.['keycloak-service-client-id'] || `${cleanConfig.name || 'go-duck'}-service`,
|
|
723
|
+
'keycloak-service-secret': cleanConfig.security?.['keycloak-service-secret'] || 'service-secret-123',
|
|
724
|
+
'keycloak-admin-client-id': cleanConfig.security?.['keycloak-admin-client-id'] || 'admin-cli',
|
|
725
|
+
'keycloak-admin-secret': cleanConfig.security?.['keycloak-admin-secret'] || 'admin-secret-123',
|
|
726
|
+
'super-admin-role': cleanConfig.security?.['super-admin-role'] || 'admin',
|
|
727
|
+
'confidential-mode': cleanConfig.security?.['confidential-mode'] ?? true,
|
|
472
728
|
'rate-limit': {
|
|
473
|
-
rps: 100,
|
|
474
|
-
burst: 200
|
|
729
|
+
rps: cleanConfig.security?.['rate-limit']?.rps || 100,
|
|
730
|
+
burst: cleanConfig.security?.['rate-limit']?.burst || 200
|
|
475
731
|
}
|
|
476
732
|
},
|
|
477
733
|
logging: {
|
|
478
734
|
datadog: {
|
|
479
|
-
enabled: false,
|
|
480
|
-
'api-key': 'YOUR_DATADOG_API_KEY',
|
|
481
|
-
site: 'datadoghq.com',
|
|
482
|
-
service: cleanConfig.name || 'go-duck-service'
|
|
735
|
+
enabled: cleanConfig.logging?.datadog?.enabled || false,
|
|
736
|
+
'api-key': cleanConfig.logging?.datadog?.['api-key'] || 'YOUR_DATADOG_API_KEY',
|
|
737
|
+
site: cleanConfig.logging?.datadog?.site || 'datadoghq.com',
|
|
738
|
+
service: cleanConfig.logging?.datadog?.service || cleanConfig.name || 'go-duck-service'
|
|
483
739
|
}
|
|
484
740
|
},
|
|
485
741
|
messaging: {
|
|
486
742
|
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'
|
|
743
|
+
enabled: cleanConfig.messaging?.mqtt?.enabled || false,
|
|
744
|
+
broker: cleanConfig.messaging?.mqtt?.broker || 'tcp://localhost:1883',
|
|
745
|
+
'client-id': cleanConfig.messaging?.mqtt?.['client-id'] || (cleanConfig.name || 'go-duck') + '-dev',
|
|
746
|
+
username: cleanConfig.messaging?.mqtt?.username || 'dev_user',
|
|
747
|
+
password: cleanConfig.messaging?.mqtt?.password || 'dev_password',
|
|
748
|
+
'topic-prefix': cleanConfig.messaging?.mqtt?.['topic-prefix'] || 'go-duck/events'
|
|
493
749
|
}
|
|
494
750
|
},
|
|
495
751
|
cache: {
|
|
496
752
|
redis: {
|
|
497
|
-
enabled: false,
|
|
498
|
-
host: 'localhost:6379',
|
|
499
|
-
password: '',
|
|
500
|
-
db: 0,
|
|
501
|
-
ttl: '10m'
|
|
753
|
+
enabled: cleanConfig.cache?.redis?.enabled || false,
|
|
754
|
+
host: cleanConfig.cache?.redis?.host || 'localhost:6379',
|
|
755
|
+
password: cleanConfig.cache?.redis?.password || '',
|
|
756
|
+
db: cleanConfig.cache?.redis?.db || 0,
|
|
757
|
+
ttl: cleanConfig.cache?.redis?.ttl || '10m'
|
|
502
758
|
}
|
|
503
759
|
},
|
|
504
760
|
resilience: {
|
|
505
761
|
'circuit-breaker': {
|
|
506
|
-
enabled: true,
|
|
507
|
-
'failure-threshold': 5,
|
|
508
|
-
'success-threshold': 2,
|
|
509
|
-
timeout: '60s'
|
|
762
|
+
enabled: cleanConfig.resilience?.['circuit-breaker']?.enabled ?? true,
|
|
763
|
+
'failure-threshold': cleanConfig.resilience?.['circuit-breaker']?.['failure-threshold'] || 5,
|
|
764
|
+
'success-threshold': cleanConfig.resilience?.['circuit-breaker']?.['success-threshold'] || 2,
|
|
765
|
+
timeout: cleanConfig.resilience?.['circuit-breaker']?.timeout || '60s'
|
|
510
766
|
}
|
|
511
767
|
},
|
|
512
768
|
telemetry: {
|
|
513
769
|
otel: {
|
|
514
|
-
enabled: false,
|
|
515
|
-
endpoint: 'localhost:4317',
|
|
516
|
-
'sampler-ratio': 1.0
|
|
770
|
+
enabled: cleanConfig.telemetry?.otel?.enabled || false,
|
|
771
|
+
endpoint: cleanConfig.telemetry?.otel?.endpoint || 'localhost:4317',
|
|
772
|
+
'sampler-ratio': cleanConfig.telemetry?.otel?.['sampler-ratio'] || 1.0
|
|
517
773
|
}
|
|
774
|
+
},
|
|
775
|
+
storage: {
|
|
776
|
+
s3: {
|
|
777
|
+
enabled: cleanConfig.storage?.s3?.enabled || false,
|
|
778
|
+
bucket: cleanConfig.storage?.s3?.bucket || 'YOUR_S3_BUCKET',
|
|
779
|
+
region: cleanConfig.storage?.s3?.region || 'us-east-1',
|
|
780
|
+
'access-key': cleanConfig.storage?.s3?.['access-key'] || 'YOUR_S3_ACCESS_KEY',
|
|
781
|
+
'secret-key': cleanConfig.storage?.s3?.['secret-key'] || 'YOUR_S3_SECRET_KEY',
|
|
782
|
+
endpoint: cleanConfig.storage?.s3?.endpoint || ''
|
|
783
|
+
},
|
|
784
|
+
gcs: {
|
|
785
|
+
enabled: cleanConfig.storage?.gcs?.enabled || false,
|
|
786
|
+
bucket: cleanConfig.storage?.gcs?.bucket || 'YOUR_GCS_BUCKET',
|
|
787
|
+
'credentials-file': cleanConfig.storage?.gcs?.['credentials-file'] || 'gcs-service-account.json'
|
|
788
|
+
},
|
|
789
|
+
minio: {
|
|
790
|
+
enabled: cleanConfig.storage?.minio?.enabled || false,
|
|
791
|
+
bucket: cleanConfig.storage?.minio?.bucket || 'YOUR_MINIO_BUCKET',
|
|
792
|
+
endpoint: cleanConfig.storage?.minio?.endpoint || 'http://localhost:9000',
|
|
793
|
+
'access-key': cleanConfig.storage?.minio?.['access-key'] || 'YOUR_MINIO_ACCESS_KEY',
|
|
794
|
+
'secret-key': cleanConfig.storage?.minio?.['secret-key'] || 'YOUR_MINIO_SECRET_KEY',
|
|
795
|
+
'use-ssl': cleanConfig.storage?.minio?.['use-ssl'] || false
|
|
796
|
+
},
|
|
797
|
+
r2: {
|
|
798
|
+
enabled: cleanConfig.storage?.r2?.enabled || false,
|
|
799
|
+
bucket: cleanConfig.storage?.r2?.bucket || 'YOUR_R2_BUCKET',
|
|
800
|
+
'account-id': cleanConfig.storage?.r2?.['account-id'] || 'YOUR_R2_ACCOUNT_ID',
|
|
801
|
+
'access-key': cleanConfig.storage?.r2?.['access-key'] || 'YOUR_R2_ACCESS_KEY',
|
|
802
|
+
'secret-key': cleanConfig.storage?.r2?.['secret-key'] || 'YOUR_R2_SECRET_KEY'
|
|
803
|
+
},
|
|
804
|
+
generic: {
|
|
805
|
+
enabled: cleanConfig.storage?.generic?.enabled || false,
|
|
806
|
+
provider: cleanConfig.storage?.generic?.provider || 'S3',
|
|
807
|
+
'access-key': cleanConfig.storage?.generic?.['access-key'] || 'YOUR_ACCESS_KEY',
|
|
808
|
+
'secret-key': cleanConfig.storage?.generic?.['secret-key'] || 'YOUR_SECRET_KEY'
|
|
809
|
+
},
|
|
810
|
+
sftp: {
|
|
811
|
+
enabled: cleanConfig.storage?.sftp?.enabled || false,
|
|
812
|
+
host: cleanConfig.storage?.sftp?.host || 'sftp.example.com',
|
|
813
|
+
port: cleanConfig.storage?.sftp?.port || 22,
|
|
814
|
+
username: cleanConfig.storage?.sftp?.username || 'sftp_user',
|
|
815
|
+
password: cleanConfig.storage?.sftp?.password || 'sftp_password',
|
|
816
|
+
'key-file': cleanConfig.storage?.sftp?.['key-file'] || 'id_rsa',
|
|
817
|
+
'remote-path': cleanConfig.storage?.sftp?.['remote-path'] || '/uploads'
|
|
818
|
+
},
|
|
819
|
+
github: {
|
|
820
|
+
enabled: cleanConfig.storage?.github?.enabled || false,
|
|
821
|
+
owner: cleanConfig.storage?.github?.owner || 'YOUR_ORG',
|
|
822
|
+
repo: cleanConfig.storage?.github?.repo || 'YOUR_REPO',
|
|
823
|
+
branch: cleanConfig.storage?.github?.branch || 'main',
|
|
824
|
+
path: cleanConfig.storage?.github?.path || '/',
|
|
825
|
+
token: cleanConfig.storage?.github?.token || 'YOUR_GITHUB_TOKEN',
|
|
826
|
+
username: cleanConfig.storage?.github?.username || '',
|
|
827
|
+
password: cleanConfig.storage?.github?.password || ''
|
|
828
|
+
}
|
|
829
|
+
},
|
|
830
|
+
serverless: {
|
|
831
|
+
enabled: cleanConfig.serverless?.enabled || false,
|
|
832
|
+
provider: cleanConfig.serverless?.provider || 'aws'
|
|
833
|
+
},
|
|
834
|
+
multitenancy: {
|
|
835
|
+
enabled: cleanConfig.multitenancy?.enabled || false,
|
|
836
|
+
'hide-silo-names': cleanConfig.multitenancy?.['hide-silo-names'] || false
|
|
837
|
+
},
|
|
838
|
+
elasticsearch: {
|
|
839
|
+
enabled: cleanConfig.elasticsearch?.enabled || false,
|
|
840
|
+
addresses: cleanConfig.elasticsearch?.addresses || ['http://localhost:9200'],
|
|
841
|
+
username: cleanConfig.elasticsearch?.username || '',
|
|
842
|
+
password: cleanConfig.elasticsearch?.password || '',
|
|
843
|
+
auto_sync: cleanConfig.elasticsearch?.auto_sync ?? true,
|
|
844
|
+
index_prefix: cleanConfig.elasticsearch?.index_prefix || 'go_duck_'
|
|
518
845
|
}
|
|
519
846
|
};
|
|
520
847
|
|
|
@@ -534,54 +861,55 @@ const generateYAMLConfigs = async (config, outputDir) => {
|
|
|
534
861
|
...extendedConfig.server,
|
|
535
862
|
cors: {
|
|
536
863
|
...extendedConfig.server.cors,
|
|
537
|
-
'allow-origins': ['https://your-domain.com']
|
|
864
|
+
'allow-origins': cleanConfig.server?.cors?.['allow-origins'] || ['https://your-domain.com']
|
|
538
865
|
}
|
|
539
866
|
},
|
|
540
867
|
logging: {
|
|
541
868
|
...extendedConfig.logging,
|
|
542
869
|
datadog: {
|
|
543
870
|
...extendedConfig.logging.datadog,
|
|
544
|
-
enabled: true
|
|
871
|
+
enabled: cleanConfig.logging?.datadog?.enabled ?? true
|
|
545
872
|
}
|
|
546
873
|
},
|
|
547
874
|
messaging: {
|
|
548
875
|
mqtt: {
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
password: 'prod_password',
|
|
554
|
-
'topic-prefix': 'go-duck/events'
|
|
876
|
+
...extendedConfig.messaging.mqtt,
|
|
877
|
+
enabled: cleanConfig.messaging?.mqtt?.enabled ?? true,
|
|
878
|
+
broker: cleanConfig.messaging?.mqtt?.broker || 'tcp://mqtt.production.svc:1883',
|
|
879
|
+
'client-id': cleanConfig.messaging?.mqtt?.['client-id'] || cleanConfig.name + '-prod'
|
|
555
880
|
}
|
|
556
881
|
},
|
|
557
882
|
cache: {
|
|
558
883
|
redis: {
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
ttl: '1h'
|
|
884
|
+
...extendedConfig.cache.redis,
|
|
885
|
+
enabled: cleanConfig.cache?.redis?.enabled ?? true,
|
|
886
|
+
host: cleanConfig.cache?.redis?.host || 'redis.production.svc:6379',
|
|
887
|
+
ttl: cleanConfig.cache?.redis?.ttl || '1h'
|
|
564
888
|
}
|
|
565
889
|
},
|
|
566
890
|
resilience: {
|
|
567
891
|
'circuit-breaker': {
|
|
568
|
-
|
|
569
|
-
'
|
|
570
|
-
'
|
|
571
|
-
|
|
892
|
+
...extendedConfig.resilience['circuit-breaker'],
|
|
893
|
+
enabled: cleanConfig.resilience?.['circuit-breaker']?.enabled ?? true,
|
|
894
|
+
'failure-threshold': cleanConfig.resilience?.['circuit-breaker']?.['failure-threshold'] || 3,
|
|
895
|
+
'success-threshold': cleanConfig.resilience?.['circuit-breaker']?.['success-threshold'] || 5
|
|
572
896
|
}
|
|
573
897
|
},
|
|
574
898
|
telemetry: {
|
|
575
899
|
otel: {
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
'
|
|
900
|
+
...extendedConfig.telemetry.otel,
|
|
901
|
+
enabled: cleanConfig.telemetry?.otel?.enabled ?? true,
|
|
902
|
+
endpoint: cleanConfig.telemetry?.otel?.endpoint || 'otel-collector.monitoring.svc:4317',
|
|
903
|
+
'sampler-ratio': cleanConfig.telemetry?.otel?.['sampler-ratio'] || 0.1
|
|
579
904
|
}
|
|
580
905
|
},
|
|
581
906
|
datasource: {
|
|
582
907
|
...extendedConfig.datasource,
|
|
583
|
-
'
|
|
584
|
-
'
|
|
908
|
+
host: cleanConfig.datasource?.host || 'db.production.svc',
|
|
909
|
+
username: cleanConfig.datasource?.username || 'go_duck_user',
|
|
910
|
+
password: cleanConfig.datasource?.password || 'go_duck_pass',
|
|
911
|
+
'max-open-conns': cleanConfig.datasource?.['max-open-conns'] || 100,
|
|
912
|
+
'max-idle-conns': cleanConfig.datasource?.['max-idle-conns'] || 50
|
|
585
913
|
}
|
|
586
914
|
},
|
|
587
915
|
environment: { active_profile: 'prod' }
|
|
@@ -589,4 +917,41 @@ const generateYAMLConfigs = async (config, outputDir) => {
|
|
|
589
917
|
await fs.writeFile(path.join(outputDir, 'application-prod.yml'), yaml.dump(prodConfig));
|
|
590
918
|
};
|
|
591
919
|
|
|
920
|
+
program
|
|
921
|
+
.command('serve')
|
|
922
|
+
.description('Birth of GO-DUCK: Start the interactive GUI and Documentation server on Port 2026')
|
|
923
|
+
.action(async () => {
|
|
924
|
+
const port = 2026;
|
|
925
|
+
const app = express();
|
|
926
|
+
const tempDir = path.join(process.cwd(), '.go_duck_birth');
|
|
927
|
+
|
|
928
|
+
console.log(chalk.blue('š¦ Initializing Birth of GO-DUCK (Industrial GUI)...'));
|
|
929
|
+
|
|
930
|
+
// Load default config and dummy data for documentation preview
|
|
931
|
+
const config = { name: 'go-duck-preview' };
|
|
932
|
+
const entities = [];
|
|
933
|
+
const enums = [];
|
|
934
|
+
const openEntities = [];
|
|
935
|
+
|
|
936
|
+
await fs.ensureDir(tempDir);
|
|
937
|
+
await generateDocumentation(config, entities, tempDir, enums, openEntities);
|
|
938
|
+
|
|
939
|
+
const docsPath = path.join(tempDir, 'docs', 'web');
|
|
940
|
+
app.use(express.static(docsPath));
|
|
941
|
+
|
|
942
|
+
app.listen(port, async () => {
|
|
943
|
+
console.log(chalk.bold.green(`\nš Birth of GO-DUCK Server is ALIVE!`));
|
|
944
|
+
console.log(chalk.cyan(`š Documentation & Config Wizard: http://localhost:${port}`));
|
|
945
|
+
console.log(chalk.gray(`Press Ctrl+C to terminate the server.\n`));
|
|
946
|
+
|
|
947
|
+
await open(`http://localhost:${port}/wizard.html`);
|
|
948
|
+
});
|
|
949
|
+
|
|
950
|
+
// Cleanup temp files on exit
|
|
951
|
+
process.on('SIGINT', async () => {
|
|
952
|
+
await fs.remove(tempDir);
|
|
953
|
+
process.exit();
|
|
954
|
+
});
|
|
955
|
+
});
|
|
956
|
+
|
|
592
957
|
program.parse(process.argv);
|