@geekmidas/cli 0.54.0 → 1.0.1
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 +23 -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 +2265 -658
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +2242 -635
- 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 +14 -8
- package/scripts/sync-versions.ts +86 -0
- 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/services.ts +28 -19
- 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 +37 -15
- package/src/deploy/__tests__/sniffer.spec.ts +4 -20
- 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 +77 -55
- 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/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/init/versions.ts +25 -53
- 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/src/openapi.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
4
4
|
import { dirname, join } from 'node:path';
|
|
5
|
-
import {
|
|
5
|
+
import { loadWorkspaceConfig } from './config.js';
|
|
6
6
|
import { EndpointGenerator } from './generators/EndpointGenerator.js';
|
|
7
7
|
import { OpenApiTsGenerator } from './generators/OpenApiTsGenerator.js';
|
|
8
8
|
import type { GkmConfig, OpenApiConfig } from './types.js';
|
|
@@ -12,7 +12,7 @@ interface OpenAPIOptions {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
15
|
+
* Default output path for generated OpenAPI client (used for single-app configs)
|
|
16
16
|
*/
|
|
17
17
|
export const OPENAPI_OUTPUT_PATH = './.gkm/openapi.ts';
|
|
18
18
|
|
|
@@ -92,17 +92,103 @@ export async function openapiCommand(
|
|
|
92
92
|
const logger = console;
|
|
93
93
|
|
|
94
94
|
try {
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
config
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
95
|
+
const loadedConfig = await loadWorkspaceConfig(options.cwd);
|
|
96
|
+
|
|
97
|
+
if (loadedConfig.type === 'single') {
|
|
98
|
+
// Single-app config - use existing behavior
|
|
99
|
+
const config = loadedConfig.raw as GkmConfig;
|
|
100
|
+
|
|
101
|
+
// Enable openapi if not configured
|
|
102
|
+
if (!config.openapi) {
|
|
103
|
+
config.openapi = { enabled: true };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const result = await generateOpenApi(config);
|
|
107
|
+
|
|
108
|
+
if (result) {
|
|
109
|
+
logger.log(`Found ${result.endpointCount} endpoints`);
|
|
110
|
+
}
|
|
111
|
+
} else {
|
|
112
|
+
// Workspace config - generate for each backend app and copy to frontend clients
|
|
113
|
+
const { workspace } = loadedConfig;
|
|
114
|
+
const workspaceRoot = options.cwd || process.cwd();
|
|
115
|
+
|
|
116
|
+
// Find backend apps with openapi enabled
|
|
117
|
+
const backendApps = Object.entries(workspace.apps).filter(
|
|
118
|
+
([_, app]) =>
|
|
119
|
+
app.type === 'backend' &&
|
|
120
|
+
(app.openapi === true ||
|
|
121
|
+
(typeof app.openapi === 'object' && app.openapi.enabled !== false)),
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
if (backendApps.length === 0) {
|
|
125
|
+
logger.log('No backend apps with OpenAPI enabled found');
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Find frontend apps with client config
|
|
130
|
+
const frontendApps = Object.entries(workspace.apps).filter(
|
|
131
|
+
([_, app]) => app.type === 'frontend' && app.client?.output,
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
// Generate OpenAPI for each backend app
|
|
135
|
+
for (const [appName, app] of backendApps) {
|
|
136
|
+
if (app.type !== 'backend' || !app.routes) continue;
|
|
137
|
+
|
|
138
|
+
const appPath = join(workspaceRoot, app.path);
|
|
139
|
+
const routes = Array.isArray(app.routes) ? app.routes : [app.routes];
|
|
140
|
+
const routesGlob = routes.map((r) => join(appPath, r));
|
|
141
|
+
|
|
142
|
+
const gkmConfig: GkmConfig = {
|
|
143
|
+
routes: routesGlob,
|
|
144
|
+
envParser: app.envParser || '',
|
|
145
|
+
logger: app.logger || '',
|
|
146
|
+
openapi: app.openapi,
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
// Change to app directory for generation
|
|
150
|
+
const originalCwd = process.cwd();
|
|
151
|
+
process.chdir(appPath);
|
|
152
|
+
|
|
153
|
+
const result = await generateOpenApi(gkmConfig, { silent: true });
|
|
154
|
+
|
|
155
|
+
process.chdir(originalCwd);
|
|
156
|
+
|
|
157
|
+
if (result) {
|
|
158
|
+
logger.log(
|
|
159
|
+
`📄 [${appName}] Generated OpenAPI (${result.endpointCount} endpoints)`,
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
// Copy to frontend apps that depend on this backend
|
|
163
|
+
for (const [frontendName, frontendApp] of frontendApps) {
|
|
164
|
+
if (frontendApp.type !== 'frontend') continue;
|
|
165
|
+
|
|
166
|
+
const dependsOnBackend =
|
|
167
|
+
!frontendApp.dependencies ||
|
|
168
|
+
frontendApp.dependencies.includes(appName);
|
|
169
|
+
|
|
170
|
+
if (dependsOnBackend && frontendApp.client?.output) {
|
|
171
|
+
const frontendPath = join(workspaceRoot, frontendApp.path);
|
|
172
|
+
const clientOutputPath = join(
|
|
173
|
+
frontendPath,
|
|
174
|
+
frontendApp.client.output,
|
|
175
|
+
'openapi.ts',
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
await mkdir(dirname(clientOutputPath), { recursive: true });
|
|
179
|
+
|
|
180
|
+
// Read the generated content and write to frontend
|
|
181
|
+
const { readFile } = await import('node:fs/promises');
|
|
182
|
+
const content = await readFile(result.outputPath, 'utf-8');
|
|
183
|
+
await writeFile(clientOutputPath, content);
|
|
184
|
+
|
|
185
|
+
logger.log(
|
|
186
|
+
` → [${frontendName}] ${frontendApp.client.output}/openapi.ts`,
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
106
192
|
}
|
|
107
193
|
} catch (error) {
|
|
108
194
|
throw new Error(`OpenAPI generation failed: ${(error as Error).message}`);
|
|
@@ -592,6 +592,113 @@ describe('WorkspaceConfigSchema', () => {
|
|
|
592
592
|
});
|
|
593
593
|
});
|
|
594
594
|
|
|
595
|
+
describe('DNS configuration', () => {
|
|
596
|
+
it('should accept multi-domain DNS config', () => {
|
|
597
|
+
const config = {
|
|
598
|
+
apps: {
|
|
599
|
+
api: {
|
|
600
|
+
type: 'backend' as const,
|
|
601
|
+
path: 'apps/api',
|
|
602
|
+
port: 3000,
|
|
603
|
+
routes: './src/**/*.ts',
|
|
604
|
+
},
|
|
605
|
+
},
|
|
606
|
+
deploy: {
|
|
607
|
+
default: 'dokploy' as const,
|
|
608
|
+
dns: {
|
|
609
|
+
'geekmidas.dev': { provider: 'hostinger' as const },
|
|
610
|
+
'geekmidas.com': {
|
|
611
|
+
provider: 'route53' as const,
|
|
612
|
+
region: 'us-east-1' as const,
|
|
613
|
+
},
|
|
614
|
+
},
|
|
615
|
+
},
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
const result = validateWorkspaceConfig(config);
|
|
619
|
+
|
|
620
|
+
expect(result.deploy?.dns).toEqual({
|
|
621
|
+
'geekmidas.dev': { provider: 'hostinger' },
|
|
622
|
+
'geekmidas.com': { provider: 'route53', region: 'us-east-1' },
|
|
623
|
+
});
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
it('should accept legacy single-domain DNS config', () => {
|
|
627
|
+
const config = {
|
|
628
|
+
apps: {
|
|
629
|
+
api: {
|
|
630
|
+
type: 'backend' as const,
|
|
631
|
+
path: 'apps/api',
|
|
632
|
+
port: 3000,
|
|
633
|
+
routes: './src/**/*.ts',
|
|
634
|
+
},
|
|
635
|
+
},
|
|
636
|
+
deploy: {
|
|
637
|
+
default: 'dokploy' as const,
|
|
638
|
+
dns: {
|
|
639
|
+
provider: 'hostinger' as const,
|
|
640
|
+
domain: 'example.com',
|
|
641
|
+
},
|
|
642
|
+
},
|
|
643
|
+
};
|
|
644
|
+
|
|
645
|
+
const result = validateWorkspaceConfig(config);
|
|
646
|
+
|
|
647
|
+
expect(result.deploy?.dns).toEqual({
|
|
648
|
+
provider: 'hostinger',
|
|
649
|
+
domain: 'example.com',
|
|
650
|
+
});
|
|
651
|
+
});
|
|
652
|
+
|
|
653
|
+
it('should accept DNS config with manual provider', () => {
|
|
654
|
+
const config = {
|
|
655
|
+
apps: {
|
|
656
|
+
api: {
|
|
657
|
+
type: 'backend' as const,
|
|
658
|
+
path: 'apps/api',
|
|
659
|
+
port: 3000,
|
|
660
|
+
routes: './src/**/*.ts',
|
|
661
|
+
},
|
|
662
|
+
},
|
|
663
|
+
deploy: {
|
|
664
|
+
default: 'dokploy' as const,
|
|
665
|
+
dns: {
|
|
666
|
+
'example.com': { provider: 'manual' as const },
|
|
667
|
+
},
|
|
668
|
+
},
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
const result = validateWorkspaceConfig(config);
|
|
672
|
+
|
|
673
|
+
expect(result.deploy?.dns).toEqual({
|
|
674
|
+
'example.com': { provider: 'manual' },
|
|
675
|
+
});
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
it('should accept DNS config with TTL', () => {
|
|
679
|
+
const config = {
|
|
680
|
+
apps: {
|
|
681
|
+
api: {
|
|
682
|
+
type: 'backend' as const,
|
|
683
|
+
path: 'apps/api',
|
|
684
|
+
port: 3000,
|
|
685
|
+
routes: './src/**/*.ts',
|
|
686
|
+
},
|
|
687
|
+
},
|
|
688
|
+
deploy: {
|
|
689
|
+
default: 'dokploy' as const,
|
|
690
|
+
dns: {
|
|
691
|
+
'example.com': { provider: 'hostinger' as const, ttl: 600 },
|
|
692
|
+
},
|
|
693
|
+
},
|
|
694
|
+
};
|
|
695
|
+
|
|
696
|
+
const result = validateWorkspaceConfig(config);
|
|
697
|
+
|
|
698
|
+
expect((result.deploy?.dns as any)['example.com'].ttl).toBe(600);
|
|
699
|
+
});
|
|
700
|
+
});
|
|
701
|
+
|
|
595
702
|
describe('deploy target helpers', () => {
|
|
596
703
|
it('isDeployTargetSupported should return true for dokploy', () => {
|
|
597
704
|
expect(isDeployTargetSupported('dokploy')).toBe(true);
|
package/src/workspace/schema.ts
CHANGED
|
@@ -75,7 +75,10 @@ const FrontendFrameworkSchema = z.enum(['nextjs', 'remix', 'vite']);
|
|
|
75
75
|
/**
|
|
76
76
|
* Combined framework schema (backend or frontend).
|
|
77
77
|
*/
|
|
78
|
-
const FrameworkSchema = z.union([
|
|
78
|
+
const FrameworkSchema = z.union([
|
|
79
|
+
BackendFrameworkSchema,
|
|
80
|
+
FrontendFrameworkSchema,
|
|
81
|
+
]);
|
|
79
82
|
|
|
80
83
|
/**
|
|
81
84
|
* Deploy target schema.
|
|
@@ -164,12 +167,251 @@ const DokployWorkspaceConfigSchema = z.object({
|
|
|
164
167
|
registryId: z.string().optional(),
|
|
165
168
|
});
|
|
166
169
|
|
|
170
|
+
// =============================================================================
|
|
171
|
+
// AWS Regions (needed by DNS and State providers)
|
|
172
|
+
// =============================================================================
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Valid AWS regions.
|
|
176
|
+
*/
|
|
177
|
+
const AwsRegionSchema = z.enum([
|
|
178
|
+
'us-east-1',
|
|
179
|
+
'us-east-2',
|
|
180
|
+
'us-west-1',
|
|
181
|
+
'us-west-2',
|
|
182
|
+
'af-south-1',
|
|
183
|
+
'ap-east-1',
|
|
184
|
+
'ap-south-1',
|
|
185
|
+
'ap-south-2',
|
|
186
|
+
'ap-southeast-1',
|
|
187
|
+
'ap-southeast-2',
|
|
188
|
+
'ap-southeast-3',
|
|
189
|
+
'ap-southeast-4',
|
|
190
|
+
'ap-northeast-1',
|
|
191
|
+
'ap-northeast-2',
|
|
192
|
+
'ap-northeast-3',
|
|
193
|
+
'ca-central-1',
|
|
194
|
+
'eu-central-1',
|
|
195
|
+
'eu-central-2',
|
|
196
|
+
'eu-west-1',
|
|
197
|
+
'eu-west-2',
|
|
198
|
+
'eu-west-3',
|
|
199
|
+
'eu-south-1',
|
|
200
|
+
'eu-south-2',
|
|
201
|
+
'eu-north-1',
|
|
202
|
+
'me-south-1',
|
|
203
|
+
'me-central-1',
|
|
204
|
+
'sa-east-1',
|
|
205
|
+
]);
|
|
206
|
+
|
|
207
|
+
// =============================================================================
|
|
208
|
+
// DNS Record Types (used by DnsProvider interface)
|
|
209
|
+
// =============================================================================
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* DNS record types supported across providers.
|
|
213
|
+
*/
|
|
214
|
+
export const DnsRecordTypeSchema = z.enum([
|
|
215
|
+
'A',
|
|
216
|
+
'AAAA',
|
|
217
|
+
'CNAME',
|
|
218
|
+
'MX',
|
|
219
|
+
'TXT',
|
|
220
|
+
'NS',
|
|
221
|
+
'SRV',
|
|
222
|
+
'CAA',
|
|
223
|
+
]);
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* A DNS record as returned by the provider.
|
|
227
|
+
*/
|
|
228
|
+
export const DnsRecordSchema = z.object({
|
|
229
|
+
/** Subdomain name (e.g., 'api' for api.example.com, '@' for root) */
|
|
230
|
+
name: z.string(),
|
|
231
|
+
/** Record type */
|
|
232
|
+
type: DnsRecordTypeSchema,
|
|
233
|
+
/** TTL in seconds */
|
|
234
|
+
ttl: z.number().int().positive(),
|
|
235
|
+
/** Record values */
|
|
236
|
+
values: z.array(z.string()),
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* A DNS record to create or update.
|
|
241
|
+
*/
|
|
242
|
+
export const UpsertDnsRecordSchema = z.object({
|
|
243
|
+
/** Subdomain name (e.g., 'api' for api.example.com, '@' for root) */
|
|
244
|
+
name: z.string(),
|
|
245
|
+
/** Record type */
|
|
246
|
+
type: DnsRecordTypeSchema,
|
|
247
|
+
/** TTL in seconds */
|
|
248
|
+
ttl: z.number().int().positive(),
|
|
249
|
+
/** Record value (IP address, hostname, etc.) */
|
|
250
|
+
value: z.string(),
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Result of an upsert operation.
|
|
255
|
+
*/
|
|
256
|
+
export const UpsertResultSchema = z.object({
|
|
257
|
+
/** The record that was upserted */
|
|
258
|
+
record: UpsertDnsRecordSchema,
|
|
259
|
+
/** Whether the record was created (true) or updated (false) */
|
|
260
|
+
created: z.boolean(),
|
|
261
|
+
/** Whether the record already existed with the same value */
|
|
262
|
+
unchanged: z.boolean(),
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// =============================================================================
|
|
266
|
+
// DNS Provider Configuration
|
|
267
|
+
// =============================================================================
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Hostinger DNS provider config (without domain - domain is the record key).
|
|
271
|
+
*/
|
|
272
|
+
export const HostingerDnsProviderSchema = z.object({
|
|
273
|
+
provider: z.literal('hostinger'),
|
|
274
|
+
/** TTL in seconds (default: 300) */
|
|
275
|
+
ttl: z.number().int().positive().optional(),
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Route53 DNS provider config (without domain - domain is the record key).
|
|
280
|
+
*/
|
|
281
|
+
export const Route53DnsProviderSchema = z.object({
|
|
282
|
+
provider: z.literal('route53'),
|
|
283
|
+
/** AWS region (optional - uses AWS_REGION env var if not provided) */
|
|
284
|
+
region: AwsRegionSchema.optional(),
|
|
285
|
+
/** AWS profile name (optional - uses default credential chain if not provided) */
|
|
286
|
+
profile: z.string().optional(),
|
|
287
|
+
/** Hosted zone ID (optional - auto-detected from domain if not provided) */
|
|
288
|
+
hostedZoneId: z.string().optional(),
|
|
289
|
+
/** TTL in seconds (default: 300) */
|
|
290
|
+
ttl: z.number().int().positive().optional(),
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Cloudflare DNS provider config (placeholder for future).
|
|
295
|
+
*/
|
|
296
|
+
export const CloudflareDnsProviderSchema = z.object({
|
|
297
|
+
provider: z.literal('cloudflare'),
|
|
298
|
+
/** TTL in seconds (default: 300) */
|
|
299
|
+
ttl: z.number().int().positive().optional(),
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Manual DNS configuration (user handles DNS themselves).
|
|
304
|
+
*/
|
|
305
|
+
export const ManualDnsProviderSchema = z.object({
|
|
306
|
+
provider: z.literal('manual'),
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Custom DNS provider config (user-provided implementation).
|
|
311
|
+
*/
|
|
312
|
+
export const CustomDnsProviderSchema = z.object({
|
|
313
|
+
/** Custom DnsProvider implementation */
|
|
314
|
+
provider: z.custom<{
|
|
315
|
+
name: string;
|
|
316
|
+
getRecords: Function;
|
|
317
|
+
upsertRecords: Function;
|
|
318
|
+
}>(
|
|
319
|
+
(val) =>
|
|
320
|
+
typeof val === 'object' &&
|
|
321
|
+
val !== null &&
|
|
322
|
+
typeof (val as any).name === 'string' &&
|
|
323
|
+
typeof (val as any).getRecords === 'function' &&
|
|
324
|
+
typeof (val as any).upsertRecords === 'function',
|
|
325
|
+
{
|
|
326
|
+
message:
|
|
327
|
+
'Custom DNS provider must implement name, getRecords(), and upsertRecords() methods',
|
|
328
|
+
},
|
|
329
|
+
),
|
|
330
|
+
/** TTL in seconds (default: 300) */
|
|
331
|
+
ttl: z.number().int().positive().optional(),
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Built-in DNS provider config (discriminated union).
|
|
336
|
+
*/
|
|
337
|
+
export const BuiltInDnsProviderSchema = z.discriminatedUnion('provider', [
|
|
338
|
+
HostingerDnsProviderSchema,
|
|
339
|
+
Route53DnsProviderSchema,
|
|
340
|
+
CloudflareDnsProviderSchema,
|
|
341
|
+
ManualDnsProviderSchema,
|
|
342
|
+
]);
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Single DNS provider config (for one domain).
|
|
346
|
+
*/
|
|
347
|
+
export const DnsProviderSchema = z.union([
|
|
348
|
+
BuiltInDnsProviderSchema,
|
|
349
|
+
CustomDnsProviderSchema,
|
|
350
|
+
]);
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* DNS configuration schema.
|
|
354
|
+
*
|
|
355
|
+
* Maps root domains to their DNS provider configuration.
|
|
356
|
+
* Example:
|
|
357
|
+
* ```
|
|
358
|
+
* dns: {
|
|
359
|
+
* 'geekmidas.dev': { provider: 'hostinger' },
|
|
360
|
+
* 'geekmidas.com': { provider: 'route53' },
|
|
361
|
+
* }
|
|
362
|
+
* ```
|
|
363
|
+
*
|
|
364
|
+
* Supported providers:
|
|
365
|
+
* - 'hostinger': Use Hostinger DNS API
|
|
366
|
+
* - 'route53': Use AWS Route53
|
|
367
|
+
* - 'cloudflare': Use Cloudflare DNS API (future)
|
|
368
|
+
* - 'manual': Don't create records, just print required records
|
|
369
|
+
* - Custom: Provide a DnsProvider implementation
|
|
370
|
+
*/
|
|
371
|
+
export const DnsConfigSchema = z.record(z.string(), DnsProviderSchema);
|
|
372
|
+
|
|
373
|
+
// Legacy single-domain config schemas (for backwards compatibility)
|
|
374
|
+
export const HostingerDnsConfigSchema = HostingerDnsProviderSchema.extend({
|
|
375
|
+
domain: z.string().min(1, 'Domain is required'),
|
|
376
|
+
});
|
|
377
|
+
export const Route53DnsConfigSchema = Route53DnsProviderSchema.extend({
|
|
378
|
+
domain: z.string().min(1, 'Domain is required'),
|
|
379
|
+
});
|
|
380
|
+
export const CloudflareDnsConfigSchema = CloudflareDnsProviderSchema.extend({
|
|
381
|
+
domain: z.string().min(1, 'Domain is required'),
|
|
382
|
+
});
|
|
383
|
+
export const ManualDnsConfigSchema = ManualDnsProviderSchema.extend({
|
|
384
|
+
domain: z.string().min(1, 'Domain is required'),
|
|
385
|
+
});
|
|
386
|
+
export const CustomDnsConfigSchema = CustomDnsProviderSchema.extend({
|
|
387
|
+
domain: z.string().min(1, 'Domain is required'),
|
|
388
|
+
});
|
|
389
|
+
export const BuiltInDnsConfigSchema = z.discriminatedUnion('provider', [
|
|
390
|
+
HostingerDnsConfigSchema,
|
|
391
|
+
Route53DnsConfigSchema,
|
|
392
|
+
CloudflareDnsConfigSchema,
|
|
393
|
+
ManualDnsConfigSchema,
|
|
394
|
+
]);
|
|
395
|
+
export const LegacyDnsConfigSchema = z.union([
|
|
396
|
+
BuiltInDnsConfigSchema,
|
|
397
|
+
CustomDnsConfigSchema,
|
|
398
|
+
]);
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Combined DNS config that supports both new multi-domain and legacy single-domain formats.
|
|
402
|
+
*/
|
|
403
|
+
export const DnsConfigWithLegacySchema = z.union([
|
|
404
|
+
DnsConfigSchema,
|
|
405
|
+
LegacyDnsConfigSchema,
|
|
406
|
+
]);
|
|
407
|
+
|
|
167
408
|
/**
|
|
168
409
|
* Deploy configuration schema.
|
|
169
410
|
*/
|
|
170
411
|
const DeployConfigSchema = z.object({
|
|
171
412
|
default: DeployTargetSchema.optional(),
|
|
172
413
|
dokploy: DokployWorkspaceConfigSchema.optional(),
|
|
414
|
+
dns: DnsConfigWithLegacySchema.optional(),
|
|
173
415
|
});
|
|
174
416
|
|
|
175
417
|
/**
|
|
@@ -197,6 +439,62 @@ const SecretsConfigSchema = z.object({
|
|
|
197
439
|
kdf: z.enum(['scrypt', 'pbkdf2']).optional(),
|
|
198
440
|
});
|
|
199
441
|
|
|
442
|
+
// =============================================================================
|
|
443
|
+
// State Provider Configuration
|
|
444
|
+
// =============================================================================
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Local state provider config.
|
|
448
|
+
*/
|
|
449
|
+
const LocalStateConfigSchema = z.object({
|
|
450
|
+
provider: z.literal('local'),
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* SSM state provider config (requires region).
|
|
455
|
+
*/
|
|
456
|
+
const SSMStateConfigSchema = z.object({
|
|
457
|
+
provider: z.literal('ssm'),
|
|
458
|
+
/** AWS region (required for SSM provider) */
|
|
459
|
+
region: AwsRegionSchema,
|
|
460
|
+
});
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Custom state provider config (user-provided implementation).
|
|
464
|
+
*/
|
|
465
|
+
const CustomStateConfigSchema = z.object({
|
|
466
|
+
/** Custom StateProvider implementation */
|
|
467
|
+
provider: z.custom<{ read: Function; write: Function }>(
|
|
468
|
+
(val) =>
|
|
469
|
+
typeof val === 'object' &&
|
|
470
|
+
val !== null &&
|
|
471
|
+
typeof (val as any).read === 'function' &&
|
|
472
|
+
typeof (val as any).write === 'function',
|
|
473
|
+
{ message: 'Custom provider must implement read() and write() methods' },
|
|
474
|
+
),
|
|
475
|
+
});
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Built-in state provider config (discriminated union).
|
|
479
|
+
*/
|
|
480
|
+
const BuiltInStateConfigSchema = z.discriminatedUnion('provider', [
|
|
481
|
+
LocalStateConfigSchema,
|
|
482
|
+
SSMStateConfigSchema,
|
|
483
|
+
]);
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* State configuration schema.
|
|
487
|
+
*
|
|
488
|
+
* Configures how deployment state is stored.
|
|
489
|
+
* - 'local': Store in .gkm/deploy-{stage}.json (default)
|
|
490
|
+
* - 'ssm': Store in AWS SSM Parameter Store (requires region)
|
|
491
|
+
* - Custom: Provide a StateProvider implementation with read/write methods
|
|
492
|
+
*/
|
|
493
|
+
const StateConfigSchema = z.union([
|
|
494
|
+
BuiltInStateConfigSchema,
|
|
495
|
+
CustomStateConfigSchema,
|
|
496
|
+
]);
|
|
497
|
+
|
|
200
498
|
/**
|
|
201
499
|
* App configuration schema.
|
|
202
500
|
*/
|
|
@@ -248,7 +546,8 @@ const AppConfigSchema = z
|
|
|
248
546
|
return true;
|
|
249
547
|
},
|
|
250
548
|
{
|
|
251
|
-
message:
|
|
549
|
+
message:
|
|
550
|
+
'Frontend apps must have a valid frontend framework (nextjs, remix, vite)',
|
|
252
551
|
path: ['framework'],
|
|
253
552
|
},
|
|
254
553
|
)
|
|
@@ -281,6 +580,7 @@ export const WorkspaceConfigSchema = z
|
|
|
281
580
|
deploy: DeployConfigSchema.optional(),
|
|
282
581
|
services: ServicesConfigSchema.optional(),
|
|
283
582
|
secrets: SecretsConfigSchema.optional(),
|
|
583
|
+
state: StateConfigSchema.optional(),
|
|
284
584
|
})
|
|
285
585
|
.refine(
|
|
286
586
|
(data) => {
|
|
@@ -343,7 +643,7 @@ export const WorkspaceConfigSchema = z
|
|
|
343
643
|
const defaultTarget = data.deploy?.default;
|
|
344
644
|
if (defaultTarget && !isDeployTargetSupported(defaultTarget)) {
|
|
345
645
|
ctx.addIssue({
|
|
346
|
-
code:
|
|
646
|
+
code: 'custom',
|
|
347
647
|
message: getDeployTargetError(defaultTarget),
|
|
348
648
|
path: ['deploy', 'default'],
|
|
349
649
|
});
|
|
@@ -353,13 +653,23 @@ export const WorkspaceConfigSchema = z
|
|
|
353
653
|
for (const [appName, app] of Object.entries(data.apps)) {
|
|
354
654
|
if (app.deploy && !isDeployTargetSupported(app.deploy)) {
|
|
355
655
|
ctx.addIssue({
|
|
356
|
-
code:
|
|
656
|
+
code: 'custom',
|
|
357
657
|
message: getDeployTargetError(app.deploy, appName),
|
|
358
658
|
path: ['apps', appName, 'deploy'],
|
|
359
659
|
});
|
|
360
660
|
return;
|
|
361
661
|
}
|
|
362
662
|
}
|
|
663
|
+
|
|
664
|
+
// Validate workspace name is required for SSM state provider
|
|
665
|
+
if (data.state?.provider === 'ssm' && !data.name) {
|
|
666
|
+
ctx.addIssue({
|
|
667
|
+
code: 'custom',
|
|
668
|
+
message:
|
|
669
|
+
'Workspace name is required when using SSM state provider. Add "name" to your gkm.config.ts.',
|
|
670
|
+
path: ['name'],
|
|
671
|
+
});
|
|
672
|
+
}
|
|
363
673
|
});
|
|
364
674
|
|
|
365
675
|
/**
|