@geekmidas/cli 10.0.0-alpha.3 → 10.0.0-alpha.30
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 +25 -48
- package/bin/adjacent-tsconfig.mjs +97 -0
- package/bin/gkm.mjs +7 -1
- package/dist/{HostingerProvider-h3EbEp-Z.cjs → HostingerProvider-BZFGYYVA.cjs} +21 -22
- package/dist/HostingerProvider-BZFGYYVA.cjs.map +1 -0
- package/dist/{HostingerProvider-Cft7lEYr.mjs → HostingerProvider-GF6NAZAK.mjs} +20 -21
- package/dist/HostingerProvider-GF6NAZAK.mjs.map +1 -0
- package/dist/{LocalStateProvider-Db9SEjyH.cjs → LocalStateProvider-7HR6SS2t.cjs} +11 -6
- package/dist/LocalStateProvider-7HR6SS2t.cjs.map +1 -0
- package/dist/{LocalStateProvider-Bn6tS-oL.mjs → LocalStateProvider-D7QyJoLM.mjs} +10 -4
- package/dist/LocalStateProvider-D7QyJoLM.mjs.map +1 -0
- package/dist/{openapi-c9bpieI5.cjs → OpenApiTsGenerator-D1nhN98m.cjs} +135 -298
- package/dist/OpenApiTsGenerator-D1nhN98m.cjs.map +1 -0
- package/dist/{openapi-CY9xFkTH.mjs → OpenApiTsGenerator-hDINScnz.mjs} +106 -250
- package/dist/OpenApiTsGenerator-hDINScnz.mjs.map +1 -0
- package/dist/{Route53Provider-RdpUDJQk.mjs → Route53Provider-Cvj40A8I.mjs} +12 -12
- package/dist/Route53Provider-Cvj40A8I.mjs.map +1 -0
- package/dist/{Route53Provider-di2fMM5N.cjs → Route53Provider-DVILBsvV.cjs} +20 -21
- package/dist/Route53Provider-DVILBsvV.cjs.map +1 -0
- package/dist/{SSMStateProvider-BRt2py-A.mjs → SSMStateProvider-B0Hu1MKZ.mjs} +13 -4
- package/dist/SSMStateProvider-B0Hu1MKZ.mjs.map +1 -0
- package/dist/{SSMStateProvider-DyX8s9HI.cjs → SSMStateProvider-VrQ-yeqd.cjs} +17 -9
- package/dist/SSMStateProvider-VrQ-yeqd.cjs.map +1 -0
- package/dist/{backup-provisioner-BrsHwYG0.cjs → backup-provisioner-BmhIO_Yr.cjs} +21 -19
- package/dist/backup-provisioner-BmhIO_Yr.cjs.map +1 -0
- package/dist/{backup-provisioner-Bgj5Vi_L.mjs → backup-provisioner-DIVCbfee.mjs} +10 -7
- package/dist/backup-provisioner-DIVCbfee.mjs.map +1 -0
- package/dist/{bundler-BY1OmV1e.cjs → bundler-CYF5mWi9.cjs} +10 -28
- package/dist/bundler-CYF5mWi9.cjs.map +1 -0
- package/dist/{bundler-Be8Zg4Wd.mjs → bundler-D9IPNvDt.mjs} +9 -26
- package/dist/bundler-D9IPNvDt.mjs.map +1 -0
- package/dist/{config-CzWnfcXM.mjs → config-BH3Ki69F.mjs} +49 -89
- package/dist/config-BH3Ki69F.mjs.map +1 -0
- package/dist/{config-BholeD-0.cjs → config-URVD4bTj.cjs} +101 -148
- package/dist/config-URVD4bTj.cjs.map +1 -0
- package/dist/config.cjs +5 -5
- package/dist/config.d.cts +25 -17
- package/dist/config.d.cts.map +1 -1
- package/dist/config.d.mts +25 -17
- package/dist/config.d.mts.map +1 -1
- package/dist/config.mjs +3 -5
- package/dist/deploy/sniffer-envkit-patch.cjs +18 -10
- package/dist/deploy/sniffer-envkit-patch.cjs.map +1 -1
- package/dist/deploy/sniffer-envkit-patch.d.cts +0 -2
- package/dist/deploy/sniffer-envkit-patch.d.cts.map +1 -1
- package/dist/deploy/sniffer-envkit-patch.d.mts +0 -2
- package/dist/deploy/sniffer-envkit-patch.d.mts.map +1 -1
- package/dist/deploy/sniffer-envkit-patch.mjs +10 -2
- package/dist/deploy/sniffer-envkit-patch.mjs.map +1 -1
- package/dist/deploy/sniffer-hooks.cjs +13 -7
- package/dist/deploy/sniffer-hooks.cjs.map +1 -1
- package/dist/deploy/sniffer-hooks.d.cts +1 -2
- package/dist/deploy/sniffer-hooks.d.cts.map +1 -1
- package/dist/deploy/sniffer-hooks.d.mts +1 -2
- package/dist/deploy/sniffer-hooks.d.mts.map +1 -1
- package/dist/deploy/sniffer-hooks.mjs +9 -2
- package/dist/deploy/sniffer-hooks.mjs.map +1 -1
- package/dist/deploy/sniffer-loader.cjs +16 -7
- package/dist/deploy/sniffer-loader.cjs.map +1 -1
- package/dist/deploy/sniffer-loader.d.cts +1 -1
- package/dist/deploy/sniffer-loader.d.mts +1 -1
- package/dist/deploy/sniffer-loader.mjs +13 -2
- package/dist/deploy/sniffer-loader.mjs.map +1 -1
- package/dist/deploy/sniffer-routes-worker.cjs +11 -11
- package/dist/deploy/sniffer-routes-worker.cjs.map +1 -1
- package/dist/deploy/sniffer-routes-worker.d.cts +1 -1
- package/dist/deploy/sniffer-routes-worker.d.mts +1 -1
- package/dist/deploy/sniffer-routes-worker.mjs +3 -3
- package/dist/deploy/sniffer-routes-worker.mjs.map +1 -1
- package/dist/deploy/sniffer-worker.cjs +15 -6
- package/dist/deploy/sniffer-worker.cjs.map +1 -1
- package/dist/deploy/sniffer-worker.d.cts +1 -3
- package/dist/deploy/sniffer-worker.d.cts.map +1 -1
- package/dist/deploy/sniffer-worker.d.mts +1 -3
- package/dist/deploy/sniffer-worker.d.mts.map +1 -1
- package/dist/deploy/sniffer-worker.mjs +15 -4
- package/dist/deploy/sniffer-worker.mjs.map +1 -1
- package/dist/{discover-CAlFgYec.mjs → discover-CMxesskG.mjs} +66 -22
- package/dist/discover-CMxesskG.mjs.map +1 -0
- package/dist/{discover-DTmFeloL.cjs → discover-CroTIm0K.cjs} +117 -66
- package/dist/discover-CroTIm0K.cjs.map +1 -0
- package/dist/{emit-CbcRolsa.cjs → emit-BdoPFjZf.cjs} +53 -36
- package/dist/emit-BdoPFjZf.cjs.map +1 -0
- package/dist/{emit-CiZS_5Ub.mjs → emit-Bzq7JsMP.mjs} +24 -6
- package/dist/emit-Bzq7JsMP.mjs.map +1 -0
- package/dist/github-B2fis34P.cjs +216 -0
- package/dist/github-B2fis34P.cjs.map +1 -0
- package/dist/github-BrDmlm1x.mjs +216 -0
- package/dist/github-BrDmlm1x.mjs.map +1 -0
- package/dist/{index-CHB4tSzX.d.cts → index-p89EzILa.d.mts} +4 -23
- package/dist/index-p89EzILa.d.mts.map +1 -0
- package/dist/{index-BThUzEIp.d.mts → index-rU9u761a.d.cts} +4 -23
- package/dist/index-rU9u761a.d.cts.map +1 -0
- package/dist/index.cjs +6001 -4452
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +5895 -4367
- package/dist/index.mjs.map +1 -1
- package/dist/migrate-BuCx7tIv.cjs +498 -0
- package/dist/migrate-BuCx7tIv.cjs.map +1 -0
- package/dist/migrate-CBICVMAi.mjs +493 -0
- package/dist/migrate-CBICVMAi.mjs.map +1 -0
- package/dist/openapi.cjs +197 -8
- package/dist/openapi.cjs.map +1 -0
- package/dist/openapi.d.cts +47 -14
- package/dist/openapi.d.cts.map +1 -1
- package/dist/openapi.d.mts +47 -14
- package/dist/openapi.d.mts.map +1 -1
- package/dist/openapi.mjs +191 -5
- package/dist/openapi.mjs.map +1 -0
- package/dist/ready-CvP3JVEw.mjs +355 -0
- package/dist/ready-CvP3JVEw.mjs.map +1 -0
- package/dist/ready-DXtNfk7v.cjs +390 -0
- package/dist/ready-DXtNfk7v.cjs.map +1 -0
- package/dist/reconcile/public.cjs +4 -4
- package/dist/reconcile/public.d.cts +27 -26
- package/dist/reconcile/public.d.cts.map +1 -1
- package/dist/reconcile/public.d.mts +27 -26
- package/dist/reconcile/public.d.mts.map +1 -1
- package/dist/reconcile/public.mjs +3 -4
- package/dist/reconcile-BxZTV2vl.cjs +332 -0
- package/dist/reconcile-BxZTV2vl.cjs.map +1 -0
- package/dist/reconcile-yNq6Shau.mjs +279 -0
- package/dist/reconcile-yNq6Shau.mjs.map +1 -0
- package/dist/rolldown-runtime-BhDjJH2R.mjs +15 -0
- package/dist/{chunk-CUT6urMc.cjs → rolldown-runtime-C0BPl7ul.cjs} +23 -10
- package/dist/ssm-BKN1Cg4z.mjs +60 -0
- package/dist/ssm-BKN1Cg4z.mjs.map +1 -0
- package/dist/ssm-FxeSAt35.cjs +60 -0
- package/dist/ssm-FxeSAt35.cjs.map +1 -0
- package/dist/{storage-Bi24WfoW.mjs → storage-BjTLjDDp.mjs} +22 -22
- package/dist/storage-BjTLjDDp.mjs.map +1 -0
- package/dist/{storage-BJmzYxt8.cjs → storage-C2JC31V9.cjs} +65 -84
- package/dist/storage-C2JC31V9.cjs.map +1 -0
- package/dist/types-CzK6hsXP.d.mts +6286 -0
- package/dist/types-CzK6hsXP.d.mts.map +1 -0
- package/dist/types-DOOAevjG.d.cts +6287 -0
- package/dist/types-DOOAevjG.d.cts.map +1 -0
- package/dist/vitest.cjs +61 -0
- package/dist/vitest.cjs.map +1 -0
- package/dist/vitest.d.cts +28 -0
- package/dist/vitest.d.cts.map +1 -0
- package/dist/vitest.d.mts +28 -0
- package/dist/vitest.d.mts.map +1 -0
- package/dist/vitest.mjs +61 -0
- package/dist/vitest.mjs.map +1 -0
- package/dist/workspace/index.cjs +3 -4
- package/dist/workspace/index.d.cts +3 -3
- package/dist/workspace/index.d.mts +3 -3
- package/dist/workspace/index.mjs +2 -3
- package/dist/workspace-Bav3TVrP.cjs +6710 -0
- package/dist/workspace-Bav3TVrP.cjs.map +1 -0
- package/dist/{credentials-DQmbRnr1.cjs → workspace-BhiMkRuA.cjs} +2076 -1963
- package/dist/workspace-BhiMkRuA.cjs.map +1 -0
- package/dist/{credentials-BZcp3CGx.mjs → workspace-C97QUCIf.mjs} +1957 -1837
- package/dist/workspace-C97QUCIf.mjs.map +1 -0
- package/dist/workspace-T4IVGSoB.mjs +6538 -0
- package/dist/workspace-T4IVGSoB.mjs.map +1 -0
- package/package.json +45 -38
- package/dist/CachedStateProvider-BLo7zv7S.cjs +0 -3
- package/dist/CachedStateProvider-BxoAm-B2.mjs +0 -3
- package/dist/CachedStateProvider-CqPdW7CB.cjs +0 -60
- package/dist/CachedStateProvider-CqPdW7CB.cjs.map +0 -1
- package/dist/CachedStateProvider-DTniehMP.mjs +0 -54
- package/dist/CachedStateProvider-DTniehMP.mjs.map +0 -1
- package/dist/HostingerProvider-Cft7lEYr.mjs.map +0 -1
- package/dist/HostingerProvider-h3EbEp-Z.cjs.map +0 -1
- package/dist/LocalStateProvider-Bn6tS-oL.mjs.map +0 -1
- package/dist/LocalStateProvider-Db9SEjyH.cjs.map +0 -1
- package/dist/Route53Provider-RdpUDJQk.mjs.map +0 -1
- package/dist/Route53Provider-di2fMM5N.cjs.map +0 -1
- package/dist/SSMStateProvider-BRt2py-A.mjs.map +0 -1
- package/dist/SSMStateProvider-DyX8s9HI.cjs.map +0 -1
- package/dist/backup-provisioner-Bgj5Vi_L.mjs.map +0 -1
- package/dist/backup-provisioner-BrsHwYG0.cjs.map +0 -1
- package/dist/bundler-BY1OmV1e.cjs.map +0 -1
- package/dist/bundler-Be8Zg4Wd.mjs.map +0 -1
- package/dist/chunk-Duj1WY3L.mjs +0 -7
- package/dist/config-BholeD-0.cjs.map +0 -1
- package/dist/config-CzWnfcXM.mjs.map +0 -1
- package/dist/credentials-B5zRHdAl.mjs +0 -141
- package/dist/credentials-B5zRHdAl.mjs.map +0 -1
- package/dist/credentials-BZcp3CGx.mjs.map +0 -1
- package/dist/credentials-BapzjyV5.cjs +0 -195
- package/dist/credentials-BapzjyV5.cjs.map +0 -1
- package/dist/credentials-DQmbRnr1.cjs.map +0 -1
- package/dist/discover-CAlFgYec.mjs.map +0 -1
- package/dist/discover-DTmFeloL.cjs.map +0 -1
- package/dist/dokploy-api-BS2xnJLZ.mjs +0 -3
- package/dist/dokploy-api-BsVH0y_s.cjs +0 -718
- package/dist/dokploy-api-BsVH0y_s.cjs.map +0 -1
- package/dist/dokploy-api-BuxDRo3l.cjs +0 -3
- package/dist/dokploy-api-aTFPz7_4.mjs +0 -706
- package/dist/dokploy-api-aTFPz7_4.mjs.map +0 -1
- package/dist/emit-CbcRolsa.cjs.map +0 -1
- package/dist/emit-CiZS_5Ub.mjs.map +0 -1
- package/dist/encryption-C6jaU0hE.cjs +0 -54
- package/dist/encryption-C6jaU0hE.cjs.map +0 -1
- package/dist/encryption-CHzzeR8a.cjs +0 -4
- package/dist/encryption-CQUncu7v.mjs +0 -3
- package/dist/encryption-D-abyhZx.mjs +0 -42
- package/dist/encryption-D-abyhZx.mjs.map +0 -1
- package/dist/fullstack-secrets-C-GGohke.cjs +0 -111
- package/dist/fullstack-secrets-C-GGohke.cjs.map +0 -1
- package/dist/fullstack-secrets-D1o9-G5l.mjs +0 -87
- package/dist/fullstack-secrets-D1o9-G5l.mjs.map +0 -1
- package/dist/generator-CNzBw7g5.cjs +0 -289
- package/dist/generator-CNzBw7g5.cjs.map +0 -1
- package/dist/generator-VE2JrFlY.mjs +0 -253
- package/dist/generator-VE2JrFlY.mjs.map +0 -1
- package/dist/index-BThUzEIp.d.mts.map +0 -1
- package/dist/index-CHB4tSzX.d.cts.map +0 -1
- package/dist/openapi-CY9xFkTH.mjs.map +0 -1
- package/dist/openapi-c9bpieI5.cjs.map +0 -1
- package/dist/openapi-react-query-Byltvm_7.mjs +0 -167
- package/dist/openapi-react-query-Byltvm_7.mjs.map +0 -1
- package/dist/openapi-react-query-CmJ1xDbn.cjs +0 -173
- package/dist/openapi-react-query-CmJ1xDbn.cjs.map +0 -1
- package/dist/openapi-react-query.cjs +0 -4
- package/dist/openapi-react-query.d.cts +0 -11
- package/dist/openapi-react-query.d.cts.map +0 -1
- package/dist/openapi-react-query.d.mts +0 -11
- package/dist/openapi-react-query.d.mts.map +0 -1
- package/dist/openapi-react-query.mjs +0 -4
- package/dist/reconcile-CFs8C_1a.mjs +0 -37
- package/dist/reconcile-CFs8C_1a.mjs.map +0 -1
- package/dist/reconcile-D0pimNiy.cjs +0 -37
- package/dist/reconcile-D0pimNiy.cjs.map +0 -1
- package/dist/setup-Bc73t0ve.mjs +0 -443
- package/dist/setup-Bc73t0ve.mjs.map +0 -1
- package/dist/setup-C48C9hto.cjs +0 -473
- package/dist/setup-C48C9hto.cjs.map +0 -1
- package/dist/setup-DCuudnCl.mjs +0 -11
- package/dist/setup-fG_ivuxO.cjs +0 -11
- package/dist/storage-BJmzYxt8.cjs.map +0 -1
- package/dist/storage-BM9hGpgv.cjs +0 -7
- package/dist/storage-Bi24WfoW.mjs.map +0 -1
- package/dist/storage-CYlGwWTj.mjs +0 -3
- package/dist/sync-CmEuNZsB.mjs +0 -76
- package/dist/sync-CmEuNZsB.mjs.map +0 -1
- package/dist/sync-D306yOWu.cjs +0 -4
- package/dist/sync-SizaChmC.cjs +0 -93
- package/dist/sync-SizaChmC.cjs.map +0 -1
- package/dist/sync-hOqAWHR6.mjs +0 -3
- package/dist/types-C-arnQ1r.d.mts +0 -2184
- package/dist/types-C-arnQ1r.d.mts.map +0 -1
- package/dist/types-abJQuk7m.d.cts +0 -2184
- package/dist/types-abJQuk7m.d.cts.map +0 -1
- package/dist/workspace-7FqprxuJ.mjs +0 -4323
- package/dist/workspace-7FqprxuJ.mjs.map +0 -1
- package/dist/workspace-BO_KWohI.cjs +0 -9
- package/dist/workspace-DK4WWJIn.mjs +0 -8
- package/dist/workspace-XvqO9jIv.cjs +0 -4472
- package/dist/workspace-XvqO9jIv.cjs.map +0 -1
package/README.md
CHANGED
|
@@ -54,6 +54,7 @@ Create a `gkm.config.ts` file in your project root:
|
|
|
54
54
|
import { defineConfig } from '@geekmidas/cli/config';
|
|
55
55
|
|
|
56
56
|
export default defineConfig({
|
|
57
|
+
stages: { local: 'dev', deployed: ['prod'] },
|
|
57
58
|
// Glob pattern to find endpoint files
|
|
58
59
|
routes: 'src/routes/**/*.ts',
|
|
59
60
|
|
|
@@ -443,7 +444,7 @@ gkm docker [options]
|
|
|
443
444
|
|
|
444
445
|
**Generated Files:**
|
|
445
446
|
- `.gkm/docker/Dockerfile` - Multi-stage or slim Dockerfile
|
|
446
|
-
-
|
|
447
|
+
- `docker-compose.constructs.yml` - the containers the constructs imply, and the app (project root)
|
|
447
448
|
- `.gkm/docker/docker-entrypoint.sh` - Entrypoint script
|
|
448
449
|
- `.dockerignore` - Docker ignore file (project root)
|
|
449
450
|
|
|
@@ -490,6 +491,7 @@ Configure Docker settings in `gkm.config.ts`:
|
|
|
490
491
|
import { defineConfig } from '@geekmidas/cli/config';
|
|
491
492
|
|
|
492
493
|
export default defineConfig({
|
|
494
|
+
stages: { local: 'dev', deployed: ['prod'] },
|
|
493
495
|
routes: 'src/routes/**/*.ts',
|
|
494
496
|
envParser: './src/env.ts',
|
|
495
497
|
logger: './src/logger.ts',
|
|
@@ -503,38 +505,20 @@ export default defineConfig({
|
|
|
503
505
|
baseImage: 'node:22-alpine',
|
|
504
506
|
// Port to expose (default: 3000)
|
|
505
507
|
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
508
|
},
|
|
515
509
|
});
|
|
516
510
|
```
|
|
517
511
|
|
|
518
|
-
**
|
|
519
|
-
|
|
520
|
-
|
|
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
|
-
}
|
|
512
|
+
**Compose:** there is no `compose` option. `gkm docker` writes
|
|
513
|
+
`docker-compose.constructs.yml` at the project root from the declared
|
|
514
|
+
constructs; an image pin or an extra service goes in your own
|
|
515
|
+
`docker-compose.yml`, which is merged over it:
|
|
535
516
|
|
|
536
|
-
|
|
537
|
-
|
|
517
|
+
```yaml
|
|
518
|
+
# docker-compose.yml
|
|
519
|
+
services:
|
|
520
|
+
postgres:
|
|
521
|
+
image: postgis/postgis:16-3.4-alpine
|
|
538
522
|
```
|
|
539
523
|
|
|
540
524
|
**Service Configuration Options:**
|
|
@@ -682,8 +666,8 @@ gkm secrets:init --stage production --force
|
|
|
682
666
|
```
|
|
683
667
|
|
|
684
668
|
**Generated:**
|
|
685
|
-
-
|
|
686
|
-
|
|
669
|
+
- Custom secrets for the stage; the containers' credentials are provisioned by
|
|
670
|
+
reconcile from the declared constructs, not stored here
|
|
687
671
|
- Stored in `.gkm/secrets/<stage>.json` (gitignored)
|
|
688
672
|
|
|
689
673
|
### `gkm secrets:set`
|
|
@@ -929,6 +913,7 @@ This allows the CLI to validate that all required secrets are configured before
|
|
|
929
913
|
```typescript
|
|
930
914
|
// gkm.config.ts
|
|
931
915
|
export default defineConfig({
|
|
916
|
+
stages: { local: 'dev', deployed: ['prod'] },
|
|
932
917
|
routes: 'src/endpoints/**/*.ts',
|
|
933
918
|
envParser: './src/env.ts',
|
|
934
919
|
logger: './src/logger.ts',
|
|
@@ -1216,6 +1201,7 @@ import { defineWorkspace } from '@geekmidas/cli/config';
|
|
|
1216
1201
|
|
|
1217
1202
|
export default defineWorkspace({
|
|
1218
1203
|
name: 'my-project',
|
|
1204
|
+
stages: { local: 'dev', deployed: ['prod'] },
|
|
1219
1205
|
constructs: './constructs/**/*.ts',
|
|
1220
1206
|
shared: {
|
|
1221
1207
|
packages: ['packages/*'],
|
|
@@ -1239,7 +1225,7 @@ them out of the manifest; none of them is listed in config.
|
|
|
1239
1225
|
|
|
1240
1226
|
```typescript
|
|
1241
1227
|
// constructs/api.ts
|
|
1242
|
-
export const api = new RestApi('Api', { defaultAuthorizer: 'none', logger });
|
|
1228
|
+
export const api = new RestApi('Api', { path: 'apps/api', defaultAuthorizer: 'none', logger });
|
|
1243
1229
|
```
|
|
1244
1230
|
|
|
1245
1231
|
`gkm build --provider server` generates a Hono server from the endpoints built
|
|
@@ -1253,6 +1239,7 @@ An auth server mounts a wildcard, so there is nothing for a glob to find:
|
|
|
1253
1239
|
```typescript
|
|
1254
1240
|
// constructs/auth.ts
|
|
1255
1241
|
export const auth = new BetterAuth('Auth', {
|
|
1242
|
+
path: 'apps/auth',
|
|
1256
1243
|
database: authDb,
|
|
1257
1244
|
basePath: '/api/auth',
|
|
1258
1245
|
});
|
|
@@ -1271,8 +1258,8 @@ from being read by it.
|
|
|
1271
1258
|
|
|
1272
1259
|
```typescript
|
|
1273
1260
|
// constructs/site.ts
|
|
1274
|
-
export const web = new StaticSite('Web').dependsOn([api, auth]);
|
|
1275
|
-
export const admin = new StaticSite('Admin', { variant: 'next' }).dependsOn([api]);
|
|
1261
|
+
export const web = new StaticSite('Web', { path: 'apps/web' }).dependsOn([api, auth]);
|
|
1262
|
+
export const admin = new StaticSite('Admin', { path: 'apps/admin', variant: 'next' }).dependsOn([api]);
|
|
1276
1263
|
```
|
|
1277
1264
|
|
|
1278
1265
|
`.dependsOn()` is the single fact behind four things that are hand-maintained
|
|
@@ -1291,7 +1278,7 @@ gkm docker
|
|
|
1291
1278
|
- `.gkm/docker/Dockerfile.api` - Routes-based backend (uses `gkm build`)
|
|
1292
1279
|
- `.gkm/docker/Dockerfile.auth` - Entry-based backend (uses esbuild bundling)
|
|
1293
1280
|
- `.gkm/docker/Dockerfile.web` - Next.js standalone output
|
|
1294
|
-
-
|
|
1281
|
+
- `docker-compose.constructs.yml` - every container the constructs imply, and each app behind the `apps` profile (project root)
|
|
1295
1282
|
- `.dockerignore` - Optimized ignore patterns
|
|
1296
1283
|
|
|
1297
1284
|
**Dockerfile types by app:**
|
|
@@ -1358,12 +1345,10 @@ interface WorkspaceConfig {
|
|
|
1358
1345
|
What an app is comes from the declaration, not from here:
|
|
1359
1346
|
|
|
1360
1347
|
```typescript
|
|
1361
|
-
/**
|
|
1348
|
+
/** How a site is built and run. A surface declares only its `path`. */
|
|
1362
1349
|
interface AppSpec {
|
|
1363
|
-
/**
|
|
1364
|
-
path
|
|
1365
|
-
/** Default: the conventional directories under `path`. */
|
|
1366
|
-
code?: string;
|
|
1350
|
+
/** Required: where the app lives, relative to the workspace root. */
|
|
1351
|
+
path: string;
|
|
1367
1352
|
/** Default: assigned in a stable order, so adding an app renumbers nothing. */
|
|
1368
1353
|
port?: number;
|
|
1369
1354
|
entry?: string;
|
|
@@ -1883,6 +1868,7 @@ Configure multiple patterns for complex project structures:
|
|
|
1883
1868
|
import { defineConfig } from '@geekmidas/cli/config';
|
|
1884
1869
|
|
|
1885
1870
|
export default defineConfig({
|
|
1871
|
+
stages: { local: 'dev', deployed: ['prod'] },
|
|
1886
1872
|
routes: [
|
|
1887
1873
|
'src/routes/**/*.ts',
|
|
1888
1874
|
'src/api/v1/**/*.ts',
|
|
@@ -2054,17 +2040,8 @@ interface DockerConfig {
|
|
|
2054
2040
|
imageName?: string; // Image name (defaults to package.json name)
|
|
2055
2041
|
baseImage?: string; // Base image (default: node:22-alpine)
|
|
2056
2042
|
port?: number; // Port to expose (default: 3000)
|
|
2057
|
-
compose?: {
|
|
2058
|
-
services?: ComposeServicesConfig | ComposeServiceName[];
|
|
2059
|
-
};
|
|
2060
2043
|
}
|
|
2061
2044
|
|
|
2062
|
-
// Service configuration for docker-compose
|
|
2063
|
-
type ComposeServiceName = 'postgres' | 'redis' | 'rabbitmq';
|
|
2064
|
-
type ComposeServicesConfig = {
|
|
2065
|
-
[K in ComposeServiceName]?: boolean | { version?: string };
|
|
2066
|
-
};
|
|
2067
|
-
|
|
2068
2045
|
// Telescope configuration
|
|
2069
2046
|
interface TelescopeConfig {
|
|
2070
2047
|
enabled?: boolean;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolve a module's path aliases through the tsconfig beside it.
|
|
3
|
+
*
|
|
4
|
+
* tsx applies one tsconfig to the whole process — the one in the directory the
|
|
5
|
+
* command ran from. The workspace's `constructs` glob loads every app's code
|
|
6
|
+
* from wherever a command started, so `~/router.ts` in `apps/api` was being
|
|
7
|
+
* read with `apps/web`'s `~` when the command ran there, and resolved to
|
|
8
|
+
* nothing. TypeScript itself resolves an import through the tsconfig that owns
|
|
9
|
+
* the importing file; this does the same at runtime.
|
|
10
|
+
*
|
|
11
|
+
* Registered after tsx, so it runs first. It only answers a non-relative
|
|
12
|
+
* specifier from TypeScript source outside `node_modules` that the adjacent
|
|
13
|
+
* tsconfig's `paths` actually map — everything else, and anything a mapped
|
|
14
|
+
* candidate fails to resolve to, goes to the next resolver untouched.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { dirname } from 'node:path';
|
|
18
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
19
|
+
import { createPathsMatcher, getTsconfig } from 'get-tsconfig';
|
|
20
|
+
|
|
21
|
+
/** Directory → the paths matcher of the tsconfig that owns it, if any. */
|
|
22
|
+
const matchers = new Map();
|
|
23
|
+
/** Shared with `get-tsconfig`, so a config `extends` is read once. */
|
|
24
|
+
const tsconfigCache = new Map();
|
|
25
|
+
|
|
26
|
+
const TS_SOURCE = /\.(?:[cm]?ts|tsx)$/;
|
|
27
|
+
/** Relative, absolute, or carrying a scheme (`node:`, `file:`, `data:`). */
|
|
28
|
+
const NOT_BARE = /^(?:\.{1,2}(?:\/|$)|\/|[a-zA-Z][a-zA-Z\d+.-]*:)/;
|
|
29
|
+
|
|
30
|
+
function matcherFor(directory) {
|
|
31
|
+
if (!matchers.has(directory)) {
|
|
32
|
+
const tsconfig = getTsconfig(directory, 'tsconfig.json', tsconfigCache);
|
|
33
|
+
matchers.set(directory, tsconfig ? createPathsMatcher(tsconfig) : null);
|
|
34
|
+
}
|
|
35
|
+
return matchers.get(directory);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The files a specifier maps to through the importing module's own tsconfig,
|
|
40
|
+
* or nothing when this hook has no business with it.
|
|
41
|
+
*/
|
|
42
|
+
function candidatesFor(specifier, parent) {
|
|
43
|
+
if (!parent?.startsWith('file:') || NOT_BARE.test(specifier)) return [];
|
|
44
|
+
|
|
45
|
+
const parentPath = fileURLToPath(parent);
|
|
46
|
+
if (!TS_SOURCE.test(parentPath) || parentPath.includes('/node_modules/')) {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return matcherFor(dirname(parentPath))?.(specifier) ?? [];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** For `module.register()` — the off-thread hooks. */
|
|
54
|
+
export async function resolve(specifier, context, nextResolve) {
|
|
55
|
+
for (const candidate of candidatesFor(specifier, context.parentURL)) {
|
|
56
|
+
try {
|
|
57
|
+
return await nextResolve(pathToFileURL(candidate).href, context);
|
|
58
|
+
} catch {
|
|
59
|
+
// The next mapping, as TypeScript tries them in order.
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return nextResolve(specifier, context);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* For `module.registerHooks()` — the in-thread hooks.
|
|
68
|
+
*
|
|
69
|
+
* tsx 4.23 resolves through these, and they are consulted before anything
|
|
70
|
+
* `register()` installed: its resolver threw on a mapped alias and the
|
|
71
|
+
* off-thread hook above was never asked. Registered the same way, after tsx,
|
|
72
|
+
* this one is asked first again.
|
|
73
|
+
*/
|
|
74
|
+
export function resolveSync(specifier, context, nextResolve) {
|
|
75
|
+
for (const candidate of candidatesFor(specifier, context.parentURL)) {
|
|
76
|
+
try {
|
|
77
|
+
return nextResolve(pathToFileURL(candidate).href, context);
|
|
78
|
+
} catch {
|
|
79
|
+
// The next mapping, as TypeScript tries them in order.
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return nextResolve(specifier, context);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Install this hook the way the running tsx installed its own, so it is asked
|
|
88
|
+
* before tsx's resolver rather than after it.
|
|
89
|
+
*/
|
|
90
|
+
export async function registerAdjacentTsconfig() {
|
|
91
|
+
const { register, registerHooks } = await import('node:module');
|
|
92
|
+
if (registerHooks) {
|
|
93
|
+
registerHooks({ resolve: resolveSync });
|
|
94
|
+
} else {
|
|
95
|
+
register(import.meta.url);
|
|
96
|
+
}
|
|
97
|
+
}
|
package/bin/gkm.mjs
CHANGED
|
@@ -35,5 +35,11 @@ if (
|
|
|
35
35
|
process.exit(0);
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
// tsx is loaded
|
|
38
|
+
// tsx is loaded. Resolve each module's path aliases through its own tsconfig
|
|
39
|
+
// rather than the one tsx took from the cwd — registered after tsx, so it is
|
|
40
|
+
// asked first. See `adjacent-tsconfig.mjs`.
|
|
41
|
+
const { registerAdjacentTsconfig } = await import('./adjacent-tsconfig.mjs');
|
|
42
|
+
await registerAdjacentTsconfig();
|
|
43
|
+
|
|
44
|
+
// Run the CLI
|
|
39
45
|
await import('../dist/index.mjs');
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
1
|
+
const require_index = require("./index.cjs");
|
|
3
2
|
//#region src/deploy/dns/hostinger-api.ts
|
|
4
3
|
/**
|
|
5
4
|
* Hostinger DNS API client
|
|
@@ -12,6 +11,9 @@ const HOSTINGER_API_BASE = "https://developers.hostinger.com";
|
|
|
12
11
|
* Hostinger API error
|
|
13
12
|
*/
|
|
14
13
|
var HostingerApiError = class extends Error {
|
|
14
|
+
status;
|
|
15
|
+
statusText;
|
|
16
|
+
errors;
|
|
15
17
|
constructor(message, status, statusText, errors) {
|
|
16
18
|
super(message);
|
|
17
19
|
this.status = status;
|
|
@@ -65,7 +67,7 @@ var HostingerApi = class {
|
|
|
65
67
|
throw new HostingerApiError(errorMessage, response.status, response.statusText, errors);
|
|
66
68
|
}
|
|
67
69
|
const text = await response.text();
|
|
68
|
-
if (!text || text.trim() === "") return
|
|
70
|
+
if (!text || text.trim() === "") return;
|
|
69
71
|
return JSON.parse(text);
|
|
70
72
|
}
|
|
71
73
|
/**
|
|
@@ -122,8 +124,7 @@ var HostingerApi = class {
|
|
|
122
124
|
* @param type - Record type (e.g., 'A')
|
|
123
125
|
*/
|
|
124
126
|
async recordExists(domain, name, type = "A") {
|
|
125
|
-
|
|
126
|
-
return records.some((r) => r.name === name && r.type === type);
|
|
127
|
+
return (await this.getRecords(domain)).some((r) => r.name === name && r.type === type);
|
|
127
128
|
}
|
|
128
129
|
/**
|
|
129
130
|
* Create a single A record if it doesn't exist
|
|
@@ -135,8 +136,7 @@ var HostingerApi = class {
|
|
|
135
136
|
* @returns true if created, false if already exists
|
|
136
137
|
*/
|
|
137
138
|
async createARecordIfNotExists(domain, subdomain, ip, ttl = 300) {
|
|
138
|
-
|
|
139
|
-
if (exists) return false;
|
|
139
|
+
if (await this.recordExists(domain, subdomain, "A")) return false;
|
|
140
140
|
await this.upsertRecords(domain, [{
|
|
141
141
|
name: subdomain,
|
|
142
142
|
type: "A",
|
|
@@ -146,10 +146,14 @@ var HostingerApi = class {
|
|
|
146
146
|
return true;
|
|
147
147
|
}
|
|
148
148
|
};
|
|
149
|
-
|
|
150
149
|
//#endregion
|
|
151
150
|
//#region src/deploy/dns/HostingerProvider.ts
|
|
152
151
|
/**
|
|
152
|
+
* Hostinger DNS Provider
|
|
153
|
+
*
|
|
154
|
+
* Implements DnsProvider interface using the Hostinger DNS API.
|
|
155
|
+
*/
|
|
156
|
+
/**
|
|
153
157
|
* Hostinger DNS provider implementation.
|
|
154
158
|
*/
|
|
155
159
|
var HostingerProvider = class {
|
|
@@ -160,15 +164,13 @@ var HostingerProvider = class {
|
|
|
160
164
|
*/
|
|
161
165
|
async getApi() {
|
|
162
166
|
if (this.api) return this.api;
|
|
163
|
-
const token = await
|
|
167
|
+
const token = await require_index.getHostingerToken();
|
|
164
168
|
if (!token) throw new Error("Hostinger API token not configured. Run `gkm login --service=hostinger` or get your token from https://hpanel.hostinger.com/profile/api");
|
|
165
169
|
this.api = new HostingerApi(token);
|
|
166
170
|
return this.api;
|
|
167
171
|
}
|
|
168
172
|
async getRecords(domain) {
|
|
169
|
-
|
|
170
|
-
const records = await api.getRecords(domain);
|
|
171
|
-
return records.map((r) => ({
|
|
173
|
+
return (await (await this.getApi()).getRecords(domain)).map((r) => ({
|
|
172
174
|
name: r.name,
|
|
173
175
|
type: r.type,
|
|
174
176
|
ttl: r.ttl,
|
|
@@ -227,18 +229,15 @@ var HostingerProvider = class {
|
|
|
227
229
|
error: String(error)
|
|
228
230
|
});
|
|
229
231
|
}
|
|
230
|
-
for (const record of records) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
notFound: true
|
|
236
|
-
});
|
|
237
|
-
}
|
|
232
|
+
for (const record of records) if (!existingRecords.find((r) => r.name === record.name && r.type === record.type)) results.push({
|
|
233
|
+
record,
|
|
234
|
+
deleted: false,
|
|
235
|
+
notFound: true
|
|
236
|
+
});
|
|
238
237
|
return results;
|
|
239
238
|
}
|
|
240
239
|
};
|
|
241
|
-
|
|
242
240
|
//#endregion
|
|
243
241
|
exports.HostingerProvider = HostingerProvider;
|
|
244
|
-
|
|
242
|
+
|
|
243
|
+
//# sourceMappingURL=HostingerProvider-BZFGYYVA.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HostingerProvider-BZFGYYVA.cjs","names":["getHostingerToken"],"sources":["../src/deploy/dns/hostinger-api.ts","../src/deploy/dns/HostingerProvider.ts"],"sourcesContent":["/**\n * Hostinger DNS API client\n *\n * API Documentation: https://developers.hostinger.com/\n * Authentication: Bearer token from hpanel.hostinger.com/profile/api\n */\n\nconst HOSTINGER_API_BASE = 'https://developers.hostinger.com';\n\n/**\n * DNS record types supported by Hostinger\n */\nexport type DnsRecordType =\n\t| 'A'\n\t| 'AAAA'\n\t| 'CNAME'\n\t| 'MX'\n\t| 'TXT'\n\t| 'NS'\n\t| 'SRV'\n\t| 'CAA';\n\n/**\n * A single DNS record\n */\nexport interface DnsRecord {\n\t/** Subdomain name (e.g., 'api.joemoer' for api.joemoer.traflabs.io) */\n\tname: string;\n\t/** Record type */\n\ttype: DnsRecordType;\n\t/** TTL in seconds */\n\tttl: number;\n\t/** Record values */\n\trecords: Array<{ content: string }>;\n}\n\n/**\n * Filter for deleting specific records\n */\nexport interface DnsRecordFilter {\n\tname: string;\n\ttype: DnsRecordType;\n}\n\n/**\n * API error response\n */\nexport interface HostingerErrorResponse {\n\tmessage?: string;\n\terrors?: Record<string, string[]>;\n}\n\n/**\n * Hostinger API error\n */\nexport class HostingerApiError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic status: number,\n\t\tpublic statusText: string,\n\t\tpublic errors?: Record<string, string[]>,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = 'HostingerApiError';\n\t}\n}\n\n/**\n * Hostinger DNS API client\n *\n * @example\n * ```ts\n * const api = new HostingerApi(token);\n *\n * // Get all records for a domain\n * const records = await api.getRecords('traflabs.io');\n *\n * // Create/update records\n * await api.upsertRecords('traflabs.io', [\n * { name: 'api.joemoer', type: 'A', ttl: 300, records: ['1.2.3.4'] }\n * ]);\n * ```\n */\nexport class HostingerApi {\n\tprivate token: string;\n\n\tconstructor(token: string) {\n\t\tthis.token = token;\n\t}\n\n\t/**\n\t * Make a request to the Hostinger API\n\t */\n\tprivate async request<T>(\n\t\tmethod: 'GET' | 'POST' | 'PUT' | 'DELETE',\n\t\tendpoint: string,\n\t\tbody?: unknown,\n\t): Promise<T> {\n\t\tconst url = `${HOSTINGER_API_BASE}${endpoint}`;\n\n\t\tconst response = await fetch(url, {\n\t\t\tmethod,\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\tAuthorization: `Bearer ${this.token}`,\n\t\t\t},\n\t\t\tbody: body ? JSON.stringify(body) : undefined,\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tlet errorMessage = `Hostinger API error: ${response.status} ${response.statusText}`;\n\t\t\tlet errors: Record<string, string[]> | undefined;\n\n\t\t\ttry {\n\t\t\t\tconst errorBody = (await response.json()) as HostingerErrorResponse;\n\t\t\t\tif (errorBody.message) {\n\t\t\t\t\terrorMessage = `Hostinger API error: ${errorBody.message}`;\n\t\t\t\t}\n\t\t\t\terrors = errorBody.errors;\n\t\t\t} catch {\n\t\t\t\t// Ignore JSON parse errors\n\t\t\t}\n\n\t\t\tthrow new HostingerApiError(\n\t\t\t\terrorMessage,\n\t\t\t\tresponse.status,\n\t\t\t\tresponse.statusText,\n\t\t\t\terrors,\n\t\t\t);\n\t\t}\n\n\t\t// Handle empty responses\n\t\tconst text = await response.text();\n\t\tif (!text || text.trim() === '') {\n\t\t\treturn undefined as T;\n\t\t}\n\t\treturn JSON.parse(text) as T;\n\t}\n\n\t/**\n\t * Get all DNS records for a domain\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t */\n\tasync getRecords(domain: string): Promise<DnsRecord[]> {\n\t\t// The zone comes back as a **bare array**, not `{ data: [...] }`.\n\t\t//\n\t\t// This read `response.data || []`, so every call returned zero records\n\t\t// against the real API while every mocked test passed. Silently: no error,\n\t\t// no warning, just an empty zone. `upsertRecords` uses this to decide what\n\t\t// already exists, so everything looked new and every write was reported as\n\t\t// a creation — harmless only because the PUT merges rather than replaces.\n\t\t//\n\t\t// Both shapes are accepted because one of them is a guess: the wrapper was\n\t\t// written against documentation and the array is what the API actually\n\t\t// sends, so tolerating an envelope costs a line and survives it appearing.\n\t\tconst response = await this.request<DnsRecord[] | { data?: DnsRecord[] }>(\n\t\t\t'GET',\n\t\t\t`/api/dns/v1/zones/${domain}`,\n\t\t);\n\n\t\tif (Array.isArray(response)) return response;\n\n\t\treturn response?.data ?? [];\n\t}\n\n\t/**\n\t * Create or update DNS records\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param records - Records to create/update\n\t * @param overwrite - If true, replaces all existing records. If false, merges with existing.\n\t */\n\tasync upsertRecords(\n\t\tdomain: string,\n\t\trecords: DnsRecord[],\n\t\toverwrite = false,\n\t): Promise<void> {\n\t\tawait this.request('PUT', `/api/dns/v1/zones/${domain}`, {\n\t\t\toverwrite,\n\t\t\tzone: records,\n\t\t});\n\t}\n\n\t/**\n\t * Validate DNS records before applying\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param records - Records to validate\n\t * @returns true if valid, throws if invalid\n\t */\n\tasync validateRecords(\n\t\tdomain: string,\n\t\trecords: DnsRecord[],\n\t): Promise<boolean> {\n\t\tawait this.request('POST', `/api/dns/v1/zones/${domain}/validate`, {\n\t\t\toverwrite: false,\n\t\t\tzone: records,\n\t\t});\n\t\treturn true;\n\t}\n\n\t/**\n\t * Delete specific DNS records\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param filters - Filters to match records for deletion\n\t */\n\tasync deleteRecords(\n\t\tdomain: string,\n\t\tfilters: DnsRecordFilter[],\n\t): Promise<void> {\n\t\tawait this.request('DELETE', `/api/dns/v1/zones/${domain}`, {\n\t\t\tfilters,\n\t\t});\n\t}\n\n\t/**\n\t * Check if a specific record exists\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param name - Subdomain name (e.g., 'api.joemoer')\n\t * @param type - Record type (e.g., 'A')\n\t */\n\tasync recordExists(\n\t\tdomain: string,\n\t\tname: string,\n\t\ttype: DnsRecordType = 'A',\n\t): Promise<boolean> {\n\t\tconst records = await this.getRecords(domain);\n\t\treturn records.some((r) => r.name === name && r.type === type);\n\t}\n\n\t/**\n\t * Create a single A record if it doesn't exist\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param subdomain - Subdomain name (e.g., 'api.joemoer')\n\t * @param ip - IP address to point to\n\t * @param ttl - TTL in seconds (default: 300)\n\t * @returns true if created, false if already exists\n\t */\n\tasync createARecordIfNotExists(\n\t\tdomain: string,\n\t\tsubdomain: string,\n\t\tip: string,\n\t\tttl = 300,\n\t): Promise<boolean> {\n\t\tconst exists = await this.recordExists(domain, subdomain, 'A');\n\t\tif (exists) {\n\t\t\treturn false;\n\t\t}\n\n\t\tawait this.upsertRecords(domain, [\n\t\t\t{\n\t\t\t\tname: subdomain,\n\t\t\t\ttype: 'A',\n\t\t\t\tttl,\n\t\t\t\trecords: [{ content: ip }],\n\t\t\t},\n\t\t]);\n\n\t\treturn true;\n\t}\n}\n","/**\n * Hostinger DNS Provider\n *\n * Implements DnsProvider interface using the Hostinger DNS API.\n */\n\nimport { getHostingerToken } from '../../auth/credentials';\nimport type {\n\tDeleteDnsRecord,\n\tDeleteResult,\n\tDnsProvider,\n\tDnsRecord,\n\tUpsertDnsRecord,\n\tUpsertResult,\n} from './DnsProvider';\nimport { HostingerApi } from './hostinger-api';\n\n/**\n * Hostinger DNS provider implementation.\n */\nexport class HostingerProvider implements DnsProvider {\n\treadonly name = 'hostinger';\n\tprivate api: HostingerApi | null = null;\n\n\t/**\n\t * Get or create the Hostinger API client.\n\t */\n\tprivate async getApi(): Promise<HostingerApi> {\n\t\tif (this.api) {\n\t\t\treturn this.api;\n\t\t}\n\n\t\tconst token = await getHostingerToken();\n\t\tif (!token) {\n\t\t\tthrow new Error(\n\t\t\t\t'Hostinger API token not configured. Run `gkm login --service=hostinger` or get your token from https://hpanel.hostinger.com/profile/api',\n\t\t\t);\n\t\t}\n\n\t\tthis.api = new HostingerApi(token);\n\t\treturn this.api;\n\t}\n\n\tasync getRecords(domain: string): Promise<DnsRecord[]> {\n\t\tconst api = await this.getApi();\n\t\tconst records = await api.getRecords(domain);\n\n\t\treturn records.map((r) => ({\n\t\t\tname: r.name,\n\t\t\ttype: r.type,\n\t\t\tttl: r.ttl,\n\t\t\tvalues: r.records.map((rec) => rec.content),\n\t\t}));\n\t}\n\n\tasync upsertRecords(\n\t\tdomain: string,\n\t\trecords: UpsertDnsRecord[],\n\t): Promise<UpsertResult[]> {\n\t\tconst api = await this.getApi();\n\t\tconst results: UpsertResult[] = [];\n\n\t\t// Get existing records to check what already exists\n\t\tconst existingRecords = await api.getRecords(domain);\n\n\t\tfor (const record of records) {\n\t\t\tconst existing = existingRecords.find(\n\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t);\n\n\t\t\tconst existingValue = existing?.records?.[0]?.content;\n\n\t\t\tif (existing && existingValue === record.value) {\n\t\t\t\t// Record exists with same value - unchanged\n\t\t\t\tresults.push({\n\t\t\t\t\trecord,\n\t\t\t\t\tcreated: false,\n\t\t\t\t\tunchanged: true,\n\t\t\t\t});\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Create or update the record\n\t\t\tawait api.upsertRecords(domain, [\n\t\t\t\t{\n\t\t\t\t\tname: record.name,\n\t\t\t\t\ttype: record.type,\n\t\t\t\t\tttl: record.ttl,\n\t\t\t\t\trecords: [{ content: record.value }],\n\t\t\t\t},\n\t\t\t]);\n\n\t\t\tresults.push({\n\t\t\t\trecord,\n\t\t\t\tcreated: !existing,\n\t\t\t\tunchanged: false,\n\t\t\t});\n\t\t}\n\n\t\treturn results;\n\t}\n\n\tasync deleteRecords(\n\t\tdomain: string,\n\t\trecords: DeleteDnsRecord[],\n\t): Promise<DeleteResult[]> {\n\t\tconst api = await this.getApi();\n\t\tconst results: DeleteResult[] = [];\n\n\t\t// Get existing records to check what exists\n\t\tconst existingRecords = await api.getRecords(domain);\n\n\t\t// Filter to only records that exist\n\t\tconst recordsToDelete = records.filter((record) =>\n\t\t\texistingRecords.some(\n\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t),\n\t\t);\n\n\t\t// Delete existing records\n\t\tif (recordsToDelete.length > 0) {\n\t\t\ttry {\n\t\t\t\tawait api.deleteRecords(\n\t\t\t\t\tdomain,\n\t\t\t\t\trecordsToDelete.map((r) => ({\n\t\t\t\t\t\tname: r.name,\n\t\t\t\t\t\ttype: r.type as\n\t\t\t\t\t\t\t| 'A'\n\t\t\t\t\t\t\t| 'AAAA'\n\t\t\t\t\t\t\t| 'CNAME'\n\t\t\t\t\t\t\t| 'MX'\n\t\t\t\t\t\t\t| 'TXT'\n\t\t\t\t\t\t\t| 'SRV'\n\t\t\t\t\t\t\t| 'CAA',\n\t\t\t\t\t})),\n\t\t\t\t);\n\n\t\t\t\tfor (const record of recordsToDelete) {\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tdeleted: true,\n\t\t\t\t\t\tnotFound: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\t// If batch delete fails, report error for all records\n\t\t\t\tfor (const record of recordsToDelete) {\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tdeleted: false,\n\t\t\t\t\t\tnotFound: false,\n\t\t\t\t\t\terror: String(error),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Mark non-existent records as not found\n\t\tfor (const record of records) {\n\t\t\tconst existing = existingRecords.find(\n\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t);\n\t\t\tif (!existing) {\n\t\t\t\tresults.push({\n\t\t\t\t\trecord,\n\t\t\t\t\tdeleted: false,\n\t\t\t\t\tnotFound: true,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { getHostingerToken } from "./
|
|
2
|
-
|
|
1
|
+
import { o as getHostingerToken } from "./index.mjs";
|
|
3
2
|
//#region src/deploy/dns/hostinger-api.ts
|
|
4
3
|
/**
|
|
5
4
|
* Hostinger DNS API client
|
|
@@ -12,6 +11,9 @@ const HOSTINGER_API_BASE = "https://developers.hostinger.com";
|
|
|
12
11
|
* Hostinger API error
|
|
13
12
|
*/
|
|
14
13
|
var HostingerApiError = class extends Error {
|
|
14
|
+
status;
|
|
15
|
+
statusText;
|
|
16
|
+
errors;
|
|
15
17
|
constructor(message, status, statusText, errors) {
|
|
16
18
|
super(message);
|
|
17
19
|
this.status = status;
|
|
@@ -65,7 +67,7 @@ var HostingerApi = class {
|
|
|
65
67
|
throw new HostingerApiError(errorMessage, response.status, response.statusText, errors);
|
|
66
68
|
}
|
|
67
69
|
const text = await response.text();
|
|
68
|
-
if (!text || text.trim() === "") return
|
|
70
|
+
if (!text || text.trim() === "") return;
|
|
69
71
|
return JSON.parse(text);
|
|
70
72
|
}
|
|
71
73
|
/**
|
|
@@ -122,8 +124,7 @@ var HostingerApi = class {
|
|
|
122
124
|
* @param type - Record type (e.g., 'A')
|
|
123
125
|
*/
|
|
124
126
|
async recordExists(domain, name, type = "A") {
|
|
125
|
-
|
|
126
|
-
return records.some((r) => r.name === name && r.type === type);
|
|
127
|
+
return (await this.getRecords(domain)).some((r) => r.name === name && r.type === type);
|
|
127
128
|
}
|
|
128
129
|
/**
|
|
129
130
|
* Create a single A record if it doesn't exist
|
|
@@ -135,8 +136,7 @@ var HostingerApi = class {
|
|
|
135
136
|
* @returns true if created, false if already exists
|
|
136
137
|
*/
|
|
137
138
|
async createARecordIfNotExists(domain, subdomain, ip, ttl = 300) {
|
|
138
|
-
|
|
139
|
-
if (exists) return false;
|
|
139
|
+
if (await this.recordExists(domain, subdomain, "A")) return false;
|
|
140
140
|
await this.upsertRecords(domain, [{
|
|
141
141
|
name: subdomain,
|
|
142
142
|
type: "A",
|
|
@@ -146,10 +146,14 @@ var HostingerApi = class {
|
|
|
146
146
|
return true;
|
|
147
147
|
}
|
|
148
148
|
};
|
|
149
|
-
|
|
150
149
|
//#endregion
|
|
151
150
|
//#region src/deploy/dns/HostingerProvider.ts
|
|
152
151
|
/**
|
|
152
|
+
* Hostinger DNS Provider
|
|
153
|
+
*
|
|
154
|
+
* Implements DnsProvider interface using the Hostinger DNS API.
|
|
155
|
+
*/
|
|
156
|
+
/**
|
|
153
157
|
* Hostinger DNS provider implementation.
|
|
154
158
|
*/
|
|
155
159
|
var HostingerProvider = class {
|
|
@@ -166,9 +170,7 @@ var HostingerProvider = class {
|
|
|
166
170
|
return this.api;
|
|
167
171
|
}
|
|
168
172
|
async getRecords(domain) {
|
|
169
|
-
|
|
170
|
-
const records = await api.getRecords(domain);
|
|
171
|
-
return records.map((r) => ({
|
|
173
|
+
return (await (await this.getApi()).getRecords(domain)).map((r) => ({
|
|
172
174
|
name: r.name,
|
|
173
175
|
type: r.type,
|
|
174
176
|
ttl: r.ttl,
|
|
@@ -227,18 +229,15 @@ var HostingerProvider = class {
|
|
|
227
229
|
error: String(error)
|
|
228
230
|
});
|
|
229
231
|
}
|
|
230
|
-
for (const record of records) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
notFound: true
|
|
236
|
-
});
|
|
237
|
-
}
|
|
232
|
+
for (const record of records) if (!existingRecords.find((r) => r.name === record.name && r.type === record.type)) results.push({
|
|
233
|
+
record,
|
|
234
|
+
deleted: false,
|
|
235
|
+
notFound: true
|
|
236
|
+
});
|
|
238
237
|
return results;
|
|
239
238
|
}
|
|
240
239
|
};
|
|
241
|
-
|
|
242
240
|
//#endregion
|
|
243
241
|
export { HostingerProvider };
|
|
244
|
-
|
|
242
|
+
|
|
243
|
+
//# sourceMappingURL=HostingerProvider-GF6NAZAK.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"HostingerProvider-GF6NAZAK.mjs","names":[],"sources":["../src/deploy/dns/hostinger-api.ts","../src/deploy/dns/HostingerProvider.ts"],"sourcesContent":["/**\n * Hostinger DNS API client\n *\n * API Documentation: https://developers.hostinger.com/\n * Authentication: Bearer token from hpanel.hostinger.com/profile/api\n */\n\nconst HOSTINGER_API_BASE = 'https://developers.hostinger.com';\n\n/**\n * DNS record types supported by Hostinger\n */\nexport type DnsRecordType =\n\t| 'A'\n\t| 'AAAA'\n\t| 'CNAME'\n\t| 'MX'\n\t| 'TXT'\n\t| 'NS'\n\t| 'SRV'\n\t| 'CAA';\n\n/**\n * A single DNS record\n */\nexport interface DnsRecord {\n\t/** Subdomain name (e.g., 'api.joemoer' for api.joemoer.traflabs.io) */\n\tname: string;\n\t/** Record type */\n\ttype: DnsRecordType;\n\t/** TTL in seconds */\n\tttl: number;\n\t/** Record values */\n\trecords: Array<{ content: string }>;\n}\n\n/**\n * Filter for deleting specific records\n */\nexport interface DnsRecordFilter {\n\tname: string;\n\ttype: DnsRecordType;\n}\n\n/**\n * API error response\n */\nexport interface HostingerErrorResponse {\n\tmessage?: string;\n\terrors?: Record<string, string[]>;\n}\n\n/**\n * Hostinger API error\n */\nexport class HostingerApiError extends Error {\n\tconstructor(\n\t\tmessage: string,\n\t\tpublic status: number,\n\t\tpublic statusText: string,\n\t\tpublic errors?: Record<string, string[]>,\n\t) {\n\t\tsuper(message);\n\t\tthis.name = 'HostingerApiError';\n\t}\n}\n\n/**\n * Hostinger DNS API client\n *\n * @example\n * ```ts\n * const api = new HostingerApi(token);\n *\n * // Get all records for a domain\n * const records = await api.getRecords('traflabs.io');\n *\n * // Create/update records\n * await api.upsertRecords('traflabs.io', [\n * { name: 'api.joemoer', type: 'A', ttl: 300, records: ['1.2.3.4'] }\n * ]);\n * ```\n */\nexport class HostingerApi {\n\tprivate token: string;\n\n\tconstructor(token: string) {\n\t\tthis.token = token;\n\t}\n\n\t/**\n\t * Make a request to the Hostinger API\n\t */\n\tprivate async request<T>(\n\t\tmethod: 'GET' | 'POST' | 'PUT' | 'DELETE',\n\t\tendpoint: string,\n\t\tbody?: unknown,\n\t): Promise<T> {\n\t\tconst url = `${HOSTINGER_API_BASE}${endpoint}`;\n\n\t\tconst response = await fetch(url, {\n\t\t\tmethod,\n\t\t\theaders: {\n\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\tAuthorization: `Bearer ${this.token}`,\n\t\t\t},\n\t\t\tbody: body ? JSON.stringify(body) : undefined,\n\t\t});\n\n\t\tif (!response.ok) {\n\t\t\tlet errorMessage = `Hostinger API error: ${response.status} ${response.statusText}`;\n\t\t\tlet errors: Record<string, string[]> | undefined;\n\n\t\t\ttry {\n\t\t\t\tconst errorBody = (await response.json()) as HostingerErrorResponse;\n\t\t\t\tif (errorBody.message) {\n\t\t\t\t\terrorMessage = `Hostinger API error: ${errorBody.message}`;\n\t\t\t\t}\n\t\t\t\terrors = errorBody.errors;\n\t\t\t} catch {\n\t\t\t\t// Ignore JSON parse errors\n\t\t\t}\n\n\t\t\tthrow new HostingerApiError(\n\t\t\t\terrorMessage,\n\t\t\t\tresponse.status,\n\t\t\t\tresponse.statusText,\n\t\t\t\terrors,\n\t\t\t);\n\t\t}\n\n\t\t// Handle empty responses\n\t\tconst text = await response.text();\n\t\tif (!text || text.trim() === '') {\n\t\t\treturn undefined as T;\n\t\t}\n\t\treturn JSON.parse(text) as T;\n\t}\n\n\t/**\n\t * Get all DNS records for a domain\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t */\n\tasync getRecords(domain: string): Promise<DnsRecord[]> {\n\t\t// The zone comes back as a **bare array**, not `{ data: [...] }`.\n\t\t//\n\t\t// This read `response.data || []`, so every call returned zero records\n\t\t// against the real API while every mocked test passed. Silently: no error,\n\t\t// no warning, just an empty zone. `upsertRecords` uses this to decide what\n\t\t// already exists, so everything looked new and every write was reported as\n\t\t// a creation — harmless only because the PUT merges rather than replaces.\n\t\t//\n\t\t// Both shapes are accepted because one of them is a guess: the wrapper was\n\t\t// written against documentation and the array is what the API actually\n\t\t// sends, so tolerating an envelope costs a line and survives it appearing.\n\t\tconst response = await this.request<DnsRecord[] | { data?: DnsRecord[] }>(\n\t\t\t'GET',\n\t\t\t`/api/dns/v1/zones/${domain}`,\n\t\t);\n\n\t\tif (Array.isArray(response)) return response;\n\n\t\treturn response?.data ?? [];\n\t}\n\n\t/**\n\t * Create or update DNS records\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param records - Records to create/update\n\t * @param overwrite - If true, replaces all existing records. If false, merges with existing.\n\t */\n\tasync upsertRecords(\n\t\tdomain: string,\n\t\trecords: DnsRecord[],\n\t\toverwrite = false,\n\t): Promise<void> {\n\t\tawait this.request('PUT', `/api/dns/v1/zones/${domain}`, {\n\t\t\toverwrite,\n\t\t\tzone: records,\n\t\t});\n\t}\n\n\t/**\n\t * Validate DNS records before applying\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param records - Records to validate\n\t * @returns true if valid, throws if invalid\n\t */\n\tasync validateRecords(\n\t\tdomain: string,\n\t\trecords: DnsRecord[],\n\t): Promise<boolean> {\n\t\tawait this.request('POST', `/api/dns/v1/zones/${domain}/validate`, {\n\t\t\toverwrite: false,\n\t\t\tzone: records,\n\t\t});\n\t\treturn true;\n\t}\n\n\t/**\n\t * Delete specific DNS records\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param filters - Filters to match records for deletion\n\t */\n\tasync deleteRecords(\n\t\tdomain: string,\n\t\tfilters: DnsRecordFilter[],\n\t): Promise<void> {\n\t\tawait this.request('DELETE', `/api/dns/v1/zones/${domain}`, {\n\t\t\tfilters,\n\t\t});\n\t}\n\n\t/**\n\t * Check if a specific record exists\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param name - Subdomain name (e.g., 'api.joemoer')\n\t * @param type - Record type (e.g., 'A')\n\t */\n\tasync recordExists(\n\t\tdomain: string,\n\t\tname: string,\n\t\ttype: DnsRecordType = 'A',\n\t): Promise<boolean> {\n\t\tconst records = await this.getRecords(domain);\n\t\treturn records.some((r) => r.name === name && r.type === type);\n\t}\n\n\t/**\n\t * Create a single A record if it doesn't exist\n\t *\n\t * @param domain - Root domain (e.g., 'traflabs.io')\n\t * @param subdomain - Subdomain name (e.g., 'api.joemoer')\n\t * @param ip - IP address to point to\n\t * @param ttl - TTL in seconds (default: 300)\n\t * @returns true if created, false if already exists\n\t */\n\tasync createARecordIfNotExists(\n\t\tdomain: string,\n\t\tsubdomain: string,\n\t\tip: string,\n\t\tttl = 300,\n\t): Promise<boolean> {\n\t\tconst exists = await this.recordExists(domain, subdomain, 'A');\n\t\tif (exists) {\n\t\t\treturn false;\n\t\t}\n\n\t\tawait this.upsertRecords(domain, [\n\t\t\t{\n\t\t\t\tname: subdomain,\n\t\t\t\ttype: 'A',\n\t\t\t\tttl,\n\t\t\t\trecords: [{ content: ip }],\n\t\t\t},\n\t\t]);\n\n\t\treturn true;\n\t}\n}\n","/**\n * Hostinger DNS Provider\n *\n * Implements DnsProvider interface using the Hostinger DNS API.\n */\n\nimport { getHostingerToken } from '../../auth/credentials';\nimport type {\n\tDeleteDnsRecord,\n\tDeleteResult,\n\tDnsProvider,\n\tDnsRecord,\n\tUpsertDnsRecord,\n\tUpsertResult,\n} from './DnsProvider';\nimport { HostingerApi } from './hostinger-api';\n\n/**\n * Hostinger DNS provider implementation.\n */\nexport class HostingerProvider implements DnsProvider {\n\treadonly name = 'hostinger';\n\tprivate api: HostingerApi | null = null;\n\n\t/**\n\t * Get or create the Hostinger API client.\n\t */\n\tprivate async getApi(): Promise<HostingerApi> {\n\t\tif (this.api) {\n\t\t\treturn this.api;\n\t\t}\n\n\t\tconst token = await getHostingerToken();\n\t\tif (!token) {\n\t\t\tthrow new Error(\n\t\t\t\t'Hostinger API token not configured. Run `gkm login --service=hostinger` or get your token from https://hpanel.hostinger.com/profile/api',\n\t\t\t);\n\t\t}\n\n\t\tthis.api = new HostingerApi(token);\n\t\treturn this.api;\n\t}\n\n\tasync getRecords(domain: string): Promise<DnsRecord[]> {\n\t\tconst api = await this.getApi();\n\t\tconst records = await api.getRecords(domain);\n\n\t\treturn records.map((r) => ({\n\t\t\tname: r.name,\n\t\t\ttype: r.type,\n\t\t\tttl: r.ttl,\n\t\t\tvalues: r.records.map((rec) => rec.content),\n\t\t}));\n\t}\n\n\tasync upsertRecords(\n\t\tdomain: string,\n\t\trecords: UpsertDnsRecord[],\n\t): Promise<UpsertResult[]> {\n\t\tconst api = await this.getApi();\n\t\tconst results: UpsertResult[] = [];\n\n\t\t// Get existing records to check what already exists\n\t\tconst existingRecords = await api.getRecords(domain);\n\n\t\tfor (const record of records) {\n\t\t\tconst existing = existingRecords.find(\n\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t);\n\n\t\t\tconst existingValue = existing?.records?.[0]?.content;\n\n\t\t\tif (existing && existingValue === record.value) {\n\t\t\t\t// Record exists with same value - unchanged\n\t\t\t\tresults.push({\n\t\t\t\t\trecord,\n\t\t\t\t\tcreated: false,\n\t\t\t\t\tunchanged: true,\n\t\t\t\t});\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// Create or update the record\n\t\t\tawait api.upsertRecords(domain, [\n\t\t\t\t{\n\t\t\t\t\tname: record.name,\n\t\t\t\t\ttype: record.type,\n\t\t\t\t\tttl: record.ttl,\n\t\t\t\t\trecords: [{ content: record.value }],\n\t\t\t\t},\n\t\t\t]);\n\n\t\t\tresults.push({\n\t\t\t\trecord,\n\t\t\t\tcreated: !existing,\n\t\t\t\tunchanged: false,\n\t\t\t});\n\t\t}\n\n\t\treturn results;\n\t}\n\n\tasync deleteRecords(\n\t\tdomain: string,\n\t\trecords: DeleteDnsRecord[],\n\t): Promise<DeleteResult[]> {\n\t\tconst api = await this.getApi();\n\t\tconst results: DeleteResult[] = [];\n\n\t\t// Get existing records to check what exists\n\t\tconst existingRecords = await api.getRecords(domain);\n\n\t\t// Filter to only records that exist\n\t\tconst recordsToDelete = records.filter((record) =>\n\t\t\texistingRecords.some(\n\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t),\n\t\t);\n\n\t\t// Delete existing records\n\t\tif (recordsToDelete.length > 0) {\n\t\t\ttry {\n\t\t\t\tawait api.deleteRecords(\n\t\t\t\t\tdomain,\n\t\t\t\t\trecordsToDelete.map((r) => ({\n\t\t\t\t\t\tname: r.name,\n\t\t\t\t\t\ttype: r.type as\n\t\t\t\t\t\t\t| 'A'\n\t\t\t\t\t\t\t| 'AAAA'\n\t\t\t\t\t\t\t| 'CNAME'\n\t\t\t\t\t\t\t| 'MX'\n\t\t\t\t\t\t\t| 'TXT'\n\t\t\t\t\t\t\t| 'SRV'\n\t\t\t\t\t\t\t| 'CAA',\n\t\t\t\t\t})),\n\t\t\t\t);\n\n\t\t\t\tfor (const record of recordsToDelete) {\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tdeleted: true,\n\t\t\t\t\t\tnotFound: false,\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\t// If batch delete fails, report error for all records\n\t\t\t\tfor (const record of recordsToDelete) {\n\t\t\t\t\tresults.push({\n\t\t\t\t\t\trecord,\n\t\t\t\t\t\tdeleted: false,\n\t\t\t\t\t\tnotFound: false,\n\t\t\t\t\t\terror: String(error),\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Mark non-existent records as not found\n\t\tfor (const record of records) {\n\t\t\tconst existing = existingRecords.find(\n\t\t\t\t(r) => r.name === record.name && r.type === record.type,\n\t\t\t);\n\t\t\tif (!existing) {\n\t\t\t\tresults.push({\n\t\t\t\t\trecord,\n\t\t\t\t\tdeleted: false,\n\t\t\t\t\tnotFound: true,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\treturn results;\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const node_fs_promises = require_chunk.__toESM(require("node:fs/promises"));
|
|
4
|
-
|
|
1
|
+
let node_fs_promises = require("node:fs/promises");
|
|
2
|
+
let node_path = require("node:path");
|
|
5
3
|
//#region src/deploy/LocalStateProvider.ts
|
|
6
4
|
/**
|
|
5
|
+
* Local Filesystem State Provider
|
|
6
|
+
*
|
|
7
|
+
* Stores deployment state in .gkm/deploy-{stage}.json files.
|
|
8
|
+
* This is the default provider for local development.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
7
11
|
* Get the state file path for a stage.
|
|
8
12
|
*/
|
|
9
13
|
function getStateFilePath(workspaceRoot, stage) {
|
|
@@ -15,6 +19,7 @@ function getStateFilePath(workspaceRoot, stage) {
|
|
|
15
19
|
* Stores state in .gkm/deploy-{stage}.json files in the workspace root.
|
|
16
20
|
*/
|
|
17
21
|
var LocalStateProvider = class {
|
|
22
|
+
workspaceRoot;
|
|
18
23
|
constructor(workspaceRoot) {
|
|
19
24
|
this.workspaceRoot = workspaceRoot;
|
|
20
25
|
}
|
|
@@ -37,7 +42,7 @@ var LocalStateProvider = class {
|
|
|
37
42
|
await (0, node_fs_promises.writeFile)(filePath, JSON.stringify(state, null, 2));
|
|
38
43
|
}
|
|
39
44
|
};
|
|
40
|
-
|
|
41
45
|
//#endregion
|
|
42
46
|
exports.LocalStateProvider = LocalStateProvider;
|
|
43
|
-
|
|
47
|
+
|
|
48
|
+
//# sourceMappingURL=LocalStateProvider-7HR6SS2t.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LocalStateProvider-7HR6SS2t.cjs","names":["join","readFile","mkdir","writeFile"],"sources":["../src/deploy/LocalStateProvider.ts"],"sourcesContent":["/**\n * Local Filesystem State Provider\n *\n * Stores deployment state in .gkm/deploy-{stage}.json files.\n * This is the default provider for local development.\n */\n\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { StateProvider } from './StateProvider';\nimport type { DokployStageState } from './state';\n\n/**\n * Get the state file path for a stage.\n */\nfunction getStateFilePath(workspaceRoot: string, stage: string): string {\n\treturn join(workspaceRoot, '.gkm', `deploy-${stage}.json`);\n}\n\n/**\n * Local filesystem state provider.\n *\n * Stores state in .gkm/deploy-{stage}.json files in the workspace root.\n */\nexport class LocalStateProvider implements StateProvider {\n\tconstructor(private readonly workspaceRoot: string) {}\n\n\tasync read(stage: string): Promise<DokployStageState | null> {\n\t\tconst filePath = getStateFilePath(this.workspaceRoot, stage);\n\n\t\ttry {\n\t\t\tconst content = await readFile(filePath, 'utf-8');\n\t\t\treturn JSON.parse(content) as DokployStageState;\n\t\t} catch (error) {\n\t\t\t// File doesn't exist - return null\n\t\t\tif ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\t// Log other errors but don't fail\n\t\t\tconsole.warn(`Warning: Could not read deploy state: ${error}`);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tasync write(stage: string, state: DokployStageState): Promise<void> {\n\t\tconst filePath = getStateFilePath(this.workspaceRoot, stage);\n\t\tconst dir = join(this.workspaceRoot, '.gkm');\n\n\t\t// Ensure .gkm directory exists\n\t\tawait mkdir(dir, { recursive: true });\n\n\t\t// Update last deployed timestamp\n\t\tstate.lastDeployedAt = new Date().toISOString();\n\n\t\tawait writeFile(filePath, JSON.stringify(state, null, 2));\n\t}\n}\n"],"mappings":";;;;;;;;;;;;AAeA,SAAS,iBAAiB,eAAuB,OAAuB;CACvE,QAAA,GAAOA,UAAAA,KAAAA,CAAK,eAAe,QAAQ,UAAU,MAAM,MAAM;AAC1D;;;;;;AAOA,IAAa,qBAAb,MAAyD;CAC3B;CAA7B,YAAY,eAAwC;EAAvB,KAAA,gBAAA;CAAwB;CAErD,MAAM,KAAK,OAAkD;EAC5D,MAAM,WAAW,iBAAiB,KAAK,eAAe,KAAK;EAE3D,IAAI;GACH,MAAM,UAAU,OAAA,GAAMC,iBAAAA,SAAAA,CAAS,UAAU,OAAO;GAChD,OAAO,KAAK,MAAM,OAAO;EAC1B,SAAS,OAAO;GAEf,IAAK,MAAgC,SAAS,UAC7C,OAAO;GAGR,QAAQ,KAAK,yCAAyC,OAAO;GAC7D,OAAO;EACR;CACD;CAEA,MAAM,MAAM,OAAe,OAAyC;EACnE,MAAM,WAAW,iBAAiB,KAAK,eAAe,KAAK;EAC3D,MAAM,OAAA,GAAMD,UAAAA,KAAAA,CAAK,KAAK,eAAe,MAAM;EAG3C,OAAA,GAAME,iBAAAA,MAAAA,CAAM,KAAK,EAAE,WAAW,KAAK,CAAC;EAGpC,MAAM,kCAAiB,IAAI,KAAK,EAAA,CAAE,YAAY;EAE9C,OAAA,GAAMC,iBAAAA,UAAAA,CAAU,UAAU,KAAK,UAAU,OAAO,MAAM,CAAC,CAAC;CACzD;AACD"}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
import { join } from "node:path";
|
|
2
1
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
-
|
|
2
|
+
import { join } from "node:path";
|
|
4
3
|
//#region src/deploy/LocalStateProvider.ts
|
|
5
4
|
/**
|
|
5
|
+
* Local Filesystem State Provider
|
|
6
|
+
*
|
|
7
|
+
* Stores deployment state in .gkm/deploy-{stage}.json files.
|
|
8
|
+
* This is the default provider for local development.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
6
11
|
* Get the state file path for a stage.
|
|
7
12
|
*/
|
|
8
13
|
function getStateFilePath(workspaceRoot, stage) {
|
|
@@ -14,6 +19,7 @@ function getStateFilePath(workspaceRoot, stage) {
|
|
|
14
19
|
* Stores state in .gkm/deploy-{stage}.json files in the workspace root.
|
|
15
20
|
*/
|
|
16
21
|
var LocalStateProvider = class {
|
|
22
|
+
workspaceRoot;
|
|
17
23
|
constructor(workspaceRoot) {
|
|
18
24
|
this.workspaceRoot = workspaceRoot;
|
|
19
25
|
}
|
|
@@ -36,7 +42,7 @@ var LocalStateProvider = class {
|
|
|
36
42
|
await writeFile(filePath, JSON.stringify(state, null, 2));
|
|
37
43
|
}
|
|
38
44
|
};
|
|
39
|
-
|
|
40
45
|
//#endregion
|
|
41
46
|
export { LocalStateProvider };
|
|
42
|
-
|
|
47
|
+
|
|
48
|
+
//# sourceMappingURL=LocalStateProvider-D7QyJoLM.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LocalStateProvider-D7QyJoLM.mjs","names":[],"sources":["../src/deploy/LocalStateProvider.ts"],"sourcesContent":["/**\n * Local Filesystem State Provider\n *\n * Stores deployment state in .gkm/deploy-{stage}.json files.\n * This is the default provider for local development.\n */\n\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport type { StateProvider } from './StateProvider';\nimport type { DokployStageState } from './state';\n\n/**\n * Get the state file path for a stage.\n */\nfunction getStateFilePath(workspaceRoot: string, stage: string): string {\n\treturn join(workspaceRoot, '.gkm', `deploy-${stage}.json`);\n}\n\n/**\n * Local filesystem state provider.\n *\n * Stores state in .gkm/deploy-{stage}.json files in the workspace root.\n */\nexport class LocalStateProvider implements StateProvider {\n\tconstructor(private readonly workspaceRoot: string) {}\n\n\tasync read(stage: string): Promise<DokployStageState | null> {\n\t\tconst filePath = getStateFilePath(this.workspaceRoot, stage);\n\n\t\ttry {\n\t\t\tconst content = await readFile(filePath, 'utf-8');\n\t\t\treturn JSON.parse(content) as DokployStageState;\n\t\t} catch (error) {\n\t\t\t// File doesn't exist - return null\n\t\t\tif ((error as NodeJS.ErrnoException).code === 'ENOENT') {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t\t// Log other errors but don't fail\n\t\t\tconsole.warn(`Warning: Could not read deploy state: ${error}`);\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tasync write(stage: string, state: DokployStageState): Promise<void> {\n\t\tconst filePath = getStateFilePath(this.workspaceRoot, stage);\n\t\tconst dir = join(this.workspaceRoot, '.gkm');\n\n\t\t// Ensure .gkm directory exists\n\t\tawait mkdir(dir, { recursive: true });\n\n\t\t// Update last deployed timestamp\n\t\tstate.lastDeployedAt = new Date().toISOString();\n\n\t\tawait writeFile(filePath, JSON.stringify(state, null, 2));\n\t}\n}\n"],"mappings":";;;;;;;;;;;;AAeA,SAAS,iBAAiB,eAAuB,OAAuB;CACvE,OAAO,KAAK,eAAe,QAAQ,UAAU,MAAM,MAAM;AAC1D;;;;;;AAOA,IAAa,qBAAb,MAAyD;CAC3B;CAA7B,YAAY,eAAwC;EAAvB,KAAA,gBAAA;CAAwB;CAErD,MAAM,KAAK,OAAkD;EAC5D,MAAM,WAAW,iBAAiB,KAAK,eAAe,KAAK;EAE3D,IAAI;GACH,MAAM,UAAU,MAAM,SAAS,UAAU,OAAO;GAChD,OAAO,KAAK,MAAM,OAAO;EAC1B,SAAS,OAAO;GAEf,IAAK,MAAgC,SAAS,UAC7C,OAAO;GAGR,QAAQ,KAAK,yCAAyC,OAAO;GAC7D,OAAO;EACR;CACD;CAEA,MAAM,MAAM,OAAe,OAAyC;EACnE,MAAM,WAAW,iBAAiB,KAAK,eAAe,KAAK;EAC3D,MAAM,MAAM,KAAK,KAAK,eAAe,MAAM;EAG3C,MAAM,MAAM,KAAK,EAAE,WAAW,KAAK,CAAC;EAGpC,MAAM,kCAAiB,IAAI,KAAK,EAAA,CAAE,YAAY;EAE9C,MAAM,UAAU,UAAU,KAAK,UAAU,OAAO,MAAM,CAAC,CAAC;CACzD;AACD"}
|