@geekmidas/cli 0.10.0 → 0.13.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.
- package/README.md +525 -0
- package/dist/bundler-B1qy9b-j.cjs +112 -0
- package/dist/bundler-B1qy9b-j.cjs.map +1 -0
- package/dist/bundler-DskIqW2t.mjs +111 -0
- package/dist/bundler-DskIqW2t.mjs.map +1 -0
- package/dist/{config-C9aXOHBe.cjs → config-AmInkU7k.cjs} +8 -8
- package/dist/config-AmInkU7k.cjs.map +1 -0
- package/dist/{config-BrkUalUh.mjs → config-DYULeEv8.mjs} +3 -3
- package/dist/config-DYULeEv8.mjs.map +1 -0
- package/dist/config.cjs +1 -1
- package/dist/config.d.cts +1 -1
- package/dist/config.d.mts +1 -1
- package/dist/config.mjs +1 -1
- package/dist/encryption-C8H-38Yy.mjs +42 -0
- package/dist/encryption-C8H-38Yy.mjs.map +1 -0
- package/dist/encryption-Dyf_r1h-.cjs +44 -0
- package/dist/encryption-Dyf_r1h-.cjs.map +1 -0
- package/dist/index.cjs +2123 -179
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +2141 -192
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-CZLI4QTr.mjs → openapi-BfFlOBCG.mjs} +801 -38
- package/dist/openapi-BfFlOBCG.mjs.map +1 -0
- package/dist/{openapi-BeHLKcwP.cjs → openapi-Bt_1FDpT.cjs} +794 -31
- package/dist/openapi-Bt_1FDpT.cjs.map +1 -0
- package/dist/{openapi-react-query-o5iMi8tz.cjs → openapi-react-query-B-sNWHFU.cjs} +5 -5
- package/dist/openapi-react-query-B-sNWHFU.cjs.map +1 -0
- package/dist/{openapi-react-query-CcciaVu5.mjs → openapi-react-query-B6XTeGqS.mjs} +5 -5
- package/dist/openapi-react-query-B6XTeGqS.mjs.map +1 -0
- package/dist/openapi-react-query.cjs +1 -1
- package/dist/openapi-react-query.d.cts.map +1 -1
- package/dist/openapi-react-query.d.mts.map +1 -1
- package/dist/openapi-react-query.mjs +1 -1
- package/dist/openapi.cjs +2 -2
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.cts.map +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/openapi.d.mts.map +1 -1
- package/dist/openapi.mjs +2 -2
- package/dist/storage-BOOpAF8N.cjs +5 -0
- package/dist/storage-Bj1E26lU.cjs +187 -0
- package/dist/storage-Bj1E26lU.cjs.map +1 -0
- package/dist/storage-kSxTjkNb.mjs +133 -0
- package/dist/storage-kSxTjkNb.mjs.map +1 -0
- package/dist/storage-tgZSUnKl.mjs +3 -0
- package/dist/{types-b-vwGpqc.d.cts → types-BR0M2v_c.d.mts} +100 -1
- package/dist/types-BR0M2v_c.d.mts.map +1 -0
- package/dist/{types-DXgiA1sF.d.mts → types-BhkZc-vm.d.cts} +100 -1
- package/dist/types-BhkZc-vm.d.cts.map +1 -0
- package/examples/cron-example.ts +27 -27
- package/examples/env.ts +27 -27
- package/examples/function-example.ts +31 -31
- package/examples/gkm.config.json +20 -20
- package/examples/gkm.config.ts +8 -8
- package/examples/gkm.minimal.config.json +5 -5
- package/examples/gkm.production.config.json +25 -25
- package/examples/logger.ts +2 -2
- package/package.json +6 -6
- package/src/__tests__/EndpointGenerator.hooks.spec.ts +191 -191
- package/src/__tests__/config.spec.ts +55 -55
- package/src/__tests__/loadEnvFiles.spec.ts +93 -93
- package/src/__tests__/normalizeHooksConfig.spec.ts +58 -58
- package/src/__tests__/openapi-react-query.spec.ts +497 -497
- package/src/__tests__/openapi.spec.ts +428 -428
- package/src/__tests__/test-helpers.ts +76 -76
- package/src/auth/__tests__/credentials.spec.ts +204 -0
- package/src/auth/__tests__/index.spec.ts +168 -0
- package/src/auth/credentials.ts +187 -0
- package/src/auth/index.ts +226 -0
- package/src/build/__tests__/bundler.spec.ts +444 -0
- package/src/build/__tests__/index-new.spec.ts +474 -474
- package/src/build/__tests__/manifests.spec.ts +333 -333
- package/src/build/bundler.ts +210 -0
- package/src/build/endpoint-analyzer.ts +236 -0
- package/src/build/handler-templates.ts +1253 -0
- package/src/build/index.ts +260 -179
- package/src/build/manifests.ts +52 -52
- package/src/build/providerResolver.ts +145 -145
- package/src/build/types.ts +64 -43
- package/src/config.ts +39 -39
- package/src/deploy/__tests__/docker.spec.ts +111 -0
- package/src/deploy/__tests__/dokploy.spec.ts +245 -0
- package/src/deploy/__tests__/init.spec.ts +662 -0
- package/src/deploy/docker.ts +128 -0
- package/src/deploy/dokploy.ts +204 -0
- package/src/deploy/index.ts +136 -0
- package/src/deploy/init.ts +484 -0
- package/src/deploy/types.ts +48 -0
- package/src/dev/__tests__/index.spec.ts +266 -266
- package/src/dev/index.ts +647 -601
- package/src/docker/__tests__/compose.spec.ts +531 -0
- package/src/docker/__tests__/templates.spec.ts +280 -0
- package/src/docker/compose.ts +273 -0
- package/src/docker/index.ts +230 -0
- package/src/docker/templates.ts +446 -0
- package/src/generators/CronGenerator.ts +72 -72
- package/src/generators/EndpointGenerator.ts +699 -398
- package/src/generators/FunctionGenerator.ts +84 -84
- package/src/generators/Generator.ts +72 -72
- package/src/generators/OpenApiTsGenerator.ts +577 -577
- package/src/generators/SubscriberGenerator.ts +124 -124
- package/src/generators/__tests__/CronGenerator.spec.ts +433 -433
- package/src/generators/__tests__/EndpointGenerator.spec.ts +532 -382
- package/src/generators/__tests__/FunctionGenerator.spec.ts +244 -244
- package/src/generators/__tests__/SubscriberGenerator.spec.ts +397 -382
- package/src/generators/index.ts +4 -4
- package/src/index.ts +623 -201
- package/src/init/__tests__/generators.spec.ts +334 -334
- package/src/init/__tests__/init.spec.ts +332 -332
- package/src/init/__tests__/utils.spec.ts +89 -89
- package/src/init/generators/config.ts +175 -175
- package/src/init/generators/docker.ts +41 -41
- package/src/init/generators/env.ts +72 -72
- package/src/init/generators/index.ts +1 -1
- package/src/init/generators/models.ts +64 -64
- package/src/init/generators/monorepo.ts +161 -161
- package/src/init/generators/package.ts +71 -71
- package/src/init/generators/source.ts +6 -6
- package/src/init/index.ts +203 -208
- package/src/init/templates/api.ts +115 -115
- package/src/init/templates/index.ts +75 -75
- package/src/init/templates/minimal.ts +98 -98
- package/src/init/templates/serverless.ts +89 -89
- package/src/init/templates/worker.ts +98 -98
- package/src/init/utils.ts +54 -56
- package/src/openapi-react-query.ts +194 -194
- package/src/openapi.ts +63 -63
- package/src/secrets/__tests__/encryption.spec.ts +226 -0
- package/src/secrets/__tests__/generator.spec.ts +319 -0
- package/src/secrets/__tests__/index.spec.ts +91 -0
- package/src/secrets/__tests__/storage.spec.ts +611 -0
- package/src/secrets/encryption.ts +91 -0
- package/src/secrets/generator.ts +164 -0
- package/src/secrets/index.ts +383 -0
- package/src/secrets/storage.ts +192 -0
- package/src/secrets/types.ts +53 -0
- package/src/types.ts +295 -176
- package/tsdown.config.ts +11 -8
- package/dist/config-BrkUalUh.mjs.map +0 -1
- package/dist/config-C9aXOHBe.cjs.map +0 -1
- package/dist/openapi-BeHLKcwP.cjs.map +0 -1
- package/dist/openapi-CZLI4QTr.mjs.map +0 -1
- package/dist/openapi-react-query-CcciaVu5.mjs.map +0 -1
- package/dist/openapi-react-query-o5iMi8tz.cjs.map +0 -1
- package/dist/types-DXgiA1sF.d.mts.map +0 -1
- 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,112 @@
|
|
|
1
|
+
const require_chunk = require('./chunk-CUT6urMc.cjs');
|
|
2
|
+
const node_fs = require_chunk.__toESM(require("node:fs"));
|
|
3
|
+
const node_path = require_chunk.__toESM(require("node:path"));
|
|
4
|
+
const node_fs_promises = require_chunk.__toESM(require("node:fs/promises"));
|
|
5
|
+
const node_child_process = require_chunk.__toESM(require("node:child_process"));
|
|
6
|
+
|
|
7
|
+
//#region src/build/bundler.ts
|
|
8
|
+
/**
|
|
9
|
+
* Collect all required environment variables from constructs.
|
|
10
|
+
* Uses the SnifferEnvironmentParser to detect which env vars each service needs.
|
|
11
|
+
*
|
|
12
|
+
* @param constructs - Array of constructs to analyze
|
|
13
|
+
* @returns Deduplicated array of required environment variable names
|
|
14
|
+
*/
|
|
15
|
+
async function collectRequiredEnvVars(constructs) {
|
|
16
|
+
const allEnvVars = /* @__PURE__ */ new Set();
|
|
17
|
+
for (const construct of constructs) {
|
|
18
|
+
const envVars = await construct.getEnvironment();
|
|
19
|
+
envVars.forEach((v) => allEnvVars.add(v));
|
|
20
|
+
}
|
|
21
|
+
return Array.from(allEnvVars).sort();
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Bundle the server application using tsdown
|
|
25
|
+
*
|
|
26
|
+
* @param options - Bundle configuration options
|
|
27
|
+
* @returns Bundle result with output path and optional master key
|
|
28
|
+
*/
|
|
29
|
+
async function bundleServer(options) {
|
|
30
|
+
const { entryPoint, outputDir, minify, sourcemap, external, stage, constructs } = options;
|
|
31
|
+
await (0, node_fs_promises.mkdir)(outputDir, { recursive: true });
|
|
32
|
+
const args = [
|
|
33
|
+
"npx",
|
|
34
|
+
"tsdown",
|
|
35
|
+
entryPoint,
|
|
36
|
+
"--no-config",
|
|
37
|
+
"--out-dir",
|
|
38
|
+
outputDir,
|
|
39
|
+
"--format",
|
|
40
|
+
"esm",
|
|
41
|
+
"--platform",
|
|
42
|
+
"node",
|
|
43
|
+
"--target",
|
|
44
|
+
"node22",
|
|
45
|
+
"--clean"
|
|
46
|
+
];
|
|
47
|
+
if (minify) args.push("--minify");
|
|
48
|
+
if (sourcemap) args.push("--sourcemap");
|
|
49
|
+
for (const ext of external) args.push("--external", ext);
|
|
50
|
+
args.push("--external", "node:*");
|
|
51
|
+
let masterKey;
|
|
52
|
+
if (stage) {
|
|
53
|
+
const { readStageSecrets, toEmbeddableSecrets, validateEnvironmentVariables } = await Promise.resolve().then(() => require("./storage-BOOpAF8N.cjs"));
|
|
54
|
+
const { encryptSecrets, generateDefineOptions } = await Promise.resolve().then(() => require("./encryption-Dyf_r1h-.cjs"));
|
|
55
|
+
const secrets = await readStageSecrets(stage);
|
|
56
|
+
if (!secrets) throw new Error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
57
|
+
if (constructs && constructs.length > 0) {
|
|
58
|
+
console.log(" Analyzing environment variable requirements...");
|
|
59
|
+
const requiredVars = await collectRequiredEnvVars(constructs);
|
|
60
|
+
if (requiredVars.length > 0) {
|
|
61
|
+
const validation = validateEnvironmentVariables(requiredVars, secrets);
|
|
62
|
+
if (!validation.valid) {
|
|
63
|
+
const errorMessage = [
|
|
64
|
+
`Missing environment variables for stage "${stage}":`,
|
|
65
|
+
"",
|
|
66
|
+
...validation.missing.map((v) => ` ❌ ${v}`),
|
|
67
|
+
"",
|
|
68
|
+
"To fix this, either:",
|
|
69
|
+
` 1. Add the missing variables to .gkm/secrets/${stage}.json using:`,
|
|
70
|
+
` gkm secrets:set <KEY> <VALUE> --stage ${stage}`,
|
|
71
|
+
"",
|
|
72
|
+
` 2. Or import from a JSON file:`,
|
|
73
|
+
` gkm secrets:import secrets.json --stage ${stage}`,
|
|
74
|
+
"",
|
|
75
|
+
"Required variables:",
|
|
76
|
+
...validation.required.map((v) => validation.missing.includes(v) ? ` ❌ ${v}` : ` ✓ ${v}`)
|
|
77
|
+
].join("\n");
|
|
78
|
+
throw new Error(errorMessage);
|
|
79
|
+
}
|
|
80
|
+
console.log(` ✓ All ${requiredVars.length} required environment variables found`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const embeddable = toEmbeddableSecrets(secrets);
|
|
84
|
+
const encrypted = encryptSecrets(embeddable);
|
|
85
|
+
masterKey = encrypted.masterKey;
|
|
86
|
+
const defines = generateDefineOptions(encrypted);
|
|
87
|
+
for (const [key, value] of Object.entries(defines)) args.push("--define", `${key}=${value}`);
|
|
88
|
+
console.log(` Secrets encrypted for stage "${stage}"`);
|
|
89
|
+
}
|
|
90
|
+
const mjsOutput = (0, node_path.join)(outputDir, "server.mjs");
|
|
91
|
+
try {
|
|
92
|
+
(0, node_child_process.execSync)(args.join(" "), {
|
|
93
|
+
cwd: process.cwd(),
|
|
94
|
+
stdio: "inherit"
|
|
95
|
+
});
|
|
96
|
+
const jsOutput = (0, node_path.join)(outputDir, "server.js");
|
|
97
|
+
if ((0, node_fs.existsSync)(jsOutput)) await (0, node_fs_promises.rename)(jsOutput, mjsOutput);
|
|
98
|
+
const { readFile } = await import("node:fs/promises");
|
|
99
|
+
const content = await readFile(mjsOutput, "utf-8");
|
|
100
|
+
if (!content.startsWith("#!")) await (0, node_fs_promises.writeFile)(mjsOutput, `#!/usr/bin/env node\n${content}`);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
throw new Error(`Failed to bundle server: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
outputPath: mjsOutput,
|
|
106
|
+
masterKey
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
//#endregion
|
|
111
|
+
exports.bundleServer = bundleServer;
|
|
112
|
+
//# sourceMappingURL=bundler-B1qy9b-j.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundler-B1qy9b-j.cjs","names":["constructs: Construct[]","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';\nimport type { Construct } from '@geekmidas/constructs';\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\t/** Constructs to validate environment variables for */\n\tconstructs?: Construct[];\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 * Collect all required environment variables from constructs.\n * Uses the SnifferEnvironmentParser to detect which env vars each service needs.\n *\n * @param constructs - Array of constructs to analyze\n * @returns Deduplicated array of required environment variable names\n */\nasync function collectRequiredEnvVars(\n\tconstructs: Construct[],\n): Promise<string[]> {\n\tconst allEnvVars = new Set<string>();\n\n\tfor (const construct of constructs) {\n\t\tconst envVars = await construct.getEnvironment();\n\t\tenvVars.forEach((v) => allEnvVars.add(v));\n\t}\n\n\treturn Array.from(allEnvVars).sort();\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 {\n\t\tentryPoint,\n\t\toutputDir,\n\t\tminify,\n\t\tsourcemap,\n\t\texternal,\n\t\tstage,\n\t\tconstructs,\n\t} = 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 {\n\t\t\treadStageSecrets,\n\t\t\ttoEmbeddableSecrets,\n\t\t\tvalidateEnvironmentVariables,\n\t\t} = await import('../secrets/storage');\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// Validate environment variables if constructs are provided\n\t\tif (constructs && constructs.length > 0) {\n\t\t\tconsole.log(' Analyzing environment variable requirements...');\n\t\t\tconst requiredVars = await collectRequiredEnvVars(constructs);\n\n\t\t\tif (requiredVars.length > 0) {\n\t\t\t\tconst validation = validateEnvironmentVariables(requiredVars, secrets);\n\n\t\t\t\tif (!validation.valid) {\n\t\t\t\t\tconst errorMessage = [\n\t\t\t\t\t\t`Missing environment variables for stage \"${stage}\":`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t...validation.missing.map((v) => ` ❌ ${v}`),\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t'To fix this, either:',\n\t\t\t\t\t\t` 1. Add the missing variables to .gkm/secrets/${stage}.json using:`,\n\t\t\t\t\t\t` gkm secrets:set <KEY> <VALUE> --stage ${stage}`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t` 2. Or import from a JSON file:`,\n\t\t\t\t\t\t` gkm secrets:import secrets.json --stage ${stage}`,\n\t\t\t\t\t\t'',\n\t\t\t\t\t\t'Required variables:',\n\t\t\t\t\t\t...validation.required.map((v) =>\n\t\t\t\t\t\t\tvalidation.missing.includes(v) ? ` ❌ ${v}` : ` ✓ ${v}`,\n\t\t\t\t\t\t),\n\t\t\t\t\t].join('\\n');\n\n\t\t\t\t\tthrow new Error(errorMessage);\n\t\t\t\t}\n\n\t\t\t\tconsole.log(\n\t\t\t\t\t` ✓ All ${requiredVars.length} required environment variables found`,\n\t\t\t\t);\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":";;;;;;;;;;;;;;AAqCA,eAAe,uBACdA,YACoB;CACpB,MAAM,6BAAa,IAAI;AAEvB,MAAK,MAAM,aAAa,YAAY;EACnC,MAAM,UAAU,MAAM,UAAU,gBAAgB;AAChD,UAAQ,QAAQ,CAAC,MAAM,WAAW,IAAI,EAAE,CAAC;CACzC;AAED,QAAO,MAAM,KAAK,WAAW,CAAC,MAAM;AACpC;;;;;;;AAQD,eAAsB,aACrBC,SACwB;CACxB,MAAM,EACL,YACA,WACA,QACA,WACA,UACA,OACA,YACA,GAAG;AAGJ,OAAM,4BAAM,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,EACL,kBACA,qBACA,8BACA,GAAG,2CAAM;EACV,MAAM,EAAE,gBAAgB,uBAAuB,GAAG,2CAAM;EAIxD,MAAM,UAAU,MAAM,iBAAiB,MAAM;AAE7C,OAAK,QACJ,OAAM,IAAI,OACR,8BAA8B,MAAM,mCAAmC,MAAM;AAKhF,MAAI,cAAc,WAAW,SAAS,GAAG;AACxC,WAAQ,IAAI,mDAAmD;GAC/D,MAAM,eAAe,MAAM,uBAAuB,WAAW;AAE7D,OAAI,aAAa,SAAS,GAAG;IAC5B,MAAM,aAAa,6BAA6B,cAAc,QAAQ;AAEtE,SAAK,WAAW,OAAO;KACtB,MAAM,eAAe;OACnB,2CAA2C,MAAM;MAClD;MACA,GAAG,WAAW,QAAQ,IAAI,CAAC,OAAO,MAAM,EAAE,EAAE;MAC5C;MACA;OACC,iDAAiD,MAAM;OACvD,6CAA6C,MAAM;MACpD;OACC;OACA,+CAA+C,MAAM;MACtD;MACA;MACA,GAAG,WAAW,SAAS,IAAI,CAAC,MAC3B,WAAW,QAAQ,SAAS,EAAE,IAAI,MAAM,EAAE,KAAK,MAAM,EAAE,EACvD;KACD,EAAC,KAAK,KAAK;AAEZ,WAAM,IAAI,MAAM;IAChB;AAED,YAAQ,KACN,UAAU,aAAa,OAAO,uCAC/B;GACD;EACD;EAGD,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,oBAAK,WAAW,aAAa;AAE/C,KAAI;AAEH,mCAAS,KAAK,KAAK,IAAI,EAAE;GACxB,KAAK,QAAQ,KAAK;GAClB,OAAO;EACP,EAAC;EAIF,MAAM,WAAW,oBAAK,WAAW,YAAY;AAE7C,MAAI,wBAAW,SAAS,CACvB,OAAM,6BAAO,UAAU,UAAU;EAIlC,MAAM,EAAE,UAAU,GAAG,MAAM,OAAO;EAClC,MAAM,UAAU,MAAM,SAAS,WAAW,QAAQ;AAClD,OAAK,QAAQ,WAAW,KAAK,CAC5B,OAAM,gCAAU,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"}
|