go-duck-cli 1.1.15 → 1.1.16

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 CHANGED
@@ -115,6 +115,11 @@ cd my-app
115
115
 
116
116
  Before running the microservice, compile the protobuf contract files using the scaffolded scripts:
117
117
 
118
+ > [!NOTE]
119
+ > **Do I need to run this?**
120
+ > * **Yes (Local Host Execution)**: You must run the compilation script whenever you first create the microservice, or whenever you modify the GDL schemas and re-run `import-gdl` to generate updated gRPC structures.
121
+ > * **No (Docker Deployment)**: If you compile and run the application via Docker (using `docker build` or `docker-compose`), the multi-stage `devops/Dockerfile` compiles the protobuf definitions internally, so you do not need to run the scripts locally.
122
+
118
123
  #### Prerequisites
119
124
  1. Ensure the `protoc` compiler is installed and added to your system's `PATH`.
120
125
  2. Install the necessary Go compiler plugins:
@@ -15,6 +15,20 @@ export const generateDeploymentArtifacts = async (config, projectRootDir) => {
15
15
 
16
16
  const appName = config.name || 'go-duck';
17
17
  const appPort = config.server?.port || 8080;
18
+ const keycloakHost = config.security?.['keycloak-host'] || 'http://localhost:8180';
19
+ const keycloakPort = keycloakHost.includes(':') ? keycloakHost.split(':').pop() : '8080';
20
+
21
+ const dbPort = config.datasource?.port || 5432;
22
+
23
+ const redisHost = config.cache?.redis?.host || 'localhost:6379';
24
+ const redisPort = redisHost.includes(':') ? redisHost.split(':').pop() : '6379';
25
+
26
+ const mqttBroker = config.messaging?.mqtt?.broker || 'tcp://localhost:1883';
27
+ const mqttPort = mqttBroker.includes(':') ? mqttBroker.split(':').pop() : '1883';
28
+
29
+ const esAddr = config.elasticsearch?.addresses?.[0] || 'http://localhost:9200';
30
+ const esAddrClean = esAddr.replace(/\/+$/, '');
31
+ const esPort = esAddrClean.includes(':') ? esAddrClean.split(':').pop() : '9200';
18
32
 
19
33
  // --- 1. Dockerfile (Multi-stage, lean production image) ---
20
34
  const dockerfile = `
@@ -63,7 +77,7 @@ COPY --from=builder /app/application.yml .
63
77
  COPY --from=builder /app/application-dev.yml .
64
78
  COPY --from=builder /app/application-prod.yml .
65
79
 
66
- EXPOSE 8080
80
+ EXPOSE ${appPort}
67
81
  ENV GO_PROFILE=prod
68
82
  ENTRYPOINT ["/app/server"]
69
83
  `;
@@ -79,7 +93,7 @@ services:
79
93
  POSTGRES_PASSWORD: ${config.datasource?.password || 'password'}
80
94
  POSTGRES_DB: ${config.datasource?.database || 'go_duck_master'}
81
95
  ports:
82
- - "5432:5432"
96
+ - "${dbPort}:5432"
83
97
  volumes:
84
98
  - postgres_data:/var/lib/postgresql/data
85
99
  networks:
@@ -89,7 +103,7 @@ services:
89
103
  image: redis:7.2.4-alpine
90
104
  container_name: ${appName}-redis
91
105
  ports:
92
- - "6379:6379"
106
+ - "${redisPort}:6379"
93
107
  command: redis-server --save 60 1 --loglevel warning
94
108
  networks:
95
109
  - go-duck-net
@@ -98,7 +112,7 @@ services:
98
112
  image: eclipse-mosquitto:2.0.18
99
113
  container_name: ${appName}-mqtt
100
114
  ports:
101
- - "1883:1883"
115
+ - "${mqttPort}:1883"
102
116
  - "9001:9001"
103
117
  volumes:
104
118
  - ./k8s/mosquitto.conf:/mosquitto/config/mosquitto.conf
@@ -136,7 +150,7 @@ services:
136
150
  - xpack.security.enabled=false
137
151
  - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
138
152
  ports:
139
- - "9200:9200"
153
+ - "${esPort}:9200"
140
154
  networks:
141
155
  - go-duck-net
142
156
 
@@ -150,7 +164,7 @@ services:
150
164
  volumes:
151
165
  - ./keycloak/realm-config:/opt/keycloak/data/import
152
166
  ports:
153
- - "8180:8080"
167
+ - "${keycloakPort}:8080"
154
168
  networks:
155
169
  - go-duck-net
156
170
 
@@ -61,11 +61,20 @@ export const generateDocumentation = async (config, entities, outputDir, enums =
61
61
  { file: 'serverless', title: 'Serverless Transformation' }
62
62
  ];
63
63
 
64
+ const grpcAddr = config.server?.grpc?.addr || ':9000';
65
+ const grpcPort = grpcAddr.includes(':') ? grpcAddr.split(':').pop() : '9000';
66
+
67
+ const mqttBroker = config.messaging?.mqtt?.broker || 'tcp://localhost:1883';
68
+ const mqttPort = mqttBroker.includes(':') ? mqttBroker.split(':').pop() : '1883';
69
+
64
70
  const context = {
65
71
  appName: config.name || 'GO-DUCK App',
66
72
  entities: entities,
67
73
  enums: enums,
68
- openEntities: openEntities
74
+ openEntities: openEntities,
75
+ serverPort: config.server?.port || 8080,
76
+ grpcPort: grpcPort,
77
+ mqttPort: mqttPort
69
78
  };
70
79
 
71
80
  for (const page of pages) {
@@ -15,16 +15,16 @@ export const generatePostmanCollection = async (config, entities, outputDir, ope
15
15
  item: [],
16
16
  variable: [
17
17
  { key: "host", value: "localhost", type: "string" },
18
- { key: "port", value: "8080", type: "string" },
18
+ { key: "port", value: String(config.server?.port || 8080), type: "string" },
19
19
  { key: "tenant", value: "tenant_1", type: "string" },
20
20
  { key: "token", value: "", type: "string" },
21
- { key: "keycloak_url", value: "http://localhost:8180", type: "string" },
21
+ { key: "keycloak_url", value: config.security?.['keycloak-host'] || "http://localhost:8180", type: "string" },
22
22
  { key: "realm", value: config.security?.['keycloak-realm'] || 'master', type: "string" },
23
- { key: "app_client_id", value: `${config.name}-app`, type: "string" },
24
- { key: "service_client_id", value: `${config.name}-service`, type: "string" },
25
- { key: "service_secret", value: "service-secret-123", type: "string" },
26
- { key: "username", value: "admin", type: "string" },
27
- { key: "password", value: "admin", type: "string" }
23
+ { key: "app_client_id", value: config.security?.['keycloak-app-client-id'] || `${config.name}-app`, type: "string" },
24
+ { key: "service_client_id", value: config.security?.['keycloak-service-client-id'] || `${config.name}-service`, type: "string" },
25
+ { key: "service_secret", value: config.security?.['keycloak-service-secret'] || "service-secret-123", type: "string" },
26
+ { key: "username", value: config.security?.['keycloak-admin-client-id'] || "admin", type: "string" },
27
+ { key: "password", value: config.security?.['keycloak-admin-secret'] || "admin", type: "string" }
28
28
  ]
29
29
  };
30
30
 
@@ -14,7 +14,7 @@ export const generateSwaggerDocs = async (config, entities, outputDir, openEntit
14
14
  description: `Generated documentation for ${config.name} microservice`
15
15
  },
16
16
  servers: [
17
- { url: 'http://localhost:8080', description: 'Local Development Server' }
17
+ { url: `http://localhost:${config.server?.port || 8080}`, description: 'Local Development Server' }
18
18
  ],
19
19
  paths: {},
20
20
  components: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "go-duck-cli",
3
- "version": "1.1.15",
3
+ "version": "1.1.16",
4
4
  "description": "The Ultimate Evolutionary Go Microservice Scaffolder.",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -60,7 +60,7 @@
60
60
  <pre><code class="language-bash"># Start local infrastructure (DB, Redis, MQTT, Keycloak, Jaeger/OTel)
61
61
  docker-compose up -d
62
62
 
63
- # Run the Go app (starts on port 8080 by default)
63
+ # Run the Go app (starts on port {{serverPort}} by default)
64
64
  go run main.go</code></pre>
65
65
  </section>
66
66
 
@@ -77,7 +77,7 @@ DELETE /api/{{#if entities.length}}{{toLowerCase entities.[0].name}}s{{else}}ent
77
77
 
78
78
  <h3 class="font-semibold mb-2 mt-6">PostgREST-like Generic Search:</h3>
79
79
  <p class="mb-2">We expose a dynamic search endpoint allowing powerful querying directly from the frontend.</p>
80
- <pre><code class="language-bash">curl -X GET "http://localhost:8080/api/rpc/{{#if entities.length}}{{toLowerCase entities.[0].name}}{{else}}your_table{{/if}}?name=eq.John&age=gt.18" \
80
+ <pre><code class="language-bash">curl -X GET "http://localhost:{{serverPort}}/api/rpc/{{#if entities.length}}{{toLowerCase entities.[0].name}}{{else}}your_table{{/if}}?name=eq.John&age=gt.18" \
81
81
  -H "Authorization: Bearer YOUR_JWT" \
82
82
  -H "X-Tenant-ID: tenant_1"</code></pre>
83
83
  <p class="text-sm text-gray-500 mt-2">Supported operators: <code>eq, neq, gt, gte, lt, lte, like, ilike</code></p>
@@ -88,10 +88,10 @@ DELETE /api/{{#if entities.length}}{{toLowerCase entities.[0].name}}s{{else}}ent
88
88
  <h2 class="text-2xl font-bold text-gray-800 mb-4 border-b pb-2">Audit & Metering</h2>
89
89
  <p class="mb-4">Entity mutations are automatically tracked via the <code>AuditMiddleware</code>. Usage limits per-tenant can be managed via the Metering API.</p>
90
90
  <pre><code class="language-bash"># View Audit Logs
91
- curl -X GET http://localhost:8080/api/audit -H "Authorization: Bearer YOUR_JWT"
91
+ curl -X GET http://localhost:{{serverPort}}/api/audit -H "Authorization: Bearer YOUR_JWT"
92
92
 
93
93
  # View Usage for Current Tenant
94
- curl -X GET http://localhost:8080/api/metering/usage -H "Authorization: Bearer YOUR_JWT" -H "X-Tenant-ID: my_tenant"</code></pre>
94
+ curl -X GET http://localhost:{{serverPort}}/api/metering/usage -H "Authorization: Bearer YOUR_JWT" -H "X-Tenant-ID: my_tenant"</code></pre>
95
95
  </section>
96
96
 
97
97
  <!-- WebSockets -->
@@ -102,7 +102,7 @@ curl -X GET http://localhost:8080/api/metering/usage -H "Authorization: Bearer Y
102
102
  <h3 class="font-semibold mb-2">Connecting via JS:</h3>
103
103
  <pre><code class="language-javascript">const token = 'YOUR_JWT_TOKEN';
104
104
  // Pass token as query param for auth
105
- const ws = new WebSocket(`ws://localhost:8080/ws?token=${token}`);
105
+ const ws = new WebSocket(`ws://localhost:{{serverPort}}/ws?token=${token}`);
106
106
 
107
107
  ws.onopen = () => {
108
108
  // Sending a signed Traced Envelope
@@ -123,14 +123,14 @@ ws.onmessage = (event) => console.log("Received:", event.data);</code></pre>
123
123
  <h2 class="text-2xl font-bold text-gray-800 mb-4 border-b pb-2">MQTT Event Streaming</h2>
124
124
  <p class="mb-4">Successful CRUD operations (Create, Update, Delete) are published as JSON payloads to the MQTT broker. This is perfect for webhooks, async processing, or triggering other microservices.</p>
125
125
  <pre><code class="language-bash"># Using mosquitto_sub to listen to events
126
- mosquitto_sub -h localhost -p 1883 -t "go-duck/events/#" -u dev_user -P dev_password</code></pre>
126
+ mosquitto_sub -h localhost -p {{mqttPort}} -t "go-duck/events/#" -u dev_user -P dev_password</code></pre>
127
127
  <p class="text-sm mt-2">Example topic: <code>go-duck/events/{{#if entities.length}}{{toLowerCase entities.[0].name}}{{else}}entity{{/if}}/CREATE</code></p>
128
128
  </section>
129
129
 
130
130
  <!-- Kratos gRPC -->
131
131
  <section id="grpc-kratos" class="content-section">
132
132
  <h2 class="text-2xl font-bold text-gray-800 mb-4 border-b pb-2">Kratos Secured gRPC APIs</h2>
133
- <p class="mb-4">The application runs a Kratos gRPC server on <code>port 9000</code> by default, sharing the internal repository with Gin. It validates Keycloak JWTs via Kratos middleware.</p>
133
+ <p class="mb-4">The application runs a Kratos gRPC server on <code>port {{grpcPort}}</code> by default, sharing the internal repository with Gin. It validates Keycloak JWTs via Kratos middleware.</p>
134
134
  <h3 class="font-semibold mb-2">Calling the gRPC Service (Go Client Example):</h3>
135
135
  <pre><code class="language-go">import (
136
136
  "context"
@@ -143,7 +143,7 @@ mosquitto_sub -h localhost -p 1883 -t "go-duck/events/#" -u dev_user -P dev_pass
143
143
  // The JWT must be generated using the Keycloak secret configured in the app
144
144
  token := oauth.TokenSource{TokenSource: oauth2.StaticTokenSource(&oauth2.Token{AccessToken: "YOUR_JWT"})}
145
145
 
146
- conn, err := grpc.Dial("localhost:9000", grpc.WithInsecure(), grpc.WithPerRPCCredentials(token))
146
+ conn, err := grpc.Dial("localhost:{{grpcPort}}", grpc.WithInsecure(), grpc.WithPerRPCCredentials(token))
147
147
  client := pb.New{{#if entities.length}}{{capitalize entities.[0].name}}{{else}}Entity{{/if}}ServiceClient(conn)
148
148
 
149
149
  // Execute Call
@@ -167,7 +167,7 @@ res, err := client.Get{{#if entities.length}}{{capitalize entities.[0].name}}{{e
167
167
  }
168
168
  }</code></pre>
169
169
  <h3 class="font-semibold mb-2 mt-4">Example Request via Fetch:</h3>
170
- <pre><code class="language-javascript">fetch('http://localhost:8080/graphql', {
170
+ <pre><code class="language-javascript">fetch('http://localhost:{{serverPort}}/graphql', {
171
171
  method: 'POST',
172
172
  headers: { 'Content-Type': 'application/json' },
173
173
  body: JSON.stringify({ query: 'query { list{{#if entities.length}}{{capitalize entities.[0].name}}s{{else}}Entities{{/if}} { id } }' })
@@ -52,6 +52,14 @@ service {{capitalize (defaultStr (lookup entities "0.name") "Entity")}}Service {
52
52
  GO-DUCK scaffolds script files in the root of the generated project to manage the compilation of <code>.proto</code> contracts natively on all operating systems.
53
53
  </p>
54
54
 
55
+ <div class="mb-8 p-6 bg-slate-900 border border-slate-800 rounded-3xl text-white shadow-lg">
56
+ <h4 class="font-bold text-amber-400 mb-2 uppercase text-xs tracking-widest font-mono">⚡ Execution Requirement Guide</h4>
57
+ <ul class="list-disc pl-6 space-y-2 text-slate-300 text-sm leading-relaxed">
58
+ <li><strong>Local Host Execution (Required)</strong>: You must run the compilation script whenever you first create the microservice, or whenever you modify the GDL schemas and re-run <code>import-gdl</code> to generate updated gRPC structures.</li>
59
+ <li><strong>Docker Deployment (Automated)</strong>: If you compile and run the application via Docker (using <code>docker build</code> or <code>docker-compose</code>), the multi-stage <code>devops/Dockerfile</code> compiles the protobuf definitions internally, so you do not need to run the scripts locally.</li>
60
+ </ul>
61
+ </div>
62
+
55
63
  <div class="bg-white border border-slate-200 rounded-3xl p-8 shadow-sm mb-8">
56
64
  <h3 class="text-xl font-bold text-slate-800 mb-4 flex items-center">
57
65
  🛠️ Scaffolded Compiler Scripts
@@ -25,7 +25,7 @@ export interface {{capitalize (defaultStr (lookup entities "0.name") "Entity")}}
25
25
 
26
26
  @Injectable({ providedIn: 'root' })
27
27
  export class {{capitalize (defaultStr (lookup entities "0.name") "Entity")}}Service {
28
- private apiUrl = 'http://localhost:8080/api/{{toLowerCase (defaultStr (lookup entities "0.name") "entity")}}s';
28
+ private apiUrl = 'http://localhost:{{serverPort}}/api/{{toLowerCase (defaultStr (lookup entities "0.name") "entity")}}s';
29
29
 
30
30
  constructor(private http: HttpClient) {}
31
31
 
@@ -40,7 +40,7 @@ export class {{capitalize (defaultStr (lookup entities "0.name") "Entity")}}Serv
40
40
  // Example: Using the powerful Generic Search RPC Engine
41
41
  search(queryField: string, operator: string, value: string): Observable&lt;any&gt; {
42
42
  let params = new HttpParams().set(queryField, \`\${operator}.\${value}\`);
43
- return this.http.get&lt;any&gt;('http://localhost:8080/api/rpc/{{toLowerCase (defaultStr (lookup entities "0.name") "entity")}}', { params });
43
+ return this.http.get&lt;any&gt;('http://localhost:{{serverPort}}/api/rpc/{{toLowerCase (defaultStr (lookup entities "0.name") "entity")}}', { params });
44
44
  }
45
45
  }
46
46
  </code></pre>
@@ -55,7 +55,7 @@ import 'package:http/http.dart' as http;
55
55
  import 'package:flutter_secure_storage/flutter_secure_storage.dart';
56
56
 
57
57
  class ApiProvider {
58
- static const String baseUrl = "http://localhost:8080/api/{{toLowerCase (defaultStr (lookup entities "0.name") "entity")}}s";
58
+ static const String baseUrl = "http://localhost:{{serverPort}}/api/{{toLowerCase (defaultStr (lookup entities "0.name") "entity")}}s";
59
59
  final storage = const FlutterSecureStorage();
60
60
 
61
61
  // Retrieve Keycloak Bearer token from secure storage
@@ -26,7 +26,7 @@ messaging.Publish("events/users", event)</code></pre>
26
26
  <h2 class="text-2xl font-bold text-gray-800 mb-4 border-b pb-2">Local Development</h2>
27
27
  <p class="mb-4">Your <code>docker-compose.yml</code> includes a pre-configured Mosquitto image. You can use any MQTT client (like MQTT.fx or mosquitto_pub/sub) to listen to your service events.</p>
28
28
  <pre><code class="language-bash"># Subscribe to all audit events locally
29
- mosquitto_sub -h localhost -p 1883 -t "audit/logs/#" -v</code></pre>
29
+ mosquitto_sub -h localhost -p {{mqttPort}} -t "audit/logs/#" -v</code></pre>
30
30
  </section>
31
31
 
32
32
  <section class="mb-10">
@@ -3,7 +3,7 @@
3
3
 
4
4
  <section class="mb-10">
5
5
  <h2 class="text-2xl font-bold text-gray-800 mb-4 border-b pb-2">1. REST-over-WS Dispatcher</h2>
6
- <p class="mb-4">Instead of traditional HTTP, you can pass highly efficient "Traced Envelopes" through our WebSocket endpoint at <code>ws://localhost:8080/ws</code>. The GO-DUCK generated engine unpacks the WS payload and routes it natively through Gin.</p>
6
+ <p class="mb-4">Instead of traditional HTTP, you can pass highly efficient "Traced Envelopes" through our WebSocket endpoint at <code>ws://localhost:{{serverPort}}/ws</code>. The GO-DUCK generated engine unpacks the WS payload and routes it natively through Gin.</p>
7
7
 
8
8
  <div class="bg-indigo-50 border-l-4 border-indigo-500 p-4 mb-6 rounded-r">
9
9
  <p class="text-indigo-900"><strong>Note:</strong> Token validation is done via query-param out of necessity due to websocket protocol limitations.</p>
@@ -13,7 +13,7 @@
13
13
  <pre><code class="language-javascript">const token = "eyJhbGciOi..."; // Valid Keycloak Access Token
14
14
 
15
15
  // Phase 1: Authentication happens precisely on the handshake (WSS Handshake via query param)
16
- const ws = new WebSocket(`ws://localhost:8080/ws?token=${token}`);
16
+ const ws = new WebSocket(`ws://localhost:{{serverPort}}/ws?token=${token}`);
17
17
 
18
18
  ws.onopen = () => {
19
19
  // Phase 2: Send HMAC-SHA256 Signed Messages