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/README.md ADDED
@@ -0,0 +1,130 @@
1
+ # GO-DUCK CLI
2
+
3
+ [![npm version](https://badge.fury.io/js/go-duck-cli.svg)](https://badge.fury.io/js/go-duck-cli)
4
+ [![License: ISC](https://img.shields.io/badge/License-ISC-blue.svg)](https://opensource.org/licenses/ISC)
5
+
6
+ **GO-DUCK CLI** is a powerful and evolutionary code generator for building production-ready microservices in Go. It scaffolds a complete project with a rich set of features, allowing you to focus on your business logic instead of boilerplate code.
7
+
8
+ ## Overview
9
+
10
+ Go-Duck is designed to accelerate backend development by generating a robust, feature-rich, and observable Go microservice from a simple definition language. It follows an "evolutionary" approach, allowing you to incrementally update your application's schema and generate the necessary code and database migrations.
11
+
12
+ The generated application is built on a modern stack, including:
13
+
14
+ * **Gin Gonic** for the REST API
15
+ * **Kratos** for gRPC
16
+ * **GORM** for database interaction (PostgreSQL)
17
+ * **Liquibase** for database migrations
18
+ * **OpenTelemetry** for observability
19
+ * And many more...
20
+
21
+ ## Features
22
+
23
+ * **Full-Stack Code Generation**: Generates everything from REST and gRPC APIs to the data access layer.
24
+ * **Dual-Protocol APIs**: Out-of-the-box support for both REST (Gin) and gRPC (Kratos).
25
+ * **Stateful Incremental Updates**: Intelligently applies changes from your GDL schema to the existing codebase.
26
+ * **Rich Feature Set**: Includes support for:
27
+ * CRUD APIs with pagination and filtering
28
+ * GraphQL integration
29
+ * Real-time communication with WebSockets and MQTT
30
+ * Auditing and API usage metering
31
+ * JWT-based security and OIDC integration
32
+ * Distributed caching with Redis
33
+ * Circuit breakers for resilience
34
+ * Full-stack observability with OpenTelemetry
35
+ * **Automated Documentation**: Generates a beautiful, multi-page HTML documentation portal for your project.
36
+ * **Cloud-Native**: Comes with Docker support and CI/CD pipelines using GitHub Actions.
37
+
38
+ ## Installation
39
+
40
+ ```bash
41
+ npm install -g go-duck-cli
42
+ ```
43
+
44
+ ## Usage
45
+
46
+ The `go-duck-cli` has two main commands: `create` and `import-gdl`.
47
+
48
+ ### `go-duck create`
49
+
50
+ This command scaffolds a new Go microservice.
51
+
52
+ ```bash
53
+ go-duck create [options]
54
+ ```
55
+
56
+ **Options:**
57
+
58
+ * `-c, --config <path>`: Path to the `config.yaml` file (default: `../CONFIG/config.yaml`).
59
+ * `-o, --output <path>`: Path where the project will be generated (default: current directory).
60
+ * `-g, --gdl <path>`: Path to the directory containing your GDL files (default: `../GDL`).
61
+
62
+ **Example:**
63
+
64
+ ```bash
65
+ go-duck create -c my-app/config.yaml -o my-app -g my-app/gdl
66
+ ```
67
+
68
+ ### `go-duck import-gdl <file>`
69
+
70
+ This command imports a GDL file to an existing project, generating new entities, updating existing ones, and creating database migrations.
71
+
72
+ ```bash
73
+ go-duck import-gdl <file> [options]
74
+ ```
75
+
76
+ **Options:**
77
+
78
+ * `-o, --output <path>`: Path to the existing application root (default: current directory).
79
+
80
+ **Example:**
81
+
82
+ ```bash
83
+ go-duck import-gdl new-entities.gdl -o my-existing-app
84
+ ```
85
+
86
+ ## GoDuck Definition Language (GDL)
87
+
88
+ GDL is a simple language for defining your application's entities, fields, and relationships.
89
+
90
+ **Example (`app.gdl`):**
91
+
92
+ ```gdl
93
+ entity Author {
94
+ name String required
95
+ email String unique
96
+ }
97
+
98
+ entity Book {
99
+ title String required
100
+ publishedDate LocalDate
101
+ }
102
+
103
+ relationship OneToMany {
104
+ Author{books} to Book{author}
105
+ }
106
+ ```
107
+
108
+ ## Configuration (`config.yaml`)
109
+
110
+ The `config.yaml` file contains the configuration for your generated application.
111
+
112
+ ```yaml
113
+ app:
114
+ name: my-app
115
+ datasource:
116
+ host: localhost
117
+ port: 5432
118
+ username: user
119
+ password: password
120
+ database: my_app_db
121
+ multitenancy:
122
+ enabled: true
123
+ security:
124
+ jwt:
125
+ secret: "your-jwt-secret"
126
+ ```
127
+
128
+ ## License
129
+
130
+ This project is licensed under the ISC License.
@@ -0,0 +1,107 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import chalk from 'chalk';
4
+
5
+ export const generateCacheCode = async (config, outputDir) => {
6
+ const cacheDir = path.join(outputDir, 'cache');
7
+ await fs.ensureDir(cacheDir);
8
+
9
+ const cacheGo = `
10
+ package cache
11
+
12
+ import (
13
+ "context"
14
+ "encoding/json"
15
+ "log"
16
+ "time"
17
+
18
+ "{{app_name}}/config"
19
+ "github.com/go-redis/redis/v8"
20
+ )
21
+
22
+ var (
23
+ RedisClient *redis.Client
24
+ ctx = context.Background()
25
+ )
26
+
27
+ // InitCache initializes Redis if enabled
28
+ func InitCache(cfg *config.Config) {
29
+ if !cfg.GoDuck.Cache.Redis.Enabled {
30
+ log.Println("Redis Caching is disabled.")
31
+ return
32
+ }
33
+
34
+ client := redis.NewClient(&redis.Options{
35
+ Addr: cfg.GoDuck.Cache.Redis.Host,
36
+ Password: cfg.GoDuck.Cache.Redis.Password,
37
+ DB: cfg.GoDuck.Cache.Redis.DB,
38
+ })
39
+
40
+ // Perform a quick ping to verify connection without crashing the app
41
+ if err := client.Ping(ctx).Err(); err != nil {
42
+ log.Printf("Warning: Redis is enabled but unreachable at %s: %v. Caching will be bypassed.", cfg.GoDuck.Cache.Redis.Host, err)
43
+ return
44
+ }
45
+
46
+ RedisClient = client
47
+ log.Printf("Connected to Redis at %s", cfg.GoDuck.Cache.Redis.Host)
48
+ }
49
+
50
+ // Get retrieves a value from cache
51
+ func Get(key string, dest interface{}) bool {
52
+ if RedisClient == nil {
53
+ return false
54
+ }
55
+
56
+ val, err := RedisClient.Get(ctx, key).Result()
57
+ if err != nil {
58
+ if err != redis.Nil {
59
+ log.Printf("Redis Get Error: %v", err)
60
+ }
61
+ return false
62
+ }
63
+
64
+ err = json.Unmarshal([]byte(val), dest)
65
+ return err == nil
66
+ }
67
+
68
+ // Set stores a value in cache with TTL
69
+ func Set(key string, value interface{}, ttl time.Duration) {
70
+ if RedisClient == nil {
71
+ return
72
+ }
73
+
74
+ data, err := json.Marshal(value)
75
+ if err != nil {
76
+ log.Printf("Redis Marshal Error: %v", err)
77
+ return
78
+ }
79
+
80
+ if err := RedisClient.Set(ctx, key, data, ttl).Err(); err != nil {
81
+ log.Printf("Redis Set Error: %v", err)
82
+ }
83
+ }
84
+
85
+ // Delete removes a key from cache
86
+ func Delete(key string) {
87
+ if RedisClient == nil {
88
+ return
89
+ }
90
+ RedisClient.Del(ctx, key)
91
+ }
92
+
93
+ // ClearPattern deletes all keys matching a pattern (e.g. for entity invalidation)
94
+ func ClearPattern(pattern string) {
95
+ if RedisClient == nil {
96
+ return
97
+ }
98
+ iter := RedisClient.Scan(ctx, 0, pattern, 0).Iterator()
99
+ for iter.Next(ctx) {
100
+ RedisClient.Del(ctx, iter.Val())
101
+ }
102
+ }
103
+ `;
104
+
105
+ await fs.writeFile(path.join(cacheDir, 'redis.go'), cacheGo.replace(/{{app_name}}/g, config.name));
106
+ console.log(chalk.gray(' Generated Resilient Redis Caching Package'));
107
+ };
@@ -0,0 +1,173 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import chalk from 'chalk';
4
+
5
+ export const generateConfigLoader = async (outputDir) => {
6
+ const configDir = path.join(outputDir, 'config');
7
+ await fs.ensureDir(configDir);
8
+
9
+ const configGo = `
10
+ package config
11
+
12
+ import (
13
+ "fmt"
14
+ "os"
15
+ "time"
16
+
17
+ "github.com/spf13/viper"
18
+ )
19
+
20
+ type Config struct {
21
+ GoDuck struct {
22
+ Name string \`mapstructure:"name"\`
23
+ Version string \`mapstructure:"version"\`
24
+ Description string \`mapstructure:"description"\`
25
+
26
+ Server struct {
27
+ Port int \`mapstructure:"port"\`
28
+ ReadTimeout time.Duration \`mapstructure:"read-timeout"\`
29
+ WriteTimeout time.Duration \`mapstructure:"write-timeout"\`
30
+ GRPC struct {
31
+ Addr string \`mapstructure:"addr"\`
32
+ Network string \`mapstructure:"network"\`
33
+ Timeout time.Duration \`mapstructure:"timeout"\`
34
+ } \`mapstructure:"grpc"\`
35
+ CORS struct {
36
+ AllowOrigins []string \`mapstructure:"allow-origins"\`
37
+ AllowMethods []string \`mapstructure:"allow-methods"\`
38
+ AllowHeaders []string \`mapstructure:"allow-headers"\`
39
+ } \`mapstructure:"cors"\`
40
+ } \`mapstructure:"server"\`
41
+
42
+ Security struct {
43
+ KeycloakHost string \`mapstructure:"keycloak-host"\`
44
+ KeycloakRealm string \`mapstructure:"keycloak-realm"\`
45
+ KeycloakClientID string \`mapstructure:"keycloak-client-id"\`
46
+ KeycloakSecret string \`mapstructure:"keycloak-secret"\`
47
+ RateLimit struct {
48
+ RPS float64 \`mapstructure:"rps"\`
49
+ Burst int \`mapstructure:"burst"\`
50
+ } \`mapstructure:"rate-limit"\`
51
+ } \`mapstructure:"security"\`
52
+
53
+ Logging struct {
54
+ Datadog struct {
55
+ Enabled bool \`mapstructure:"enabled"\`
56
+ APIKey string \`mapstructure:"api-key"\`
57
+ Site string \`mapstructure:"site"\`
58
+ Service string \`mapstructure:"service"\`
59
+ } \`mapstructure:"datadog"\`
60
+ } \`mapstructure:"logging"\`
61
+
62
+ Messaging struct {
63
+ MQTT struct {
64
+ Enabled bool \`mapstructure:"enabled"\`
65
+ Broker string \`mapstructure:"broker"\`
66
+ ClientID string \`mapstructure:"client-id"\`
67
+ Username string \`mapstructure:"username"\`
68
+ Password string \`mapstructure:"password"\`
69
+ TopicPrefix string \`mapstructure:"topic-prefix"\`
70
+ } \`mapstructure:"mqtt"\`
71
+ } \`mapstructure:"messaging"\`
72
+
73
+ Cache struct {
74
+ Redis struct {
75
+ Enabled bool \`mapstructure:"enabled"\`
76
+ Host string \`mapstructure:"host"\`
77
+ Password string \`mapstructure:"password"\`
78
+ DB int \`mapstructure:"db"\`
79
+ TTL time.Duration \`mapstructure:"ttl"\`
80
+ } \`mapstructure:"redis"\`
81
+ } \`mapstructure:"cache"\`
82
+
83
+ Telemetry struct {
84
+ OTel struct {
85
+ Enabled bool \`mapstructure:"enabled"\`
86
+ Endpoint string \`mapstructure:"endpoint"\`
87
+ SamplerRatio float64 \`mapstructure:"sampler-ratio"\`
88
+ } \`mapstructure:"otel"\`
89
+ } \`mapstructure:"telemetry"\`
90
+
91
+ Resilience struct {
92
+ CircuitBreaker struct {
93
+ Enabled bool \`mapstructure:"enabled"\`
94
+ FailureThreshold uint32 \`mapstructure:"failure-threshold"\`
95
+ SuccessThreshold uint32 \`mapstructure:"success-threshold"\`
96
+ Timeout time.Duration \`mapstructure:"timeout"\`
97
+ } \`mapstructure:"circuit-breaker"\`
98
+ } \`mapstructure:"resilience"\`
99
+
100
+ Multitenancy struct {
101
+ Enabled bool \`mapstructure:"enabled"\`
102
+ } \`mapstructure:"multitenancy"\`
103
+
104
+ Datasource struct {
105
+ Host string \`mapstructure:"host"\`
106
+ Port int \`mapstructure:"port"\`
107
+ Username string \`mapstructure:"username"\`
108
+ Password string \`mapstructure:"password"\`
109
+ Database string \`mapstructure:"database"\`
110
+ MaxOpenConns int \`mapstructure:"max-open-conns"\`
111
+ MaxIdleConns int \`mapstructure:"max-idle-conns"\`
112
+ ConnMaxLifetime time.Duration \`mapstructure:"conn-max-lifetime"\`
113
+ } \`mapstructure:"datasource"\`
114
+ } \`mapstructure:"go-duck"\`
115
+ Environment struct {
116
+ ActiveProfile string \`mapstructure:"active_profile"\`
117
+ } \`mapstructure:"environment"\`
118
+ }
119
+
120
+ func LoadConfig() (*Config, error) {
121
+ v := viper.New()
122
+
123
+ profile := os.Getenv("GO_PROFILE")
124
+ if profile == "" {
125
+ profile = "dev"
126
+ }
127
+
128
+ v.SetConfigName(fmt.Sprintf("application-%s", profile))
129
+ v.SetConfigType("yml")
130
+ v.AddConfigPath(".")
131
+
132
+ // Default values
133
+ v.SetDefault("go-duck.server.port", 8080)
134
+ v.SetDefault("go-duck.security.rate-limit.rps", 100.0)
135
+ v.SetDefault("go-duck.security.rate-limit.burst", 200)
136
+ v.SetDefault("go-duck.logging.datadog.enabled", false)
137
+ v.SetDefault("go-duck.logging.datadog.site", "datadoghq.com")
138
+ v.SetDefault("go-duck.messaging.mqtt.enabled", false)
139
+ v.SetDefault("go-duck.messaging.mqtt.topic-prefix", "go-duck/events")
140
+ v.SetDefault("go-duck.cache.redis.enabled", false)
141
+ v.SetDefault("go-duck.cache.redis.ttl", "10m")
142
+ v.SetDefault("go-duck.telemetry.otel.enabled", false)
143
+ v.SetDefault("go-duck.telemetry.otel.endpoint", "localhost:4317")
144
+ v.SetDefault("go-duck.telemetry.otel.sampler-ratio", 1.0)
145
+ v.SetDefault("go-duck.server.grpc.addr", ":9000")
146
+ v.SetDefault("go-duck.server.grpc.network", "tcp")
147
+ v.SetDefault("go-duck.server.grpc.timeout", "1s")
148
+ v.SetDefault("go-duck.resilience.circuit-breaker.enabled", true)
149
+ v.SetDefault("go-duck.resilience.circuit-breaker.failure-threshold", 5)
150
+ v.SetDefault("go-duck.resilience.circuit-breaker.timeout", "60s")
151
+
152
+ if err := v.ReadInConfig(); err != nil {
153
+ return nil, err
154
+ }
155
+
156
+ var config Config
157
+ if err := v.Unmarshal(&config); err != nil {
158
+ return nil, err
159
+ }
160
+
161
+ return &config, nil
162
+ }
163
+
164
+ func (c *Config) GetDSN() string {
165
+ ds := c.GoDuck.Datasource
166
+ return fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%d sslmode=disable",
167
+ ds.Host, ds.Username, ds.Password, ds.Database, ds.Port)
168
+ }
169
+ `;
170
+
171
+ await fs.writeFile(path.join(configDir, 'config.go'), configGo);
172
+ console.log(chalk.gray(' Generated Go Config Loader with OTel support'));
173
+ };
@@ -0,0 +1,212 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import chalk from 'chalk';
4
+
5
+ export const generateDeploymentArtifacts = async (config, outputDir) => {
6
+ const k8sDir = path.join(outputDir, 'k8s');
7
+ const githubDir = path.join(outputDir, '.github/workflows');
8
+ await fs.ensureDir(k8sDir);
9
+ await fs.ensureDir(githubDir);
10
+
11
+ const appName = config.name || 'go-duck-app';
12
+ const appPort = 8080;
13
+
14
+ // --- 1. Dockerfile (Multi-stage, lean production image) ---
15
+ const dockerfile = `
16
+ # ---- Build Stage ----
17
+ FROM golang:1.22-alpine AS builder
18
+ WORKDIR /app
19
+ COPY go.mod go.sum ./
20
+ RUN go mod download
21
+ COPY . .
22
+ RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /app/server .
23
+
24
+ # ---- Final Stage ----
25
+ FROM gcr.io/distroless/static-debian12
26
+ WORKDIR /app
27
+ COPY --from=builder /app/server .
28
+ COPY --from=builder /app/application.yml .
29
+ COPY --from=builder /app/application-dev.yml .
30
+ COPY --from=builder /app/application-prod.yml .
31
+ EXPOSE ${appPort}
32
+ ENV GO_PROFILE=prod
33
+ ENTRYPOINT ["/app/server"]
34
+ `;
35
+
36
+ // --- 2. Docker Compose (Full local dev environment) ---
37
+ const dockerCompose = `
38
+ version: '3.9'
39
+
40
+ services:
41
+ app:
42
+ build: .
43
+ container_name: ${appName}
44
+ ports:
45
+ - "${appPort}:${appPort}"
46
+ environment:
47
+ - GO_PROFILE=dev
48
+ depends_on:
49
+ - postgres
50
+ - redis
51
+ - mosquitto
52
+ - otel-collector
53
+ networks:
54
+ - go-duck-net
55
+
56
+ postgres:
57
+ image: postgres:15-alpine
58
+ container_name: ${appName}-postgres
59
+ environment:
60
+ POSTGRES_USER: go_duck_user
61
+ POSTGRES_PASSWORD: go_duck_pass
62
+ POSTGRES_DB: ${appName}
63
+ ports:
64
+ - "5432:5432"
65
+ volumes:
66
+ - postgres_data:/var/lib/postgresql/data
67
+ networks:
68
+ - go-duck-net
69
+
70
+ redis:
71
+ image: redis:7-alpine
72
+ container_name: ${appName}-redis
73
+ ports:
74
+ - "6379:6379"
75
+ command: redis-server --save 60 1 --loglevel warning
76
+ networks:
77
+ - go-duck-net
78
+
79
+ mosquitto:
80
+ image: eclipse-mosquitto:2
81
+ container_name: ${appName}-mqtt
82
+ ports:
83
+ - "1883:1883"
84
+ - "9001:9001"
85
+ volumes:
86
+ - ./k8s/mosquitto.conf:/mosquitto/config/mosquitto.conf
87
+ networks:
88
+ - go-duck-net
89
+
90
+ otel-collector:
91
+ image: otel/opentelemetry-collector-contrib:latest
92
+ container_name: ${appName}-otel
93
+ command: ["--config=/etc/otel-collector-config.yaml"]
94
+ volumes:
95
+ - ./k8s/otel-collector.yml:/etc/otel-collector-config.yaml
96
+ ports:
97
+ - "4317:4317"
98
+ - "4318:4318"
99
+ depends_on:
100
+ - jaeger
101
+ networks:
102
+ - go-duck-net
103
+
104
+ jaeger:
105
+ image: jaegertracing/all-in-one:latest
106
+ container_name: ${appName}-jaeger
107
+ ports:
108
+ - "16686:16686"
109
+ - "14317:4317"
110
+ networks:
111
+ - go-duck-net
112
+
113
+ keycloak:
114
+ image: quay.io/keycloak/keycloak:23.0
115
+ container_name: ${appName}-keycloak
116
+ command: start-dev
117
+ environment:
118
+ KEYCLOAK_ADMIN: admin
119
+ KEYCLOAK_ADMIN_PASSWORD: admin
120
+ ports:
121
+ - "8180:8080"
122
+ networks:
123
+ - go-duck-net
124
+
125
+ volumes:
126
+ postgres_data:
127
+
128
+ networks:
129
+ go-duck-net:
130
+ driver: bridge
131
+ `;
132
+
133
+ // --- 3. MQTT Broker Config ---
134
+ const mosquittoConf = `
135
+ listener 1883
136
+ listener 9001
137
+ protocol websockets
138
+ allow_anonymous true
139
+ `;
140
+
141
+ // --- 4. GitHub Actions CI/CD ---
142
+ const ciWorkflow = `
143
+ name: CI - Build & Test
144
+
145
+ on:
146
+ push:
147
+ branches: [main, develop]
148
+ pull_request:
149
+ branches: [main]
150
+
151
+ jobs:
152
+ build:
153
+ runs-on: ubuntu-latest
154
+ steps:
155
+ - uses: actions/checkout@v4
156
+
157
+ - name: Set up Go
158
+ uses: actions/setup-go@v5
159
+ with:
160
+ go-version: '1.22'
161
+
162
+ - name: Cache Go modules
163
+ uses: actions/cache@v3
164
+ with:
165
+ path: ~/go/pkg/mod
166
+ key: \${{ runner.os }}-go-\${{ hashFiles('**/go.sum') }}
167
+
168
+ - name: Download dependencies
169
+ run: go mod download
170
+
171
+ - name: Build
172
+ run: go build -v ./...
173
+
174
+ - name: Run Tests
175
+ run: go test -v ./...
176
+ `;
177
+
178
+ const cdWorkflow = `
179
+ name: CD - Build & Push Docker Image
180
+
181
+ on:
182
+ push:
183
+ branches: [main]
184
+
185
+ jobs:
186
+ docker:
187
+ runs-on: ubuntu-latest
188
+ steps:
189
+ - uses: actions/checkout@v4
190
+
191
+ - name: Log in to Docker Hub
192
+ uses: docker/login-action@v3
193
+ with:
194
+ username: \${{ secrets.DOCKER_USERNAME }}
195
+ password: \${{ secrets.DOCKER_PASSWORD }}
196
+
197
+ - name: Build and push
198
+ uses: docker/build-push-action@v5
199
+ with:
200
+ context: .
201
+ push: true
202
+ tags: \${{ secrets.DOCKER_USERNAME }}/${appName}:latest,\${{ secrets.DOCKER_USERNAME }}/${appName}:\${{ github.sha }}
203
+ `;
204
+
205
+ await fs.writeFile(path.join(outputDir, 'Dockerfile'), dockerfile);
206
+ await fs.writeFile(path.join(outputDir, 'docker-compose.yml'), dockerCompose);
207
+ await fs.writeFile(path.join(k8sDir, 'mosquitto.conf'), mosquittoConf);
208
+ await fs.writeFile(path.join(githubDir, 'ci.yml'), ciWorkflow);
209
+ await fs.writeFile(path.join(githubDir, 'cd.yml'), cdWorkflow);
210
+
211
+ console.log(chalk.gray(' Generated Dockerfile, Docker Compose & GitHub Actions CI/CD'));
212
+ };
@@ -0,0 +1,74 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import Handlebars from 'handlebars';
4
+ import chalk from 'chalk';
5
+ import { fileURLToPath } from 'url';
6
+
7
+ export const generateDocumentation = async (config, entities, outputDir, enums = []) => {
8
+ console.log(chalk.cyan('Generating Multi-Page Developer Guide Web App...'));
9
+
10
+ const docsDir = path.join(outputDir, 'docs', 'web');
11
+ await fs.ensureDir(docsDir);
12
+
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = path.dirname(__filename);
15
+ const templatesDir = path.resolve(__dirname, '../templates/docs');
16
+
17
+ if (!await fs.pathExists(templatesDir)) {
18
+ console.log(chalk.yellow(`⚠️ Documentation templates not found at ${templatesDir}. Skipping docs generation.`));
19
+ return;
20
+ }
21
+
22
+ const layoutSource = await fs.readFile(path.join(templatesDir, 'layout.hbs'), 'utf8');
23
+ const layout = Handlebars.compile(layoutSource);
24
+
25
+ // Register Helpers
26
+ Handlebars.registerHelper('eq', (a, b) => a === b);
27
+ Handlebars.registerHelper('toLowerCase', (str) => typeof str === 'string' ? str.toLowerCase() : '');
28
+ Handlebars.registerHelper('capitalize', (str) => typeof str === 'string' ? str.charAt(0).toUpperCase() + str.slice(1) : '');
29
+ Handlebars.registerHelper('defaultStr', (value, safeVal) => (value !== null && value !== undefined && value !== '') ? value : safeVal);
30
+
31
+ const pages = [
32
+ { file: 'index', title: 'Home' },
33
+ { file: 'gdl', title: 'GDL Reference' },
34
+ { file: 'cli', title: 'CLI & Code Injection' },
35
+ { file: 'rest', title: 'REST & Search API' },
36
+ { file: 'grpc', title: 'Kratos gRPC API' },
37
+ { file: 'graphql', title: 'GraphQL Framework' },
38
+ { file: 'realtime', title: 'WebSockets & MQTT' },
39
+ { file: 'audit', title: 'Audit & Metering' },
40
+ { file: 'security', title: 'Security & Auth' },
41
+ { file: 'observability', title: 'Observability' },
42
+ { file: 'integrations', title: 'Client Integrations' }
43
+ ];
44
+
45
+ const context = {
46
+ appName: config.name || 'GO-DUCK App',
47
+ entities: entities,
48
+ enums: enums
49
+ };
50
+
51
+ for (const page of pages) {
52
+ const pageSource = await fs.readFile(path.join(templatesDir, 'pages', `${page.file}.hbs`), 'utf8');
53
+ const pageTemplate = Handlebars.compile(pageSource);
54
+ const pageHtml = pageTemplate(context);
55
+
56
+ const fullHtml = layout({
57
+ ...context,
58
+ body: pageHtml,
59
+ activePage: page.file,
60
+ title: page.title
61
+ });
62
+
63
+ await fs.writeFile(path.join(docsDir, `${page.file}.html`), fullHtml);
64
+ }
65
+
66
+ const files = await fs.readdir(templatesDir);
67
+ for (const file of files) {
68
+ if (file.endsWith('.png') || file.endsWith('.mp4')) {
69
+ await fs.copy(path.join(templatesDir, file), path.join(docsDir, file));
70
+ }
71
+ }
72
+
73
+ console.log(chalk.green('✅ Multi-Page Developer Guide HTML Web App generated at: docs/web/index.html'));
74
+ };