@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/src/deploy/dns/index.ts
CHANGED
|
@@ -5,17 +5,100 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { lookup } from 'node:dns/promises';
|
|
8
|
-
import {
|
|
9
|
-
|
|
8
|
+
import type {
|
|
9
|
+
DnsConfig,
|
|
10
|
+
DnsProvider as DnsProviderConfig,
|
|
11
|
+
} from '../../workspace/types';
|
|
10
12
|
import {
|
|
11
13
|
type DokployStageState,
|
|
12
14
|
isDnsVerified,
|
|
13
15
|
setDnsVerification,
|
|
14
16
|
} from '../state';
|
|
15
|
-
import {
|
|
17
|
+
import {
|
|
18
|
+
createDnsProvider,
|
|
19
|
+
type DnsProvider,
|
|
20
|
+
type DnsConfig as SchemaDnsConfig,
|
|
21
|
+
type UpsertDnsRecord,
|
|
22
|
+
} from './DnsProvider';
|
|
16
23
|
|
|
17
24
|
const logger = console;
|
|
18
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Check if DNS config is legacy format (single domain with `domain` property)
|
|
28
|
+
*/
|
|
29
|
+
export function isLegacyDnsConfig(
|
|
30
|
+
config: DnsConfig,
|
|
31
|
+
): config is SchemaDnsConfig & { domain: string } {
|
|
32
|
+
return (
|
|
33
|
+
typeof config === 'object' &&
|
|
34
|
+
config !== null &&
|
|
35
|
+
'provider' in config &&
|
|
36
|
+
'domain' in config
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Normalize DNS config to new multi-domain format
|
|
42
|
+
*/
|
|
43
|
+
export function normalizeDnsConfig(
|
|
44
|
+
config: DnsConfig,
|
|
45
|
+
): Record<string, DnsProviderConfig> {
|
|
46
|
+
if (isLegacyDnsConfig(config)) {
|
|
47
|
+
// Convert legacy format to new format
|
|
48
|
+
const { domain, ...providerConfig } = config;
|
|
49
|
+
return { [domain]: providerConfig as DnsProviderConfig };
|
|
50
|
+
}
|
|
51
|
+
return config as Record<string, DnsProviderConfig>;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Find the root domain for a hostname from available DNS configs
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* findRootDomain('api.geekmidas.com', { 'geekmidas.com': {...}, 'geekmidas.dev': {...} })
|
|
59
|
+
* // Returns 'geekmidas.com'
|
|
60
|
+
*/
|
|
61
|
+
export function findRootDomain(
|
|
62
|
+
hostname: string,
|
|
63
|
+
dnsConfig: Record<string, DnsProviderConfig>,
|
|
64
|
+
): string | null {
|
|
65
|
+
// Sort domains by length descending to match most specific first
|
|
66
|
+
const domains = Object.keys(dnsConfig).sort((a, b) => b.length - a.length);
|
|
67
|
+
|
|
68
|
+
for (const domain of domains) {
|
|
69
|
+
if (hostname === domain || hostname.endsWith(`.${domain}`)) {
|
|
70
|
+
return domain;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Group hostnames by their root domain
|
|
79
|
+
*/
|
|
80
|
+
export function groupHostnamesByDomain(
|
|
81
|
+
appHostnames: Map<string, string>,
|
|
82
|
+
dnsConfig: Record<string, DnsProviderConfig>,
|
|
83
|
+
): Map<string, Map<string, string>> {
|
|
84
|
+
const grouped = new Map<string, Map<string, string>>();
|
|
85
|
+
|
|
86
|
+
for (const [appName, hostname] of appHostnames) {
|
|
87
|
+
const rootDomain = findRootDomain(hostname, dnsConfig);
|
|
88
|
+
if (!rootDomain) {
|
|
89
|
+
logger.log(` ⚠ No DNS config found for hostname: ${hostname}`);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!grouped.has(rootDomain)) {
|
|
94
|
+
grouped.set(rootDomain, new Map());
|
|
95
|
+
}
|
|
96
|
+
grouped.get(rootDomain)!.set(appName, hostname);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return grouped;
|
|
100
|
+
}
|
|
101
|
+
|
|
19
102
|
/**
|
|
20
103
|
* Required DNS record for an app
|
|
21
104
|
*/
|
|
@@ -165,162 +248,139 @@ export function printDnsRecordsSimple(
|
|
|
165
248
|
}
|
|
166
249
|
|
|
167
250
|
/**
|
|
168
|
-
*
|
|
169
|
-
*/
|
|
170
|
-
async function promptForToken(message: string): Promise<string> {
|
|
171
|
-
const { stdin, stdout } = await import('node:process');
|
|
172
|
-
|
|
173
|
-
if (!stdin.isTTY) {
|
|
174
|
-
throw new Error('Interactive input required for Hostinger token.');
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Hidden input for token
|
|
178
|
-
stdout.write(message);
|
|
179
|
-
return new Promise((resolve) => {
|
|
180
|
-
let value = '';
|
|
181
|
-
const onData = (char: Buffer) => {
|
|
182
|
-
const c = char.toString();
|
|
183
|
-
if (c === '\n' || c === '\r') {
|
|
184
|
-
stdin.setRawMode(false);
|
|
185
|
-
stdin.pause();
|
|
186
|
-
stdin.removeListener('data', onData);
|
|
187
|
-
stdout.write('\n');
|
|
188
|
-
resolve(value);
|
|
189
|
-
} else if (c === '\u0003') {
|
|
190
|
-
stdin.setRawMode(false);
|
|
191
|
-
stdin.pause();
|
|
192
|
-
stdout.write('\n');
|
|
193
|
-
process.exit(1);
|
|
194
|
-
} else if (c === '\u007F' || c === '\b') {
|
|
195
|
-
if (value.length > 0) value = value.slice(0, -1);
|
|
196
|
-
} else {
|
|
197
|
-
value += c;
|
|
198
|
-
}
|
|
199
|
-
};
|
|
200
|
-
stdin.setRawMode(true);
|
|
201
|
-
stdin.resume();
|
|
202
|
-
stdin.on('data', onData);
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/**
|
|
207
|
-
* Create DNS records using the configured provider
|
|
208
|
-
*/
|
|
209
|
-
export async function createDnsRecords(
|
|
210
|
-
records: RequiredDnsRecord[],
|
|
211
|
-
dnsConfig: DnsConfig,
|
|
212
|
-
): Promise<RequiredDnsRecord[]> {
|
|
213
|
-
const { provider, domain: rootDomain, ttl = 300 } = dnsConfig;
|
|
214
|
-
|
|
215
|
-
if (provider === 'manual') {
|
|
216
|
-
// Just mark all records as needing manual creation
|
|
217
|
-
return records.map((r) => ({ ...r, created: false, existed: false }));
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (provider === 'hostinger') {
|
|
221
|
-
return createHostingerRecords(records, rootDomain, ttl);
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
if (provider === 'cloudflare') {
|
|
225
|
-
logger.log(' ⚠ Cloudflare DNS integration not yet implemented');
|
|
226
|
-
return records.map((r) => ({
|
|
227
|
-
...r,
|
|
228
|
-
error: 'Cloudflare not implemented',
|
|
229
|
-
}));
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
return records;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/**
|
|
236
|
-
* Create DNS records at Hostinger
|
|
251
|
+
* Create DNS records for a single domain using its configured provider
|
|
237
252
|
*/
|
|
238
|
-
async function
|
|
253
|
+
export async function createDnsRecordsForDomain(
|
|
239
254
|
records: RequiredDnsRecord[],
|
|
240
255
|
rootDomain: string,
|
|
241
|
-
|
|
256
|
+
providerConfig: DnsProviderConfig,
|
|
242
257
|
): Promise<RequiredDnsRecord[]> {
|
|
243
|
-
// Get
|
|
244
|
-
|
|
258
|
+
// Get TTL from config, default to 300. Manual mode doesn't have ttl property.
|
|
259
|
+
const ttl =
|
|
260
|
+
'ttl' in providerConfig && providerConfig.ttl ? providerConfig.ttl : 300;
|
|
245
261
|
|
|
246
|
-
|
|
247
|
-
|
|
262
|
+
// Get DNS provider from factory
|
|
263
|
+
let provider: DnsProvider | null;
|
|
264
|
+
try {
|
|
265
|
+
// Cast to schema-derived DnsConfig for provider factory
|
|
266
|
+
provider = await createDnsProvider({
|
|
267
|
+
config: providerConfig as SchemaDnsConfig,
|
|
268
|
+
});
|
|
269
|
+
} catch (error) {
|
|
270
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
248
271
|
logger.log(
|
|
249
|
-
|
|
272
|
+
` ⚠ Failed to create DNS provider for ${rootDomain}: ${message}`,
|
|
250
273
|
);
|
|
274
|
+
return records.map((r) => ({ ...r, error: message }));
|
|
275
|
+
}
|
|
251
276
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
logger.log(' ✓ Token saved');
|
|
256
|
-
} catch {
|
|
257
|
-
logger.log(' ⚠ Could not get token, skipping DNS creation');
|
|
258
|
-
return records.map((r) => ({
|
|
259
|
-
...r,
|
|
260
|
-
error: 'No API token',
|
|
261
|
-
}));
|
|
262
|
-
}
|
|
277
|
+
// Manual mode - no provider, just mark records as needing manual creation
|
|
278
|
+
if (!provider) {
|
|
279
|
+
return records.map((r) => ({ ...r, created: false, existed: false }));
|
|
263
280
|
}
|
|
264
281
|
|
|
265
|
-
const api = new HostingerApi(token);
|
|
266
282
|
const results: RequiredDnsRecord[] = [];
|
|
267
283
|
|
|
268
|
-
//
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
return records.map((r) => ({ ...r, error: message }));
|
|
276
|
-
}
|
|
284
|
+
// Convert RequiredDnsRecord to UpsertDnsRecord format
|
|
285
|
+
const upsertRecords: UpsertDnsRecord[] = records.map((r) => ({
|
|
286
|
+
name: r.subdomain,
|
|
287
|
+
type: r.type,
|
|
288
|
+
ttl,
|
|
289
|
+
value: r.value,
|
|
290
|
+
}));
|
|
277
291
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
const
|
|
281
|
-
|
|
292
|
+
try {
|
|
293
|
+
// Use provider to upsert records
|
|
294
|
+
const upsertResults = await provider.upsertRecords(
|
|
295
|
+
rootDomain,
|
|
296
|
+
upsertRecords,
|
|
282
297
|
);
|
|
283
298
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
...record,
|
|
288
|
-
existed: true,
|
|
289
|
-
created: false,
|
|
290
|
-
});
|
|
291
|
-
continue;
|
|
292
|
-
}
|
|
299
|
+
// Map results back to RequiredDnsRecord format
|
|
300
|
+
for (const [i, record] of records.entries()) {
|
|
301
|
+
const result = upsertResults[i];
|
|
293
302
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
type:
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
303
|
+
// Handle case where upsertResults has fewer items (shouldn't happen but be safe)
|
|
304
|
+
if (!result) {
|
|
305
|
+
results.push({
|
|
306
|
+
hostname: record.hostname,
|
|
307
|
+
subdomain: record.subdomain,
|
|
308
|
+
type: record.type,
|
|
309
|
+
value: record.value,
|
|
310
|
+
appName: record.appName,
|
|
311
|
+
error: 'No result returned from provider',
|
|
312
|
+
});
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
304
315
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
}
|
|
316
|
+
if (result.unchanged) {
|
|
317
|
+
results.push({
|
|
318
|
+
hostname: record.hostname,
|
|
319
|
+
subdomain: record.subdomain,
|
|
320
|
+
type: record.type,
|
|
321
|
+
value: record.value,
|
|
322
|
+
appName: record.appName,
|
|
323
|
+
existed: true,
|
|
324
|
+
created: false,
|
|
325
|
+
});
|
|
326
|
+
} else {
|
|
327
|
+
results.push({
|
|
328
|
+
hostname: record.hostname,
|
|
329
|
+
subdomain: record.subdomain,
|
|
330
|
+
type: record.type,
|
|
331
|
+
value: record.value,
|
|
332
|
+
appName: record.appName,
|
|
333
|
+
created: result.created,
|
|
334
|
+
existed: !result.created,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
316
337
|
}
|
|
338
|
+
} catch (error) {
|
|
339
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
340
|
+
logger.log(
|
|
341
|
+
` ⚠ Failed to create DNS records for ${rootDomain}: ${message}`,
|
|
342
|
+
);
|
|
343
|
+
return records.map((r) => ({
|
|
344
|
+
hostname: r.hostname,
|
|
345
|
+
subdomain: r.subdomain,
|
|
346
|
+
type: r.type,
|
|
347
|
+
value: r.value,
|
|
348
|
+
appName: r.appName,
|
|
349
|
+
error: message,
|
|
350
|
+
}));
|
|
317
351
|
}
|
|
318
352
|
|
|
319
353
|
return results;
|
|
320
354
|
}
|
|
321
355
|
|
|
356
|
+
/**
|
|
357
|
+
* Create DNS records using the configured provider
|
|
358
|
+
* @deprecated Use createDnsRecordsForDomain for multi-domain support
|
|
359
|
+
*/
|
|
360
|
+
export async function createDnsRecords(
|
|
361
|
+
records: RequiredDnsRecord[],
|
|
362
|
+
dnsConfig: DnsConfig,
|
|
363
|
+
): Promise<RequiredDnsRecord[]> {
|
|
364
|
+
// Handle legacy config format
|
|
365
|
+
if (!isLegacyDnsConfig(dnsConfig)) {
|
|
366
|
+
throw new Error(
|
|
367
|
+
'createDnsRecords requires legacy DnsConfig with domain property. Use createDnsRecordsForDomain instead.',
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
const { domain: rootDomain, ...providerConfig } = dnsConfig;
|
|
371
|
+
return createDnsRecordsForDomain(
|
|
372
|
+
records,
|
|
373
|
+
rootDomain,
|
|
374
|
+
providerConfig as DnsProviderConfig,
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
|
|
322
378
|
/**
|
|
323
379
|
* Main DNS orchestration function for deployments
|
|
380
|
+
*
|
|
381
|
+
* Supports both legacy single-domain format and new multi-domain format:
|
|
382
|
+
* - Legacy: { provider: 'hostinger', domain: 'example.com' }
|
|
383
|
+
* - Multi: { 'example.com': { provider: 'hostinger' }, 'example.dev': { provider: 'route53' } }
|
|
324
384
|
*/
|
|
325
385
|
export async function orchestrateDns(
|
|
326
386
|
appHostnames: Map<string, string>, // appName -> hostname
|
|
@@ -331,7 +391,8 @@ export async function orchestrateDns(
|
|
|
331
391
|
return null;
|
|
332
392
|
}
|
|
333
393
|
|
|
334
|
-
|
|
394
|
+
// Normalize config to multi-domain format
|
|
395
|
+
const normalizedConfig = normalizeDnsConfig(dnsConfig);
|
|
335
396
|
|
|
336
397
|
// Resolve Dokploy server IP from endpoint
|
|
337
398
|
logger.log('\n🌐 Setting up DNS records...');
|
|
@@ -347,56 +408,87 @@ export async function orchestrateDns(
|
|
|
347
408
|
return null;
|
|
348
409
|
}
|
|
349
410
|
|
|
350
|
-
//
|
|
351
|
-
const
|
|
411
|
+
// Group hostnames by their root domain
|
|
412
|
+
const groupedHostnames = groupHostnamesByDomain(
|
|
352
413
|
appHostnames,
|
|
353
|
-
|
|
354
|
-
serverIp,
|
|
414
|
+
normalizedConfig,
|
|
355
415
|
);
|
|
356
416
|
|
|
357
|
-
if (
|
|
358
|
-
logger.log(
|
|
417
|
+
if (groupedHostnames.size === 0) {
|
|
418
|
+
logger.log(
|
|
419
|
+
' No DNS records needed (no hostnames match configured domains)',
|
|
420
|
+
);
|
|
359
421
|
return { records: [], success: true, serverIp };
|
|
360
422
|
}
|
|
361
423
|
|
|
362
|
-
|
|
363
|
-
let
|
|
424
|
+
const allRecords: RequiredDnsRecord[] = [];
|
|
425
|
+
let hasFailures = false;
|
|
426
|
+
|
|
427
|
+
// Process each domain group with its specific provider
|
|
428
|
+
for (const [rootDomain, domainHostnames] of groupedHostnames) {
|
|
429
|
+
const providerConfig = normalizedConfig[rootDomain];
|
|
430
|
+
if (!providerConfig) {
|
|
431
|
+
logger.log(` ⚠ No provider config for ${rootDomain}`);
|
|
432
|
+
continue;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const providerName =
|
|
436
|
+
typeof providerConfig.provider === 'string'
|
|
437
|
+
? providerConfig.provider
|
|
438
|
+
: 'custom';
|
|
439
|
+
|
|
440
|
+
// Generate required records for this domain
|
|
441
|
+
const requiredRecords = generateRequiredRecords(
|
|
442
|
+
domainHostnames,
|
|
443
|
+
rootDomain,
|
|
444
|
+
serverIp,
|
|
445
|
+
);
|
|
446
|
+
|
|
447
|
+
if (requiredRecords.length === 0) {
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
364
450
|
|
|
365
|
-
|
|
366
|
-
logger.log(
|
|
367
|
-
|
|
451
|
+
// Create records for this domain
|
|
452
|
+
logger.log(
|
|
453
|
+
` Creating DNS records for ${rootDomain} (${providerName})...`,
|
|
454
|
+
);
|
|
455
|
+
const domainRecords = await createDnsRecordsForDomain(
|
|
456
|
+
requiredRecords,
|
|
457
|
+
rootDomain,
|
|
458
|
+
providerConfig,
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
allRecords.push(...domainRecords);
|
|
368
462
|
|
|
369
|
-
const created =
|
|
370
|
-
const existed =
|
|
371
|
-
const failed =
|
|
463
|
+
const created = domainRecords.filter((r) => r.created).length;
|
|
464
|
+
const existed = domainRecords.filter((r) => r.existed).length;
|
|
465
|
+
const failed = domainRecords.filter((r) => r.error).length;
|
|
372
466
|
|
|
373
467
|
if (created > 0) {
|
|
374
|
-
logger.log(` ✓ Created ${created} DNS record(s)`);
|
|
468
|
+
logger.log(` ✓ Created ${created} DNS record(s) for ${rootDomain}`);
|
|
375
469
|
}
|
|
376
470
|
if (existed > 0) {
|
|
377
|
-
logger.log(` ✓ ${existed} record(s) already exist`);
|
|
471
|
+
logger.log(` ✓ ${existed} record(s) already exist for ${rootDomain}`);
|
|
378
472
|
}
|
|
379
473
|
if (failed > 0) {
|
|
380
|
-
logger.log(` ⚠ ${failed} record(s) failed`);
|
|
474
|
+
logger.log(` ⚠ ${failed} record(s) failed for ${rootDomain}`);
|
|
475
|
+
hasFailures = true;
|
|
381
476
|
}
|
|
382
|
-
} else {
|
|
383
|
-
finalRecords = requiredRecords;
|
|
384
|
-
}
|
|
385
477
|
|
|
386
|
-
|
|
387
|
-
|
|
478
|
+
// Print summary table for this domain
|
|
479
|
+
printDnsRecordsTable(domainRecords, rootDomain);
|
|
388
480
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
481
|
+
// If manual mode or some failed, print simple instructions
|
|
482
|
+
if (providerConfig.provider === 'manual' || failed > 0) {
|
|
483
|
+
printDnsRecordsSimple(
|
|
484
|
+
domainRecords.filter((r) => !r.created && !r.existed),
|
|
485
|
+
rootDomain,
|
|
486
|
+
);
|
|
487
|
+
}
|
|
396
488
|
}
|
|
397
489
|
|
|
398
490
|
return {
|
|
399
|
-
records:
|
|
491
|
+
records: allRecords,
|
|
400
492
|
success: !hasFailures,
|
|
401
493
|
serverIp,
|
|
402
494
|
};
|
|
@@ -10,10 +10,10 @@ import { randomBytes } from 'node:crypto';
|
|
|
10
10
|
import type { StageSecrets } from '../secrets/types';
|
|
11
11
|
import type { NormalizedAppConfig } from '../workspace/types';
|
|
12
12
|
import {
|
|
13
|
-
getGeneratedSecret,
|
|
14
|
-
setGeneratedSecret,
|
|
15
13
|
type AppDbCredentials,
|
|
16
14
|
type DokployStageState,
|
|
15
|
+
getGeneratedSecret,
|
|
16
|
+
setGeneratedSecret,
|
|
17
17
|
} from './state';
|
|
18
18
|
|
|
19
19
|
/**
|
|
@@ -82,7 +82,9 @@ export type AutoSupportedVar = (typeof AUTO_SUPPORTED_VARS)[number];
|
|
|
82
82
|
/**
|
|
83
83
|
* Check if a variable name is auto-supported
|
|
84
84
|
*/
|
|
85
|
-
export function isAutoSupportedVar(
|
|
85
|
+
export function isAutoSupportedVar(
|
|
86
|
+
varName: string,
|
|
87
|
+
): varName is AutoSupportedVar {
|
|
86
88
|
return AUTO_SUPPORTED_VARS.includes(varName as AutoSupportedVar);
|
|
87
89
|
}
|
|
88
90
|
|
|
@@ -212,11 +214,16 @@ export function resolveEnvVar(
|
|
|
212
214
|
|
|
213
215
|
// Check URLs (DATABASE_URL, REDIS_URL, RABBITMQ_URL)
|
|
214
216
|
if (varName in context.userSecrets.urls) {
|
|
215
|
-
return context.userSecrets.urls[
|
|
217
|
+
return context.userSecrets.urls[
|
|
218
|
+
varName as keyof typeof context.userSecrets.urls
|
|
219
|
+
];
|
|
216
220
|
}
|
|
217
221
|
|
|
218
222
|
// Check service-specific vars
|
|
219
|
-
if (
|
|
223
|
+
if (
|
|
224
|
+
varName === 'POSTGRES_PASSWORD' &&
|
|
225
|
+
context.userSecrets.services.postgres
|
|
226
|
+
) {
|
|
220
227
|
return context.userSecrets.services.postgres.password;
|
|
221
228
|
}
|
|
222
229
|
if (varName === 'REDIS_PASSWORD' && context.userSecrets.services.redis) {
|
package/src/deploy/index.ts
CHANGED
|
@@ -64,11 +64,7 @@ import {
|
|
|
64
64
|
isDeployTargetSupported,
|
|
65
65
|
} from '../workspace/index.js';
|
|
66
66
|
import type { NormalizedWorkspace } from '../workspace/types.js';
|
|
67
|
-
import {
|
|
68
|
-
orchestrateDns,
|
|
69
|
-
resolveHostnameToIp,
|
|
70
|
-
verifyDnsRecords,
|
|
71
|
-
} from './dns/index.js';
|
|
67
|
+
import { orchestrateDns, verifyDnsRecords } from './dns/index.js';
|
|
72
68
|
import { deployDocker, resolveDockerConfig } from './docker';
|
|
73
69
|
import { deployDokploy } from './dokploy';
|
|
74
70
|
import {
|
|
@@ -89,6 +85,7 @@ import {
|
|
|
89
85
|
validateEnvVars,
|
|
90
86
|
} from './env-resolver.js';
|
|
91
87
|
import { updateConfig } from './init';
|
|
88
|
+
import { createStateProvider } from './StateProvider.js';
|
|
92
89
|
import { generateSecretsReport, prepareSecretsForAllApps } from './secrets.js';
|
|
93
90
|
import { sniffAllApps } from './sniffer.js';
|
|
94
91
|
import {
|
|
@@ -98,12 +95,10 @@ import {
|
|
|
98
95
|
getApplicationId,
|
|
99
96
|
getPostgresId,
|
|
100
97
|
getRedisId,
|
|
101
|
-
readStageState,
|
|
102
98
|
setAppCredentials,
|
|
103
99
|
setApplicationId,
|
|
104
100
|
setPostgresId,
|
|
105
101
|
setRedisId,
|
|
106
|
-
writeStageState,
|
|
107
102
|
} from './state.js';
|
|
108
103
|
import type {
|
|
109
104
|
AppDeployResult,
|
|
@@ -404,7 +399,7 @@ function getServerHostname(endpoint: string): string {
|
|
|
404
399
|
* // Returns: postgresql://api:secret123@postgres-abc:5432/myproject
|
|
405
400
|
* ```
|
|
406
401
|
*/
|
|
407
|
-
function
|
|
402
|
+
function _buildPerAppDatabaseUrl(
|
|
408
403
|
appName: string,
|
|
409
404
|
appPassword: string,
|
|
410
405
|
postgresAppName: string,
|
|
@@ -1073,10 +1068,18 @@ export async function workspaceDeployCommand(
|
|
|
1073
1068
|
}
|
|
1074
1069
|
|
|
1075
1070
|
// ==================================================================
|
|
1076
|
-
// STATE:
|
|
1071
|
+
// STATE: Create state provider and load deploy state
|
|
1077
1072
|
// ==================================================================
|
|
1078
1073
|
logger.log('\n📋 Loading deploy state...');
|
|
1079
|
-
|
|
1074
|
+
|
|
1075
|
+
// Create state provider based on workspace config
|
|
1076
|
+
const stateProvider = await createStateProvider({
|
|
1077
|
+
config: workspace.state,
|
|
1078
|
+
workspaceRoot: workspace.root,
|
|
1079
|
+
workspaceName: workspace.name,
|
|
1080
|
+
});
|
|
1081
|
+
|
|
1082
|
+
let state = await stateProvider.read(stage);
|
|
1080
1083
|
|
|
1081
1084
|
if (state) {
|
|
1082
1085
|
logger.log(` Found existing state for stage "${stage}"`);
|
|
@@ -1684,8 +1687,8 @@ export async function workspaceDeployCommand(
|
|
|
1684
1687
|
// STATE: Save deploy state
|
|
1685
1688
|
// ==================================================================
|
|
1686
1689
|
logger.log('\n📋 Saving deploy state...');
|
|
1687
|
-
await
|
|
1688
|
-
logger.log(
|
|
1690
|
+
await stateProvider.write(stage, state);
|
|
1691
|
+
logger.log(' ✓ State saved');
|
|
1689
1692
|
|
|
1690
1693
|
// ==================================================================
|
|
1691
1694
|
// DNS: Create DNS records, verify propagation, and validate for SSL
|
|
@@ -1703,7 +1706,7 @@ export async function workspaceDeployCommand(
|
|
|
1703
1706
|
await verifyDnsRecords(appHostnames, dnsResult.serverIp, state);
|
|
1704
1707
|
|
|
1705
1708
|
// Save state again to persist DNS verification results
|
|
1706
|
-
await
|
|
1709
|
+
await stateProvider.write(stage, state);
|
|
1707
1710
|
}
|
|
1708
1711
|
|
|
1709
1712
|
// Validate domains to trigger SSL certificate generation
|
|
@@ -48,7 +48,9 @@ class PatchedEnvironmentParser {
|
|
|
48
48
|
create<TReturn extends Record<string, unknown>>(
|
|
49
49
|
builder: (get: EnvFetcher) => TReturn,
|
|
50
50
|
): ConfigParser<TReturn> {
|
|
51
|
-
return globalThis.__envSniffer!.create(
|
|
51
|
+
return globalThis.__envSniffer!.create(
|
|
52
|
+
builder as any,
|
|
53
|
+
) as unknown as ConfigParser<TReturn>;
|
|
52
54
|
}
|
|
53
55
|
}
|
|
54
56
|
|