@geekmidas/cli 0.45.0 → 0.47.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/dist/{config-C0b0jdmU.mjs → config-C3LSBNSl.mjs} +2 -2
- package/dist/{config-C0b0jdmU.mjs.map → config-C3LSBNSl.mjs.map} +1 -1
- package/dist/{config-xVZsRjN7.cjs → config-HYiM3iQJ.cjs} +2 -2
- package/dist/{config-xVZsRjN7.cjs.map → config-HYiM3iQJ.cjs.map} +1 -1
- package/dist/config.cjs +2 -2
- package/dist/config.d.cts +1 -1
- package/dist/config.d.mts +1 -1
- package/dist/config.mjs +2 -2
- package/dist/dokploy-api-4a6h35VY.cjs +3 -0
- package/dist/{dokploy-api-BdxOMH_V.cjs → dokploy-api-BnX2OxyF.cjs} +121 -1
- package/dist/dokploy-api-BnX2OxyF.cjs.map +1 -0
- package/dist/{dokploy-api-DWsqNjwP.mjs → dokploy-api-CMWlWq7-.mjs} +121 -1
- package/dist/dokploy-api-CMWlWq7-.mjs.map +1 -0
- package/dist/dokploy-api-DQvi9iZa.mjs +3 -0
- package/dist/{index-CXa3odEw.d.mts → index-A70abJ1m.d.mts} +598 -46
- package/dist/index-A70abJ1m.d.mts.map +1 -0
- package/dist/{index-E8Nu2Rxl.d.cts → index-pOA56MWT.d.cts} +598 -46
- package/dist/index-pOA56MWT.d.cts.map +1 -0
- package/dist/index.cjs +916 -357
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +916 -357
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-D3pA6FfZ.mjs → openapi-C3C-BzIZ.mjs} +2 -2
- package/dist/{openapi-D3pA6FfZ.mjs.map → openapi-C3C-BzIZ.mjs.map} +1 -1
- package/dist/{openapi-DhcCtKzM.cjs → openapi-D7WwlpPF.cjs} +2 -2
- package/dist/{openapi-DhcCtKzM.cjs.map → openapi-D7WwlpPF.cjs.map} +1 -1
- package/dist/openapi.cjs +3 -3
- package/dist/openapi.mjs +3 -3
- package/dist/workspace/index.cjs +1 -1
- package/dist/workspace/index.d.cts +1 -1
- package/dist/workspace/index.d.mts +1 -1
- package/dist/workspace/index.mjs +1 -1
- package/dist/{workspace-BDAhr6Kb.cjs → workspace-CaVW6j2q.cjs} +10 -1
- package/dist/{workspace-BDAhr6Kb.cjs.map → workspace-CaVW6j2q.cjs.map} +1 -1
- package/dist/{workspace-D_6ZCaR_.mjs → workspace-DLFRaDc-.mjs} +10 -1
- package/dist/{workspace-D_6ZCaR_.mjs.map → workspace-DLFRaDc-.mjs.map} +1 -1
- package/package.json +3 -3
- package/src/auth/credentials.ts +66 -0
- package/src/deploy/dns/hostinger-api.ts +258 -0
- package/src/deploy/dns/index.ts +399 -0
- package/src/deploy/dokploy-api.ts +175 -0
- package/src/deploy/index.ts +389 -240
- package/src/deploy/state.ts +146 -0
- package/src/workspace/types.ts +629 -47
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/dokploy-api-Bdmk5ImW.cjs +0 -3
- package/dist/dokploy-api-BdxOMH_V.cjs.map +0 -1
- package/dist/dokploy-api-DWsqNjwP.mjs.map +0 -1
- package/dist/dokploy-api-tZSZaHd9.mjs +0 -3
- package/dist/index-CXa3odEw.d.mts.map +0 -1
- package/dist/index-E8Nu2Rxl.d.cts.map +0 -1
package/src/deploy/index.ts
CHANGED
|
@@ -18,7 +18,24 @@ import {
|
|
|
18
18
|
import type { NormalizedWorkspace } from '../workspace/types.js';
|
|
19
19
|
import { deployDocker, resolveDockerConfig } from './docker';
|
|
20
20
|
import { deployDokploy } from './dokploy';
|
|
21
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
DokployApi,
|
|
23
|
+
type DokployApplication,
|
|
24
|
+
type DokployPostgres,
|
|
25
|
+
type DokployRedis,
|
|
26
|
+
} from './dokploy-api';
|
|
27
|
+
import {
|
|
28
|
+
createEmptyState,
|
|
29
|
+
getApplicationId,
|
|
30
|
+
getPostgresId,
|
|
31
|
+
getRedisId,
|
|
32
|
+
readStageState,
|
|
33
|
+
setApplicationId,
|
|
34
|
+
setPostgresId,
|
|
35
|
+
setRedisId,
|
|
36
|
+
writeStageState,
|
|
37
|
+
} from './state.js';
|
|
38
|
+
import { orchestrateDns } from './dns/index.js';
|
|
22
39
|
import {
|
|
23
40
|
generatePublicUrlBuildArgs,
|
|
24
41
|
getPublicUrlArgNames,
|
|
@@ -121,6 +138,17 @@ interface DokploySetupResult {
|
|
|
121
138
|
serviceUrls?: ServiceUrls;
|
|
122
139
|
}
|
|
123
140
|
|
|
141
|
+
/**
|
|
142
|
+
* Result from provisioning services
|
|
143
|
+
*/
|
|
144
|
+
export interface ProvisionServicesResult {
|
|
145
|
+
serviceUrls: ServiceUrls;
|
|
146
|
+
serviceIds: {
|
|
147
|
+
postgresId?: string;
|
|
148
|
+
redisId?: string;
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
124
152
|
/**
|
|
125
153
|
* Provision docker compose services in Dokploy
|
|
126
154
|
* @internal Exported for testing
|
|
@@ -131,8 +159,8 @@ export async function provisionServices(
|
|
|
131
159
|
environmentId: string | undefined,
|
|
132
160
|
appName: string,
|
|
133
161
|
services?: DockerComposeServices,
|
|
134
|
-
|
|
135
|
-
): Promise<
|
|
162
|
+
existingServiceIds?: { postgresId?: string; redisId?: string },
|
|
163
|
+
): Promise<ProvisionServicesResult | undefined> {
|
|
136
164
|
logger.log(
|
|
137
165
|
`\n🔍 provisionServices called: services=${JSON.stringify(services)}, envId=${environmentId}`,
|
|
138
166
|
);
|
|
@@ -142,113 +170,142 @@ export async function provisionServices(
|
|
|
142
170
|
}
|
|
143
171
|
|
|
144
172
|
const serviceUrls: ServiceUrls = {};
|
|
173
|
+
const serviceIds: { postgresId?: string; redisId?: string } = {};
|
|
145
174
|
|
|
146
175
|
if (services.postgres) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
logger.log('\n🐘 PostgreSQL: Already configured (skipping)');
|
|
150
|
-
} else {
|
|
151
|
-
logger.log('\n🐘 Provisioning PostgreSQL...');
|
|
152
|
-
const postgresName = `${appName}-db`;
|
|
176
|
+
logger.log('\n🐘 Checking PostgreSQL...');
|
|
177
|
+
const postgresName = 'db';
|
|
153
178
|
|
|
154
|
-
|
|
155
|
-
|
|
179
|
+
try {
|
|
180
|
+
let postgres: DokployPostgres | null = null;
|
|
181
|
+
let created = false;
|
|
182
|
+
|
|
183
|
+
// Check if we have an existing ID from state
|
|
184
|
+
if (existingServiceIds?.postgresId) {
|
|
185
|
+
logger.log(` Using cached ID: ${existingServiceIds.postgresId}`);
|
|
186
|
+
postgres = await api.getPostgres(existingServiceIds.postgresId);
|
|
187
|
+
if (postgres) {
|
|
188
|
+
logger.log(` ✓ PostgreSQL found: ${postgres.postgresId}`);
|
|
189
|
+
} else {
|
|
190
|
+
logger.log(` ⚠ Cached ID invalid, will create new`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// If not found by ID, use findOrCreate
|
|
195
|
+
if (!postgres) {
|
|
156
196
|
const { randomBytes } = await import('node:crypto');
|
|
157
197
|
const databasePassword = randomBytes(16).toString('hex');
|
|
158
198
|
|
|
159
|
-
const
|
|
199
|
+
const result = await api.findOrCreatePostgres(
|
|
160
200
|
postgresName,
|
|
161
201
|
projectId,
|
|
162
202
|
environmentId,
|
|
163
203
|
{ databasePassword },
|
|
164
204
|
);
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
serviceUrls.DATABASE_NAME = postgres.databaseName;
|
|
175
|
-
serviceUrls.DATABASE_USER = postgres.databaseUser;
|
|
176
|
-
serviceUrls.DATABASE_PASSWORD = postgres.databasePassword;
|
|
177
|
-
|
|
178
|
-
// Construct connection URL using internal docker network hostname
|
|
179
|
-
serviceUrls.DATABASE_URL = `postgresql://${postgres.databaseUser}:${postgres.databasePassword}@${postgres.appName}:5432/${postgres.databaseName}`;
|
|
180
|
-
logger.log(` ✓ Database credentials configured`);
|
|
181
|
-
} catch (error) {
|
|
182
|
-
const message =
|
|
183
|
-
error instanceof Error ? error.message : 'Unknown error';
|
|
184
|
-
if (
|
|
185
|
-
message.includes('already exists') ||
|
|
186
|
-
message.includes('duplicate')
|
|
187
|
-
) {
|
|
188
|
-
logger.log(` ℹ PostgreSQL already exists`);
|
|
205
|
+
postgres = result.postgres;
|
|
206
|
+
created = result.created;
|
|
207
|
+
|
|
208
|
+
if (created) {
|
|
209
|
+
logger.log(` ✓ Created PostgreSQL: ${postgres.postgresId}`);
|
|
210
|
+
|
|
211
|
+
// Deploy the database (only for new instances)
|
|
212
|
+
await api.deployPostgres(postgres.postgresId);
|
|
213
|
+
logger.log(' ✓ PostgreSQL deployed');
|
|
189
214
|
} else {
|
|
190
|
-
logger.log(`
|
|
215
|
+
logger.log(` ✓ PostgreSQL already exists: ${postgres.postgresId}`);
|
|
191
216
|
}
|
|
192
217
|
}
|
|
218
|
+
|
|
219
|
+
// Store the ID for state
|
|
220
|
+
serviceIds.postgresId = postgres.postgresId;
|
|
221
|
+
|
|
222
|
+
// Store individual connection parameters
|
|
223
|
+
serviceUrls.DATABASE_HOST = postgres.appName;
|
|
224
|
+
serviceUrls.DATABASE_PORT = '5432';
|
|
225
|
+
serviceUrls.DATABASE_NAME = postgres.databaseName;
|
|
226
|
+
serviceUrls.DATABASE_USER = postgres.databaseUser;
|
|
227
|
+
serviceUrls.DATABASE_PASSWORD = postgres.databasePassword;
|
|
228
|
+
|
|
229
|
+
// Construct connection URL using internal docker network hostname
|
|
230
|
+
serviceUrls.DATABASE_URL = `postgresql://${postgres.databaseUser}:${postgres.databasePassword}@${postgres.appName}:5432/${postgres.databaseName}`;
|
|
231
|
+
logger.log(` ✓ Database credentials configured`);
|
|
232
|
+
} catch (error) {
|
|
233
|
+
const message =
|
|
234
|
+
error instanceof Error ? error.message : 'Unknown error';
|
|
235
|
+
logger.log(` ⚠ Failed to provision PostgreSQL: ${message}`);
|
|
193
236
|
}
|
|
194
237
|
}
|
|
195
238
|
|
|
196
239
|
if (services.redis) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
logger.log('\n🔴 Redis: Already configured (skipping)');
|
|
200
|
-
} else {
|
|
201
|
-
logger.log('\n🔴 Provisioning Redis...');
|
|
202
|
-
const redisName = `${appName}-cache`;
|
|
240
|
+
logger.log('\n🔴 Checking Redis...');
|
|
241
|
+
const redisName = 'cache';
|
|
203
242
|
|
|
204
|
-
|
|
205
|
-
|
|
243
|
+
try {
|
|
244
|
+
let redis: DokployRedis | null = null;
|
|
245
|
+
let created = false;
|
|
246
|
+
|
|
247
|
+
// Check if we have an existing ID from state
|
|
248
|
+
if (existingServiceIds?.redisId) {
|
|
249
|
+
logger.log(` Using cached ID: ${existingServiceIds.redisId}`);
|
|
250
|
+
redis = await api.getRedis(existingServiceIds.redisId);
|
|
251
|
+
if (redis) {
|
|
252
|
+
logger.log(` ✓ Redis found: ${redis.redisId}`);
|
|
253
|
+
} else {
|
|
254
|
+
logger.log(` ⚠ Cached ID invalid, will create new`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// If not found by ID, use findOrCreate
|
|
259
|
+
if (!redis) {
|
|
206
260
|
const { randomBytes } = await import('node:crypto');
|
|
207
261
|
const databasePassword = randomBytes(16).toString('hex');
|
|
208
262
|
|
|
209
|
-
const
|
|
263
|
+
const result = await api.findOrCreateRedis(
|
|
210
264
|
redisName,
|
|
211
265
|
projectId,
|
|
212
266
|
environmentId,
|
|
213
|
-
{
|
|
214
|
-
databasePassword,
|
|
215
|
-
},
|
|
267
|
+
{ databasePassword },
|
|
216
268
|
);
|
|
217
|
-
|
|
269
|
+
redis = result.redis;
|
|
270
|
+
created = result.created;
|
|
218
271
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
logger.log(' ✓ Redis deployed');
|
|
222
|
-
|
|
223
|
-
// Store individual connection parameters
|
|
224
|
-
serviceUrls.REDIS_HOST = redis.appName;
|
|
225
|
-
serviceUrls.REDIS_PORT = '6379';
|
|
226
|
-
if (redis.databasePassword) {
|
|
227
|
-
serviceUrls.REDIS_PASSWORD = redis.databasePassword;
|
|
228
|
-
}
|
|
272
|
+
if (created) {
|
|
273
|
+
logger.log(` ✓ Created Redis: ${redis.redisId}`);
|
|
229
274
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
: '';
|
|
234
|
-
serviceUrls.REDIS_URL = `redis://${password}${redis.appName}:6379`;
|
|
235
|
-
logger.log(` ✓ Redis credentials configured`);
|
|
236
|
-
} catch (error) {
|
|
237
|
-
const message =
|
|
238
|
-
error instanceof Error ? error.message : 'Unknown error';
|
|
239
|
-
if (
|
|
240
|
-
message.includes('already exists') ||
|
|
241
|
-
message.includes('duplicate')
|
|
242
|
-
) {
|
|
243
|
-
logger.log(` ℹ Redis already exists`);
|
|
275
|
+
// Deploy the redis instance (only for new instances)
|
|
276
|
+
await api.deployRedis(redis.redisId);
|
|
277
|
+
logger.log(' ✓ Redis deployed');
|
|
244
278
|
} else {
|
|
245
|
-
logger.log(`
|
|
279
|
+
logger.log(` ✓ Redis already exists: ${redis.redisId}`);
|
|
246
280
|
}
|
|
247
281
|
}
|
|
282
|
+
|
|
283
|
+
// Store the ID for state
|
|
284
|
+
serviceIds.redisId = redis.redisId;
|
|
285
|
+
|
|
286
|
+
// Store individual connection parameters
|
|
287
|
+
serviceUrls.REDIS_HOST = redis.appName;
|
|
288
|
+
serviceUrls.REDIS_PORT = '6379';
|
|
289
|
+
if (redis.databasePassword) {
|
|
290
|
+
serviceUrls.REDIS_PASSWORD = redis.databasePassword;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Construct connection URL
|
|
294
|
+
const password = redis.databasePassword
|
|
295
|
+
? `:${redis.databasePassword}@`
|
|
296
|
+
: '';
|
|
297
|
+
serviceUrls.REDIS_URL = `redis://${password}${redis.appName}:6379`;
|
|
298
|
+
logger.log(` ✓ Redis credentials configured`);
|
|
299
|
+
} catch (error) {
|
|
300
|
+
const message =
|
|
301
|
+
error instanceof Error ? error.message : 'Unknown error';
|
|
302
|
+
logger.log(` ⚠ Failed to provision Redis: ${message}`);
|
|
248
303
|
}
|
|
249
304
|
}
|
|
250
305
|
|
|
251
|
-
return Object.keys(serviceUrls).length > 0
|
|
306
|
+
return Object.keys(serviceUrls).length > 0
|
|
307
|
+
? { serviceUrls, serviceIds }
|
|
308
|
+
: undefined;
|
|
252
309
|
}
|
|
253
310
|
|
|
254
311
|
/**
|
|
@@ -345,13 +402,14 @@ async function ensureDokploySetup(
|
|
|
345
402
|
logger.log(
|
|
346
403
|
` Services config: ${JSON.stringify(services)}, envId: ${environmentId}`,
|
|
347
404
|
);
|
|
348
|
-
|
|
405
|
+
// For single-app mode, we don't have state persistence yet, so pass undefined
|
|
406
|
+
const provisionResult = await provisionServices(
|
|
349
407
|
api,
|
|
350
408
|
existingConfig.projectId,
|
|
351
409
|
environmentId,
|
|
352
410
|
dockerConfig.appName!,
|
|
353
411
|
services,
|
|
354
|
-
|
|
412
|
+
undefined, // No state in single-app mode
|
|
355
413
|
);
|
|
356
414
|
|
|
357
415
|
return {
|
|
@@ -362,7 +420,7 @@ async function ensureDokploySetup(
|
|
|
362
420
|
registry: existingConfig.registry,
|
|
363
421
|
registryId: storedRegistryId ?? undefined,
|
|
364
422
|
},
|
|
365
|
-
serviceUrls,
|
|
423
|
+
serviceUrls: provisionResult?.serviceUrls,
|
|
366
424
|
};
|
|
367
425
|
} catch {
|
|
368
426
|
logger.log('⚠ Project not found, will recover...');
|
|
@@ -546,18 +604,19 @@ async function ensureDokploySetup(
|
|
|
546
604
|
}
|
|
547
605
|
|
|
548
606
|
// Step 8: Provision docker compose services if configured
|
|
549
|
-
|
|
607
|
+
// For single-app mode, we don't have state persistence yet, so pass undefined
|
|
608
|
+
const provisionResult = await provisionServices(
|
|
550
609
|
api,
|
|
551
610
|
project.projectId,
|
|
552
611
|
environmentId,
|
|
553
612
|
dockerConfig.appName!,
|
|
554
613
|
services,
|
|
555
|
-
|
|
614
|
+
undefined, // No state in single-app mode
|
|
556
615
|
);
|
|
557
616
|
|
|
558
617
|
return {
|
|
559
618
|
config: dokployConfig,
|
|
560
|
-
serviceUrls,
|
|
619
|
+
serviceUrls: provisionResult?.serviceUrls,
|
|
561
620
|
};
|
|
562
621
|
}
|
|
563
622
|
|
|
@@ -754,6 +813,24 @@ export async function workspaceDeployCommand(
|
|
|
754
813
|
logger.log(` ✓ Created project: ${project.projectId}`);
|
|
755
814
|
}
|
|
756
815
|
|
|
816
|
+
// ==================================================================
|
|
817
|
+
// STATE: Load or create deploy state for this stage
|
|
818
|
+
// ==================================================================
|
|
819
|
+
logger.log('\n📋 Loading deploy state...');
|
|
820
|
+
let state = await readStageState(workspace.root, stage);
|
|
821
|
+
|
|
822
|
+
if (state) {
|
|
823
|
+
logger.log(` Found existing state for stage "${stage}"`);
|
|
824
|
+
// Verify environment ID matches (in case of recreation)
|
|
825
|
+
if (state.environmentId !== environmentId) {
|
|
826
|
+
logger.log(` ⚠ Environment ID changed, updating state`);
|
|
827
|
+
state.environmentId = environmentId;
|
|
828
|
+
}
|
|
829
|
+
} else {
|
|
830
|
+
logger.log(` Creating new state for stage "${stage}"`);
|
|
831
|
+
state = createEmptyState(stage, environmentId);
|
|
832
|
+
}
|
|
833
|
+
|
|
757
834
|
// Get or set up registry
|
|
758
835
|
logger.log('\n🐳 Checking registry...');
|
|
759
836
|
let registryId = await getDokployRegistryId();
|
|
@@ -808,21 +885,30 @@ export async function workspaceDeployCommand(
|
|
|
808
885
|
|
|
809
886
|
if (dockerServices.postgres || dockerServices.redis) {
|
|
810
887
|
logger.log('\n🔧 Provisioning infrastructure services...');
|
|
811
|
-
// Pass existing
|
|
812
|
-
const
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
await provisionServices(
|
|
888
|
+
// Pass existing service IDs from state (prefer state over URL sniffing)
|
|
889
|
+
const existingServiceIds = {
|
|
890
|
+
postgresId: getPostgresId(state),
|
|
891
|
+
redisId: getRedisId(state),
|
|
892
|
+
};
|
|
893
|
+
|
|
894
|
+
const provisionResult = await provisionServices(
|
|
819
895
|
api,
|
|
820
896
|
project.projectId,
|
|
821
897
|
environmentId,
|
|
822
898
|
workspace.name,
|
|
823
899
|
dockerServices,
|
|
824
|
-
|
|
900
|
+
existingServiceIds,
|
|
825
901
|
);
|
|
902
|
+
|
|
903
|
+
// Update state with returned service IDs
|
|
904
|
+
if (provisionResult?.serviceIds) {
|
|
905
|
+
if (provisionResult.serviceIds.postgresId) {
|
|
906
|
+
setPostgresId(state, provisionResult.serviceIds.postgresId);
|
|
907
|
+
}
|
|
908
|
+
if (provisionResult.serviceIds.redisId) {
|
|
909
|
+
setRedisId(state, provisionResult.serviceIds.redisId);
|
|
910
|
+
}
|
|
911
|
+
}
|
|
826
912
|
}
|
|
827
913
|
|
|
828
914
|
// ==================================================================
|
|
@@ -840,6 +926,10 @@ export async function workspaceDeployCommand(
|
|
|
840
926
|
const results: AppDeployResult[] = [];
|
|
841
927
|
const dokployConfig = workspace.deploy.dokploy;
|
|
842
928
|
|
|
929
|
+
// Track domain IDs and hostnames for DNS orchestration
|
|
930
|
+
const appHostnames = new Map<string, string>(); // appName -> hostname
|
|
931
|
+
const appDomainIds = new Map<string, string>(); // appName -> domainId
|
|
932
|
+
|
|
843
933
|
// ==================================================================
|
|
844
934
|
// PHASE 1: Deploy backend apps (with encrypted secrets)
|
|
845
935
|
// ==================================================================
|
|
@@ -852,30 +942,42 @@ export async function workspaceDeployCommand(
|
|
|
852
942
|
logger.log(`\n ⚙️ Deploying ${appName}...`);
|
|
853
943
|
|
|
854
944
|
try {
|
|
855
|
-
|
|
856
|
-
|
|
945
|
+
// Use simple app name - project already provides namespace
|
|
946
|
+
const dokployAppName = appName;
|
|
947
|
+
|
|
948
|
+
// Check state for cached application ID
|
|
949
|
+
let application: DokployApplication | null = null;
|
|
950
|
+
const cachedAppId = getApplicationId(state, appName);
|
|
951
|
+
|
|
952
|
+
if (cachedAppId) {
|
|
953
|
+
logger.log(` Using cached ID: ${cachedAppId}`);
|
|
954
|
+
application = await api.getApplication(cachedAppId);
|
|
955
|
+
if (application) {
|
|
956
|
+
logger.log(` ✓ Application found: ${application.applicationId}`);
|
|
957
|
+
} else {
|
|
958
|
+
logger.log(` ⚠ Cached ID invalid, will create new`);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
857
961
|
|
|
858
|
-
//
|
|
859
|
-
|
|
860
|
-
|
|
962
|
+
// If not found by ID, use findOrCreate
|
|
963
|
+
if (!application) {
|
|
964
|
+
const result = await api.findOrCreateApplication(
|
|
861
965
|
dokployAppName,
|
|
862
966
|
project.projectId,
|
|
863
967
|
environmentId,
|
|
864
968
|
);
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
if (
|
|
870
|
-
message.includes('already exists') ||
|
|
871
|
-
message.includes('duplicate')
|
|
872
|
-
) {
|
|
873
|
-
logger.log(` Application already exists`);
|
|
969
|
+
application = result.application;
|
|
970
|
+
|
|
971
|
+
if (result.created) {
|
|
972
|
+
logger.log(` Created application: ${application.applicationId}`);
|
|
874
973
|
} else {
|
|
875
|
-
|
|
974
|
+
logger.log(` Found existing application: ${application.applicationId}`);
|
|
876
975
|
}
|
|
877
976
|
}
|
|
878
977
|
|
|
978
|
+
// Store application ID in state
|
|
979
|
+
setApplicationId(state, appName, application.applicationId);
|
|
980
|
+
|
|
879
981
|
// Get encrypted secrets for this app
|
|
880
982
|
const appSecrets = encryptedSecrets.get(appName);
|
|
881
983
|
const buildArgs: string[] = [];
|
|
@@ -917,70 +1019,75 @@ export async function workspaceDeployCommand(
|
|
|
917
1019
|
}
|
|
918
1020
|
|
|
919
1021
|
// Configure and deploy application in Dokploy
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
});
|
|
924
|
-
|
|
925
|
-
await api.saveApplicationEnv(
|
|
926
|
-
application.applicationId,
|
|
927
|
-
envVars.join('\n'),
|
|
928
|
-
);
|
|
1022
|
+
await api.saveDockerProvider(application.applicationId, imageRef, {
|
|
1023
|
+
registryId,
|
|
1024
|
+
});
|
|
929
1025
|
|
|
930
|
-
|
|
931
|
-
|
|
1026
|
+
await api.saveApplicationEnv(
|
|
1027
|
+
application.applicationId,
|
|
1028
|
+
envVars.join('\n'),
|
|
1029
|
+
);
|
|
932
1030
|
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
const host = resolveHost(
|
|
936
|
-
appName,
|
|
937
|
-
app,
|
|
938
|
-
stage,
|
|
939
|
-
dokployConfig,
|
|
940
|
-
false, // Backend apps are not main frontend
|
|
941
|
-
);
|
|
1031
|
+
logger.log(` Deploying to Dokploy...`);
|
|
1032
|
+
await api.deployApplication(application.applicationId);
|
|
942
1033
|
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
const publicUrl = `https://${host}`;
|
|
952
|
-
publicUrls[appName] = publicUrl;
|
|
953
|
-
logger.log(` ✓ Domain: ${publicUrl}`);
|
|
954
|
-
} catch (domainError) {
|
|
955
|
-
// Domain might already exist, try to get public URL anyway
|
|
956
|
-
const host = resolveHost(appName, app, stage, dokployConfig, false);
|
|
957
|
-
publicUrls[appName] = `https://${host}`;
|
|
958
|
-
logger.log(` ℹ Domain already configured: https://${host}`);
|
|
959
|
-
}
|
|
1034
|
+
// Create domain for this app
|
|
1035
|
+
const backendHost = resolveHost(
|
|
1036
|
+
appName,
|
|
1037
|
+
app,
|
|
1038
|
+
stage,
|
|
1039
|
+
dokployConfig,
|
|
1040
|
+
false, // Backend apps are not main frontend
|
|
1041
|
+
);
|
|
960
1042
|
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
1043
|
+
try {
|
|
1044
|
+
const domain = await api.createDomain({
|
|
1045
|
+
host: backendHost,
|
|
1046
|
+
port: app.port,
|
|
1047
|
+
https: true,
|
|
1048
|
+
certificateType: 'letsencrypt',
|
|
965
1049
|
applicationId: application.applicationId,
|
|
966
|
-
imageRef,
|
|
967
1050
|
});
|
|
968
1051
|
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
const host = resolveHost(appName, app, stage, dokployConfig, false);
|
|
973
|
-
publicUrls[appName] = `https://${host}`;
|
|
1052
|
+
// Track for DNS orchestration
|
|
1053
|
+
appHostnames.set(appName, backendHost);
|
|
1054
|
+
appDomainIds.set(appName, domain.domainId);
|
|
974
1055
|
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
1056
|
+
const publicUrl = `https://${backendHost}`;
|
|
1057
|
+
publicUrls[appName] = publicUrl;
|
|
1058
|
+
logger.log(` ✓ Domain: ${publicUrl}`);
|
|
1059
|
+
} catch (domainError) {
|
|
1060
|
+
// Domain might already exist, try to get the existing domain
|
|
1061
|
+
appHostnames.set(appName, backendHost);
|
|
981
1062
|
|
|
982
|
-
|
|
1063
|
+
// Try to get existing domain ID for validation
|
|
1064
|
+
try {
|
|
1065
|
+
const existingDomains = await api.getDomainsByApplicationId(
|
|
1066
|
+
application.applicationId,
|
|
1067
|
+
);
|
|
1068
|
+
const matchingDomain = existingDomains.find(
|
|
1069
|
+
(d) => d.host === backendHost,
|
|
1070
|
+
);
|
|
1071
|
+
if (matchingDomain) {
|
|
1072
|
+
appDomainIds.set(appName, matchingDomain.domainId);
|
|
1073
|
+
}
|
|
1074
|
+
} catch {
|
|
1075
|
+
// Ignore - we'll just skip validation for this domain
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
publicUrls[appName] = `https://${backendHost}`;
|
|
1079
|
+
logger.log(` ℹ Domain already configured: https://${backendHost}`);
|
|
983
1080
|
}
|
|
1081
|
+
|
|
1082
|
+
results.push({
|
|
1083
|
+
appName,
|
|
1084
|
+
type: app.type,
|
|
1085
|
+
success: true,
|
|
1086
|
+
applicationId: application.applicationId,
|
|
1087
|
+
imageRef,
|
|
1088
|
+
});
|
|
1089
|
+
|
|
1090
|
+
logger.log(` ✓ ${appName} deployed successfully`);
|
|
984
1091
|
} catch (error) {
|
|
985
1092
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
986
1093
|
logger.log(` ✗ Failed to deploy ${appName}: ${message}`);
|
|
@@ -1012,30 +1119,42 @@ export async function workspaceDeployCommand(
|
|
|
1012
1119
|
logger.log(`\n 🌐 Deploying ${appName}...`);
|
|
1013
1120
|
|
|
1014
1121
|
try {
|
|
1015
|
-
|
|
1016
|
-
|
|
1122
|
+
// Use simple app name - project already provides namespace
|
|
1123
|
+
const dokployAppName = appName;
|
|
1124
|
+
|
|
1125
|
+
// Check state for cached application ID
|
|
1126
|
+
let application: DokployApplication | null = null;
|
|
1127
|
+
const cachedAppId = getApplicationId(state, appName);
|
|
1128
|
+
|
|
1129
|
+
if (cachedAppId) {
|
|
1130
|
+
logger.log(` Using cached ID: ${cachedAppId}`);
|
|
1131
|
+
application = await api.getApplication(cachedAppId);
|
|
1132
|
+
if (application) {
|
|
1133
|
+
logger.log(` ✓ Application found: ${application.applicationId}`);
|
|
1134
|
+
} else {
|
|
1135
|
+
logger.log(` ⚠ Cached ID invalid, will create new`);
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1017
1138
|
|
|
1018
|
-
//
|
|
1019
|
-
|
|
1020
|
-
|
|
1139
|
+
// If not found by ID, use findOrCreate
|
|
1140
|
+
if (!application) {
|
|
1141
|
+
const result = await api.findOrCreateApplication(
|
|
1021
1142
|
dokployAppName,
|
|
1022
1143
|
project.projectId,
|
|
1023
1144
|
environmentId,
|
|
1024
1145
|
);
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
if (
|
|
1030
|
-
message.includes('already exists') ||
|
|
1031
|
-
message.includes('duplicate')
|
|
1032
|
-
) {
|
|
1033
|
-
logger.log(` Application already exists`);
|
|
1146
|
+
application = result.application;
|
|
1147
|
+
|
|
1148
|
+
if (result.created) {
|
|
1149
|
+
logger.log(` Created application: ${application.applicationId}`);
|
|
1034
1150
|
} else {
|
|
1035
|
-
|
|
1151
|
+
logger.log(` Found existing application: ${application.applicationId}`);
|
|
1036
1152
|
}
|
|
1037
1153
|
}
|
|
1038
1154
|
|
|
1155
|
+
// Store application ID in state
|
|
1156
|
+
setApplicationId(state, appName, application.applicationId);
|
|
1157
|
+
|
|
1039
1158
|
// Generate public URL build args from dependencies
|
|
1040
1159
|
const buildArgs = generatePublicUrlBuildArgs(app, publicUrls);
|
|
1041
1160
|
if (buildArgs.length > 0) {
|
|
@@ -1068,82 +1187,76 @@ export async function workspaceDeployCommand(
|
|
|
1068
1187
|
const envVars: string[] = [`NODE_ENV=production`, `PORT=${app.port}`];
|
|
1069
1188
|
|
|
1070
1189
|
// Configure and deploy application in Dokploy
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1190
|
+
await api.saveDockerProvider(application.applicationId, imageRef, {
|
|
1191
|
+
registryId,
|
|
1192
|
+
});
|
|
1193
|
+
|
|
1194
|
+
await api.saveApplicationEnv(
|
|
1195
|
+
application.applicationId,
|
|
1196
|
+
envVars.join('\n'),
|
|
1197
|
+
);
|
|
1198
|
+
|
|
1199
|
+
logger.log(` Deploying to Dokploy...`);
|
|
1200
|
+
await api.deployApplication(application.applicationId);
|
|
1201
|
+
|
|
1202
|
+
// Create domain for this app
|
|
1203
|
+
const isMainFrontend = isMainFrontendApp(appName, app, workspace.apps);
|
|
1204
|
+
const frontendHost = resolveHost(
|
|
1205
|
+
appName,
|
|
1206
|
+
app,
|
|
1207
|
+
stage,
|
|
1208
|
+
dokployConfig,
|
|
1209
|
+
isMainFrontend,
|
|
1210
|
+
);
|
|
1211
|
+
|
|
1212
|
+
try {
|
|
1213
|
+
const domain = await api.createDomain({
|
|
1214
|
+
host: frontendHost,
|
|
1215
|
+
port: app.port,
|
|
1216
|
+
https: true,
|
|
1217
|
+
certificateType: 'letsencrypt',
|
|
1218
|
+
applicationId: application.applicationId,
|
|
1074
1219
|
});
|
|
1075
1220
|
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
);
|
|
1221
|
+
// Track for DNS orchestration
|
|
1222
|
+
appHostnames.set(appName, frontendHost);
|
|
1223
|
+
appDomainIds.set(appName, domain.domainId);
|
|
1080
1224
|
|
|
1081
|
-
|
|
1082
|
-
|
|
1225
|
+
const publicUrl = `https://${frontendHost}`;
|
|
1226
|
+
publicUrls[appName] = publicUrl;
|
|
1227
|
+
logger.log(` ✓ Domain: ${publicUrl}`);
|
|
1228
|
+
} catch (domainError) {
|
|
1229
|
+
// Domain might already exist, try to get the existing domain
|
|
1230
|
+
appHostnames.set(appName, frontendHost);
|
|
1083
1231
|
|
|
1084
|
-
//
|
|
1085
|
-
const isMainFrontend = isMainFrontendApp(appName, app, workspace.apps);
|
|
1232
|
+
// Try to get existing domain ID for validation
|
|
1086
1233
|
try {
|
|
1087
|
-
const
|
|
1088
|
-
|
|
1089
|
-
app,
|
|
1090
|
-
stage,
|
|
1091
|
-
dokployConfig,
|
|
1092
|
-
isMainFrontend,
|
|
1234
|
+
const existingDomains = await api.getDomainsByApplicationId(
|
|
1235
|
+
application.applicationId,
|
|
1093
1236
|
);
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
host,
|
|
1097
|
-
port: app.port,
|
|
1098
|
-
https: true,
|
|
1099
|
-
certificateType: 'letsencrypt',
|
|
1100
|
-
applicationId: application.applicationId,
|
|
1101
|
-
});
|
|
1102
|
-
|
|
1103
|
-
const publicUrl = `https://${host}`;
|
|
1104
|
-
publicUrls[appName] = publicUrl;
|
|
1105
|
-
logger.log(` ✓ Domain: ${publicUrl}`);
|
|
1106
|
-
} catch (domainError) {
|
|
1107
|
-
const host = resolveHost(
|
|
1108
|
-
appName,
|
|
1109
|
-
app,
|
|
1110
|
-
stage,
|
|
1111
|
-
dokployConfig,
|
|
1112
|
-
isMainFrontend,
|
|
1237
|
+
const matchingDomain = existingDomains.find(
|
|
1238
|
+
(d) => d.host === frontendHost,
|
|
1113
1239
|
);
|
|
1114
|
-
|
|
1115
|
-
|
|
1240
|
+
if (matchingDomain) {
|
|
1241
|
+
appDomainIds.set(appName, matchingDomain.domainId);
|
|
1242
|
+
}
|
|
1243
|
+
} catch {
|
|
1244
|
+
// Ignore - we'll just skip validation for this domain
|
|
1116
1245
|
}
|
|
1117
1246
|
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
success: true,
|
|
1122
|
-
applicationId: application.applicationId,
|
|
1123
|
-
imageRef,
|
|
1124
|
-
});
|
|
1125
|
-
|
|
1126
|
-
logger.log(` ✓ ${appName} deployed successfully`);
|
|
1127
|
-
} else {
|
|
1128
|
-
const isMainFrontend = isMainFrontendApp(appName, app, workspace.apps);
|
|
1129
|
-
const host = resolveHost(
|
|
1130
|
-
appName,
|
|
1131
|
-
app,
|
|
1132
|
-
stage,
|
|
1133
|
-
dokployConfig,
|
|
1134
|
-
isMainFrontend,
|
|
1135
|
-
);
|
|
1136
|
-
publicUrls[appName] = `https://${host}`;
|
|
1247
|
+
publicUrls[appName] = `https://${frontendHost}`;
|
|
1248
|
+
logger.log(` ℹ Domain already configured: https://${frontendHost}`);
|
|
1249
|
+
}
|
|
1137
1250
|
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1251
|
+
results.push({
|
|
1252
|
+
appName,
|
|
1253
|
+
type: app.type,
|
|
1254
|
+
success: true,
|
|
1255
|
+
applicationId: application.applicationId,
|
|
1256
|
+
imageRef,
|
|
1257
|
+
});
|
|
1144
1258
|
|
|
1145
|
-
|
|
1146
|
-
}
|
|
1259
|
+
logger.log(` ✓ ${appName} deployed successfully`);
|
|
1147
1260
|
} catch (error) {
|
|
1148
1261
|
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
1149
1262
|
logger.log(` ✗ Failed to deploy ${appName}: ${message}`);
|
|
@@ -1159,6 +1272,42 @@ export async function workspaceDeployCommand(
|
|
|
1159
1272
|
}
|
|
1160
1273
|
}
|
|
1161
1274
|
|
|
1275
|
+
// ==================================================================
|
|
1276
|
+
// STATE: Save deploy state
|
|
1277
|
+
// ==================================================================
|
|
1278
|
+
logger.log('\n📋 Saving deploy state...');
|
|
1279
|
+
await writeStageState(workspace.root, stage, state);
|
|
1280
|
+
logger.log(` ✓ State saved to .gkm/deploy-${stage}.json`);
|
|
1281
|
+
|
|
1282
|
+
// ==================================================================
|
|
1283
|
+
// DNS: Create DNS records and validate domains for SSL
|
|
1284
|
+
// ==================================================================
|
|
1285
|
+
const dnsConfig = workspace.deploy.dns;
|
|
1286
|
+
if (dnsConfig && appHostnames.size > 0) {
|
|
1287
|
+
const dnsResult = await orchestrateDns(
|
|
1288
|
+
appHostnames,
|
|
1289
|
+
dnsConfig,
|
|
1290
|
+
creds.endpoint,
|
|
1291
|
+
);
|
|
1292
|
+
|
|
1293
|
+
// Validate domains to trigger SSL certificate generation
|
|
1294
|
+
if (dnsResult?.success && appDomainIds.size > 0) {
|
|
1295
|
+
logger.log('\n🔒 Validating domains for SSL certificates...');
|
|
1296
|
+
for (const [appName, domainId] of appDomainIds) {
|
|
1297
|
+
try {
|
|
1298
|
+
await api.validateDomain(domainId);
|
|
1299
|
+
logger.log(` ✓ ${appName}: SSL validation triggered`);
|
|
1300
|
+
} catch (validationError) {
|
|
1301
|
+
const message =
|
|
1302
|
+
validationError instanceof Error
|
|
1303
|
+
? validationError.message
|
|
1304
|
+
: 'Unknown error';
|
|
1305
|
+
logger.log(` ⚠ ${appName}: SSL validation failed - ${message}`);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
}
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1162
1311
|
// ==================================================================
|
|
1163
1312
|
// Summary
|
|
1164
1313
|
// ==================================================================
|