go-duck-cli 1.0.9 → 1.1.12

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 (70) hide show
  1. package/README.md +30 -15
  2. package/generators/ai_docs.js +130 -0
  3. package/generators/broker.js +63 -0
  4. package/generators/config.js +149 -7
  5. package/generators/devops.js +210 -43
  6. package/generators/docs.js +23 -4
  7. package/generators/elasticsearch.js +263 -0
  8. package/generators/kratos.js +229 -41
  9. package/generators/metering.js +280 -48
  10. package/generators/migrations.js +92 -198
  11. package/generators/mqtt.js +2 -39
  12. package/generators/multitenancy.js +274 -71
  13. package/generators/nats.js +39 -0
  14. package/generators/outbox.js +171 -0
  15. package/generators/postgrest.js +7 -3
  16. package/generators/postman.js +405 -0
  17. package/generators/repository.js +27 -0
  18. package/generators/router.js +27 -0
  19. package/generators/security.js +95 -14
  20. package/generators/serverless.js +147 -0
  21. package/generators/storage.js +589 -0
  22. package/generators/swagger.js +84 -60
  23. package/generators/telemetry.js +23 -32
  24. package/generators/websocket.js +55 -21
  25. package/index.js +493 -116
  26. package/package.json +6 -4
  27. package/parser/gdl.js +163 -24
  28. package/templates/docs/index.html.hbs +5 -5
  29. package/templates/docs/layout.hbs +221 -62
  30. package/templates/docs/pages/audit.hbs +83 -35
  31. package/templates/docs/pages/cli.hbs +18 -0
  32. package/templates/docs/pages/configuration.hbs +241 -0
  33. package/templates/docs/pages/datadog.hbs +46 -0
  34. package/templates/docs/pages/elasticsearch.hbs +121 -0
  35. package/templates/docs/pages/federation.hbs +241 -0
  36. package/templates/docs/pages/gdl-advanced.hbs +91 -0
  37. package/templates/docs/pages/gdl-annotations.hbs +137 -0
  38. package/templates/docs/pages/gdl-entities.hbs +134 -0
  39. package/templates/docs/pages/gdl-relationships.hbs +80 -0
  40. package/templates/docs/pages/gdl.hbs +60 -204
  41. package/templates/docs/pages/graphql.hbs +58 -44
  42. package/templates/docs/pages/grpc.hbs +53 -90
  43. package/templates/docs/pages/hybrid-store.hbs +127 -0
  44. package/templates/docs/pages/index.hbs +418 -149
  45. package/templates/docs/pages/keycloak.hbs +43 -0
  46. package/templates/docs/pages/legend.hbs +116 -0
  47. package/templates/docs/pages/mosquitto.hbs +39 -0
  48. package/templates/docs/pages/multitenancy.hbs +139 -71
  49. package/templates/docs/pages/otel.hbs +40 -0
  50. package/templates/docs/pages/realtime.hbs +38 -12
  51. package/templates/docs/pages/redis.hbs +40 -0
  52. package/templates/docs/pages/rest.hbs +120 -202
  53. package/templates/docs/pages/saga.hbs +94 -0
  54. package/templates/docs/pages/security.hbs +150 -44
  55. package/templates/docs/pages/serverless.hbs +157 -0
  56. package/templates/docs/pages/storage.hbs +127 -0
  57. package/templates/docs/pages/wizard.hbs +683 -0
  58. package/templates/docs/triple_identity_registry.png +0 -0
  59. package/templates/go/controller.go.hbs +287 -283
  60. package/templates/go/entity.go.hbs +17 -15
  61. package/templates/go/main.go.hbs +47 -180
  62. package/templates/go/migrator.go.hbs +65 -0
  63. package/templates/go/router.go.hbs +272 -0
  64. package/templates/graphql/resolver.go.hbs +53 -34
  65. package/templates/graphql/schema.graphql.hbs +17 -5
  66. package/templates/kratos/service.go.hbs +169 -34
  67. package/templates/proto/entity.proto.hbs +10 -14
  68. package/test_nested.gdl +21 -0
  69. package/templates/docs/intro.mp4 +0 -0
  70. package/test_parser.js +0 -9
@@ -0,0 +1,589 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import chalk from 'chalk';
4
+
5
+ export const generateStorageCode = async (config, outputDir, configSourceDir) => {
6
+ const storageDir = path.join(outputDir, 'internal/storage');
7
+ await fs.ensureDir(storageDir);
8
+
9
+ // 1. Manage GCS Credentials File (Local CLI Flow - for backward compatibility)
10
+ if (config.storage?.gcs?.enabled && config.storage.gcs['credentials-file']) {
11
+ const credsFile = config.storage.gcs['credentials-file'];
12
+ const sourcePath = path.join(configSourceDir, credsFile);
13
+ const destPath = path.join(outputDir, 'config', credsFile);
14
+
15
+ if (await fs.pathExists(sourcePath)) {
16
+ await fs.ensureDir(path.dirname(destPath));
17
+ await fs.copy(sourcePath, destPath);
18
+ console.log(chalk.gray(` Copied GCS credentials: ${credsFile}`));
19
+ } else {
20
+ console.warn(chalk.yellow(` ⚠️ GCS credentials file not found at ${sourcePath}`));
21
+ }
22
+ }
23
+
24
+ const storageInterfaceGo = `package storage
25
+
26
+ import (
27
+ "context"
28
+ "time"
29
+ )
30
+
31
+ // StorageProvider defines the universal interface for object storage across multiple providers
32
+ type StorageProvider interface {
33
+ Upload(ctx context.Context, key string, data []byte) (string, error)
34
+ Download(ctx context.Context, key string) ([]byte, error)
35
+ Delete(ctx context.Context, key string) error
36
+ GetSignedURL(ctx context.Context, key string, expires time.Duration) (string, error)
37
+ }
38
+
39
+ var Providers = make(map[string]StorageProvider)
40
+ `;
41
+
42
+ const bootstrapperGo = `package storage
43
+
44
+ import (
45
+ "fmt"
46
+ "io/ioutil"
47
+ "log"
48
+ "net/http"
49
+ "os"
50
+ "path/filepath"
51
+ "{{app_name}}/config"
52
+ )
53
+
54
+ // BootstrapCredentials pulls secret files (id_rsa, service.json) from GitHub before app start
55
+ func BootstrapCredentials(cfg *config.Config) error {
56
+ if !cfg.GoDuck.Storage.Bootstrap.Enabled {
57
+ return nil
58
+ }
59
+
60
+ log.Println("🚀 Starting Remote GitHub Credential Bootstrapping...")
61
+
62
+ client := &http.Client{}
63
+ b := cfg.GoDuck.Storage.Bootstrap
64
+
65
+ for _, filename := range b.Files {
66
+ // GitHub Raw Content URL
67
+ url := fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/%s/%s", b.Owner, b.Repo, b.Branch, filename)
68
+ req, _ := http.NewRequest("GET", url, nil)
69
+
70
+ if b.Token != "" {
71
+ req.Header.Set("Authorization", "token "+b.Token)
72
+ }
73
+
74
+ resp, err := client.Do(req)
75
+ if err != nil {
76
+ return fmt.Errorf("failed to fetch %s: %v", filename, err)
77
+ }
78
+ defer resp.Body.Close()
79
+
80
+ if resp.StatusCode != 200 {
81
+ return fmt.Errorf("GitHub returned %d for %s", resp.StatusCode, filename)
82
+ }
83
+
84
+ body, _ := ioutil.ReadAll(resp.Body)
85
+
86
+ // Save to local config folder (K8s ephemeral storage)
87
+ dest := filepath.Join("config", filename)
88
+ os.MkdirAll("config", 0755)
89
+
90
+ if err := ioutil.WriteFile(dest, body, 0600); err != nil {
91
+ return fmt.Errorf("failed to write bootstrap file %s: %v", filename, err)
92
+ }
93
+
94
+ log.Printf("✅ Successfully bootstrapped: %s", filename)
95
+ }
96
+
97
+ return nil
98
+ }
99
+ `;
100
+
101
+ const s3ProviderGo = `package storage
102
+
103
+ import (
104
+ "context"
105
+ "time"
106
+ "{{app_name}}/config"
107
+ s3vg2 "github.com/aws/aws-sdk-go-v2/service/s3"
108
+ )
109
+
110
+ type S3Provider struct {
111
+ client *s3vg2.Client
112
+ bucket string
113
+ }
114
+
115
+ func NewS3Provider(cfg config.Config) *S3Provider {
116
+ s3Cfg := cfg.GoDuck.Storage.S3
117
+ return &S3Provider{bucket: s3Cfg.Bucket}
118
+ }
119
+
120
+ func (p *S3Provider) Upload(ctx context.Context, key string, data []byte) (string, error) {
121
+ return "s3://" + p.bucket + "/" + key, nil
122
+ }
123
+
124
+ func (p *S3Provider) Download(ctx context.Context, key string) ([]byte, error) {
125
+ return nil, nil
126
+ }
127
+
128
+ func (p *S3Provider) Delete(ctx context.Context, key string) error {
129
+ return nil
130
+ }
131
+
132
+ func (p *S3Provider) GetSignedURL(ctx context.Context, key string, expires time.Duration) (string, error) {
133
+ return "", nil
134
+ }
135
+ `;
136
+
137
+ const gcsProviderGo = `package storage
138
+
139
+ import (
140
+ "context"
141
+ "time"
142
+ "{{app_name}}/config"
143
+ )
144
+
145
+ type GCSProvider struct {
146
+ bucket string
147
+ }
148
+
149
+ func NewGCSProvider(cfg config.Config) *GCSProvider {
150
+ return &GCSProvider{bucket: cfg.GoDuck.Storage.GCS.Bucket}
151
+ }
152
+
153
+ func (p *GCSProvider) Upload(ctx context.Context, key string, data []byte) (string, error) {
154
+ return "gs://" + p.bucket + "/" + key, nil
155
+ }
156
+
157
+ func (p *GCSProvider) Download(ctx context.Context, key string) ([]byte, error) {
158
+ return nil, nil
159
+ }
160
+
161
+ func (p *GCSProvider) Delete(ctx context.Context, key string) error {
162
+ return nil
163
+ }
164
+
165
+ func (p *GCSProvider) GetSignedURL(ctx context.Context, key string, expires time.Duration) (string, error) {
166
+ return "", nil
167
+ }
168
+ `;
169
+
170
+ const providerFactoryGo = `package storage
171
+
172
+ import (
173
+ "bytes"
174
+ "context"
175
+ "encoding/base64"
176
+ "encoding/json"
177
+ "fmt"
178
+ "io"
179
+ "net/http"
180
+ "os"
181
+ "path/filepath"
182
+ "time"
183
+
184
+ "golang.org/x/crypto/ssh"
185
+ "github.com/pkg/sftp"
186
+ "{{app_name}}/config"
187
+ )
188
+
189
+ func InitStorage(cfg *config.Config) error {
190
+ // 1. Fetch Remote Credentials if enabled
191
+ if err := BootstrapCredentials(cfg); err != nil {
192
+ return err
193
+ }
194
+
195
+ if cfg.GoDuck.Storage.S3.Enabled {
196
+ Providers["s3"] = NewS3Provider(*cfg)
197
+ }
198
+ if cfg.GoDuck.Storage.GCS.Enabled {
199
+ Providers["gcs"] = NewGCSProvider(*cfg)
200
+ }
201
+ if cfg.GoDuck.Storage.MinIO.Enabled {
202
+ Providers["minio"] = NewS3Provider(*cfg)
203
+ }
204
+ if cfg.GoDuck.Storage.R2.Enabled {
205
+ Providers["r2"] = NewS3Provider(*cfg)
206
+ }
207
+ if cfg.GoDuck.Storage.Generic.Enabled {
208
+ Providers["generic"] = NewS3Provider(*cfg)
209
+ }
210
+ if cfg.GoDuck.Storage.SFTP.Enabled {
211
+ Providers["sftp"] = NewSFTPProvider(*cfg)
212
+ }
213
+ if cfg.GoDuck.Storage.GitHub.Enabled {
214
+ Providers["github"] = NewGitHubProvider(*cfg)
215
+ }
216
+
217
+ return nil
218
+ }
219
+
220
+ type SFTPProvider struct {
221
+ host string
222
+ remotePath string
223
+ client *sftp.Client
224
+ sshClient *ssh.Client
225
+ }
226
+
227
+ func NewSFTPProvider(cfg config.Config) *SFTPProvider {
228
+ sftpCfg := cfg.GoDuck.Storage.SFTP
229
+
230
+ var authMethod ssh.AuthMethod
231
+ if sftpCfg.Password == "" && sftpCfg.KeyFile != "" {
232
+ keyPath := filepath.Join("config", sftpCfg.KeyFile) // Loaded from github bootstrap
233
+ if key, err := os.ReadFile(keyPath); err == nil {
234
+ if signer, parseErr := ssh.ParsePrivateKey(key); parseErr == nil {
235
+ authMethod = ssh.PublicKeys(signer)
236
+ }
237
+ }
238
+ } else if sftpCfg.Password != "" {
239
+ authMethod = ssh.Password(sftpCfg.Password)
240
+ }
241
+
242
+ if authMethod == nil {
243
+ authMethod = ssh.Password("fallback")
244
+ }
245
+
246
+ clientConfig := &ssh.ClientConfig{
247
+ User: sftpCfg.Username,
248
+ Auth: []ssh.AuthMethod{authMethod},
249
+ HostKeyCallback: ssh.InsecureIgnoreHostKey(),
250
+ Timeout: 5 * time.Second,
251
+ }
252
+
253
+ addr := fmt.Sprintf("%s:%d", sftpCfg.Host, sftpCfg.Port)
254
+ sshConn, err := ssh.Dial("tcp", addr, clientConfig)
255
+ var sftpClient *sftp.Client
256
+ if err == nil {
257
+ sftpClient, _ = sftp.NewClient(sshConn)
258
+ }
259
+
260
+ return &SFTPProvider{
261
+ host: sftpCfg.Host,
262
+ remotePath: sftpCfg.RemotePath,
263
+ client: sftpClient,
264
+ sshClient: sshConn,
265
+ }
266
+ }
267
+
268
+ func (p *SFTPProvider) Upload(ctx context.Context, key string, data []byte) (string, error) {
269
+ if p.client == nil {
270
+ return "", fmt.Errorf("sftp client is not connected")
271
+ }
272
+
273
+ // Calculate absolute remote path
274
+ remotePath := key
275
+ if p.remotePath != "" {
276
+ remotePath = filepath.Join(p.remotePath, key)
277
+ }
278
+
279
+ // Ensure structural integrity
280
+ dir := filepath.Dir(remotePath)
281
+ p.client.MkdirAll(dir) // Automatically handles parent folders
282
+
283
+ f, err := p.client.Create(remotePath)
284
+ if err != nil {
285
+ return "", err
286
+ }
287
+ defer f.Close()
288
+ if _, err := f.Write(data); err != nil {
289
+ return "", err
290
+ }
291
+ return "sftp://" + p.host + "/" + key, nil
292
+ }
293
+
294
+ func (p *SFTPProvider) Download(ctx context.Context, key string) ([]byte, error) {
295
+ if p.client == nil {
296
+ return nil, fmt.Errorf("sftp client is not connected")
297
+ }
298
+
299
+ f, err := p.client.Open(key)
300
+ if err != nil {
301
+ return nil, err
302
+ }
303
+ defer f.Close()
304
+
305
+ return io.ReadAll(f)
306
+ }
307
+
308
+ func (p *SFTPProvider) Delete(ctx context.Context, key string) error {
309
+ return nil
310
+ }
311
+
312
+ func (p *SFTPProvider) GetSignedURL(ctx context.Context, key string, expires time.Duration) (string, error) {
313
+ return "", nil
314
+ }
315
+
316
+ type GitHubProvider struct {
317
+ owner string
318
+ repo string
319
+ branch string
320
+ token string
321
+ }
322
+
323
+ func NewGitHubProvider(cfg config.Config) *GitHubProvider {
324
+ ghCfg := cfg.GoDuck.Storage.GitHub
325
+ branch := ghCfg.Branch
326
+ if branch == "" {
327
+ branch = "main" // Default safeguard
328
+ }
329
+ return &GitHubProvider{
330
+ owner: ghCfg.Owner,
331
+ repo: ghCfg.Repo,
332
+ branch: branch,
333
+ token: ghCfg.Token,
334
+ }
335
+ }
336
+
337
+ type ghCommitPayload struct {
338
+ Message string \`json:"message"\`
339
+ Content string \`json:"content"\`
340
+ Branch string \`json:"branch"\`
341
+ }
342
+
343
+ func (p *GitHubProvider) Upload(ctx context.Context, key string, data []byte) (string, error) {
344
+ if p.owner == "" || p.repo == "" {
345
+ return "", fmt.Errorf("GitHub provider missing owner or repo in config")
346
+ }
347
+
348
+ url := fmt.Sprintf("https://api.github.com/repos/%s/%s/contents/%s", p.owner, p.repo, key)
349
+
350
+ payload := ghCommitPayload{
351
+ Message: "Automated upload via GO-DUCK GitHub Provider",
352
+ Content: base64.StdEncoding.EncodeToString(data),
353
+ Branch: p.branch,
354
+ }
355
+ bodyBytes, err := json.Marshal(payload)
356
+ if err != nil {
357
+ return "", err
358
+ }
359
+
360
+ req, err := http.NewRequestWithContext(ctx, "PUT", url, bytes.NewBuffer(bodyBytes))
361
+ if err != nil {
362
+ return "", err
363
+ }
364
+
365
+ if p.token != "" {
366
+ req.Header.Set("Authorization", "token "+p.token)
367
+ }
368
+
369
+ client := &http.Client{Timeout: 15 * time.Second}
370
+ resp, err := client.Do(req)
371
+ if err != nil {
372
+ return "", err
373
+ }
374
+ defer resp.Body.Close()
375
+
376
+ if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK {
377
+ return "", fmt.Errorf("GitHub failed to upload %s (Status: %d)", url, resp.StatusCode)
378
+ }
379
+
380
+ return "gh://" + p.repo + "/" + key, nil
381
+ }
382
+
383
+ func (p *GitHubProvider) Download(ctx context.Context, key string) ([]byte, error) {
384
+ if p.owner == "" || p.repo == "" {
385
+ return nil, fmt.Errorf("GitHub provider missing owner or repo in config")
386
+ }
387
+
388
+ url := fmt.Sprintf("https://raw.githubusercontent.com/%s/%s/%s/%s", p.owner, p.repo, p.branch, key)
389
+ req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
390
+ if err != nil {
391
+ return nil, err
392
+ }
393
+
394
+ if p.token != "" {
395
+ req.Header.Set("Authorization", "token "+p.token)
396
+ }
397
+
398
+ client := &http.Client{Timeout: 10 * time.Second}
399
+ resp, err := client.Do(req)
400
+ if err != nil {
401
+ return nil, err
402
+ }
403
+ defer resp.Body.Close()
404
+
405
+ if resp.StatusCode != http.StatusOK {
406
+ return nil, fmt.Errorf("GitHub failed to fetch %s (Status: %d)", url, resp.StatusCode)
407
+ }
408
+
409
+ return io.ReadAll(resp.Body)
410
+ }
411
+
412
+ func (p *GitHubProvider) Delete(ctx context.Context, key string) error {
413
+ return nil
414
+ }
415
+
416
+ func (p *GitHubProvider) GetSignedURL(ctx context.Context, key string, expires time.Duration) (string, error) {
417
+ return "", nil
418
+ }
419
+ `;
420
+
421
+ const storageControllerGo = `package controllers
422
+
423
+ import (
424
+ "fmt"
425
+ "io"
426
+ "net/http"
427
+ "strings"
428
+ "time"
429
+
430
+ "github.com/gin-gonic/gin"
431
+ "{{app_name}}/internal/storage"
432
+ )
433
+
434
+ type StorageController struct{}
435
+
436
+ func (ctrl *StorageController) Upload(c *gin.Context) {
437
+ if len(storage.Providers) == 0 {
438
+ c.JSON(http.StatusNotImplemented, gin.H{"error": "No storage providers enabled"})
439
+ return
440
+ }
441
+
442
+ providerName := c.Query("provider")
443
+ if providerName == "" {
444
+ for k := range storage.Providers {
445
+ providerName = k
446
+ break
447
+ }
448
+ }
449
+
450
+ provider, exists := storage.Providers[providerName]
451
+ if !exists {
452
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Requested storage provider is not enabled: " + providerName})
453
+ return
454
+ }
455
+
456
+ file, header, err := c.Request.FormFile("file")
457
+ if err != nil {
458
+ c.JSON(http.StatusBadRequest, gin.H{"error": "No file uploaded (form-data: 'file')"})
459
+ return
460
+ }
461
+ defer file.Close()
462
+
463
+ // Optional precise structured folder path mapping
464
+ folder := c.PostForm("folder")
465
+
466
+ data, err := io.ReadAll(file)
467
+ if err != nil {
468
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to read file"})
469
+ return
470
+ }
471
+
472
+ timestamp := time.Now().UnixNano()
473
+ var key string
474
+ if folder != "" {
475
+ folder = strings.Trim(folder, "/")
476
+ key = fmt.Sprintf("%s/%d_%s", folder, timestamp, header.Filename)
477
+ } else {
478
+ key = fmt.Sprintf("%d_%s", timestamp, header.Filename)
479
+ }
480
+
481
+ _, err = provider.Upload(c.Request.Context(), key, data)
482
+ if err != nil {
483
+ c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
484
+ return
485
+ }
486
+
487
+ c.JSON(http.StatusOK, gin.H{
488
+ "message": "Upload successful",
489
+ "key": key,
490
+ "provider": providerName,
491
+ "url": fmt.Sprintf("/api/storage/download/%s?provider=%s", key, providerName),
492
+ })
493
+ }
494
+
495
+ func (ctrl *StorageController) Download(c *gin.Context) {
496
+ if len(storage.Providers) == 0 {
497
+ c.JSON(http.StatusNotImplemented, gin.H{"error": "No storage providers enabled"})
498
+ return
499
+ }
500
+
501
+ key := c.Param("key")
502
+ key = strings.TrimPrefix(key, "/")
503
+
504
+ prefixes := []string{"sftp:/", "s3:/", "gs:/", "gh:/"}
505
+ for _, p := range prefixes {
506
+ if strings.HasPrefix(key, p) {
507
+ parts := strings.Split(key, "/")
508
+ if len(parts) > 0 {
509
+ key = parts[len(parts)-1]
510
+ }
511
+ break
512
+ }
513
+ }
514
+
515
+ providerName := c.Query("provider")
516
+ if providerName == "" {
517
+ for k := range storage.Providers {
518
+ providerName = k
519
+ break
520
+ }
521
+ }
522
+
523
+ provider, exists := storage.Providers[providerName]
524
+ if !exists {
525
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Requested storage provider is not enabled: " + providerName})
526
+ return
527
+ }
528
+
529
+ data, err := provider.Download(c.Request.Context(), key)
530
+ if err != nil {
531
+ c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
532
+ return
533
+ }
534
+
535
+ contentType := http.DetectContentType(data)
536
+ c.Data(http.StatusOK, contentType, data)
537
+ }
538
+
539
+ func (ctrl *StorageController) CrossScanDownload(c *gin.Context) {
540
+ if len(storage.Providers) == 0 {
541
+ c.JSON(http.StatusNotImplemented, gin.H{"error": "No storage providers enabled"})
542
+ return
543
+ }
544
+
545
+ key := c.Param("key")
546
+ key = strings.TrimPrefix(key, "/")
547
+
548
+ prefixes := []string{"sftp:/", "s3:/", "gs:/", "gh:/"}
549
+ for _, p := range prefixes {
550
+ if strings.HasPrefix(key, p) {
551
+ parts := strings.Split(key, "/")
552
+ if len(parts) > 0 {
553
+ key = parts[len(parts)-1]
554
+ }
555
+ break
556
+ }
557
+ }
558
+
559
+ // Distributed Cross-Scan: Iterate over every single active data lake node until the file is found
560
+ for pName, provider := range storage.Providers {
561
+ data, err := provider.Download(c.Request.Context(), key)
562
+ if err == nil && len(data) > 0 {
563
+ // File successfully located in this provider!
564
+ contentType := http.DetectContentType(data)
565
+
566
+ // Expose which node successfully returned the payload
567
+ c.Header("X-Storage-Provider-Found", pName)
568
+ c.Data(http.StatusOK, contentType, data)
569
+ return
570
+ }
571
+ }
572
+
573
+ c.JSON(http.StatusNotFound, gin.H{"error": "Cross-Scan Failed: File not found across any active storage providers"})
574
+ }
575
+ `
576
+
577
+ const appName = config.name;
578
+ await fs.writeFile(path.join(storageDir, 'storage.go'), storageInterfaceGo);
579
+ await fs.writeFile(path.join(storageDir, 'bootstrap.go'), bootstrapperGo.replace(/{{app_name}}/g, appName));
580
+ await fs.writeFile(path.join(storageDir, 's3.go'), s3ProviderGo.replace(/{{app_name}}/g, appName));
581
+ await fs.writeFile(path.join(storageDir, 'gcs.go'), gcsProviderGo.replace(/{{app_name}}/g, appName));
582
+ await fs.writeFile(path.join(storageDir, 'provider.go'), providerFactoryGo.replace(/{{app_name}}/g, appName));
583
+
584
+ const controllersDir = path.join(outputDir, 'controllers');
585
+ await fs.ensureDir(controllersDir);
586
+ await fs.writeFile(path.join(controllersDir, 'storage_controller.go'), storageControllerGo.replace(/{{app_name}}/g, appName));
587
+
588
+ console.log(chalk.gray(' Generated Native Object Storage Engine (Includes HTTP StorageController endpoints)'));
589
+ };