@geekmidas/cli 0.53.0 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/README.md +26 -5
- package/dist/CachedStateProvider-D73dCqfH.cjs +60 -0
- package/dist/CachedStateProvider-D73dCqfH.cjs.map +1 -0
- package/dist/CachedStateProvider-DVyKfaMm.mjs +54 -0
- package/dist/CachedStateProvider-DVyKfaMm.mjs.map +1 -0
- package/dist/CachedStateProvider-D_uISMmJ.cjs +3 -0
- package/dist/CachedStateProvider-OiFUGr7p.mjs +3 -0
- package/dist/HostingerProvider-DUV9-Tzg.cjs +210 -0
- package/dist/HostingerProvider-DUV9-Tzg.cjs.map +1 -0
- package/dist/HostingerProvider-DqUq6e9i.mjs +210 -0
- package/dist/HostingerProvider-DqUq6e9i.mjs.map +1 -0
- package/dist/LocalStateProvider-CdspeSVL.cjs +43 -0
- package/dist/LocalStateProvider-CdspeSVL.cjs.map +1 -0
- package/dist/LocalStateProvider-DxoSaWUV.mjs +42 -0
- package/dist/LocalStateProvider-DxoSaWUV.mjs.map +1 -0
- package/dist/Route53Provider-CpRIqu69.cjs +157 -0
- package/dist/Route53Provider-CpRIqu69.cjs.map +1 -0
- package/dist/Route53Provider-KUAX3vz9.mjs +156 -0
- package/dist/Route53Provider-KUAX3vz9.mjs.map +1 -0
- package/dist/SSMStateProvider-BxAPU99a.cjs +53 -0
- package/dist/SSMStateProvider-BxAPU99a.cjs.map +1 -0
- package/dist/SSMStateProvider-C4wp4AZe.mjs +52 -0
- package/dist/SSMStateProvider-C4wp4AZe.mjs.map +1 -0
- package/dist/{bundler-DGry2vaR.mjs → bundler-BqTN5Dj5.mjs} +3 -3
- package/dist/{bundler-DGry2vaR.mjs.map → bundler-BqTN5Dj5.mjs.map} +1 -1
- package/dist/{bundler-BB-kETMd.cjs → bundler-tHLLwYuU.cjs} +3 -3
- package/dist/{bundler-BB-kETMd.cjs.map → bundler-tHLLwYuU.cjs.map} +1 -1
- package/dist/{config-HYiM3iQJ.cjs → config-BGeJsW1r.cjs} +2 -2
- package/dist/{config-HYiM3iQJ.cjs.map → config-BGeJsW1r.cjs.map} +1 -1
- package/dist/{config-C3LSBNSl.mjs → config-C6awcFBx.mjs} +2 -2
- package/dist/{config-C3LSBNSl.mjs.map → config-C6awcFBx.mjs.map} +1 -1
- package/dist/config.cjs +2 -2
- package/dist/config.d.cts +1 -1
- package/dist/config.d.mts +2 -2
- package/dist/config.mjs +2 -2
- package/dist/credentials-C8DWtnMY.cjs +174 -0
- package/dist/credentials-C8DWtnMY.cjs.map +1 -0
- package/dist/credentials-DT1dSxIx.mjs +126 -0
- package/dist/credentials-DT1dSxIx.mjs.map +1 -0
- package/dist/deploy/sniffer-envkit-patch.cjs.map +1 -1
- package/dist/deploy/sniffer-envkit-patch.mjs.map +1 -1
- package/dist/deploy/sniffer-loader.cjs +1 -1
- package/dist/{dokploy-api-94KzmTVf.mjs → dokploy-api-7k3t7_zd.mjs} +1 -1
- package/dist/{dokploy-api-94KzmTVf.mjs.map → dokploy-api-7k3t7_zd.mjs.map} +1 -1
- package/dist/dokploy-api-CHa8G51l.mjs +3 -0
- package/dist/{dokploy-api-YD8WCQfW.cjs → dokploy-api-CQvhV6Hd.cjs} +1 -1
- package/dist/{dokploy-api-YD8WCQfW.cjs.map → dokploy-api-CQvhV6Hd.cjs.map} +1 -1
- package/dist/dokploy-api-CWc02yyg.cjs +3 -0
- package/dist/{encryption-DaCB_NmS.cjs → encryption-BE0UOb8j.cjs} +1 -1
- package/dist/{encryption-DaCB_NmS.cjs.map → encryption-BE0UOb8j.cjs.map} +1 -1
- package/dist/{encryption-Biq0EZ4m.cjs → encryption-Cv3zips0.cjs} +1 -1
- package/dist/{encryption-BC4MAODn.mjs → encryption-JtMsiGNp.mjs} +1 -1
- package/dist/{encryption-BC4MAODn.mjs.map → encryption-JtMsiGNp.mjs.map} +1 -1
- package/dist/encryption-UUmaWAmz.mjs +3 -0
- package/dist/{index-pOA56MWT.d.cts → index-B5rGIc4g.d.cts} +553 -196
- package/dist/index-B5rGIc4g.d.cts.map +1 -0
- package/dist/{index-A70abJ1m.d.mts → index-KFEbMIRa.d.mts} +554 -197
- package/dist/index-KFEbMIRa.d.mts.map +1 -0
- package/dist/index.cjs +2242 -568
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +2219 -545
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-C3C-BzIZ.mjs → openapi-BMFmLnX6.mjs} +51 -7
- package/dist/openapi-BMFmLnX6.mjs.map +1 -0
- package/dist/{openapi-D7WwlpPF.cjs → openapi-D1KXv2Ml.cjs} +51 -7
- package/dist/openapi-D1KXv2Ml.cjs.map +1 -0
- package/dist/{openapi-react-query-C_MxpBgF.cjs → openapi-react-query-BeXvk-wa.cjs} +1 -1
- package/dist/{openapi-react-query-C_MxpBgF.cjs.map → openapi-react-query-BeXvk-wa.cjs.map} +1 -1
- package/dist/{openapi-react-query-ZoP9DPbY.mjs → openapi-react-query-DGEkD39r.mjs} +1 -1
- package/dist/{openapi-react-query-ZoP9DPbY.mjs.map → openapi-react-query-DGEkD39r.mjs.map} +1 -1
- package/dist/openapi-react-query.cjs +1 -1
- package/dist/openapi-react-query.mjs +1 -1
- package/dist/openapi.cjs +3 -3
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +2 -2
- package/dist/openapi.mjs +3 -3
- package/dist/{storage-Dhst7BhI.mjs → storage-BMW6yLu3.mjs} +1 -1
- package/dist/{storage-Dhst7BhI.mjs.map → storage-BMW6yLu3.mjs.map} +1 -1
- package/dist/{storage-fOR8dMu5.cjs → storage-C7pmBq1u.cjs} +1 -1
- package/dist/{storage-BPRgh3DU.cjs → storage-CoCNe0Pt.cjs} +1 -1
- package/dist/{storage-BPRgh3DU.cjs.map → storage-CoCNe0Pt.cjs.map} +1 -1
- package/dist/{storage-DNj_I11J.mjs → storage-D8XzjVaO.mjs} +1 -1
- package/dist/{types-BtGL-8QS.d.mts → types-BldpmqQX.d.mts} +1 -1
- package/dist/{types-BtGL-8QS.d.mts.map → types-BldpmqQX.d.mts.map} +1 -1
- package/dist/workspace/index.cjs +1 -1
- package/dist/workspace/index.d.cts +1 -1
- package/dist/workspace/index.d.mts +2 -2
- package/dist/workspace/index.mjs +1 -1
- package/dist/{workspace-CaVW6j2q.cjs → workspace-BFRUOOrh.cjs} +309 -25
- package/dist/workspace-BFRUOOrh.cjs.map +1 -0
- package/dist/{workspace-DLFRaDc-.mjs → workspace-DAxG3_H2.mjs} +309 -25
- package/dist/workspace-DAxG3_H2.mjs.map +1 -0
- package/package.json +12 -8
- package/src/build/__tests__/handler-templates.spec.ts +115 -47
- package/src/deploy/CachedStateProvider.ts +86 -0
- package/src/deploy/LocalStateProvider.ts +57 -0
- package/src/deploy/SSMStateProvider.ts +93 -0
- package/src/deploy/StateProvider.ts +171 -0
- package/src/deploy/__tests__/CachedStateProvider.spec.ts +228 -0
- package/src/deploy/__tests__/HostingerProvider.spec.ts +347 -0
- package/src/deploy/__tests__/LocalStateProvider.spec.ts +126 -0
- package/src/deploy/__tests__/Route53Provider.spec.ts +402 -0
- package/src/deploy/__tests__/SSMStateProvider.spec.ts +177 -0
- package/src/deploy/__tests__/__fixtures__/env-parsers/throwing-env-parser.ts +1 -3
- package/src/deploy/__tests__/__fixtures__/route-apps/endpoints/auth.ts +16 -0
- package/src/deploy/__tests__/__fixtures__/route-apps/endpoints/health.ts +13 -0
- package/src/deploy/__tests__/__fixtures__/route-apps/endpoints/users.ts +15 -0
- package/src/deploy/__tests__/__fixtures__/route-apps/services.ts +55 -0
- package/src/deploy/__tests__/createDnsProvider.spec.ts +172 -0
- package/src/deploy/__tests__/createStateProvider.spec.ts +116 -0
- package/src/deploy/__tests__/dns-orchestration.spec.ts +192 -0
- package/src/deploy/__tests__/dns-verification.spec.ts +2 -2
- package/src/deploy/__tests__/env-resolver.spec.ts +41 -17
- package/src/deploy/__tests__/sniffer.spec.ts +168 -10
- package/src/deploy/__tests__/state.spec.ts +13 -5
- package/src/deploy/dns/DnsProvider.ts +163 -0
- package/src/deploy/dns/HostingerProvider.ts +100 -0
- package/src/deploy/dns/Route53Provider.ts +256 -0
- package/src/deploy/dns/index.ts +257 -165
- package/src/deploy/env-resolver.ts +12 -5
- package/src/deploy/index.ts +16 -13
- package/src/deploy/sniffer-envkit-patch.ts +3 -1
- package/src/deploy/sniffer-routes-worker.ts +104 -0
- package/src/deploy/sniffer.ts +130 -5
- package/src/deploy/state-commands.ts +274 -0
- package/src/dev/__tests__/entry.spec.ts +8 -2
- package/src/dev/__tests__/index.spec.ts +1 -3
- package/src/dev/index.ts +9 -3
- package/src/docker/__tests__/templates.spec.ts +3 -1
- package/src/docker/templates.ts +3 -3
- package/src/index.ts +88 -0
- package/src/init/__tests__/generators.spec.ts +273 -0
- package/src/init/__tests__/init.spec.ts +3 -3
- package/src/init/generators/auth.ts +1 -0
- package/src/init/generators/config.ts +2 -0
- package/src/init/generators/models.ts +6 -1
- package/src/init/generators/monorepo.ts +3 -0
- package/src/init/generators/ui.ts +1472 -0
- package/src/init/generators/web.ts +134 -87
- package/src/init/index.ts +22 -3
- package/src/init/templates/api.ts +109 -3
- package/src/openapi.ts +99 -13
- package/src/workspace/__tests__/schema.spec.ts +107 -0
- package/src/workspace/schema.ts +314 -4
- package/src/workspace/types.ts +22 -36
- package/dist/dokploy-api-CItuaWTq.mjs +0 -3
- package/dist/dokploy-api-DBNE8MDt.cjs +0 -3
- package/dist/encryption-CQXBZGkt.mjs +0 -3
- package/dist/index-A70abJ1m.d.mts.map +0 -1
- package/dist/index-pOA56MWT.d.cts.map +0 -1
- package/dist/openapi-C3C-BzIZ.mjs.map +0 -1
- package/dist/openapi-D7WwlpPF.cjs.map +0 -1
- package/dist/workspace-CaVW6j2q.cjs.map +0 -1
- package/dist/workspace-DLFRaDc-.mjs.map +0 -1
- package/tsconfig.tsbuildinfo +0 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@geekmidas/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -40,6 +40,9 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"@apidevtools/swagger-parser": "^10.1.0",
|
|
43
|
+
"@aws-sdk/client-route-53": "~3.971.0",
|
|
44
|
+
"@aws-sdk/client-ssm": "~3.971.0",
|
|
45
|
+
"@aws-sdk/credential-providers": "~3.971.0",
|
|
43
46
|
"chokidar": "~4.0.3",
|
|
44
47
|
"commander": "^12.1.0",
|
|
45
48
|
"dotenv": "~17.2.3",
|
|
@@ -49,11 +52,12 @@
|
|
|
49
52
|
"openapi-typescript": "^7.4.2",
|
|
50
53
|
"pg": "~8.17.1",
|
|
51
54
|
"prompts": "~2.4.2",
|
|
52
|
-
"
|
|
53
|
-
"@geekmidas/
|
|
54
|
-
"@geekmidas/
|
|
55
|
-
"@geekmidas/
|
|
56
|
-
"@geekmidas/
|
|
55
|
+
"tsx": "~4.20.3",
|
|
56
|
+
"@geekmidas/constructs": "~1.0.0",
|
|
57
|
+
"@geekmidas/envkit": "~1.0.0",
|
|
58
|
+
"@geekmidas/errors": "~1.0.0",
|
|
59
|
+
"@geekmidas/logger": "~1.0.0",
|
|
60
|
+
"@geekmidas/schema": "~1.0.0"
|
|
57
61
|
},
|
|
58
62
|
"devDependencies": {
|
|
59
63
|
"@types/lodash.kebabcase": "^4.1.9",
|
|
@@ -63,10 +67,10 @@
|
|
|
63
67
|
"typescript": "^5.8.2",
|
|
64
68
|
"vitest": "^3.2.4",
|
|
65
69
|
"zod": "~4.1.13",
|
|
66
|
-
"@geekmidas/testkit": "0.
|
|
70
|
+
"@geekmidas/testkit": "1.0.0"
|
|
67
71
|
},
|
|
68
72
|
"peerDependencies": {
|
|
69
|
-
"@geekmidas/telescope": "~0.
|
|
73
|
+
"@geekmidas/telescope": "~1.0.0"
|
|
70
74
|
},
|
|
71
75
|
"peerDependenciesMeta": {
|
|
72
76
|
"@geekmidas/telescope": {
|
|
@@ -528,7 +528,9 @@ describe('handler-templates', () => {
|
|
|
528
528
|
}),
|
|
529
529
|
];
|
|
530
530
|
|
|
531
|
-
const result = generateOptimizedSetupFunction(analyses, [
|
|
531
|
+
const result = generateOptimizedSetupFunction(analyses, [
|
|
532
|
+
'healthEndpoint',
|
|
533
|
+
]);
|
|
532
534
|
|
|
533
535
|
expect(result).toContain('export async function setupEndpoints');
|
|
534
536
|
expect(result).toContain('Minimal handlers (1 endpoints)');
|
|
@@ -548,7 +550,9 @@ describe('handler-templates', () => {
|
|
|
548
550
|
}),
|
|
549
551
|
];
|
|
550
552
|
|
|
551
|
-
const result = generateOptimizedSetupFunction(analyses, [
|
|
553
|
+
const result = generateOptimizedSetupFunction(analyses, [
|
|
554
|
+
'auditEndpoint',
|
|
555
|
+
]);
|
|
552
556
|
|
|
553
557
|
expect(result).toContain('import { HonoEndpoint }');
|
|
554
558
|
expect(result).toContain('HonoEndpoint.addRoutes');
|
|
@@ -603,7 +607,9 @@ describe('handler-templates', () => {
|
|
|
603
607
|
}),
|
|
604
608
|
];
|
|
605
609
|
|
|
606
|
-
const result = generateOptimizedSetupFunction(analyses, [
|
|
610
|
+
const result = generateOptimizedSetupFunction(analyses, [
|
|
611
|
+
'healthEndpoint',
|
|
612
|
+
]);
|
|
607
613
|
|
|
608
614
|
expect(result).toContain('if (enableOpenApi)');
|
|
609
615
|
expect(result).toContain('swaggerUI');
|
|
@@ -631,19 +637,18 @@ describe('handler-templates', () => {
|
|
|
631
637
|
|
|
632
638
|
const endpointImports = `import { healthEndpoint } from './endpoints/health';\nimport { createUserEndpoint } from './endpoints/users';`;
|
|
633
639
|
|
|
634
|
-
const result = generateOptimizedEndpointsFile(
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
);
|
|
640
|
+
const result = generateOptimizedEndpointsFile(analyses, endpointImports, [
|
|
641
|
+
'healthEndpoint',
|
|
642
|
+
'createUserEndpoint',
|
|
643
|
+
]);
|
|
639
644
|
|
|
640
645
|
expect(result).toContain('Generated optimized endpoints file');
|
|
641
646
|
expect(result).toContain('minimal: 1 endpoints');
|
|
642
647
|
expect(result).toContain('standard: 1 endpoints');
|
|
643
648
|
expect(result).toContain('full: 0 endpoints');
|
|
644
|
-
expect(result).toContain(
|
|
645
|
-
expect(result).toContain(
|
|
646
|
-
expect(result).toContain(
|
|
649
|
+
expect(result).toContain('import type { EnvironmentParser }');
|
|
650
|
+
expect(result).toContain('import { healthEndpoint }');
|
|
651
|
+
expect(result).toContain('import { createUserEndpoint }');
|
|
647
652
|
expect(result).toContain('validateBody');
|
|
648
653
|
expect(result).toContain('export async function setupEndpoints');
|
|
649
654
|
});
|
|
@@ -753,8 +758,10 @@ describe('handler-templates', () => {
|
|
|
753
758
|
|
|
754
759
|
expect(files['minimal.ts']).toContain('Minimal-tier endpoint handlers');
|
|
755
760
|
expect(files['minimal.ts']).toContain('1 endpoints');
|
|
756
|
-
expect(files['minimal.ts']).toContain(
|
|
757
|
-
|
|
761
|
+
expect(files['minimal.ts']).toContain(
|
|
762
|
+
'export function setupMinimalEndpoints',
|
|
763
|
+
);
|
|
764
|
+
expect(files['minimal.ts']).toContain('import { healthEndpoint }');
|
|
758
765
|
expect(files['minimal.ts']).toContain("app.get('/health'");
|
|
759
766
|
});
|
|
760
767
|
|
|
@@ -798,8 +805,10 @@ describe('handler-templates', () => {
|
|
|
798
805
|
|
|
799
806
|
expect(files['standard.ts']).toContain('Standard-tier endpoint handlers');
|
|
800
807
|
expect(files['standard.ts']).toContain('1 endpoints');
|
|
801
|
-
expect(files['standard.ts']).toContain(
|
|
802
|
-
|
|
808
|
+
expect(files['standard.ts']).toContain(
|
|
809
|
+
'export function setupStandardEndpoints',
|
|
810
|
+
);
|
|
811
|
+
expect(files['standard.ts']).toContain('import { usersEndpoint }');
|
|
803
812
|
});
|
|
804
813
|
|
|
805
814
|
it('should generate standard file with events import when needed', () => {
|
|
@@ -814,12 +823,17 @@ describe('handler-templates', () => {
|
|
|
814
823
|
];
|
|
815
824
|
|
|
816
825
|
const endpointImports = [
|
|
817
|
-
{
|
|
826
|
+
{
|
|
827
|
+
exportName: 'createOrderEndpoint',
|
|
828
|
+
importPath: '../endpoints/orders',
|
|
829
|
+
},
|
|
818
830
|
];
|
|
819
831
|
|
|
820
832
|
const files = generateEndpointFilesByTier(analyses, endpointImports);
|
|
821
833
|
|
|
822
|
-
expect(files['standard.ts']).toContain(
|
|
834
|
+
expect(files['standard.ts']).toContain(
|
|
835
|
+
'import { publishConstructEvents }',
|
|
836
|
+
);
|
|
823
837
|
});
|
|
824
838
|
|
|
825
839
|
it('should generate full file with HonoEndpoint', () => {
|
|
@@ -842,7 +856,7 @@ describe('handler-templates', () => {
|
|
|
842
856
|
expect(files['full.ts']).toContain('Full-tier endpoint handlers');
|
|
843
857
|
expect(files['full.ts']).toContain('import { HonoEndpoint }');
|
|
844
858
|
expect(files['full.ts']).toContain('HonoEndpoint.addRoutes');
|
|
845
|
-
expect(files['full.ts']).toContain(
|
|
859
|
+
expect(files['full.ts']).toContain('import { auditEndpoint }');
|
|
846
860
|
});
|
|
847
861
|
|
|
848
862
|
it('should generate empty full file when no full endpoints', () => {
|
|
@@ -899,10 +913,12 @@ describe('handler-templates', () => {
|
|
|
899
913
|
expect(files['index.ts']).toContain('minimal: 1 endpoints');
|
|
900
914
|
expect(files['index.ts']).toContain('standard: 1 endpoints');
|
|
901
915
|
expect(files['index.ts']).toContain('full: 1 endpoints');
|
|
902
|
-
expect(files['index.ts']).toContain(
|
|
903
|
-
expect(files['index.ts']).toContain(
|
|
904
|
-
expect(files['index.ts']).toContain(
|
|
905
|
-
expect(files['index.ts']).toContain(
|
|
916
|
+
expect(files['index.ts']).toContain('import { setupMinimalEndpoints }');
|
|
917
|
+
expect(files['index.ts']).toContain('import { setupStandardEndpoints }');
|
|
918
|
+
expect(files['index.ts']).toContain('import { setupFullEndpoints }');
|
|
919
|
+
expect(files['index.ts']).toContain(
|
|
920
|
+
'export async function setupEndpoints',
|
|
921
|
+
);
|
|
906
922
|
});
|
|
907
923
|
|
|
908
924
|
it('should include validator imports in minimal file when needed', () => {
|
|
@@ -922,7 +938,9 @@ describe('handler-templates', () => {
|
|
|
922
938
|
|
|
923
939
|
const files = generateEndpointFilesByTier(analyses, endpointImports);
|
|
924
940
|
|
|
925
|
-
expect(files['minimal.ts']).toContain(
|
|
941
|
+
expect(files['minimal.ts']).toContain(
|
|
942
|
+
"import { validateBody } from './validators.js'",
|
|
943
|
+
);
|
|
926
944
|
});
|
|
927
945
|
|
|
928
946
|
it('should include validator imports in standard file when needed', () => {
|
|
@@ -942,7 +960,9 @@ describe('handler-templates', () => {
|
|
|
942
960
|
|
|
943
961
|
const files = generateEndpointFilesByTier(analyses, endpointImports);
|
|
944
962
|
|
|
945
|
-
expect(files['standard.ts']).toContain(
|
|
963
|
+
expect(files['standard.ts']).toContain(
|
|
964
|
+
"import { validateBody } from './validators.js'",
|
|
965
|
+
);
|
|
946
966
|
});
|
|
947
967
|
});
|
|
948
968
|
|
|
@@ -987,9 +1007,15 @@ describe('handler-templates', () => {
|
|
|
987
1007
|
const files = generateEndpointFilesNested(analyses, endpointImports);
|
|
988
1008
|
|
|
989
1009
|
expect(files).toHaveProperty('minimal/healthEndpoint.ts');
|
|
990
|
-
expect(files['minimal/healthEndpoint.ts']).toContain(
|
|
991
|
-
|
|
992
|
-
|
|
1010
|
+
expect(files['minimal/healthEndpoint.ts']).toContain(
|
|
1011
|
+
'Minimal endpoint: /health (GET)',
|
|
1012
|
+
);
|
|
1013
|
+
expect(files['minimal/healthEndpoint.ts']).toContain(
|
|
1014
|
+
'export function setupHealthEndpoint',
|
|
1015
|
+
);
|
|
1016
|
+
expect(files['minimal/healthEndpoint.ts']).toContain(
|
|
1017
|
+
'import { healthEndpoint }',
|
|
1018
|
+
);
|
|
993
1019
|
});
|
|
994
1020
|
|
|
995
1021
|
it('should generate individual endpoint files for standard tier', () => {
|
|
@@ -1010,9 +1036,15 @@ describe('handler-templates', () => {
|
|
|
1010
1036
|
const files = generateEndpointFilesNested(analyses, endpointImports);
|
|
1011
1037
|
|
|
1012
1038
|
expect(files).toHaveProperty('standard/usersEndpoint.ts');
|
|
1013
|
-
expect(files['standard/usersEndpoint.ts']).toContain(
|
|
1014
|
-
|
|
1015
|
-
|
|
1039
|
+
expect(files['standard/usersEndpoint.ts']).toContain(
|
|
1040
|
+
'Standard endpoint: /api/users (GET)',
|
|
1041
|
+
);
|
|
1042
|
+
expect(files['standard/usersEndpoint.ts']).toContain(
|
|
1043
|
+
'export function setupUsersEndpoint',
|
|
1044
|
+
);
|
|
1045
|
+
expect(files['standard/usersEndpoint.ts']).toContain(
|
|
1046
|
+
'import { usersEndpoint }',
|
|
1047
|
+
);
|
|
1016
1048
|
});
|
|
1017
1049
|
|
|
1018
1050
|
it('should generate individual endpoint files for full tier', () => {
|
|
@@ -1033,9 +1065,15 @@ describe('handler-templates', () => {
|
|
|
1033
1065
|
const files = generateEndpointFilesNested(analyses, endpointImports);
|
|
1034
1066
|
|
|
1035
1067
|
expect(files).toHaveProperty('full/auditEndpoint.ts');
|
|
1036
|
-
expect(files['full/auditEndpoint.ts']).toContain(
|
|
1037
|
-
|
|
1038
|
-
|
|
1068
|
+
expect(files['full/auditEndpoint.ts']).toContain(
|
|
1069
|
+
'Full endpoint: /api/audit (POST)',
|
|
1070
|
+
);
|
|
1071
|
+
expect(files['full/auditEndpoint.ts']).toContain(
|
|
1072
|
+
'export function setupAuditEndpoint',
|
|
1073
|
+
);
|
|
1074
|
+
expect(files['full/auditEndpoint.ts']).toContain(
|
|
1075
|
+
'import { HonoEndpoint }',
|
|
1076
|
+
);
|
|
1039
1077
|
});
|
|
1040
1078
|
|
|
1041
1079
|
it('should generate tier index files that import individual endpoints', () => {
|
|
@@ -1061,10 +1099,18 @@ describe('handler-templates', () => {
|
|
|
1061
1099
|
|
|
1062
1100
|
const files = generateEndpointFilesNested(analyses, endpointImports);
|
|
1063
1101
|
|
|
1064
|
-
expect(files['minimal/index.ts']).toContain(
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
expect(files['minimal/index.ts']).toContain(
|
|
1102
|
+
expect(files['minimal/index.ts']).toContain(
|
|
1103
|
+
"import { setupHealthEndpoint } from './healthEndpoint.js'",
|
|
1104
|
+
);
|
|
1105
|
+
expect(files['minimal/index.ts']).toContain(
|
|
1106
|
+
"import { setupReadyEndpoint } from './readyEndpoint.js'",
|
|
1107
|
+
);
|
|
1108
|
+
expect(files['minimal/index.ts']).toContain(
|
|
1109
|
+
'setupHealthEndpoint(app, logger)',
|
|
1110
|
+
);
|
|
1111
|
+
expect(files['minimal/index.ts']).toContain(
|
|
1112
|
+
'setupReadyEndpoint(app, logger)',
|
|
1113
|
+
);
|
|
1068
1114
|
});
|
|
1069
1115
|
|
|
1070
1116
|
it('should generate empty tier index for tiers with no endpoints', () => {
|
|
@@ -1083,7 +1129,9 @@ describe('handler-templates', () => {
|
|
|
1083
1129
|
|
|
1084
1130
|
const files = generateEndpointFilesNested(analyses, endpointImports);
|
|
1085
1131
|
|
|
1086
|
-
expect(files['standard/index.ts']).toContain(
|
|
1132
|
+
expect(files['standard/index.ts']).toContain(
|
|
1133
|
+
'No standard-tier endpoints',
|
|
1134
|
+
);
|
|
1087
1135
|
expect(files['full/index.ts']).toContain('No full-tier endpoints');
|
|
1088
1136
|
});
|
|
1089
1137
|
|
|
@@ -1103,9 +1151,15 @@ describe('handler-templates', () => {
|
|
|
1103
1151
|
|
|
1104
1152
|
const files = generateEndpointFilesNested(analyses, endpointImports);
|
|
1105
1153
|
|
|
1106
|
-
expect(files['index.ts']).toContain(
|
|
1107
|
-
|
|
1108
|
-
|
|
1154
|
+
expect(files['index.ts']).toContain(
|
|
1155
|
+
"import { setupMinimalEndpoints } from './minimal/index.js'",
|
|
1156
|
+
);
|
|
1157
|
+
expect(files['index.ts']).toContain(
|
|
1158
|
+
"import { setupStandardEndpoints } from './standard/index.js'",
|
|
1159
|
+
);
|
|
1160
|
+
expect(files['index.ts']).toContain(
|
|
1161
|
+
"import { setupFullEndpoints } from './full/index.js'",
|
|
1162
|
+
);
|
|
1109
1163
|
});
|
|
1110
1164
|
|
|
1111
1165
|
it('should include validator imports in individual endpoint files when needed', () => {
|
|
@@ -1120,12 +1174,17 @@ describe('handler-templates', () => {
|
|
|
1120
1174
|
];
|
|
1121
1175
|
|
|
1122
1176
|
const endpointImports = [
|
|
1123
|
-
{
|
|
1177
|
+
{
|
|
1178
|
+
exportName: 'createUserEndpoint',
|
|
1179
|
+
importPath: '../../endpoints/users',
|
|
1180
|
+
},
|
|
1124
1181
|
];
|
|
1125
1182
|
|
|
1126
1183
|
const files = generateEndpointFilesNested(analyses, endpointImports);
|
|
1127
1184
|
|
|
1128
|
-
expect(files['minimal/createUserEndpoint.ts']).toContain(
|
|
1185
|
+
expect(files['minimal/createUserEndpoint.ts']).toContain(
|
|
1186
|
+
"import { validateBody } from '../validators.js'",
|
|
1187
|
+
);
|
|
1129
1188
|
});
|
|
1130
1189
|
|
|
1131
1190
|
it('should include events import in standard endpoint files when needed', () => {
|
|
@@ -1140,12 +1199,17 @@ describe('handler-templates', () => {
|
|
|
1140
1199
|
];
|
|
1141
1200
|
|
|
1142
1201
|
const endpointImports = [
|
|
1143
|
-
{
|
|
1202
|
+
{
|
|
1203
|
+
exportName: 'createOrderEndpoint',
|
|
1204
|
+
importPath: '../../endpoints/orders',
|
|
1205
|
+
},
|
|
1144
1206
|
];
|
|
1145
1207
|
|
|
1146
1208
|
const files = generateEndpointFilesNested(analyses, endpointImports);
|
|
1147
1209
|
|
|
1148
|
-
expect(files['standard/createOrderEndpoint.ts']).toContain(
|
|
1210
|
+
expect(files['standard/createOrderEndpoint.ts']).toContain(
|
|
1211
|
+
'import { publishConstructEvents }',
|
|
1212
|
+
);
|
|
1149
1213
|
});
|
|
1150
1214
|
|
|
1151
1215
|
it('should skip endpoints without matching imports', () => {
|
|
@@ -1192,7 +1256,9 @@ describe('handler-templates', () => {
|
|
|
1192
1256
|
|
|
1193
1257
|
const files = generateEndpointFilesNested(analyses, endpointImports);
|
|
1194
1258
|
|
|
1195
|
-
expect(files['standard/index.ts']).toContain(
|
|
1259
|
+
expect(files['standard/index.ts']).toContain(
|
|
1260
|
+
'setupUsersEndpoint(app, serviceDiscovery, logger)',
|
|
1261
|
+
);
|
|
1196
1262
|
});
|
|
1197
1263
|
|
|
1198
1264
|
it('should generate full tier index with correct function calls', () => {
|
|
@@ -1212,7 +1278,9 @@ describe('handler-templates', () => {
|
|
|
1212
1278
|
|
|
1213
1279
|
const files = generateEndpointFilesNested(analyses, endpointImports);
|
|
1214
1280
|
|
|
1215
|
-
expect(files['full/index.ts']).toContain(
|
|
1281
|
+
expect(files['full/index.ts']).toContain(
|
|
1282
|
+
'setupAuditEndpoint(app, serviceDiscovery, openApiOptions)',
|
|
1283
|
+
);
|
|
1216
1284
|
expect(files['full/index.ts']).toContain('const openApiOptions');
|
|
1217
1285
|
});
|
|
1218
1286
|
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cached State Provider
|
|
3
|
+
*
|
|
4
|
+
* Wraps a remote state provider (SSM) with local filesystem cache.
|
|
5
|
+
* - Read: Local first, then remote if missing
|
|
6
|
+
* - Write: Remote first, then update local cache
|
|
7
|
+
*
|
|
8
|
+
* This enables fast local development while keeping SSM as source of truth.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { StateProvider } from './StateProvider';
|
|
12
|
+
import type { DokployStageState } from './state';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Cached state provider that wraps a remote provider with local cache.
|
|
16
|
+
*/
|
|
17
|
+
export class CachedStateProvider implements StateProvider {
|
|
18
|
+
constructor(
|
|
19
|
+
private readonly remote: StateProvider,
|
|
20
|
+
private readonly local: StateProvider,
|
|
21
|
+
) {}
|
|
22
|
+
|
|
23
|
+
async read(stage: string): Promise<DokployStageState | null> {
|
|
24
|
+
// Try local cache first
|
|
25
|
+
const localState = await this.local.read(stage);
|
|
26
|
+
if (localState) {
|
|
27
|
+
return localState;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Fall back to remote
|
|
31
|
+
const remoteState = await this.remote.read(stage);
|
|
32
|
+
if (remoteState) {
|
|
33
|
+
// Update local cache
|
|
34
|
+
await this.local.write(stage, remoteState);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return remoteState;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async write(stage: string, state: DokployStageState): Promise<void> {
|
|
41
|
+
// Write to remote first (source of truth)
|
|
42
|
+
await this.remote.write(stage, state);
|
|
43
|
+
|
|
44
|
+
// Update local cache
|
|
45
|
+
await this.local.write(stage, state);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Force pull from remote to local.
|
|
50
|
+
* Used by `gkm state pull` command.
|
|
51
|
+
*/
|
|
52
|
+
async pull(stage: string): Promise<DokployStageState | null> {
|
|
53
|
+
const remoteState = await this.remote.read(stage);
|
|
54
|
+
if (remoteState) {
|
|
55
|
+
await this.local.write(stage, remoteState);
|
|
56
|
+
}
|
|
57
|
+
return remoteState;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Force push from local to remote.
|
|
62
|
+
* Used by `gkm state push` command.
|
|
63
|
+
*/
|
|
64
|
+
async push(stage: string): Promise<DokployStageState | null> {
|
|
65
|
+
const localState = await this.local.read(stage);
|
|
66
|
+
if (localState) {
|
|
67
|
+
await this.remote.write(stage, localState);
|
|
68
|
+
}
|
|
69
|
+
return localState;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get both local and remote state for comparison.
|
|
74
|
+
* Used by `gkm state diff` command.
|
|
75
|
+
*/
|
|
76
|
+
async diff(stage: string): Promise<{
|
|
77
|
+
local: DokployStageState | null;
|
|
78
|
+
remote: DokployStageState | null;
|
|
79
|
+
}> {
|
|
80
|
+
const [local, remote] = await Promise.all([
|
|
81
|
+
this.local.read(stage),
|
|
82
|
+
this.remote.read(stage),
|
|
83
|
+
]);
|
|
84
|
+
return { local, remote };
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local Filesystem State Provider
|
|
3
|
+
*
|
|
4
|
+
* Stores deployment state in .gkm/deploy-{stage}.json files.
|
|
5
|
+
* This is the default provider for local development.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import type { StateProvider } from './StateProvider';
|
|
11
|
+
import type { DokployStageState } from './state';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Get the state file path for a stage.
|
|
15
|
+
*/
|
|
16
|
+
function getStateFilePath(workspaceRoot: string, stage: string): string {
|
|
17
|
+
return join(workspaceRoot, '.gkm', `deploy-${stage}.json`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Local filesystem state provider.
|
|
22
|
+
*
|
|
23
|
+
* Stores state in .gkm/deploy-{stage}.json files in the workspace root.
|
|
24
|
+
*/
|
|
25
|
+
export class LocalStateProvider implements StateProvider {
|
|
26
|
+
constructor(private readonly workspaceRoot: string) {}
|
|
27
|
+
|
|
28
|
+
async read(stage: string): Promise<DokployStageState | null> {
|
|
29
|
+
const filePath = getStateFilePath(this.workspaceRoot, stage);
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const content = await readFile(filePath, 'utf-8');
|
|
33
|
+
return JSON.parse(content) as DokployStageState;
|
|
34
|
+
} catch (error) {
|
|
35
|
+
// File doesn't exist - return null
|
|
36
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
// Log other errors but don't fail
|
|
40
|
+
console.warn(`Warning: Could not read deploy state: ${error}`);
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async write(stage: string, state: DokployStageState): Promise<void> {
|
|
46
|
+
const filePath = getStateFilePath(this.workspaceRoot, stage);
|
|
47
|
+
const dir = join(this.workspaceRoot, '.gkm');
|
|
48
|
+
|
|
49
|
+
// Ensure .gkm directory exists
|
|
50
|
+
await mkdir(dir, { recursive: true });
|
|
51
|
+
|
|
52
|
+
// Update last deployed timestamp
|
|
53
|
+
state.lastDeployedAt = new Date().toISOString();
|
|
54
|
+
|
|
55
|
+
await writeFile(filePath, JSON.stringify(state, null, 2));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AWS SSM Parameter Store State Provider
|
|
3
|
+
*
|
|
4
|
+
* Stores deployment state as SecureString parameters in AWS SSM.
|
|
5
|
+
* Uses AWS-managed KMS key for encryption (free tier).
|
|
6
|
+
*
|
|
7
|
+
* Parameter naming: /gkm/{workspaceName}/{stage}/state
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
GetParameterCommand,
|
|
12
|
+
ParameterNotFound,
|
|
13
|
+
PutParameterCommand,
|
|
14
|
+
SSMClient,
|
|
15
|
+
} from '@aws-sdk/client-ssm';
|
|
16
|
+
import type { AwsRegion, StateProvider } from './StateProvider';
|
|
17
|
+
import type { DokployStageState } from './state';
|
|
18
|
+
|
|
19
|
+
export interface SSMStateProviderOptions {
|
|
20
|
+
/** Workspace name (used in parameter path) */
|
|
21
|
+
workspaceName: string;
|
|
22
|
+
/** AWS region (required) */
|
|
23
|
+
region: AwsRegion;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* AWS SSM Parameter Store state provider.
|
|
28
|
+
*
|
|
29
|
+
* Stores state as encrypted SecureString parameters.
|
|
30
|
+
* Parameter path: /gkm/{workspaceName}/{stage}/state
|
|
31
|
+
*/
|
|
32
|
+
export class SSMStateProvider implements StateProvider {
|
|
33
|
+
private readonly client: SSMClient;
|
|
34
|
+
private readonly workspaceName: string;
|
|
35
|
+
|
|
36
|
+
constructor(options: SSMStateProviderOptions) {
|
|
37
|
+
this.workspaceName = options.workspaceName;
|
|
38
|
+
this.client = new SSMClient({
|
|
39
|
+
region: options.region,
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get the SSM parameter name for a stage.
|
|
45
|
+
*/
|
|
46
|
+
private getParameterName(stage: string): string {
|
|
47
|
+
return `/gkm/${this.workspaceName}/${stage}/state`;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async read(stage: string): Promise<DokployStageState | null> {
|
|
51
|
+
const parameterName = this.getParameterName(stage);
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const response = await this.client.send(
|
|
55
|
+
new GetParameterCommand({
|
|
56
|
+
Name: parameterName,
|
|
57
|
+
WithDecryption: true,
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
if (!response.Parameter?.Value) {
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return JSON.parse(response.Parameter.Value) as DokployStageState;
|
|
66
|
+
} catch (error) {
|
|
67
|
+
// Parameter doesn't exist - return null (new deployment)
|
|
68
|
+
if (error instanceof ParameterNotFound) {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Re-throw other errors (permission denied, network, etc.)
|
|
73
|
+
throw error;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async write(stage: string, state: DokployStageState): Promise<void> {
|
|
78
|
+
const parameterName = this.getParameterName(stage);
|
|
79
|
+
|
|
80
|
+
// Update last deployed timestamp
|
|
81
|
+
state.lastDeployedAt = new Date().toISOString();
|
|
82
|
+
|
|
83
|
+
await this.client.send(
|
|
84
|
+
new PutParameterCommand({
|
|
85
|
+
Name: parameterName,
|
|
86
|
+
Value: JSON.stringify(state),
|
|
87
|
+
Type: 'SecureString',
|
|
88
|
+
Overwrite: true,
|
|
89
|
+
Description: `GKM deployment state for ${this.workspaceName}/${stage}`,
|
|
90
|
+
}),
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|