@geekmidas/cli 0.10.0 → 0.12.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 (145) hide show
  1. package/README.md +525 -0
  2. package/dist/bundler-DRXCw_YR.mjs +70 -0
  3. package/dist/bundler-DRXCw_YR.mjs.map +1 -0
  4. package/dist/bundler-WsEvH_b2.cjs +71 -0
  5. package/dist/bundler-WsEvH_b2.cjs.map +1 -0
  6. package/dist/{config-C9aXOHBe.cjs → config-AmInkU7k.cjs} +8 -8
  7. package/dist/config-AmInkU7k.cjs.map +1 -0
  8. package/dist/{config-BrkUalUh.mjs → config-DYULeEv8.mjs} +3 -3
  9. package/dist/config-DYULeEv8.mjs.map +1 -0
  10. package/dist/config.cjs +1 -1
  11. package/dist/config.d.cts +1 -1
  12. package/dist/config.d.mts +1 -1
  13. package/dist/config.mjs +1 -1
  14. package/dist/encryption-C8H-38Yy.mjs +42 -0
  15. package/dist/encryption-C8H-38Yy.mjs.map +1 -0
  16. package/dist/encryption-Dyf_r1h-.cjs +44 -0
  17. package/dist/encryption-Dyf_r1h-.cjs.map +1 -0
  18. package/dist/index.cjs +2116 -179
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.mjs +2134 -192
  21. package/dist/index.mjs.map +1 -1
  22. package/dist/{openapi-CZLI4QTr.mjs → openapi-BfFlOBCG.mjs} +801 -38
  23. package/dist/openapi-BfFlOBCG.mjs.map +1 -0
  24. package/dist/{openapi-BeHLKcwP.cjs → openapi-Bt_1FDpT.cjs} +794 -31
  25. package/dist/openapi-Bt_1FDpT.cjs.map +1 -0
  26. package/dist/{openapi-react-query-o5iMi8tz.cjs → openapi-react-query-B-sNWHFU.cjs} +5 -5
  27. package/dist/openapi-react-query-B-sNWHFU.cjs.map +1 -0
  28. package/dist/{openapi-react-query-CcciaVu5.mjs → openapi-react-query-B6XTeGqS.mjs} +5 -5
  29. package/dist/openapi-react-query-B6XTeGqS.mjs.map +1 -0
  30. package/dist/openapi-react-query.cjs +1 -1
  31. package/dist/openapi-react-query.d.cts.map +1 -1
  32. package/dist/openapi-react-query.d.mts.map +1 -1
  33. package/dist/openapi-react-query.mjs +1 -1
  34. package/dist/openapi.cjs +2 -2
  35. package/dist/openapi.d.cts +1 -1
  36. package/dist/openapi.d.cts.map +1 -1
  37. package/dist/openapi.d.mts +1 -1
  38. package/dist/openapi.d.mts.map +1 -1
  39. package/dist/openapi.mjs +2 -2
  40. package/dist/storage-BUYQJgz7.cjs +4 -0
  41. package/dist/storage-BXoJvmv2.cjs +149 -0
  42. package/dist/storage-BXoJvmv2.cjs.map +1 -0
  43. package/dist/storage-C9PU_30f.mjs +101 -0
  44. package/dist/storage-C9PU_30f.mjs.map +1 -0
  45. package/dist/storage-DLJAYxzJ.mjs +3 -0
  46. package/dist/{types-b-vwGpqc.d.cts → types-BR0M2v_c.d.mts} +100 -1
  47. package/dist/types-BR0M2v_c.d.mts.map +1 -0
  48. package/dist/{types-DXgiA1sF.d.mts → types-BhkZc-vm.d.cts} +100 -1
  49. package/dist/types-BhkZc-vm.d.cts.map +1 -0
  50. package/examples/cron-example.ts +27 -27
  51. package/examples/env.ts +27 -27
  52. package/examples/function-example.ts +31 -31
  53. package/examples/gkm.config.json +20 -20
  54. package/examples/gkm.config.ts +8 -8
  55. package/examples/gkm.minimal.config.json +5 -5
  56. package/examples/gkm.production.config.json +25 -25
  57. package/examples/logger.ts +2 -2
  58. package/package.json +6 -6
  59. package/src/__tests__/EndpointGenerator.hooks.spec.ts +191 -191
  60. package/src/__tests__/config.spec.ts +55 -55
  61. package/src/__tests__/loadEnvFiles.spec.ts +93 -93
  62. package/src/__tests__/normalizeHooksConfig.spec.ts +58 -58
  63. package/src/__tests__/openapi-react-query.spec.ts +497 -497
  64. package/src/__tests__/openapi.spec.ts +428 -428
  65. package/src/__tests__/test-helpers.ts +76 -76
  66. package/src/auth/__tests__/credentials.spec.ts +204 -0
  67. package/src/auth/__tests__/index.spec.ts +168 -0
  68. package/src/auth/credentials.ts +187 -0
  69. package/src/auth/index.ts +226 -0
  70. package/src/build/__tests__/index-new.spec.ts +474 -474
  71. package/src/build/__tests__/manifests.spec.ts +333 -333
  72. package/src/build/bundler.ts +141 -0
  73. package/src/build/endpoint-analyzer.ts +236 -0
  74. package/src/build/handler-templates.ts +1253 -0
  75. package/src/build/index.ts +250 -179
  76. package/src/build/manifests.ts +52 -52
  77. package/src/build/providerResolver.ts +145 -145
  78. package/src/build/types.ts +64 -43
  79. package/src/config.ts +39 -39
  80. package/src/deploy/__tests__/docker.spec.ts +111 -0
  81. package/src/deploy/__tests__/dokploy.spec.ts +245 -0
  82. package/src/deploy/__tests__/init.spec.ts +662 -0
  83. package/src/deploy/docker.ts +128 -0
  84. package/src/deploy/dokploy.ts +204 -0
  85. package/src/deploy/index.ts +136 -0
  86. package/src/deploy/init.ts +484 -0
  87. package/src/deploy/types.ts +48 -0
  88. package/src/dev/__tests__/index.spec.ts +266 -266
  89. package/src/dev/index.ts +647 -601
  90. package/src/docker/__tests__/compose.spec.ts +531 -0
  91. package/src/docker/__tests__/templates.spec.ts +280 -0
  92. package/src/docker/compose.ts +273 -0
  93. package/src/docker/index.ts +230 -0
  94. package/src/docker/templates.ts +446 -0
  95. package/src/generators/CronGenerator.ts +72 -72
  96. package/src/generators/EndpointGenerator.ts +699 -398
  97. package/src/generators/FunctionGenerator.ts +84 -84
  98. package/src/generators/Generator.ts +72 -72
  99. package/src/generators/OpenApiTsGenerator.ts +577 -577
  100. package/src/generators/SubscriberGenerator.ts +124 -124
  101. package/src/generators/__tests__/CronGenerator.spec.ts +433 -433
  102. package/src/generators/__tests__/EndpointGenerator.spec.ts +532 -382
  103. package/src/generators/__tests__/FunctionGenerator.spec.ts +244 -244
  104. package/src/generators/__tests__/SubscriberGenerator.spec.ts +397 -382
  105. package/src/generators/index.ts +4 -4
  106. package/src/index.ts +623 -201
  107. package/src/init/__tests__/generators.spec.ts +334 -334
  108. package/src/init/__tests__/init.spec.ts +332 -332
  109. package/src/init/__tests__/utils.spec.ts +89 -89
  110. package/src/init/generators/config.ts +175 -175
  111. package/src/init/generators/docker.ts +41 -41
  112. package/src/init/generators/env.ts +72 -72
  113. package/src/init/generators/index.ts +1 -1
  114. package/src/init/generators/models.ts +64 -64
  115. package/src/init/generators/monorepo.ts +161 -161
  116. package/src/init/generators/package.ts +71 -71
  117. package/src/init/generators/source.ts +6 -6
  118. package/src/init/index.ts +203 -208
  119. package/src/init/templates/api.ts +115 -115
  120. package/src/init/templates/index.ts +75 -75
  121. package/src/init/templates/minimal.ts +98 -98
  122. package/src/init/templates/serverless.ts +89 -89
  123. package/src/init/templates/worker.ts +98 -98
  124. package/src/init/utils.ts +54 -56
  125. package/src/openapi-react-query.ts +194 -194
  126. package/src/openapi.ts +63 -63
  127. package/src/secrets/__tests__/encryption.spec.ts +226 -0
  128. package/src/secrets/__tests__/generator.spec.ts +319 -0
  129. package/src/secrets/__tests__/index.spec.ts +91 -0
  130. package/src/secrets/__tests__/storage.spec.ts +403 -0
  131. package/src/secrets/encryption.ts +91 -0
  132. package/src/secrets/generator.ts +164 -0
  133. package/src/secrets/index.ts +383 -0
  134. package/src/secrets/storage.ts +134 -0
  135. package/src/secrets/types.ts +53 -0
  136. package/src/types.ts +295 -176
  137. package/tsdown.config.ts +11 -8
  138. package/dist/config-BrkUalUh.mjs.map +0 -1
  139. package/dist/config-C9aXOHBe.cjs.map +0 -1
  140. package/dist/openapi-BeHLKcwP.cjs.map +0 -1
  141. package/dist/openapi-CZLI4QTr.mjs.map +0 -1
  142. package/dist/openapi-react-query-CcciaVu5.mjs.map +0 -1
  143. package/dist/openapi-react-query-o5iMi8tz.cjs.map +0 -1
  144. package/dist/types-DXgiA1sF.d.mts.map +0 -1
  145. package/dist/types-b-vwGpqc.d.cts.map +0 -1
package/README.md CHANGED
@@ -9,6 +9,10 @@ A powerful CLI tool for building and managing TypeScript-based backend APIs with
9
9
  - **Development Server**: Hot-reload development server with file watching
10
10
  - **Telescope Integration**: Laravel-style debugging dashboard for inspecting requests, logs, and exceptions
11
11
  - **OpenAPI Generation**: Auto-generate OpenAPI 3.0 specifications from your endpoints
12
+ - **Docker Support**: Generate optimized Dockerfiles with multi-stage builds, turbo prune for monorepos
13
+ - **Secrets Management**: Secure credential generation, encryption, and stage-based secrets storage
14
+ - **Deploy Commands**: One-command deployment to Docker registries and Dokploy with encrypted secrets
15
+ - **Authentication**: Store credentials locally for seamless deployment without environment variables
12
16
  - **Type-Safe Configuration**: Configuration with TypeScript support and validation
13
17
  - **Endpoint Auto-Discovery**: Automatically find and load endpoints from your codebase
14
18
  - **Flexible Routing**: Support for glob patterns to discover route files
@@ -310,6 +314,7 @@ gkm build [options]
310
314
  - `aws-apigatewayv1`: AWS API Gateway v1 Lambda handlers
311
315
  - `aws-apigatewayv2`: AWS API Gateway v2 Lambda handlers
312
316
  - `server`: Server application with Hono
317
+ - `--production`: Generate production-optimized bundle (server provider only)
313
318
 
314
319
  **Example:**
315
320
  ```bash
@@ -318,8 +323,18 @@ gkm build --provider aws-apigatewayv1
318
323
 
319
324
  # Generate server application
320
325
  gkm build --provider server
326
+
327
+ # Generate production bundle for Docker
328
+ gkm build --provider server --production
321
329
  ```
322
330
 
331
+ **Production Builds:**
332
+
333
+ When using `--production` with the server provider, the CLI generates an optimized bundle at `.gkm/server/dist/server.mjs`. This bundle:
334
+ - Is minified and tree-shaken for smaller size
335
+ - Includes all dependencies (single-file deployment)
336
+ - Can be used with `gkm docker --slim` for minimal Docker images
337
+
323
338
  ### `gkm openapi`
324
339
 
325
340
  Generate OpenAPI TypeScript module from your endpoints. This is the recommended approach as it provides full type safety and a ready-to-use API client.
@@ -409,6 +424,135 @@ const { data } = api.useQuery('GET /users');
409
424
  const mutation = api.useMutation('POST /users');
410
425
  ```
411
426
 
427
+ ### `gkm docker`
428
+
429
+ Generate Docker configuration files for containerized deployment.
430
+
431
+ ```bash
432
+ gkm docker [options]
433
+ ```
434
+
435
+ **Options:**
436
+ - `--build`: Build Docker image after generating files
437
+ - `--push`: Push image to registry after building
438
+ - `--tag <tag>`: Image tag (default: `latest`)
439
+ - `--registry <url>`: Container registry URL
440
+ - `--slim`: Use slim Dockerfile (requires pre-built bundle from `gkm build --production`)
441
+ - `--turbo`: Enable turbo prune for monorepo optimization
442
+ - `--turbo-package <name>`: Package name for turbo prune (defaults to package.json name)
443
+
444
+ **Generated Files:**
445
+ - `.gkm/docker/Dockerfile` - Multi-stage or slim Dockerfile
446
+ - `.gkm/docker/docker-compose.yml` - Docker Compose configuration
447
+ - `.gkm/docker/docker-entrypoint.sh` - Entrypoint script
448
+ - `.dockerignore` - Docker ignore file (project root)
449
+
450
+ **Dockerfile Types:**
451
+
452
+ | Type | Flag | Description |
453
+ |------|------|-------------|
454
+ | Multi-stage | (default) | Builds from source inside Docker, most reproducible |
455
+ | Turbo | `--turbo` | Optimized for monorepos with turbo prune |
456
+ | Slim | `--slim` | Uses pre-built bundle, requires prior `gkm build --production` |
457
+
458
+ **Example:**
459
+ ```bash
460
+ # Generate multi-stage Dockerfile (default, recommended)
461
+ gkm docker
462
+
463
+ # Generate and build image
464
+ gkm docker --build --tag v1.0.0
465
+
466
+ # Build and push to registry
467
+ gkm docker --build --push --registry ghcr.io/myorg --tag v1.0.0
468
+
469
+ # Use turbo prune for monorepo
470
+ gkm docker --turbo --turbo-package my-api
471
+
472
+ # Use slim Dockerfile (after running gkm build --production)
473
+ gkm build --provider server --production
474
+ gkm docker --slim
475
+ ```
476
+
477
+ **Package Manager Support:**
478
+
479
+ The CLI auto-detects your package manager from lockfiles and generates optimized Dockerfiles:
480
+ - **pnpm**: Uses `pnpm fetch` for better layer caching
481
+ - **npm**: Uses `npm ci` with cache mounts
482
+ - **yarn**: Uses `yarn install --frozen-lockfile`
483
+ - **bun**: Uses `bun install --frozen-lockfile`
484
+
485
+ **Configuration:**
486
+
487
+ Configure Docker settings in `gkm.config.ts`:
488
+
489
+ ```typescript
490
+ import { defineConfig } from '@geekmidas/cli/config';
491
+
492
+ export default defineConfig({
493
+ routes: 'src/routes/**/*.ts',
494
+ envParser: './src/env.ts',
495
+ logger: './src/logger.ts',
496
+
497
+ docker: {
498
+ // Container registry
499
+ registry: 'ghcr.io/myorg',
500
+ // Image name (defaults to package.json name)
501
+ imageName: 'my-api',
502
+ // Base image (default: node:22-alpine)
503
+ baseImage: 'node:22-alpine',
504
+ // Port to expose (default: 3000)
505
+ port: 3000,
506
+ // Docker Compose services
507
+ compose: {
508
+ services: {
509
+ postgres: { image: 'postgis/postgis:16-3.4-alpine' }, // Custom image
510
+ redis: true, // Default version
511
+ rabbitmq: { version: '3.12-management-alpine' }, // Custom version
512
+ },
513
+ },
514
+ },
515
+ });
516
+ ```
517
+
518
+ **Docker Compose Services:**
519
+
520
+ Services can be configured with custom versions, custom images, or use defaults:
521
+
522
+ ```typescript
523
+ // Object format (recommended)
524
+ services: {
525
+ postgres: { version: '15-alpine' }, // Custom version
526
+ redis: true, // Default: redis:7-alpine
527
+ rabbitmq: { version: '3.12-management-alpine' },
528
+ }
529
+
530
+ // Custom images (e.g., PostGIS, Redis Stack)
531
+ services: {
532
+ postgres: { image: 'postgis/postgis:16-3.4-alpine' }, // Full image reference
533
+ redis: { image: 'redis/redis-stack:latest' },
534
+ }
535
+
536
+ // Legacy array format - uses default versions
537
+ services: ['postgres', 'redis', 'rabbitmq']
538
+ ```
539
+
540
+ **Service Configuration Options:**
541
+
542
+ | Property | Description |
543
+ |----------|-------------|
544
+ | `true` | Use default image and version |
545
+ | `{ version: string }` | Use default image with custom version/tag |
546
+ | `{ image: string }` | Use completely custom image reference |
547
+
548
+ **Default Images:**
549
+
550
+ | Service | Default Image | Environment Variables |
551
+ |---------|---------------|----------------------|
552
+ | `postgres` | `postgres:16-alpine` | `DATABASE_URL`, `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB` |
553
+ | `redis` | `redis:7-alpine` | `REDIS_URL` |
554
+ | `rabbitmq` | `rabbitmq:3-management-alpine` | `RABBITMQ_URL`, `RABBITMQ_USER`, `RABBITMQ_PASSWORD` |
555
+
412
556
  ### `gkm dev`
413
557
 
414
558
  Start a development server with hot-reload and optional Telescope debugging dashboard.
@@ -459,6 +603,369 @@ When files change, the server automatically rebuilds and restarts:
459
603
  ✅ Rebuild complete, restarting server...
460
604
  ```
461
605
 
606
+ ### `gkm secrets:init`
607
+
608
+ Initialize secrets for a deployment stage. Generates secure random passwords for configured Docker Compose services.
609
+
610
+ ```bash
611
+ gkm secrets:init --stage <stage> [options]
612
+ ```
613
+
614
+ **Options:**
615
+ - `--stage <stage>`: Stage name (e.g., `production`, `staging`)
616
+ - `--force`: Overwrite existing secrets
617
+
618
+ **Example:**
619
+ ```bash
620
+ # Initialize production secrets
621
+ gkm secrets:init --stage production
622
+
623
+ # Overwrite existing secrets
624
+ gkm secrets:init --stage production --force
625
+ ```
626
+
627
+ **Generated:**
628
+ - Secure passwords for postgres, redis, rabbitmq (based on `docker.compose.services` config)
629
+ - Connection URLs (`DATABASE_URL`, `REDIS_URL`, `RABBITMQ_URL`)
630
+ - Stored in `.gkm/secrets/<stage>.json` (gitignored)
631
+
632
+ ### `gkm secrets:set`
633
+
634
+ Set a custom secret for a stage.
635
+
636
+ ```bash
637
+ gkm secrets:set <key> [value] --stage <stage>
638
+ ```
639
+
640
+ **Arguments:**
641
+ - `<key>`: Secret key (e.g., `API_KEY`, `STRIPE_SECRET`)
642
+ - `[value]`: Secret value (optional - reads from stdin if omitted)
643
+
644
+ **Options:**
645
+ - `--stage <stage>`: Stage name
646
+
647
+ **Examples:**
648
+ ```bash
649
+ # Direct value
650
+ gkm secrets:set API_KEY sk_live_xxx --stage production
651
+
652
+ # From stdin (pipe)
653
+ echo "sk_live_xxx" | gkm secrets:set API_KEY --stage production
654
+
655
+ # From file (for multiline secrets like private keys)
656
+ gkm secrets:set PRIVATE_KEY --stage production < private_key.pem
657
+
658
+ # From command output
659
+ openssl rand -base64 32 | gkm secrets:set JWT_SECRET --stage production
660
+ ```
661
+
662
+ ### `gkm secrets:import`
663
+
664
+ Import multiple secrets from a JSON file.
665
+
666
+ ```bash
667
+ gkm secrets:import <file> --stage <stage> [options]
668
+ ```
669
+
670
+ **Arguments:**
671
+ - `<file>`: Path to JSON file with key-value pairs
672
+
673
+ **Options:**
674
+ - `--stage <stage>`: Stage name
675
+ - `--no-merge`: Replace all custom secrets instead of merging
676
+
677
+ **JSON Format:**
678
+ ```json
679
+ {
680
+ "API_KEY": "sk_live_xxx",
681
+ "STRIPE_WEBHOOK_SECRET": "whsec_xxx",
682
+ "SENDGRID_API_KEY": "SG.xxx"
683
+ }
684
+ ```
685
+
686
+ **Examples:**
687
+ ```bash
688
+ # Import and merge with existing secrets (default)
689
+ gkm secrets:import secrets.json --stage production
690
+
691
+ # Replace all custom secrets
692
+ gkm secrets:import secrets.json --stage production --no-merge
693
+ ```
694
+
695
+ ### `gkm secrets:show`
696
+
697
+ Display secrets for a stage (passwords masked by default).
698
+
699
+ ```bash
700
+ gkm secrets:show --stage <stage> [options]
701
+ ```
702
+
703
+ **Options:**
704
+ - `--stage <stage>`: Stage name
705
+ - `--reveal`: Show actual secret values (not masked)
706
+
707
+ **Example:**
708
+ ```bash
709
+ # Show masked secrets
710
+ gkm secrets:show --stage production
711
+
712
+ # Show actual values
713
+ gkm secrets:show --stage production --reveal
714
+ ```
715
+
716
+ ### `gkm secrets:rotate`
717
+
718
+ Rotate passwords for services.
719
+
720
+ ```bash
721
+ gkm secrets:rotate --stage <stage> [options]
722
+ ```
723
+
724
+ **Options:**
725
+ - `--stage <stage>`: Stage name
726
+ - `--service <service>`: Specific service to rotate (`postgres`, `redis`, `rabbitmq`)
727
+
728
+ **Examples:**
729
+ ```bash
730
+ # Rotate all service passwords
731
+ gkm secrets:rotate --stage production
732
+
733
+ # Rotate only postgres password
734
+ gkm secrets:rotate --stage production --service postgres
735
+ ```
736
+
737
+ ## Authentication
738
+
739
+ Store credentials locally to avoid setting environment variables for every command.
740
+
741
+ ### `gkm login`
742
+
743
+ Authenticate with a deployment service. Credentials are stored in `~/.gkm/credentials.json`.
744
+
745
+ ```bash
746
+ gkm login [options]
747
+ ```
748
+
749
+ **Options:**
750
+ - `--service <service>`: Service to login to (`dokploy`) - default: `dokploy`
751
+ - `--token <token>`: API token (will prompt interactively if not provided)
752
+ - `--endpoint <url>`: Service endpoint URL (will prompt if not provided)
753
+
754
+ **Examples:**
755
+ ```bash
756
+ # Interactive login (prompts for endpoint and token)
757
+ gkm login
758
+
759
+ # Non-interactive login
760
+ gkm login --endpoint https://dokploy.example.com --token your-api-token
761
+
762
+ # Login with just endpoint (prompts for token)
763
+ gkm login --endpoint https://dokploy.example.com
764
+ ```
765
+
766
+ **After logging in:**
767
+ ```bash
768
+ # These commands no longer need DOKPLOY_API_TOKEN
769
+ gkm deploy:list
770
+ gkm deploy:init --project my-project --app api
771
+ gkm deploy --provider dokploy --stage production
772
+ ```
773
+
774
+ ### `gkm logout`
775
+
776
+ Remove stored credentials.
777
+
778
+ ```bash
779
+ gkm logout [options]
780
+ ```
781
+
782
+ **Options:**
783
+ - `--service <service>`: Service to logout from (`dokploy`, `all`) - default: `dokploy`
784
+
785
+ **Examples:**
786
+ ```bash
787
+ # Logout from Dokploy
788
+ gkm logout
789
+
790
+ # Logout from all services
791
+ gkm logout --service all
792
+ ```
793
+
794
+ ### `gkm whoami`
795
+
796
+ Show current authentication status.
797
+
798
+ ```bash
799
+ gkm whoami
800
+ ```
801
+
802
+ **Example output:**
803
+ ```
804
+ 📋 Current credentials:
805
+
806
+ Dokploy:
807
+ Endpoint: https://dokploy.example.com
808
+ Token: abc1...xyz9
809
+
810
+ Credentials file: /Users/you/.gkm/credentials.json
811
+ ```
812
+
813
+ ## Deployment
814
+
815
+ ### `gkm deploy`
816
+
817
+ Deploy application to a provider. Builds for production, injects encrypted secrets, and deploys.
818
+
819
+ ```bash
820
+ gkm deploy --provider <provider> --stage <stage> [options]
821
+ ```
822
+
823
+ **Options:**
824
+ - `--provider <provider>`: Deploy provider (`docker`, `dokploy`, `aws-lambda`)
825
+ - `--stage <stage>`: Deployment stage
826
+ - `--tag <tag>`: Image tag (default: `stage-timestamp`)
827
+ - `--skip-push`: Skip pushing image to registry
828
+ - `--skip-build`: Skip build step (use existing build)
829
+
830
+ **Examples:**
831
+ ```bash
832
+ # Docker: build and push image
833
+ gkm deploy --provider docker --stage production
834
+
835
+ # Dokploy: build, push, and trigger deployment
836
+ DOKPLOY_API_TOKEN=xxx gkm deploy --provider dokploy --stage production
837
+
838
+ # Custom tag
839
+ gkm deploy --provider docker --stage production --tag v1.0.0
840
+ ```
841
+
842
+ **Workflow:**
843
+ 1. Builds production bundle with `gkm build --provider server --production --stage <stage>`
844
+ 2. Encrypts secrets from `.gkm/secrets/<stage>.json` into the bundle
845
+ 3. Generates Docker files with `gkm docker`
846
+ 4. Builds and pushes Docker image
847
+ 5. (Dokploy) Triggers deployment via API with `GKM_MASTER_KEY`
848
+
849
+ **Configuration:**
850
+
851
+ ```typescript
852
+ // gkm.config.ts
853
+ export default defineConfig({
854
+ routes: 'src/endpoints/**/*.ts',
855
+ envParser: './src/env.ts',
856
+ logger: './src/logger.ts',
857
+
858
+ docker: {
859
+ registry: 'ghcr.io/myorg',
860
+ imageName: 'my-api',
861
+ },
862
+
863
+ // For Dokploy deployments
864
+ providers: {
865
+ dokploy: {
866
+ endpoint: 'https://dokploy.example.com',
867
+ projectId: 'proj_xxx',
868
+ applicationId: 'app_xxx',
869
+ },
870
+ },
871
+ });
872
+ ```
873
+
874
+ **Environment Variables:**
875
+ - `DOKPLOY_API_TOKEN`: API token for Dokploy (not needed if logged in via `gkm login`)
876
+ - `GKM_MASTER_KEY`: Automatically set by Dokploy, or manually for Docker deployments
877
+
878
+ ### `gkm deploy:init`
879
+
880
+ Initialize a new Dokploy deployment by creating a project and application via the Dokploy API. Automatically updates `gkm.config.ts` with the configuration.
881
+
882
+ ```bash
883
+ gkm deploy:init --project <name> --app <name> [options]
884
+ ```
885
+
886
+ **Options:**
887
+ - `--endpoint <url>`: Dokploy server URL (uses stored credentials if logged in)
888
+ - `--project <name>`: Project name (creates if not exists)
889
+ - `--app <name>`: Application name to create
890
+ - `--project-id <id>`: Use existing project ID instead of finding/creating
891
+ - `--registry-id <id>`: Configure a registry for the application
892
+
893
+ **Examples:**
894
+ ```bash
895
+ # After gkm login (no endpoint needed)
896
+ gkm deploy:init --project my-project --app api
897
+
898
+ # With explicit endpoint (or if not logged in)
899
+ gkm deploy:init \
900
+ --endpoint https://dokploy.example.com \
901
+ --project my-project \
902
+ --app api
903
+
904
+ # With registry configuration
905
+ gkm deploy:init \
906
+ --project my-project \
907
+ --app api \
908
+ --registry-id reg_xyz789
909
+ ```
910
+
911
+ **What it does:**
912
+ 1. Searches for existing project by name, or creates a new one
913
+ 2. Creates a new application in the project
914
+ 3. Configures registry if `--registry-id` is provided
915
+ 4. Updates `gkm.config.ts` with the Dokploy configuration
916
+ 5. Shows next steps for secrets and deployment
917
+
918
+ ### `gkm deploy:list`
919
+
920
+ List available Dokploy resources (projects and registries).
921
+
922
+ ```bash
923
+ gkm deploy:list [options]
924
+ ```
925
+
926
+ **Options:**
927
+ - `--endpoint <url>`: Dokploy server URL (uses stored credentials if logged in)
928
+ - `--projects`: List projects only
929
+ - `--registries`: List registries only
930
+
931
+ **Examples:**
932
+ ```bash
933
+ # After gkm login (no endpoint needed)
934
+ gkm deploy:list
935
+ gkm deploy:list --projects
936
+ gkm deploy:list --registries
937
+
938
+ # With explicit endpoint
939
+ gkm deploy:list --endpoint https://dokploy.example.com
940
+ ```
941
+
942
+ ### Using Encrypted Credentials
943
+
944
+ After deploying with secrets, your application decrypts credentials at runtime:
945
+
946
+ ```typescript
947
+ // src/env.ts
948
+ import { EnvironmentParser } from '@geekmidas/envkit';
949
+ import { Credentials } from '@geekmidas/envkit/credentials';
950
+
951
+ export const envParser = new EnvironmentParser({...process.env, ...Credentials})
952
+ .create((get) => ({
953
+ database: {
954
+ url: get('DATABASE_URL').string(),
955
+ },
956
+ stripe: {
957
+ key: get('STRIPE_KEY').string(),
958
+ },
959
+ }))
960
+ .parse();
961
+ ```
962
+
963
+ **How it works:**
964
+ - At build time, secrets are encrypted with AES-256-GCM and embedded in the bundle
965
+ - An ephemeral master key is generated per build
966
+ - At runtime, `Credentials` decrypts using `GKM_MASTER_KEY` environment variable
967
+ - In development (no embedded secrets), `Credentials` returns `{}`
968
+
462
969
  ### Future Commands
463
970
 
464
971
  The following commands are planned for future releases:
@@ -1259,8 +1766,26 @@ interface GkmConfig {
1259
1766
  subscribers?: string | string[];
1260
1767
  runtime?: Runtime;
1261
1768
  telescope?: boolean | TelescopeConfig;
1769
+ docker?: DockerConfig;
1262
1770
  }
1263
1771
 
1772
+ // Docker configuration
1773
+ interface DockerConfig {
1774
+ registry?: string; // Container registry URL
1775
+ imageName?: string; // Image name (defaults to package.json name)
1776
+ baseImage?: string; // Base image (default: node:22-alpine)
1777
+ port?: number; // Port to expose (default: 3000)
1778
+ compose?: {
1779
+ services?: ComposeServicesConfig | ComposeServiceName[];
1780
+ };
1781
+ }
1782
+
1783
+ // Service configuration for docker-compose
1784
+ type ComposeServiceName = 'postgres' | 'redis' | 'rabbitmq';
1785
+ type ComposeServicesConfig = {
1786
+ [K in ComposeServiceName]?: boolean | { version?: string };
1787
+ };
1788
+
1264
1789
  // Telescope configuration
1265
1790
  interface TelescopeConfig {
1266
1791
  enabled?: boolean;
@@ -0,0 +1,70 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { mkdir, rename, writeFile } from "node:fs/promises";
4
+ import { execSync } from "node:child_process";
5
+
6
+ //#region src/build/bundler.ts
7
+ /**
8
+ * Bundle the server application using tsdown
9
+ *
10
+ * @param options - Bundle configuration options
11
+ * @returns Bundle result with output path and optional master key
12
+ */
13
+ async function bundleServer(options) {
14
+ const { entryPoint, outputDir, minify, sourcemap, external, stage } = options;
15
+ await mkdir(outputDir, { recursive: true });
16
+ const args = [
17
+ "npx",
18
+ "tsdown",
19
+ entryPoint,
20
+ "--no-config",
21
+ "--out-dir",
22
+ outputDir,
23
+ "--format",
24
+ "esm",
25
+ "--platform",
26
+ "node",
27
+ "--target",
28
+ "node22",
29
+ "--clean"
30
+ ];
31
+ if (minify) args.push("--minify");
32
+ if (sourcemap) args.push("--sourcemap");
33
+ for (const ext of external) args.push("--external", ext);
34
+ args.push("--external", "node:*");
35
+ let masterKey;
36
+ if (stage) {
37
+ const { readStageSecrets, toEmbeddableSecrets } = await import("./storage-DLJAYxzJ.mjs");
38
+ const { encryptSecrets, generateDefineOptions } = await import("./encryption-C8H-38Yy.mjs");
39
+ const secrets = await readStageSecrets(stage);
40
+ if (!secrets) throw new Error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
41
+ const embeddable = toEmbeddableSecrets(secrets);
42
+ const encrypted = encryptSecrets(embeddable);
43
+ masterKey = encrypted.masterKey;
44
+ const defines = generateDefineOptions(encrypted);
45
+ for (const [key, value] of Object.entries(defines)) args.push("--define", `${key}=${value}`);
46
+ console.log(` Secrets encrypted for stage "${stage}"`);
47
+ }
48
+ const mjsOutput = join(outputDir, "server.mjs");
49
+ try {
50
+ execSync(args.join(" "), {
51
+ cwd: process.cwd(),
52
+ stdio: "inherit"
53
+ });
54
+ const jsOutput = join(outputDir, "server.js");
55
+ if (existsSync(jsOutput)) await rename(jsOutput, mjsOutput);
56
+ const { readFile: readFile$1 } = await import("node:fs/promises");
57
+ const content = await readFile$1(mjsOutput, "utf-8");
58
+ if (!content.startsWith("#!")) await writeFile(mjsOutput, `#!/usr/bin/env node\n${content}`);
59
+ } catch (error) {
60
+ throw new Error(`Failed to bundle server: ${error instanceof Error ? error.message : "Unknown error"}`);
61
+ }
62
+ return {
63
+ outputPath: mjsOutput,
64
+ masterKey
65
+ };
66
+ }
67
+
68
+ //#endregion
69
+ export { bundleServer };
70
+ //# sourceMappingURL=bundler-DRXCw_YR.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundler-DRXCw_YR.mjs","names":["options: BundleOptions","masterKey: string | undefined"],"sources":["../src/build/bundler.ts"],"sourcesContent":["import { execSync } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { mkdir, rename, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\n\nexport interface BundleOptions {\n\t/** Entry point file (e.g., .gkm/server/server.ts) */\n\tentryPoint: string;\n\t/** Output directory for bundled files */\n\toutputDir: string;\n\t/** Minify the output (default: true) */\n\tminify: boolean;\n\t/** Generate sourcemaps (default: false) */\n\tsourcemap: boolean;\n\t/** Packages to exclude from bundling */\n\texternal: string[];\n\t/** Stage for secrets injection (optional) */\n\tstage?: string;\n}\n\nexport interface BundleResult {\n\t/** Path to the bundled output */\n\toutputPath: string;\n\t/** Ephemeral master key for deployment (only if stage was provided) */\n\tmasterKey?: string;\n}\n\n/**\n * Bundle the server application using tsdown\n *\n * @param options - Bundle configuration options\n * @returns Bundle result with output path and optional master key\n */\nexport async function bundleServer(\n\toptions: BundleOptions,\n): Promise<BundleResult> {\n\tconst { entryPoint, outputDir, minify, sourcemap, external, stage } = options;\n\n\t// Ensure output directory exists\n\tawait mkdir(outputDir, { recursive: true });\n\n\t// Build command-line arguments for tsdown\n\tconst args = [\n\t\t'npx',\n\t\t'tsdown',\n\t\tentryPoint,\n\t\t'--no-config', // Don't use any config file from workspace\n\t\t'--out-dir',\n\t\toutputDir,\n\t\t'--format',\n\t\t'esm',\n\t\t'--platform',\n\t\t'node',\n\t\t'--target',\n\t\t'node22',\n\t\t'--clean',\n\t];\n\n\tif (minify) {\n\t\targs.push('--minify');\n\t}\n\n\tif (sourcemap) {\n\t\targs.push('--sourcemap');\n\t}\n\n\t// Add external packages\n\tfor (const ext of external) {\n\t\targs.push('--external', ext);\n\t}\n\n\t// Always exclude node: builtins\n\targs.push('--external', 'node:*');\n\n\t// Handle secrets injection if stage is provided\n\tlet masterKey: string | undefined;\n\n\tif (stage) {\n\t\tconst { readStageSecrets, toEmbeddableSecrets } = await import(\n\t\t\t'../secrets/storage'\n\t\t);\n\t\tconst { encryptSecrets, generateDefineOptions } = await import(\n\t\t\t'../secrets/encryption'\n\t\t);\n\n\t\tconst secrets = await readStageSecrets(stage);\n\n\t\tif (!secrets) {\n\t\t\tthrow new Error(\n\t\t\t\t`No secrets found for stage \"${stage}\". Run \"gkm secrets:init --stage ${stage}\" first.`,\n\t\t\t);\n\t\t}\n\n\t\t// Convert to embeddable format and encrypt\n\t\tconst embeddable = toEmbeddableSecrets(secrets);\n\t\tconst encrypted = encryptSecrets(embeddable);\n\t\tmasterKey = encrypted.masterKey;\n\n\t\t// Add define options for build-time injection\n\t\tconst defines = generateDefineOptions(encrypted);\n\t\tfor (const [key, value] of Object.entries(defines)) {\n\t\t\targs.push('--define', `${key}=${value}`);\n\t\t}\n\n\t\tconsole.log(` Secrets encrypted for stage \"${stage}\"`);\n\t}\n\n\tconst mjsOutput = join(outputDir, 'server.mjs');\n\n\ttry {\n\t\t// Run tsdown with command-line arguments\n\t\texecSync(args.join(' '), {\n\t\t\tcwd: process.cwd(),\n\t\t\tstdio: 'inherit',\n\t\t});\n\n\t\t// Rename output to .mjs for explicit ESM\n\t\t// tsdown outputs as server.js for ESM format\n\t\tconst jsOutput = join(outputDir, 'server.js');\n\n\t\tif (existsSync(jsOutput)) {\n\t\t\tawait rename(jsOutput, mjsOutput);\n\t\t}\n\n\t\t// Add shebang to the bundled file\n\t\tconst { readFile } = await import('node:fs/promises');\n\t\tconst content = await readFile(mjsOutput, 'utf-8');\n\t\tif (!content.startsWith('#!')) {\n\t\t\tawait writeFile(mjsOutput, `#!/usr/bin/env node\\n${content}`);\n\t\t}\n\t} catch (error) {\n\t\tthrow new Error(\n\t\t\t`Failed to bundle server: ${error instanceof Error ? error.message : 'Unknown error'}`,\n\t\t);\n\t}\n\n\treturn {\n\t\toutputPath: mjsOutput,\n\t\tmasterKey,\n\t};\n}\n"],"mappings":";;;;;;;;;;;;AAiCA,eAAsB,aACrBA,SACwB;CACxB,MAAM,EAAE,YAAY,WAAW,QAAQ,WAAW,UAAU,OAAO,GAAG;AAGtE,OAAM,MAAM,WAAW,EAAE,WAAW,KAAM,EAAC;CAG3C,MAAM,OAAO;EACZ;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACA;AAED,KAAI,OACH,MAAK,KAAK,WAAW;AAGtB,KAAI,UACH,MAAK,KAAK,cAAc;AAIzB,MAAK,MAAM,OAAO,SACjB,MAAK,KAAK,cAAc,IAAI;AAI7B,MAAK,KAAK,cAAc,SAAS;CAGjC,IAAIC;AAEJ,KAAI,OAAO;EACV,MAAM,EAAE,kBAAkB,qBAAqB,GAAG,MAAM,OACvD;EAED,MAAM,EAAE,gBAAgB,uBAAuB,GAAG,MAAM,OACvD;EAGD,MAAM,UAAU,MAAM,iBAAiB,MAAM;AAE7C,OAAK,QACJ,OAAM,IAAI,OACR,8BAA8B,MAAM,mCAAmC,MAAM;EAKhF,MAAM,aAAa,oBAAoB,QAAQ;EAC/C,MAAM,YAAY,eAAe,WAAW;AAC5C,cAAY,UAAU;EAGtB,MAAM,UAAU,sBAAsB,UAAU;AAChD,OAAK,MAAM,CAAC,KAAK,MAAM,IAAI,OAAO,QAAQ,QAAQ,CACjD,MAAK,KAAK,aAAa,EAAE,IAAI,GAAG,MAAM,EAAE;AAGzC,UAAQ,KAAK,iCAAiC,MAAM,GAAG;CACvD;CAED,MAAM,YAAY,KAAK,WAAW,aAAa;AAE/C,KAAI;AAEH,WAAS,KAAK,KAAK,IAAI,EAAE;GACxB,KAAK,QAAQ,KAAK;GAClB,OAAO;EACP,EAAC;EAIF,MAAM,WAAW,KAAK,WAAW,YAAY;AAE7C,MAAI,WAAW,SAAS,CACvB,OAAM,OAAO,UAAU,UAAU;EAIlC,MAAM,EAAE,sBAAU,GAAG,MAAM,OAAO;EAClC,MAAM,UAAU,MAAM,WAAS,WAAW,QAAQ;AAClD,OAAK,QAAQ,WAAW,KAAK,CAC5B,OAAM,UAAU,YAAY,uBAAuB,QAAQ,EAAE;CAE9D,SAAQ,OAAO;AACf,QAAM,IAAI,OACR,2BAA2B,iBAAiB,QAAQ,MAAM,UAAU,gBAAgB;CAEtF;AAED,QAAO;EACN,YAAY;EACZ;CACA;AACD"}