@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/dist/index.cjs
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env -S npx tsx
|
|
2
2
|
const require_chunk = require('./chunk-CUT6urMc.cjs');
|
|
3
|
-
const require_workspace = require('./workspace-
|
|
4
|
-
const require_config = require('./config-
|
|
5
|
-
const require_openapi = require('./openapi-
|
|
3
|
+
const require_workspace = require('./workspace-CaVW6j2q.cjs');
|
|
4
|
+
const require_config = require('./config-HYiM3iQJ.cjs');
|
|
5
|
+
const require_openapi = require('./openapi-D7WwlpPF.cjs');
|
|
6
6
|
const require_storage = require('./storage-BPRgh3DU.cjs');
|
|
7
|
-
const require_dokploy_api = require('./dokploy-api-
|
|
7
|
+
const require_dokploy_api = require('./dokploy-api-BnX2OxyF.cjs');
|
|
8
8
|
const require_encryption = require('./encryption-DaCB_NmS.cjs');
|
|
9
9
|
const require_openapi_react_query = require('./openapi-react-query-C_MxpBgF.cjs');
|
|
10
10
|
const node_fs = require_chunk.__toESM(require("node:fs"));
|
|
@@ -23,13 +23,14 @@ const __geekmidas_constructs_crons = require_chunk.__toESM(require("@geekmidas/c
|
|
|
23
23
|
const __geekmidas_constructs_functions = require_chunk.__toESM(require("@geekmidas/constructs/functions"));
|
|
24
24
|
const __geekmidas_constructs_subscribers = require_chunk.__toESM(require("@geekmidas/constructs/subscribers"));
|
|
25
25
|
const node_crypto = require_chunk.__toESM(require("node:crypto"));
|
|
26
|
+
const node_dns_promises = require_chunk.__toESM(require("node:dns/promises"));
|
|
26
27
|
const node_url = require_chunk.__toESM(require("node:url"));
|
|
27
28
|
const prompts = require_chunk.__toESM(require("prompts"));
|
|
28
29
|
const node_module = require_chunk.__toESM(require("node:module"));
|
|
29
30
|
|
|
30
31
|
//#region package.json
|
|
31
32
|
var name = "@geekmidas/cli";
|
|
32
|
-
var version = "0.
|
|
33
|
+
var version = "0.47.0";
|
|
33
34
|
var description = "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs";
|
|
34
35
|
var private$1 = false;
|
|
35
36
|
var type = "module";
|
|
@@ -220,15 +221,40 @@ async function getDokployRegistryId(options) {
|
|
|
220
221
|
const stored = await getDokployCredentials(options);
|
|
221
222
|
return stored?.registryId ?? void 0;
|
|
222
223
|
}
|
|
224
|
+
/**
|
|
225
|
+
* Store Hostinger API token
|
|
226
|
+
*
|
|
227
|
+
* @param token - API token from hpanel.hostinger.com/profile/api
|
|
228
|
+
*/
|
|
229
|
+
async function storeHostingerToken(token, options) {
|
|
230
|
+
const credentials = await readCredentials(options);
|
|
231
|
+
credentials.hostinger = {
|
|
232
|
+
token,
|
|
233
|
+
storedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
234
|
+
};
|
|
235
|
+
await writeCredentials(credentials, options);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Get stored Hostinger API token
|
|
239
|
+
*
|
|
240
|
+
* Checks environment variable first (HOSTINGER_API_TOKEN),
|
|
241
|
+
* then falls back to stored credentials.
|
|
242
|
+
*/
|
|
243
|
+
async function getHostingerToken(options) {
|
|
244
|
+
const envToken = process.env.HOSTINGER_API_TOKEN;
|
|
245
|
+
if (envToken) return envToken;
|
|
246
|
+
const credentials = await readCredentials(options);
|
|
247
|
+
return credentials.hostinger?.token ?? null;
|
|
248
|
+
}
|
|
223
249
|
|
|
224
250
|
//#endregion
|
|
225
251
|
//#region src/auth/index.ts
|
|
226
|
-
const logger$
|
|
252
|
+
const logger$11 = console;
|
|
227
253
|
/**
|
|
228
254
|
* Validate Dokploy token by making a test API call
|
|
229
255
|
*/
|
|
230
256
|
async function validateDokployToken(endpoint, token) {
|
|
231
|
-
const { DokployApi: DokployApi$1 } = await Promise.resolve().then(() => require("./dokploy-api-
|
|
257
|
+
const { DokployApi: DokployApi$1 } = await Promise.resolve().then(() => require("./dokploy-api-4a6h35VY.cjs"));
|
|
232
258
|
const api = new DokployApi$1({
|
|
233
259
|
baseUrl: endpoint,
|
|
234
260
|
token
|
|
@@ -291,36 +317,36 @@ async function prompt$1(message, hidden = false) {
|
|
|
291
317
|
async function loginCommand(options) {
|
|
292
318
|
const { service, token: providedToken, endpoint: providedEndpoint } = options;
|
|
293
319
|
if (service === "dokploy") {
|
|
294
|
-
logger$
|
|
320
|
+
logger$11.log("\n🔐 Logging in to Dokploy...\n");
|
|
295
321
|
let endpoint = providedEndpoint;
|
|
296
322
|
if (!endpoint) endpoint = await prompt$1("Dokploy URL (e.g., https://dokploy.example.com): ");
|
|
297
323
|
endpoint = endpoint.replace(/\/$/, "");
|
|
298
324
|
try {
|
|
299
325
|
new URL(endpoint);
|
|
300
326
|
} catch {
|
|
301
|
-
logger$
|
|
327
|
+
logger$11.error("Invalid URL format");
|
|
302
328
|
process.exit(1);
|
|
303
329
|
}
|
|
304
330
|
let token = providedToken;
|
|
305
331
|
if (!token) {
|
|
306
|
-
logger$
|
|
332
|
+
logger$11.log(`\nGenerate a token at: ${endpoint}/settings/profile\n`);
|
|
307
333
|
token = await prompt$1("API Token: ", true);
|
|
308
334
|
}
|
|
309
335
|
if (!token) {
|
|
310
|
-
logger$
|
|
336
|
+
logger$11.error("Token is required");
|
|
311
337
|
process.exit(1);
|
|
312
338
|
}
|
|
313
|
-
logger$
|
|
339
|
+
logger$11.log("\nValidating credentials...");
|
|
314
340
|
const isValid = await validateDokployToken(endpoint, token);
|
|
315
341
|
if (!isValid) {
|
|
316
|
-
logger$
|
|
342
|
+
logger$11.error("\n✗ Invalid credentials. Please check your token and try again.");
|
|
317
343
|
process.exit(1);
|
|
318
344
|
}
|
|
319
345
|
await storeDokployCredentials(token, endpoint);
|
|
320
|
-
logger$
|
|
321
|
-
logger$
|
|
322
|
-
logger$
|
|
323
|
-
logger$
|
|
346
|
+
logger$11.log("\n✓ Successfully logged in to Dokploy!");
|
|
347
|
+
logger$11.log(` Endpoint: ${endpoint}`);
|
|
348
|
+
logger$11.log(` Credentials stored in: ${getCredentialsPath()}`);
|
|
349
|
+
logger$11.log("\nYou can now use deploy commands without setting DOKPLOY_API_TOKEN.");
|
|
324
350
|
}
|
|
325
351
|
}
|
|
326
352
|
/**
|
|
@@ -330,28 +356,28 @@ async function logoutCommand(options) {
|
|
|
330
356
|
const { service = "dokploy" } = options;
|
|
331
357
|
if (service === "all") {
|
|
332
358
|
const dokployRemoved = await removeDokployCredentials();
|
|
333
|
-
if (dokployRemoved) logger$
|
|
334
|
-
else logger$
|
|
359
|
+
if (dokployRemoved) logger$11.log("\n✓ Logged out from all services");
|
|
360
|
+
else logger$11.log("\nNo stored credentials found");
|
|
335
361
|
return;
|
|
336
362
|
}
|
|
337
363
|
if (service === "dokploy") {
|
|
338
364
|
const removed = await removeDokployCredentials();
|
|
339
|
-
if (removed) logger$
|
|
340
|
-
else logger$
|
|
365
|
+
if (removed) logger$11.log("\n✓ Logged out from Dokploy");
|
|
366
|
+
else logger$11.log("\nNo Dokploy credentials found");
|
|
341
367
|
}
|
|
342
368
|
}
|
|
343
369
|
/**
|
|
344
370
|
* Show current login status
|
|
345
371
|
*/
|
|
346
372
|
async function whoamiCommand() {
|
|
347
|
-
logger$
|
|
373
|
+
logger$11.log("\n📋 Current credentials:\n");
|
|
348
374
|
const dokploy = await getDokployCredentials();
|
|
349
375
|
if (dokploy) {
|
|
350
|
-
logger$
|
|
351
|
-
logger$
|
|
352
|
-
logger$
|
|
353
|
-
} else logger$
|
|
354
|
-
logger$
|
|
376
|
+
logger$11.log(" Dokploy:");
|
|
377
|
+
logger$11.log(` Endpoint: ${dokploy.endpoint}`);
|
|
378
|
+
logger$11.log(` Token: ${maskToken(dokploy.token)}`);
|
|
379
|
+
} else logger$11.log(" Dokploy: Not logged in");
|
|
380
|
+
logger$11.log(`\n Credentials file: ${getCredentialsPath()}`);
|
|
355
381
|
}
|
|
356
382
|
/**
|
|
357
383
|
* Mask a token for display
|
|
@@ -437,7 +463,7 @@ function isEnabled(config) {
|
|
|
437
463
|
var CronGenerator = class extends require_openapi.ConstructGenerator {
|
|
438
464
|
async build(context, constructs, outputDir, options) {
|
|
439
465
|
const provider = options?.provider || "aws-lambda";
|
|
440
|
-
const logger$
|
|
466
|
+
const logger$12 = console;
|
|
441
467
|
const cronInfos = [];
|
|
442
468
|
if (constructs.length === 0 || provider !== "aws-lambda") return cronInfos;
|
|
443
469
|
const cronsDir = (0, node_path.join)(outputDir, "crons");
|
|
@@ -452,7 +478,7 @@ var CronGenerator = class extends require_openapi.ConstructGenerator {
|
|
|
452
478
|
memorySize: construct.memorySize,
|
|
453
479
|
environment: await construct.getEnvironment()
|
|
454
480
|
});
|
|
455
|
-
logger$
|
|
481
|
+
logger$12.log(`Generated cron handler: ${key}`);
|
|
456
482
|
}
|
|
457
483
|
return cronInfos;
|
|
458
484
|
}
|
|
@@ -488,7 +514,7 @@ var FunctionGenerator = class extends require_openapi.ConstructGenerator {
|
|
|
488
514
|
}
|
|
489
515
|
async build(context, constructs, outputDir, options) {
|
|
490
516
|
const provider = options?.provider || "aws-lambda";
|
|
491
|
-
const logger$
|
|
517
|
+
const logger$12 = console;
|
|
492
518
|
const functionInfos = [];
|
|
493
519
|
if (constructs.length === 0 || provider !== "aws-lambda") return functionInfos;
|
|
494
520
|
const functionsDir = (0, node_path.join)(outputDir, "functions");
|
|
@@ -502,7 +528,7 @@ var FunctionGenerator = class extends require_openapi.ConstructGenerator {
|
|
|
502
528
|
memorySize: construct.memorySize,
|
|
503
529
|
environment: await construct.getEnvironment()
|
|
504
530
|
});
|
|
505
|
-
logger$
|
|
531
|
+
logger$12.log(`Generated function handler: ${key}`);
|
|
506
532
|
}
|
|
507
533
|
return functionInfos;
|
|
508
534
|
}
|
|
@@ -535,11 +561,11 @@ var SubscriberGenerator = class extends require_openapi.ConstructGenerator {
|
|
|
535
561
|
}
|
|
536
562
|
async build(context, constructs, outputDir, options) {
|
|
537
563
|
const provider = options?.provider || "aws-lambda";
|
|
538
|
-
const logger$
|
|
564
|
+
const logger$12 = console;
|
|
539
565
|
const subscriberInfos = [];
|
|
540
566
|
if (provider === "server") {
|
|
541
567
|
await this.generateServerSubscribersFile(outputDir, constructs);
|
|
542
|
-
logger$
|
|
568
|
+
logger$12.log(`Generated server subscribers file with ${constructs.length} subscribers (polling mode)`);
|
|
543
569
|
return subscriberInfos;
|
|
544
570
|
}
|
|
545
571
|
if (constructs.length === 0) return subscriberInfos;
|
|
@@ -556,7 +582,7 @@ var SubscriberGenerator = class extends require_openapi.ConstructGenerator {
|
|
|
556
582
|
memorySize: construct.memorySize,
|
|
557
583
|
environment: await construct.getEnvironment()
|
|
558
584
|
});
|
|
559
|
-
logger$
|
|
585
|
+
logger$12.log(`Generated subscriber handler: ${key}`);
|
|
560
586
|
}
|
|
561
587
|
return subscriberInfos;
|
|
562
588
|
}
|
|
@@ -722,7 +748,7 @@ export async function setupSubscribers(
|
|
|
722
748
|
|
|
723
749
|
//#endregion
|
|
724
750
|
//#region src/workspace/client-generator.ts
|
|
725
|
-
const logger$
|
|
751
|
+
const logger$10 = console;
|
|
726
752
|
/**
|
|
727
753
|
* Cache of OpenAPI spec hashes to detect changes.
|
|
728
754
|
*/
|
|
@@ -842,7 +868,7 @@ ${spec.content}
|
|
|
842
868
|
* Generate clients for all frontend apps in the workspace.
|
|
843
869
|
*/
|
|
844
870
|
async function generateAllClients(workspace, options = {}) {
|
|
845
|
-
const log = options.silent ? () => {} : logger$
|
|
871
|
+
const log = options.silent ? () => {} : logger$10.log.bind(logger$10);
|
|
846
872
|
const allResults = [];
|
|
847
873
|
for (const [appName, app] of Object.entries(workspace.apps)) if (app.type === "frontend" && app.dependencies.length > 0) {
|
|
848
874
|
const results = await generateClientForFrontend(workspace, appName, { force: options.force });
|
|
@@ -864,7 +890,7 @@ function getDependentFrontends(workspace, backendAppName) {
|
|
|
864
890
|
|
|
865
891
|
//#endregion
|
|
866
892
|
//#region src/dev/index.ts
|
|
867
|
-
const logger$
|
|
893
|
+
const logger$9 = console;
|
|
868
894
|
/**
|
|
869
895
|
* Load environment files
|
|
870
896
|
* @internal Exported for testing
|
|
@@ -915,7 +941,7 @@ async function findAvailablePort(preferredPort, maxAttempts = 10) {
|
|
|
915
941
|
for (let i = 0; i < maxAttempts; i++) {
|
|
916
942
|
const port = preferredPort + i;
|
|
917
943
|
if (await isPortAvailable(port)) return port;
|
|
918
|
-
logger$
|
|
944
|
+
logger$9.log(`⚠️ Port ${port} is in use, trying ${port + 1}...`);
|
|
919
945
|
}
|
|
920
946
|
throw new Error(`Could not find an available port after trying ${maxAttempts} ports starting from ${preferredPort}`);
|
|
921
947
|
}
|
|
@@ -1016,7 +1042,7 @@ function getProductionConfigFromGkm(config) {
|
|
|
1016
1042
|
async function devCommand(options) {
|
|
1017
1043
|
if (options.entry) return entryDevCommand(options);
|
|
1018
1044
|
const defaultEnv = loadEnvFiles(".env");
|
|
1019
|
-
if (defaultEnv.loaded.length > 0) logger$
|
|
1045
|
+
if (defaultEnv.loaded.length > 0) logger$9.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
1020
1046
|
const appName = require_config.getAppNameFromCwd();
|
|
1021
1047
|
let config;
|
|
1022
1048
|
let appRoot = process.cwd();
|
|
@@ -1030,9 +1056,9 @@ async function devCommand(options) {
|
|
|
1030
1056
|
secretsRoot = appConfig.workspaceRoot;
|
|
1031
1057
|
workspaceAppName = appConfig.appName;
|
|
1032
1058
|
workspaceAppPort = appConfig.app.port;
|
|
1033
|
-
logger$
|
|
1059
|
+
logger$9.log(`📦 Running app: ${appConfig.appName} on port ${workspaceAppPort}`);
|
|
1034
1060
|
if (appConfig.app.entry) {
|
|
1035
|
-
logger$
|
|
1061
|
+
logger$9.log(`📄 Using entry point: ${appConfig.app.entry}`);
|
|
1036
1062
|
return entryDevCommand({
|
|
1037
1063
|
...options,
|
|
1038
1064
|
entry: appConfig.app.entry,
|
|
@@ -1043,7 +1069,7 @@ async function devCommand(options) {
|
|
|
1043
1069
|
} catch {
|
|
1044
1070
|
const loadedConfig = await require_config.loadWorkspaceConfig();
|
|
1045
1071
|
if (loadedConfig.type === "workspace") {
|
|
1046
|
-
logger$
|
|
1072
|
+
logger$9.log("📦 Detected workspace configuration");
|
|
1047
1073
|
return workspaceDevCommand(loadedConfig.workspace, options);
|
|
1048
1074
|
}
|
|
1049
1075
|
config = loadedConfig.raw;
|
|
@@ -1051,34 +1077,34 @@ async function devCommand(options) {
|
|
|
1051
1077
|
else {
|
|
1052
1078
|
const loadedConfig = await require_config.loadWorkspaceConfig();
|
|
1053
1079
|
if (loadedConfig.type === "workspace") {
|
|
1054
|
-
logger$
|
|
1080
|
+
logger$9.log("📦 Detected workspace configuration");
|
|
1055
1081
|
return workspaceDevCommand(loadedConfig.workspace, options);
|
|
1056
1082
|
}
|
|
1057
1083
|
config = loadedConfig.raw;
|
|
1058
1084
|
}
|
|
1059
1085
|
if (config.env) {
|
|
1060
1086
|
const { loaded, missing } = loadEnvFiles(config.env, appRoot);
|
|
1061
|
-
if (loaded.length > 0) logger$
|
|
1062
|
-
if (missing.length > 0) logger$
|
|
1087
|
+
if (loaded.length > 0) logger$9.log(`📦 Loaded env: ${loaded.join(", ")}`);
|
|
1088
|
+
if (missing.length > 0) logger$9.warn(`⚠️ Missing env files: ${missing.join(", ")}`);
|
|
1063
1089
|
}
|
|
1064
1090
|
const resolved = resolveProviders(config, { provider: "server" });
|
|
1065
|
-
logger$
|
|
1066
|
-
logger$
|
|
1067
|
-
if (config.functions) logger$
|
|
1068
|
-
if (config.crons) logger$
|
|
1069
|
-
if (config.subscribers) logger$
|
|
1070
|
-
logger$
|
|
1091
|
+
logger$9.log("🚀 Starting development server...");
|
|
1092
|
+
logger$9.log(`Loading routes from: ${config.routes}`);
|
|
1093
|
+
if (config.functions) logger$9.log(`Loading functions from: ${config.functions}`);
|
|
1094
|
+
if (config.crons) logger$9.log(`Loading crons from: ${config.crons}`);
|
|
1095
|
+
if (config.subscribers) logger$9.log(`Loading subscribers from: ${config.subscribers}`);
|
|
1096
|
+
logger$9.log(`Using envParser: ${config.envParser}`);
|
|
1071
1097
|
const { path: envParserPath, importPattern: envParserImportPattern } = require_config.parseModuleConfig(config.envParser, "envParser");
|
|
1072
1098
|
const { path: loggerPath, importPattern: loggerImportPattern } = require_config.parseModuleConfig(config.logger, "logger");
|
|
1073
1099
|
const telescope = normalizeTelescopeConfig(config.telescope);
|
|
1074
|
-
if (telescope) logger$
|
|
1100
|
+
if (telescope) logger$9.log(`🔭 Telescope enabled at ${telescope.path}`);
|
|
1075
1101
|
const studio = normalizeStudioConfig(config.studio);
|
|
1076
|
-
if (studio) logger$
|
|
1102
|
+
if (studio) logger$9.log(`🗄️ Studio enabled at ${studio.path}`);
|
|
1077
1103
|
const hooks = normalizeHooksConfig(config.hooks, appRoot);
|
|
1078
|
-
if (hooks) logger$
|
|
1104
|
+
if (hooks) logger$9.log(`🪝 Server hooks enabled from ${config.hooks?.server}`);
|
|
1079
1105
|
const openApiConfig = require_openapi.resolveOpenApiConfig(config);
|
|
1080
1106
|
const enableOpenApi = openApiConfig.enabled || resolved.enableOpenApi;
|
|
1081
|
-
if (enableOpenApi) logger$
|
|
1107
|
+
if (enableOpenApi) logger$9.log(`📄 OpenAPI output: ${require_openapi.OPENAPI_OUTPUT_PATH}`);
|
|
1082
1108
|
const buildContext = {
|
|
1083
1109
|
envParserPath,
|
|
1084
1110
|
envParserImportPattern,
|
|
@@ -1098,7 +1124,7 @@ async function devCommand(options) {
|
|
|
1098
1124
|
await (0, node_fs_promises.mkdir)(secretsDir, { recursive: true });
|
|
1099
1125
|
secretsJsonPath = (0, node_path.join)(secretsDir, "dev-secrets.json");
|
|
1100
1126
|
await (0, node_fs_promises.writeFile)(secretsJsonPath, JSON.stringify(appSecrets, null, 2));
|
|
1101
|
-
logger$
|
|
1127
|
+
logger$9.log(`🔐 Loaded ${Object.keys(appSecrets).length} secret(s)`);
|
|
1102
1128
|
}
|
|
1103
1129
|
const devServer = new DevServer(resolved.providers[0], options.port ?? workspaceAppPort ?? 3e3, options.portExplicit ?? false, enableOpenApi, telescope, studio, runtime, appRoot, secretsJsonPath);
|
|
1104
1130
|
await devServer.start();
|
|
@@ -1116,7 +1142,7 @@ async function devCommand(options) {
|
|
|
1116
1142
|
...hooksFile ? [hooksFile.endsWith(".ts") ? hooksFile : `${hooksFile}.ts`] : []
|
|
1117
1143
|
].flat().filter((p) => typeof p === "string");
|
|
1118
1144
|
const normalizedPatterns = watchPatterns.map((p) => p.startsWith("./") ? p.slice(2) : p);
|
|
1119
|
-
logger$
|
|
1145
|
+
logger$9.log(`👀 Watching for changes in: ${normalizedPatterns.join(", ")}`);
|
|
1120
1146
|
const resolvedFiles = await (0, fast_glob.default)(normalizedPatterns, {
|
|
1121
1147
|
cwd: appRoot,
|
|
1122
1148
|
absolute: false,
|
|
@@ -1126,7 +1152,7 @@ async function devCommand(options) {
|
|
|
1126
1152
|
const parts = f.split("/");
|
|
1127
1153
|
return parts.slice(0, -1).join("/");
|
|
1128
1154
|
}))];
|
|
1129
|
-
logger$
|
|
1155
|
+
logger$9.log(`📁 Found ${resolvedFiles.length} files in ${dirsToWatch.length} directories`);
|
|
1130
1156
|
const watcher = chokidar.default.watch([...resolvedFiles, ...dirsToWatch], {
|
|
1131
1157
|
ignored: /(^|[/\\])\../,
|
|
1132
1158
|
persistent: true,
|
|
@@ -1134,24 +1160,24 @@ async function devCommand(options) {
|
|
|
1134
1160
|
cwd: appRoot
|
|
1135
1161
|
});
|
|
1136
1162
|
watcher.on("ready", () => {
|
|
1137
|
-
logger$
|
|
1163
|
+
logger$9.log("🔍 File watcher ready");
|
|
1138
1164
|
});
|
|
1139
1165
|
watcher.on("error", (error) => {
|
|
1140
|
-
logger$
|
|
1166
|
+
logger$9.error("❌ Watcher error:", error);
|
|
1141
1167
|
});
|
|
1142
1168
|
let rebuildTimeout = null;
|
|
1143
1169
|
watcher.on("change", async (path) => {
|
|
1144
|
-
logger$
|
|
1170
|
+
logger$9.log(`📝 File changed: ${path}`);
|
|
1145
1171
|
if (rebuildTimeout) clearTimeout(rebuildTimeout);
|
|
1146
1172
|
rebuildTimeout = setTimeout(async () => {
|
|
1147
1173
|
try {
|
|
1148
|
-
logger$
|
|
1174
|
+
logger$9.log("🔄 Rebuilding...");
|
|
1149
1175
|
await buildServer(config, buildContext, resolved.providers[0], enableOpenApi, appRoot);
|
|
1150
1176
|
if (enableOpenApi) await require_openapi.generateOpenApi(config, { silent: true });
|
|
1151
|
-
logger$
|
|
1177
|
+
logger$9.log("✅ Rebuild complete, restarting server...");
|
|
1152
1178
|
await devServer.restart();
|
|
1153
1179
|
} catch (error) {
|
|
1154
|
-
logger$
|
|
1180
|
+
logger$9.error("❌ Rebuild failed:", error.message);
|
|
1155
1181
|
}
|
|
1156
1182
|
}, 300);
|
|
1157
1183
|
});
|
|
@@ -1159,9 +1185,9 @@ async function devCommand(options) {
|
|
|
1159
1185
|
const shutdown = () => {
|
|
1160
1186
|
if (isShuttingDown) return;
|
|
1161
1187
|
isShuttingDown = true;
|
|
1162
|
-
logger$
|
|
1188
|
+
logger$9.log("\n🛑 Shutting down...");
|
|
1163
1189
|
Promise.all([watcher.close(), devServer.stop()]).catch((err) => {
|
|
1164
|
-
logger$
|
|
1190
|
+
logger$9.error("Error during shutdown:", err);
|
|
1165
1191
|
}).finally(() => {
|
|
1166
1192
|
process.exit(0);
|
|
1167
1193
|
});
|
|
@@ -1264,11 +1290,11 @@ async function loadDevSecrets(workspace) {
|
|
|
1264
1290
|
for (const stage of stages) if (require_storage.secretsExist(stage, workspace.root)) {
|
|
1265
1291
|
const secrets = await require_storage.readStageSecrets(stage, workspace.root);
|
|
1266
1292
|
if (secrets) {
|
|
1267
|
-
logger$
|
|
1293
|
+
logger$9.log(`🔐 Loading secrets from stage: ${stage}`);
|
|
1268
1294
|
return require_storage.toEmbeddableSecrets(secrets);
|
|
1269
1295
|
}
|
|
1270
1296
|
}
|
|
1271
|
-
logger$
|
|
1297
|
+
logger$9.warn("⚠️ Secrets enabled but no dev/development secrets found. Run \"gkm secrets:init --stage dev\"");
|
|
1272
1298
|
return {};
|
|
1273
1299
|
}
|
|
1274
1300
|
/**
|
|
@@ -1283,7 +1309,7 @@ async function loadSecretsForApp(secretsRoot, appName) {
|
|
|
1283
1309
|
for (const stage of stages) if (require_storage.secretsExist(stage, secretsRoot)) {
|
|
1284
1310
|
const stageSecrets = await require_storage.readStageSecrets(stage, secretsRoot);
|
|
1285
1311
|
if (stageSecrets) {
|
|
1286
|
-
logger$
|
|
1312
|
+
logger$9.log(`🔐 Loading secrets from stage: ${stage}`);
|
|
1287
1313
|
secrets = require_storage.toEmbeddableSecrets(stageSecrets);
|
|
1288
1314
|
break;
|
|
1289
1315
|
}
|
|
@@ -1308,20 +1334,20 @@ async function startWorkspaceServices(workspace) {
|
|
|
1308
1334
|
if (services.cache) servicesToStart.push("redis");
|
|
1309
1335
|
if (services.mail) servicesToStart.push("mailpit");
|
|
1310
1336
|
if (servicesToStart.length === 0) return;
|
|
1311
|
-
logger$
|
|
1337
|
+
logger$9.log(`🐳 Starting services: ${servicesToStart.join(", ")}`);
|
|
1312
1338
|
try {
|
|
1313
1339
|
const composeFile = (0, node_path.join)(workspace.root, "docker-compose.yml");
|
|
1314
1340
|
if (!(0, node_fs.existsSync)(composeFile)) {
|
|
1315
|
-
logger$
|
|
1341
|
+
logger$9.warn("⚠️ No docker-compose.yml found. Services will not be started.");
|
|
1316
1342
|
return;
|
|
1317
1343
|
}
|
|
1318
1344
|
(0, node_child_process.execSync)(`docker compose up -d ${servicesToStart.join(" ")}`, {
|
|
1319
1345
|
cwd: workspace.root,
|
|
1320
1346
|
stdio: "inherit"
|
|
1321
1347
|
});
|
|
1322
|
-
logger$
|
|
1348
|
+
logger$9.log("✅ Services started");
|
|
1323
1349
|
} catch (error) {
|
|
1324
|
-
logger$
|
|
1350
|
+
logger$9.error("❌ Failed to start services:", error.message);
|
|
1325
1351
|
throw error;
|
|
1326
1352
|
}
|
|
1327
1353
|
}
|
|
@@ -1338,40 +1364,40 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1338
1364
|
const appCount = Object.keys(workspace.apps).length;
|
|
1339
1365
|
const backendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "backend");
|
|
1340
1366
|
const frontendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "frontend");
|
|
1341
|
-
logger$
|
|
1342
|
-
logger$
|
|
1367
|
+
logger$9.log(`\n🚀 Starting workspace: ${workspace.name}`);
|
|
1368
|
+
logger$9.log(` ${backendApps.length} backend app(s), ${frontendApps.length} frontend app(s)`);
|
|
1343
1369
|
const conflicts = checkPortConflicts(workspace);
|
|
1344
1370
|
if (conflicts.length > 0) {
|
|
1345
|
-
for (const conflict of conflicts) logger$
|
|
1371
|
+
for (const conflict of conflicts) logger$9.error(`❌ Port conflict: Apps "${conflict.app1}" and "${conflict.app2}" both use port ${conflict.port}`);
|
|
1346
1372
|
throw new Error("Port conflicts detected. Please assign unique ports to each app.");
|
|
1347
1373
|
}
|
|
1348
1374
|
if (frontendApps.length > 0) {
|
|
1349
|
-
logger$
|
|
1375
|
+
logger$9.log("\n🔍 Validating frontend apps...");
|
|
1350
1376
|
const validationResults = await validateFrontendApps(workspace);
|
|
1351
1377
|
let hasErrors = false;
|
|
1352
1378
|
for (const result of validationResults) {
|
|
1353
1379
|
if (!result.valid) {
|
|
1354
1380
|
hasErrors = true;
|
|
1355
|
-
logger$
|
|
1356
|
-
for (const error of result.errors) logger$
|
|
1381
|
+
logger$9.error(`\n❌ Frontend app "${result.appName}" validation failed:`);
|
|
1382
|
+
for (const error of result.errors) logger$9.error(` • ${error}`);
|
|
1357
1383
|
}
|
|
1358
|
-
for (const warning of result.warnings) logger$
|
|
1384
|
+
for (const warning of result.warnings) logger$9.warn(` ⚠️ ${result.appName}: ${warning}`);
|
|
1359
1385
|
}
|
|
1360
1386
|
if (hasErrors) throw new Error("Frontend app validation failed. Fix the issues above and try again.");
|
|
1361
|
-
logger$
|
|
1387
|
+
logger$9.log("✅ Frontend apps validated");
|
|
1362
1388
|
}
|
|
1363
1389
|
if (frontendApps.length > 0) {
|
|
1364
1390
|
const clientResults = await generateAllClients(workspace, { force: true });
|
|
1365
1391
|
const generatedCount = clientResults.filter((r) => r.generated).length;
|
|
1366
|
-
if (generatedCount > 0) logger$
|
|
1392
|
+
if (generatedCount > 0) logger$9.log(`\n📦 Generated ${generatedCount} API client(s)`);
|
|
1367
1393
|
}
|
|
1368
1394
|
await startWorkspaceServices(workspace);
|
|
1369
1395
|
const secretsEnv = await loadDevSecrets(workspace);
|
|
1370
|
-
if (Object.keys(secretsEnv).length > 0) logger$
|
|
1396
|
+
if (Object.keys(secretsEnv).length > 0) logger$9.log(` Loaded ${Object.keys(secretsEnv).length} secret(s)`);
|
|
1371
1397
|
const dependencyEnv = generateAllDependencyEnvVars(workspace);
|
|
1372
1398
|
if (Object.keys(dependencyEnv).length > 0) {
|
|
1373
|
-
logger$
|
|
1374
|
-
for (const [key, value] of Object.entries(dependencyEnv)) logger$
|
|
1399
|
+
logger$9.log("📡 Dependency URLs:");
|
|
1400
|
+
for (const [key, value] of Object.entries(dependencyEnv)) logger$9.log(` ${key}=${value}`);
|
|
1375
1401
|
}
|
|
1376
1402
|
let turboFilter = [];
|
|
1377
1403
|
if (options.app) {
|
|
@@ -1380,18 +1406,18 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1380
1406
|
throw new Error(`App "${options.app}" not found. Available apps: ${appNames}`);
|
|
1381
1407
|
}
|
|
1382
1408
|
turboFilter = ["--filter", options.app];
|
|
1383
|
-
logger$
|
|
1409
|
+
logger$9.log(`\n🎯 Running single app: ${options.app}`);
|
|
1384
1410
|
} else if (options.filter) {
|
|
1385
1411
|
turboFilter = ["--filter", options.filter];
|
|
1386
|
-
logger$
|
|
1387
|
-
} else logger$
|
|
1412
|
+
logger$9.log(`\n🔍 Using filter: ${options.filter}`);
|
|
1413
|
+
} else logger$9.log(`\n🎯 Running all ${appCount} apps`);
|
|
1388
1414
|
const buildOrder = require_workspace.getAppBuildOrder(workspace);
|
|
1389
|
-
logger$
|
|
1415
|
+
logger$9.log("\n📋 Apps (in dependency order):");
|
|
1390
1416
|
for (const appName of buildOrder) {
|
|
1391
1417
|
const app = workspace.apps[appName];
|
|
1392
1418
|
if (!app) continue;
|
|
1393
1419
|
const deps = app.dependencies.length > 0 ? ` (depends on: ${app.dependencies.join(", ")})` : "";
|
|
1394
|
-
logger$
|
|
1420
|
+
logger$9.log(` ${app.type === "backend" ? "🔧" : "🌐"} ${appName} → http://localhost:${app.port}${deps}`);
|
|
1395
1421
|
}
|
|
1396
1422
|
const configFiles = [
|
|
1397
1423
|
"gkm.config.ts",
|
|
@@ -1413,7 +1439,7 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1413
1439
|
NODE_ENV: "development",
|
|
1414
1440
|
...configPath ? { GKM_CONFIG_PATH: configPath } : {}
|
|
1415
1441
|
};
|
|
1416
|
-
logger$
|
|
1442
|
+
logger$9.log("\n🏃 Starting turbo run dev...\n");
|
|
1417
1443
|
const turboProcess = (0, node_child_process.spawn)("pnpm", [
|
|
1418
1444
|
"turbo",
|
|
1419
1445
|
"run",
|
|
@@ -1445,7 +1471,7 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1445
1471
|
onlyFiles: true
|
|
1446
1472
|
});
|
|
1447
1473
|
if (resolvedFiles.length > 0) {
|
|
1448
|
-
logger$
|
|
1474
|
+
logger$9.log(`\n👀 Watching ${resolvedFiles.length} endpoint file(s) for schema changes`);
|
|
1449
1475
|
endpointWatcher = chokidar.default.watch(resolvedFiles, {
|
|
1450
1476
|
ignored: /(^|[/\\])\../,
|
|
1451
1477
|
persistent: true,
|
|
@@ -1473,12 +1499,12 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1473
1499
|
for (const frontend of dependents) affectedFrontends.add(frontend);
|
|
1474
1500
|
}
|
|
1475
1501
|
if (affectedFrontends.size === 0) return;
|
|
1476
|
-
logger$
|
|
1502
|
+
logger$9.log(`\n🔄 Detected schema change in ${changedBackends.join(", ")}`);
|
|
1477
1503
|
for (const frontend of affectedFrontends) try {
|
|
1478
1504
|
const results = await generateClientForFrontend(workspace, frontend);
|
|
1479
|
-
for (const result of results) if (result.generated) logger$
|
|
1505
|
+
for (const result of results) if (result.generated) logger$9.log(` 📦 Regenerated client for ${result.frontendApp} (${result.endpointCount} endpoints)`);
|
|
1480
1506
|
} catch (error) {
|
|
1481
|
-
logger$
|
|
1507
|
+
logger$9.error(` ❌ Failed to regenerate client for ${frontend}: ${error.message}`);
|
|
1482
1508
|
}
|
|
1483
1509
|
}, 500);
|
|
1484
1510
|
});
|
|
@@ -1489,7 +1515,7 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1489
1515
|
const shutdown = () => {
|
|
1490
1516
|
if (isShuttingDown) return;
|
|
1491
1517
|
isShuttingDown = true;
|
|
1492
|
-
logger$
|
|
1518
|
+
logger$9.log("\n🛑 Shutting down workspace...");
|
|
1493
1519
|
if (endpointWatcher) endpointWatcher.close().catch(() => {});
|
|
1494
1520
|
if (turboProcess.pid) try {
|
|
1495
1521
|
process.kill(-turboProcess.pid, "SIGTERM");
|
|
@@ -1504,7 +1530,7 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1504
1530
|
process.on("SIGTERM", shutdown);
|
|
1505
1531
|
return new Promise((resolve$3, reject) => {
|
|
1506
1532
|
turboProcess.on("error", (error) => {
|
|
1507
|
-
logger$
|
|
1533
|
+
logger$9.error("❌ Turbo error:", error);
|
|
1508
1534
|
reject(error);
|
|
1509
1535
|
});
|
|
1510
1536
|
turboProcess.on("exit", (code) => {
|
|
@@ -1616,7 +1642,7 @@ async function prepareEntryCredentials(options) {
|
|
|
1616
1642
|
secretsRoot = appConfig.workspaceRoot;
|
|
1617
1643
|
appName = appConfig.appName;
|
|
1618
1644
|
} catch (error) {
|
|
1619
|
-
logger$
|
|
1645
|
+
logger$9.log(`⚠️ Could not load workspace config: ${error.message}`);
|
|
1620
1646
|
secretsRoot = findSecretsRoot(cwd);
|
|
1621
1647
|
appName = require_config.getAppNameFromCwd(cwd) ?? void 0;
|
|
1622
1648
|
}
|
|
@@ -1646,11 +1672,11 @@ async function entryDevCommand(options) {
|
|
|
1646
1672
|
const entryPath = (0, node_path.resolve)(process.cwd(), entry);
|
|
1647
1673
|
if (!(0, node_fs.existsSync)(entryPath)) throw new Error(`Entry file not found: ${entryPath}`);
|
|
1648
1674
|
const defaultEnv = loadEnvFiles(".env");
|
|
1649
|
-
if (defaultEnv.loaded.length > 0) logger$
|
|
1675
|
+
if (defaultEnv.loaded.length > 0) logger$9.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
1650
1676
|
const { credentials, resolvedPort, secretsJsonPath, appName } = await prepareEntryCredentials({ explicitPort: options.portExplicit ? options.port : void 0 });
|
|
1651
|
-
if (appName) logger$
|
|
1652
|
-
logger$
|
|
1653
|
-
if (Object.keys(credentials).length > 1) logger$
|
|
1677
|
+
if (appName) logger$9.log(`📦 App: ${appName} (port ${resolvedPort})`);
|
|
1678
|
+
logger$9.log(`🚀 Starting entry file: ${entry} on port ${resolvedPort}`);
|
|
1679
|
+
if (Object.keys(credentials).length > 1) logger$9.log(`🔐 Loaded ${Object.keys(credentials).length - 1} secret(s) + PORT`);
|
|
1654
1680
|
const wrapperDir = (0, node_path.join)(process.cwd(), ".gkm");
|
|
1655
1681
|
await (0, node_fs_promises.mkdir)(wrapperDir, { recursive: true });
|
|
1656
1682
|
const wrapperPath = (0, node_path.join)(wrapperDir, "entry-wrapper.ts");
|
|
@@ -1661,7 +1687,7 @@ async function entryDevCommand(options) {
|
|
|
1661
1687
|
const shutdown = () => {
|
|
1662
1688
|
if (isShuttingDown) return;
|
|
1663
1689
|
isShuttingDown = true;
|
|
1664
|
-
logger$
|
|
1690
|
+
logger$9.log("\n🛑 Shutting down...");
|
|
1665
1691
|
runner.stop();
|
|
1666
1692
|
process.exit(0);
|
|
1667
1693
|
};
|
|
@@ -1693,14 +1719,14 @@ var EntryRunner = class {
|
|
|
1693
1719
|
});
|
|
1694
1720
|
let restartTimeout = null;
|
|
1695
1721
|
this.watcher.on("change", (path) => {
|
|
1696
|
-
logger$
|
|
1722
|
+
logger$9.log(`📝 File changed: ${path}`);
|
|
1697
1723
|
if (restartTimeout) clearTimeout(restartTimeout);
|
|
1698
1724
|
restartTimeout = setTimeout(async () => {
|
|
1699
|
-
logger$
|
|
1725
|
+
logger$9.log("🔄 Restarting...");
|
|
1700
1726
|
await this.restart();
|
|
1701
1727
|
}, 300);
|
|
1702
1728
|
});
|
|
1703
|
-
logger$
|
|
1729
|
+
logger$9.log(`👀 Watching for changes in: ${watchDir}`);
|
|
1704
1730
|
}
|
|
1705
1731
|
}
|
|
1706
1732
|
async runProcess() {
|
|
@@ -1715,14 +1741,14 @@ var EntryRunner = class {
|
|
|
1715
1741
|
});
|
|
1716
1742
|
this.isRunning = true;
|
|
1717
1743
|
this.childProcess.on("error", (error) => {
|
|
1718
|
-
logger$
|
|
1744
|
+
logger$9.error("❌ Process error:", error);
|
|
1719
1745
|
});
|
|
1720
1746
|
this.childProcess.on("exit", (code) => {
|
|
1721
|
-
if (code !== null && code !== 0 && code !== 143) logger$
|
|
1747
|
+
if (code !== null && code !== 0 && code !== 143) logger$9.error(`❌ Process exited with code ${code}`);
|
|
1722
1748
|
this.isRunning = false;
|
|
1723
1749
|
});
|
|
1724
1750
|
await new Promise((resolve$3) => setTimeout(resolve$3, 500));
|
|
1725
|
-
if (this.isRunning) logger$
|
|
1751
|
+
if (this.isRunning) logger$9.log(`\n🎉 Running at http://localhost:${this.port}`);
|
|
1726
1752
|
}
|
|
1727
1753
|
async restart() {
|
|
1728
1754
|
this.stopProcess();
|
|
@@ -1772,11 +1798,11 @@ var DevServer = class {
|
|
|
1772
1798
|
this.actualPort = this.requestedPort;
|
|
1773
1799
|
} else {
|
|
1774
1800
|
this.actualPort = await findAvailablePort(this.requestedPort);
|
|
1775
|
-
if (this.actualPort !== this.requestedPort) logger$
|
|
1801
|
+
if (this.actualPort !== this.requestedPort) logger$9.log(`ℹ️ Port ${this.requestedPort} was in use, using port ${this.actualPort} instead`);
|
|
1776
1802
|
}
|
|
1777
1803
|
const serverEntryPath = (0, node_path.join)(this.appRoot, ".gkm", this.provider, "server.ts");
|
|
1778
1804
|
await this.createServerEntry();
|
|
1779
|
-
logger$
|
|
1805
|
+
logger$9.log(`\n✨ Starting server on port ${this.actualPort}...`);
|
|
1780
1806
|
this.serverProcess = (0, node_child_process.spawn)("npx", [
|
|
1781
1807
|
"tsx",
|
|
1782
1808
|
serverEntryPath,
|
|
@@ -1792,18 +1818,18 @@ var DevServer = class {
|
|
|
1792
1818
|
});
|
|
1793
1819
|
this.isRunning = true;
|
|
1794
1820
|
this.serverProcess.on("error", (error) => {
|
|
1795
|
-
logger$
|
|
1821
|
+
logger$9.error("❌ Server error:", error);
|
|
1796
1822
|
});
|
|
1797
1823
|
this.serverProcess.on("exit", (code, signal) => {
|
|
1798
|
-
if (code !== null && code !== 0 && signal !== "SIGTERM") logger$
|
|
1824
|
+
if (code !== null && code !== 0 && signal !== "SIGTERM") logger$9.error(`❌ Server exited with code ${code}`);
|
|
1799
1825
|
this.isRunning = false;
|
|
1800
1826
|
});
|
|
1801
1827
|
await new Promise((resolve$3) => setTimeout(resolve$3, 1e3));
|
|
1802
1828
|
if (this.isRunning) {
|
|
1803
|
-
logger$
|
|
1804
|
-
if (this.enableOpenApi) logger$
|
|
1805
|
-
if (this.telescope) logger$
|
|
1806
|
-
if (this.studio) logger$
|
|
1829
|
+
logger$9.log(`\n🎉 Server running at http://localhost:${this.actualPort}`);
|
|
1830
|
+
if (this.enableOpenApi) logger$9.log(`📚 API Docs available at http://localhost:${this.actualPort}/__docs`);
|
|
1831
|
+
if (this.telescope) logger$9.log(`🔭 Telescope available at http://localhost:${this.actualPort}${this.telescope.path}`);
|
|
1832
|
+
if (this.studio) logger$9.log(`🗄️ Studio available at http://localhost:${this.actualPort}${this.studio.path}`);
|
|
1807
1833
|
}
|
|
1808
1834
|
}
|
|
1809
1835
|
async stop() {
|
|
@@ -1911,18 +1937,18 @@ async function execCommand(commandArgs, options = {}) {
|
|
|
1911
1937
|
const cwd = options.cwd ?? process.cwd();
|
|
1912
1938
|
if (commandArgs.length === 0) throw new Error("No command specified. Usage: gkm exec -- <command>");
|
|
1913
1939
|
const defaultEnv = loadEnvFiles(".env");
|
|
1914
|
-
if (defaultEnv.loaded.length > 0) logger$
|
|
1940
|
+
if (defaultEnv.loaded.length > 0) logger$9.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
1915
1941
|
const { credentials, secretsJsonPath, appName } = await prepareEntryCredentials({ cwd });
|
|
1916
|
-
if (appName) logger$
|
|
1942
|
+
if (appName) logger$9.log(`📦 App: ${appName}`);
|
|
1917
1943
|
const secretCount = Object.keys(credentials).filter((k) => k !== "PORT").length;
|
|
1918
|
-
if (secretCount > 0) logger$
|
|
1944
|
+
if (secretCount > 0) logger$9.log(`🔐 Loaded ${secretCount} secret(s)`);
|
|
1919
1945
|
const preloadDir = (0, node_path.join)(cwd, ".gkm");
|
|
1920
1946
|
await (0, node_fs_promises.mkdir)(preloadDir, { recursive: true });
|
|
1921
1947
|
const preloadPath = (0, node_path.join)(preloadDir, "credentials-preload.ts");
|
|
1922
1948
|
await createCredentialsPreload(preloadPath, secretsJsonPath);
|
|
1923
1949
|
const [cmd, ...args] = commandArgs;
|
|
1924
1950
|
if (!cmd) throw new Error("No command specified");
|
|
1925
|
-
logger$
|
|
1951
|
+
logger$9.log(`🚀 Running: ${commandArgs.join(" ")}`);
|
|
1926
1952
|
const existingNodeOptions = process.env.NODE_OPTIONS ?? "";
|
|
1927
1953
|
const tsxImport = "--import tsx";
|
|
1928
1954
|
const preloadImport = `--import ${preloadPath}`;
|
|
@@ -1943,7 +1969,7 @@ async function execCommand(commandArgs, options = {}) {
|
|
|
1943
1969
|
const exitCode = await new Promise((resolve$3) => {
|
|
1944
1970
|
child.on("close", (code) => resolve$3(code ?? 0));
|
|
1945
1971
|
child.on("error", (error) => {
|
|
1946
|
-
logger$
|
|
1972
|
+
logger$9.error(`Failed to run command: ${error.message}`);
|
|
1947
1973
|
resolve$3(1);
|
|
1948
1974
|
});
|
|
1949
1975
|
});
|
|
@@ -1952,7 +1978,7 @@ async function execCommand(commandArgs, options = {}) {
|
|
|
1952
1978
|
|
|
1953
1979
|
//#endregion
|
|
1954
1980
|
//#region src/build/manifests.ts
|
|
1955
|
-
const logger$
|
|
1981
|
+
const logger$8 = console;
|
|
1956
1982
|
async function generateAwsManifest(outputDir, routes, functions, crons, subscribers) {
|
|
1957
1983
|
const manifestDir = (0, node_path.join)(outputDir, "manifest");
|
|
1958
1984
|
await (0, node_fs_promises.mkdir)(manifestDir, { recursive: true });
|
|
@@ -1977,8 +2003,8 @@ export type RoutePath = Route['path'];
|
|
|
1977
2003
|
`;
|
|
1978
2004
|
const manifestPath = (0, node_path.join)(manifestDir, "aws.ts");
|
|
1979
2005
|
await (0, node_fs_promises.writeFile)(manifestPath, content);
|
|
1980
|
-
logger$
|
|
1981
|
-
logger$
|
|
2006
|
+
logger$8.log(`Generated AWS manifest with ${awsRoutes.length} routes, ${functions.length} functions, ${crons.length} crons, ${subscribers.length} subscribers`);
|
|
2007
|
+
logger$8.log(`Manifest: ${(0, node_path.relative)(process.cwd(), manifestPath)}`);
|
|
1982
2008
|
}
|
|
1983
2009
|
async function generateServerManifest(outputDir, appInfo, routes, subscribers) {
|
|
1984
2010
|
const manifestDir = (0, node_path.join)(outputDir, "manifest");
|
|
@@ -2009,13 +2035,13 @@ export type RoutePath = Route['path'];
|
|
|
2009
2035
|
`;
|
|
2010
2036
|
const manifestPath = (0, node_path.join)(manifestDir, "server.ts");
|
|
2011
2037
|
await (0, node_fs_promises.writeFile)(manifestPath, content);
|
|
2012
|
-
logger$
|
|
2013
|
-
logger$
|
|
2038
|
+
logger$8.log(`Generated server manifest with ${serverRoutes.length} routes, ${serverSubscribers.length} subscribers`);
|
|
2039
|
+
logger$8.log(`Manifest: ${(0, node_path.relative)(process.cwd(), manifestPath)}`);
|
|
2014
2040
|
}
|
|
2015
2041
|
|
|
2016
2042
|
//#endregion
|
|
2017
2043
|
//#region src/build/index.ts
|
|
2018
|
-
const logger$
|
|
2044
|
+
const logger$7 = console;
|
|
2019
2045
|
async function buildCommand(options) {
|
|
2020
2046
|
const loadedConfig = await require_config.loadWorkspaceConfig();
|
|
2021
2047
|
if (loadedConfig.type === "workspace") {
|
|
@@ -2023,7 +2049,7 @@ async function buildCommand(options) {
|
|
|
2023
2049
|
const workspaceRoot = (0, node_path.resolve)(loadedConfig.workspace.root);
|
|
2024
2050
|
const isAtWorkspaceRoot = cwd === workspaceRoot;
|
|
2025
2051
|
if (isAtWorkspaceRoot) {
|
|
2026
|
-
logger$
|
|
2052
|
+
logger$7.log("📦 Detected workspace configuration");
|
|
2027
2053
|
return workspaceBuildCommand(loadedConfig.workspace, options);
|
|
2028
2054
|
}
|
|
2029
2055
|
}
|
|
@@ -2031,21 +2057,21 @@ async function buildCommand(options) {
|
|
|
2031
2057
|
const resolved = resolveProviders(config, options);
|
|
2032
2058
|
const productionConfigFromGkm = getProductionConfigFromGkm(config);
|
|
2033
2059
|
const production = normalizeProductionConfig(options.production ?? false, productionConfigFromGkm);
|
|
2034
|
-
if (production) logger$
|
|
2035
|
-
logger$
|
|
2036
|
-
logger$
|
|
2037
|
-
if (config.functions) logger$
|
|
2038
|
-
if (config.crons) logger$
|
|
2039
|
-
if (config.subscribers) logger$
|
|
2040
|
-
logger$
|
|
2060
|
+
if (production) logger$7.log(`🏭 Building for PRODUCTION`);
|
|
2061
|
+
logger$7.log(`Building with providers: ${resolved.providers.join(", ")}`);
|
|
2062
|
+
logger$7.log(`Loading routes from: ${config.routes}`);
|
|
2063
|
+
if (config.functions) logger$7.log(`Loading functions from: ${config.functions}`);
|
|
2064
|
+
if (config.crons) logger$7.log(`Loading crons from: ${config.crons}`);
|
|
2065
|
+
if (config.subscribers) logger$7.log(`Loading subscribers from: ${config.subscribers}`);
|
|
2066
|
+
logger$7.log(`Using envParser: ${config.envParser}`);
|
|
2041
2067
|
const { path: envParserPath, importPattern: envParserImportPattern } = require_config.parseModuleConfig(config.envParser, "envParser");
|
|
2042
2068
|
const { path: loggerPath, importPattern: loggerImportPattern } = require_config.parseModuleConfig(config.logger, "logger");
|
|
2043
2069
|
const telescope = production ? void 0 : normalizeTelescopeConfig(config.telescope);
|
|
2044
|
-
if (telescope) logger$
|
|
2070
|
+
if (telescope) logger$7.log(`🔭 Telescope enabled at ${telescope.path}`);
|
|
2045
2071
|
const studio = production ? void 0 : normalizeStudioConfig(config.studio);
|
|
2046
|
-
if (studio) logger$
|
|
2072
|
+
if (studio) logger$7.log(`🗄️ Studio enabled at ${studio.path}`);
|
|
2047
2073
|
const hooks = normalizeHooksConfig(config.hooks);
|
|
2048
|
-
if (hooks) logger$
|
|
2074
|
+
if (hooks) logger$7.log(`🪝 Server hooks enabled`);
|
|
2049
2075
|
const services = config.docker?.compose?.services;
|
|
2050
2076
|
const dockerServices = services ? Array.isArray(services) ? {
|
|
2051
2077
|
postgres: services.includes("postgres"),
|
|
@@ -2077,12 +2103,12 @@ async function buildCommand(options) {
|
|
|
2077
2103
|
config.crons ? cronGenerator.load(config.crons) : [],
|
|
2078
2104
|
config.subscribers ? subscriberGenerator.load(config.subscribers) : []
|
|
2079
2105
|
]);
|
|
2080
|
-
logger$
|
|
2081
|
-
logger$
|
|
2082
|
-
logger$
|
|
2083
|
-
logger$
|
|
2106
|
+
logger$7.log(`Found ${allEndpoints.length} endpoints`);
|
|
2107
|
+
logger$7.log(`Found ${allFunctions.length} functions`);
|
|
2108
|
+
logger$7.log(`Found ${allCrons.length} crons`);
|
|
2109
|
+
logger$7.log(`Found ${allSubscribers.length} subscribers`);
|
|
2084
2110
|
if (allEndpoints.length === 0 && allFunctions.length === 0 && allCrons.length === 0 && allSubscribers.length === 0) {
|
|
2085
|
-
logger$
|
|
2111
|
+
logger$7.log("No endpoints, functions, crons, or subscribers found to process");
|
|
2086
2112
|
return {};
|
|
2087
2113
|
}
|
|
2088
2114
|
const rootOutputDir = (0, node_path.join)(process.cwd(), ".gkm");
|
|
@@ -2097,7 +2123,7 @@ async function buildCommand(options) {
|
|
|
2097
2123
|
async function buildForProvider(provider, context, rootOutputDir, endpointGenerator, functionGenerator, cronGenerator, subscriberGenerator, endpoints, functions, crons, subscribers, enableOpenApi, skipBundle, stage) {
|
|
2098
2124
|
const outputDir = (0, node_path.join)(process.cwd(), ".gkm", provider);
|
|
2099
2125
|
await (0, node_fs_promises.mkdir)(outputDir, { recursive: true });
|
|
2100
|
-
logger$
|
|
2126
|
+
logger$7.log(`\nGenerating handlers for provider: ${provider}`);
|
|
2101
2127
|
const [routes, functionInfos, cronInfos, subscriberInfos] = await Promise.all([
|
|
2102
2128
|
endpointGenerator.build(context, endpoints, outputDir, {
|
|
2103
2129
|
provider,
|
|
@@ -2107,7 +2133,7 @@ async function buildForProvider(provider, context, rootOutputDir, endpointGenera
|
|
|
2107
2133
|
cronGenerator.build(context, crons, outputDir, { provider }),
|
|
2108
2134
|
subscriberGenerator.build(context, subscribers, outputDir, { provider })
|
|
2109
2135
|
]);
|
|
2110
|
-
logger$
|
|
2136
|
+
logger$7.log(`Generated ${routes.length} routes, ${functionInfos.length} functions, ${cronInfos.length} crons, ${subscriberInfos.length} subscribers for ${provider}`);
|
|
2111
2137
|
if (provider === "server") {
|
|
2112
2138
|
const routeMetadata = await Promise.all(endpoints.map(async ({ construct }) => ({
|
|
2113
2139
|
path: construct._path,
|
|
@@ -2122,7 +2148,7 @@ async function buildForProvider(provider, context, rootOutputDir, endpointGenera
|
|
|
2122
2148
|
await generateServerManifest(rootOutputDir, appInfo, routeMetadata, subscriberInfos);
|
|
2123
2149
|
let masterKey;
|
|
2124
2150
|
if (context.production?.bundle && !skipBundle) {
|
|
2125
|
-
logger$
|
|
2151
|
+
logger$7.log(`\n📦 Bundling production server...`);
|
|
2126
2152
|
const { bundleServer } = await Promise.resolve().then(() => require("./bundler-BB-kETMd.cjs"));
|
|
2127
2153
|
const allConstructs = [
|
|
2128
2154
|
...endpoints.map((e) => e.construct),
|
|
@@ -2142,10 +2168,10 @@ async function buildForProvider(provider, context, rootOutputDir, endpointGenera
|
|
|
2142
2168
|
dockerServices
|
|
2143
2169
|
});
|
|
2144
2170
|
masterKey = bundleResult.masterKey;
|
|
2145
|
-
logger$
|
|
2171
|
+
logger$7.log(`✅ Bundle complete: .gkm/server/dist/server.mjs`);
|
|
2146
2172
|
if (masterKey) {
|
|
2147
|
-
logger$
|
|
2148
|
-
logger$
|
|
2173
|
+
logger$7.log(`\n🔐 Secrets encrypted for deployment`);
|
|
2174
|
+
logger$7.log(` Deploy with: GKM_MASTER_KEY=${masterKey}`);
|
|
2149
2175
|
}
|
|
2150
2176
|
}
|
|
2151
2177
|
return { masterKey };
|
|
@@ -2182,17 +2208,17 @@ async function workspaceBuildCommand(workspace, options) {
|
|
|
2182
2208
|
const apps = Object.entries(workspace.apps);
|
|
2183
2209
|
const backendApps = apps.filter(([, app]) => app.type === "backend");
|
|
2184
2210
|
const frontendApps = apps.filter(([, app]) => app.type === "frontend");
|
|
2185
|
-
logger$
|
|
2186
|
-
logger$
|
|
2187
|
-
logger$
|
|
2188
|
-
if (options.production) logger$
|
|
2211
|
+
logger$7.log(`\n🏗️ Building workspace: ${workspace.name}`);
|
|
2212
|
+
logger$7.log(` Backend apps: ${backendApps.map(([name$1]) => name$1).join(", ") || "none"}`);
|
|
2213
|
+
logger$7.log(` Frontend apps: ${frontendApps.map(([name$1]) => name$1).join(", ") || "none"}`);
|
|
2214
|
+
if (options.production) logger$7.log(` 🏭 Production mode enabled`);
|
|
2189
2215
|
const buildOrder = require_workspace.getAppBuildOrder(workspace);
|
|
2190
|
-
logger$
|
|
2216
|
+
logger$7.log(` Build order: ${buildOrder.join(" → ")}`);
|
|
2191
2217
|
const pm = detectPackageManager$2();
|
|
2192
|
-
logger$
|
|
2218
|
+
logger$7.log(`\n📦 Using ${pm} with Turbo for parallel builds...\n`);
|
|
2193
2219
|
try {
|
|
2194
2220
|
const turboCommand = getTurboCommand(pm);
|
|
2195
|
-
logger$
|
|
2221
|
+
logger$7.log(`Running: ${turboCommand}`);
|
|
2196
2222
|
await new Promise((resolve$3, reject) => {
|
|
2197
2223
|
const child = (0, node_child_process.spawn)(turboCommand, {
|
|
2198
2224
|
shell: true,
|
|
@@ -2220,15 +2246,15 @@ async function workspaceBuildCommand(workspace, options) {
|
|
|
2220
2246
|
outputPath
|
|
2221
2247
|
});
|
|
2222
2248
|
}
|
|
2223
|
-
logger$
|
|
2224
|
-
logger$
|
|
2249
|
+
logger$7.log(`\n✅ Workspace build complete!`);
|
|
2250
|
+
logger$7.log(`\n📋 Build Summary:`);
|
|
2225
2251
|
for (const result of results) {
|
|
2226
2252
|
const icon = result.type === "backend" ? "⚙️" : "🌐";
|
|
2227
|
-
logger$
|
|
2253
|
+
logger$7.log(` ${icon} ${result.appName}: ${result.outputPath || "built"}`);
|
|
2228
2254
|
}
|
|
2229
2255
|
} catch (error) {
|
|
2230
2256
|
const errorMessage = error instanceof Error ? error.message : "Build failed";
|
|
2231
|
-
logger$
|
|
2257
|
+
logger$7.log(`\n❌ Build failed: ${errorMessage}`);
|
|
2232
2258
|
for (const [appName, app] of apps) results.push({
|
|
2233
2259
|
appName,
|
|
2234
2260
|
type: app.type,
|
|
@@ -3336,7 +3362,7 @@ CMD ["node", "index.mjs"]
|
|
|
3336
3362
|
|
|
3337
3363
|
//#endregion
|
|
3338
3364
|
//#region src/docker/index.ts
|
|
3339
|
-
const logger$
|
|
3365
|
+
const logger$6 = console;
|
|
3340
3366
|
/**
|
|
3341
3367
|
* Docker command implementation
|
|
3342
3368
|
* Generates Dockerfile, docker-compose.yml, and related files
|
|
@@ -3347,7 +3373,7 @@ const logger$5 = console;
|
|
|
3347
3373
|
async function dockerCommand(options) {
|
|
3348
3374
|
const loadedConfig = await require_config.loadWorkspaceConfig();
|
|
3349
3375
|
if (loadedConfig.type === "workspace") {
|
|
3350
|
-
logger$
|
|
3376
|
+
logger$6.log("📦 Detected workspace configuration");
|
|
3351
3377
|
return workspaceDockerCommand(loadedConfig.workspace, options);
|
|
3352
3378
|
}
|
|
3353
3379
|
const config = await require_config.loadConfig();
|
|
@@ -3368,14 +3394,14 @@ async function dockerCommand(options) {
|
|
|
3368
3394
|
let useTurbo = options.turbo ?? false;
|
|
3369
3395
|
if (inMonorepo && !useSlim) if (hasTurbo) {
|
|
3370
3396
|
useTurbo = true;
|
|
3371
|
-
logger$
|
|
3397
|
+
logger$6.log(" Detected monorepo with turbo.json - using turbo prune");
|
|
3372
3398
|
} else throw new Error("Monorepo detected but turbo.json not found.\n\nDocker builds in monorepos require Turborepo for proper dependency isolation.\n\nTo fix this:\n 1. Install turbo: pnpm add -Dw turbo\n 2. Create turbo.json in your monorepo root\n 3. Run this command again\n\nSee: https://turbo.build/repo/docs/guides/tools/docker");
|
|
3373
3399
|
let turboPackage = options.turboPackage ?? dockerConfig.imageName;
|
|
3374
3400
|
if (useTurbo && !options.turboPackage) try {
|
|
3375
3401
|
const pkg$1 = require(`${process.cwd()}/package.json`);
|
|
3376
3402
|
if (pkg$1.name) {
|
|
3377
3403
|
turboPackage = pkg$1.name;
|
|
3378
|
-
logger$
|
|
3404
|
+
logger$6.log(` Turbo package: ${turboPackage}`);
|
|
3379
3405
|
}
|
|
3380
3406
|
} catch {}
|
|
3381
3407
|
const templateOptions = {
|
|
@@ -3392,7 +3418,7 @@ async function dockerCommand(options) {
|
|
|
3392
3418
|
const dockerMode = useSlim ? "slim" : useTurbo ? "turbo" : "multi-stage";
|
|
3393
3419
|
const dockerfilePath = (0, node_path.join)(dockerDir, "Dockerfile");
|
|
3394
3420
|
await (0, node_fs_promises.writeFile)(dockerfilePath, dockerfile);
|
|
3395
|
-
logger$
|
|
3421
|
+
logger$6.log(`Generated: .gkm/docker/Dockerfile (${dockerMode}, ${packageManager})`);
|
|
3396
3422
|
const composeOptions = {
|
|
3397
3423
|
imageName: dockerConfig.imageName,
|
|
3398
3424
|
registry: options.registry ?? dockerConfig.registry,
|
|
@@ -3404,15 +3430,15 @@ async function dockerCommand(options) {
|
|
|
3404
3430
|
const dockerCompose = hasServices ? generateDockerCompose(composeOptions) : generateMinimalDockerCompose(composeOptions);
|
|
3405
3431
|
const composePath = (0, node_path.join)(dockerDir, "docker-compose.yml");
|
|
3406
3432
|
await (0, node_fs_promises.writeFile)(composePath, dockerCompose);
|
|
3407
|
-
logger$
|
|
3433
|
+
logger$6.log("Generated: .gkm/docker/docker-compose.yml");
|
|
3408
3434
|
const dockerignore = generateDockerignore();
|
|
3409
3435
|
const dockerignorePath = (0, node_path.join)(process.cwd(), ".dockerignore");
|
|
3410
3436
|
await (0, node_fs_promises.writeFile)(dockerignorePath, dockerignore);
|
|
3411
|
-
logger$
|
|
3437
|
+
logger$6.log("Generated: .dockerignore (project root)");
|
|
3412
3438
|
const entrypoint = generateDockerEntrypoint();
|
|
3413
3439
|
const entrypointPath = (0, node_path.join)(dockerDir, "docker-entrypoint.sh");
|
|
3414
3440
|
await (0, node_fs_promises.writeFile)(entrypointPath, entrypoint);
|
|
3415
|
-
logger$
|
|
3441
|
+
logger$6.log("Generated: .gkm/docker/docker-entrypoint.sh");
|
|
3416
3442
|
const result = {
|
|
3417
3443
|
dockerfile: dockerfilePath,
|
|
3418
3444
|
dockerCompose: composePath,
|
|
@@ -3431,13 +3457,13 @@ async function dockerCommand(options) {
|
|
|
3431
3457
|
function ensureLockfile(cwd) {
|
|
3432
3458
|
const lockfilePath = findLockfilePath(cwd);
|
|
3433
3459
|
if (!lockfilePath) {
|
|
3434
|
-
logger$
|
|
3460
|
+
logger$6.warn("\n⚠️ No lockfile found. Docker build may fail or use stale dependencies.");
|
|
3435
3461
|
return null;
|
|
3436
3462
|
}
|
|
3437
3463
|
const lockfileName = (0, node_path.basename)(lockfilePath);
|
|
3438
3464
|
const localLockfile = (0, node_path.join)(cwd, lockfileName);
|
|
3439
3465
|
if (lockfilePath === localLockfile) return null;
|
|
3440
|
-
logger$
|
|
3466
|
+
logger$6.log(` Copying ${lockfileName} from monorepo root...`);
|
|
3441
3467
|
(0, node_fs.copyFileSync)(lockfilePath, localLockfile);
|
|
3442
3468
|
return () => {
|
|
3443
3469
|
try {
|
|
@@ -3453,7 +3479,7 @@ async function buildDockerImage(imageName, options) {
|
|
|
3453
3479
|
const tag = options.tag ?? "latest";
|
|
3454
3480
|
const registry = options.registry;
|
|
3455
3481
|
const fullImageName = registry ? `${registry}/${imageName}:${tag}` : `${imageName}:${tag}`;
|
|
3456
|
-
logger$
|
|
3482
|
+
logger$6.log(`\n🐳 Building Docker image: ${fullImageName}`);
|
|
3457
3483
|
const cwd = process.cwd();
|
|
3458
3484
|
const cleanup = ensureLockfile(cwd);
|
|
3459
3485
|
try {
|
|
@@ -3465,7 +3491,7 @@ async function buildDockerImage(imageName, options) {
|
|
|
3465
3491
|
DOCKER_BUILDKIT: "1"
|
|
3466
3492
|
}
|
|
3467
3493
|
});
|
|
3468
|
-
logger$
|
|
3494
|
+
logger$6.log(`✅ Docker image built: ${fullImageName}`);
|
|
3469
3495
|
} catch (error) {
|
|
3470
3496
|
throw new Error(`Failed to build Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3471
3497
|
} finally {
|
|
@@ -3480,13 +3506,13 @@ async function pushDockerImage(imageName, options) {
|
|
|
3480
3506
|
const registry = options.registry;
|
|
3481
3507
|
if (!registry) throw new Error("Registry is required to push Docker image. Use --registry or configure docker.registry in gkm.config.ts");
|
|
3482
3508
|
const fullImageName = `${registry}/${imageName}:${tag}`;
|
|
3483
|
-
logger$
|
|
3509
|
+
logger$6.log(`\n🚀 Pushing Docker image: ${fullImageName}`);
|
|
3484
3510
|
try {
|
|
3485
3511
|
(0, node_child_process.execSync)(`docker push ${fullImageName}`, {
|
|
3486
3512
|
cwd: process.cwd(),
|
|
3487
3513
|
stdio: "inherit"
|
|
3488
3514
|
});
|
|
3489
|
-
logger$
|
|
3515
|
+
logger$6.log(`✅ Docker image pushed: ${fullImageName}`);
|
|
3490
3516
|
} catch (error) {
|
|
3491
3517
|
throw new Error(`Failed to push Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3492
3518
|
}
|
|
@@ -3512,11 +3538,11 @@ function getAppPackageName(appPath) {
|
|
|
3512
3538
|
async function workspaceDockerCommand(workspace, options) {
|
|
3513
3539
|
const results = [];
|
|
3514
3540
|
const apps = Object.entries(workspace.apps);
|
|
3515
|
-
logger$
|
|
3541
|
+
logger$6.log(`\n🐳 Generating Dockerfiles for workspace: ${workspace.name}`);
|
|
3516
3542
|
const dockerDir = (0, node_path.join)(workspace.root, ".gkm", "docker");
|
|
3517
3543
|
await (0, node_fs_promises.mkdir)(dockerDir, { recursive: true });
|
|
3518
3544
|
const packageManager = detectPackageManager$1(workspace.root);
|
|
3519
|
-
logger$
|
|
3545
|
+
logger$6.log(` Package manager: ${packageManager}`);
|
|
3520
3546
|
for (const [appName, app] of apps) {
|
|
3521
3547
|
const appPath = app.path;
|
|
3522
3548
|
const fullAppPath = (0, node_path.join)(workspace.root, appPath);
|
|
@@ -3524,7 +3550,7 @@ async function workspaceDockerCommand(workspace, options) {
|
|
|
3524
3550
|
const imageName = appName;
|
|
3525
3551
|
const hasEntry = !!app.entry;
|
|
3526
3552
|
const buildType = hasEntry ? "entry" : app.type;
|
|
3527
|
-
logger$
|
|
3553
|
+
logger$6.log(`\n 📄 Generating Dockerfile for ${appName} (${buildType})`);
|
|
3528
3554
|
let dockerfile;
|
|
3529
3555
|
if (app.type === "frontend") dockerfile = generateNextjsDockerfile({
|
|
3530
3556
|
imageName,
|
|
@@ -3555,7 +3581,7 @@ async function workspaceDockerCommand(workspace, options) {
|
|
|
3555
3581
|
});
|
|
3556
3582
|
const dockerfilePath = (0, node_path.join)(dockerDir, `Dockerfile.${appName}`);
|
|
3557
3583
|
await (0, node_fs_promises.writeFile)(dockerfilePath, dockerfile);
|
|
3558
|
-
logger$
|
|
3584
|
+
logger$6.log(` Generated: .gkm/docker/Dockerfile.${appName}`);
|
|
3559
3585
|
results.push({
|
|
3560
3586
|
appName,
|
|
3561
3587
|
type: app.type,
|
|
@@ -3566,19 +3592,19 @@ async function workspaceDockerCommand(workspace, options) {
|
|
|
3566
3592
|
const dockerignore = generateDockerignore();
|
|
3567
3593
|
const dockerignorePath = (0, node_path.join)(workspace.root, ".dockerignore");
|
|
3568
3594
|
await (0, node_fs_promises.writeFile)(dockerignorePath, dockerignore);
|
|
3569
|
-
logger$
|
|
3595
|
+
logger$6.log(`\n Generated: .dockerignore (workspace root)`);
|
|
3570
3596
|
const dockerCompose = generateWorkspaceCompose(workspace, { registry: options.registry });
|
|
3571
3597
|
const composePath = (0, node_path.join)(dockerDir, "docker-compose.yml");
|
|
3572
3598
|
await (0, node_fs_promises.writeFile)(composePath, dockerCompose);
|
|
3573
|
-
logger$
|
|
3574
|
-
logger$
|
|
3575
|
-
logger$
|
|
3599
|
+
logger$6.log(` Generated: .gkm/docker/docker-compose.yml`);
|
|
3600
|
+
logger$6.log(`\n✅ Generated ${results.length} Dockerfile(s) + docker-compose.yml`);
|
|
3601
|
+
logger$6.log("\n📋 Build commands:");
|
|
3576
3602
|
for (const result of results) {
|
|
3577
3603
|
const icon = result.type === "backend" ? "⚙️" : "🌐";
|
|
3578
|
-
logger$
|
|
3604
|
+
logger$6.log(` ${icon} docker build -f .gkm/docker/Dockerfile.${result.appName} -t ${result.imageName} .`);
|
|
3579
3605
|
}
|
|
3580
|
-
logger$
|
|
3581
|
-
logger$
|
|
3606
|
+
logger$6.log("\n📋 Run all services:");
|
|
3607
|
+
logger$6.log(" docker compose -f .gkm/docker/docker-compose.yml up --build");
|
|
3582
3608
|
return {
|
|
3583
3609
|
apps: results,
|
|
3584
3610
|
dockerCompose: composePath,
|
|
@@ -3618,7 +3644,7 @@ function getAppNameFromPackageJson() {
|
|
|
3618
3644
|
} catch {}
|
|
3619
3645
|
return void 0;
|
|
3620
3646
|
}
|
|
3621
|
-
const logger$
|
|
3647
|
+
const logger$5 = console;
|
|
3622
3648
|
/**
|
|
3623
3649
|
* Get the full image reference
|
|
3624
3650
|
*/
|
|
@@ -3633,18 +3659,18 @@ function getImageRef(registry, imageName, tag) {
|
|
|
3633
3659
|
* @param buildArgs - Build arguments to pass to docker build
|
|
3634
3660
|
*/
|
|
3635
3661
|
async function buildImage(imageRef, appName, buildArgs) {
|
|
3636
|
-
logger$
|
|
3662
|
+
logger$5.log(`\n🔨 Building Docker image: ${imageRef}`);
|
|
3637
3663
|
const cwd = process.cwd();
|
|
3638
3664
|
const lockfilePath = findLockfilePath(cwd);
|
|
3639
3665
|
const lockfileDir = lockfilePath ? (0, node_path.dirname)(lockfilePath) : cwd;
|
|
3640
3666
|
const inMonorepo = lockfileDir !== cwd;
|
|
3641
|
-
if (appName || inMonorepo) logger$
|
|
3642
|
-
else logger$
|
|
3667
|
+
if (appName || inMonorepo) logger$5.log(" Generating Dockerfile for monorepo (turbo prune)...");
|
|
3668
|
+
else logger$5.log(" Generating Dockerfile...");
|
|
3643
3669
|
await dockerCommand({});
|
|
3644
3670
|
const dockerfileSuffix = appName ? `.${appName}` : "";
|
|
3645
3671
|
const dockerfilePath = `.gkm/docker/Dockerfile${dockerfileSuffix}`;
|
|
3646
3672
|
const buildCwd = lockfilePath && (inMonorepo || appName) ? lockfileDir : cwd;
|
|
3647
|
-
if (buildCwd !== cwd) logger$
|
|
3673
|
+
if (buildCwd !== cwd) logger$5.log(` Building from workspace root: ${buildCwd}`);
|
|
3648
3674
|
const buildArgsString = buildArgs && buildArgs.length > 0 ? buildArgs.map((arg) => `--build-arg "${arg}"`).join(" ") : "";
|
|
3649
3675
|
try {
|
|
3650
3676
|
const cmd = [
|
|
@@ -3663,7 +3689,7 @@ async function buildImage(imageRef, appName, buildArgs) {
|
|
|
3663
3689
|
DOCKER_BUILDKIT: "1"
|
|
3664
3690
|
}
|
|
3665
3691
|
});
|
|
3666
|
-
logger$
|
|
3692
|
+
logger$5.log(`✅ Image built: ${imageRef}`);
|
|
3667
3693
|
} catch (error) {
|
|
3668
3694
|
throw new Error(`Failed to build Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3669
3695
|
}
|
|
@@ -3672,13 +3698,13 @@ async function buildImage(imageRef, appName, buildArgs) {
|
|
|
3672
3698
|
* Push Docker image to registry
|
|
3673
3699
|
*/
|
|
3674
3700
|
async function pushImage(imageRef) {
|
|
3675
|
-
logger$
|
|
3701
|
+
logger$5.log(`\n☁️ Pushing image: ${imageRef}`);
|
|
3676
3702
|
try {
|
|
3677
3703
|
(0, node_child_process.execSync)(`docker push ${imageRef}`, {
|
|
3678
3704
|
cwd: process.cwd(),
|
|
3679
3705
|
stdio: "inherit"
|
|
3680
3706
|
});
|
|
3681
|
-
logger$
|
|
3707
|
+
logger$5.log(`✅ Image pushed: ${imageRef}`);
|
|
3682
3708
|
} catch (error) {
|
|
3683
3709
|
throw new Error(`Failed to push Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3684
3710
|
}
|
|
@@ -3691,17 +3717,17 @@ async function deployDocker(options) {
|
|
|
3691
3717
|
const imageName = config.imageName;
|
|
3692
3718
|
const imageRef = getImageRef(config.registry, imageName, tag);
|
|
3693
3719
|
await buildImage(imageRef, config.appName, buildArgs);
|
|
3694
|
-
if (!skipPush) if (!config.registry) logger$
|
|
3720
|
+
if (!skipPush) if (!config.registry) logger$5.warn("\n⚠️ No registry configured. Use --skip-push or configure docker.registry in gkm.config.ts");
|
|
3695
3721
|
else await pushImage(imageRef);
|
|
3696
|
-
logger$
|
|
3697
|
-
logger$
|
|
3698
|
-
logger$
|
|
3699
|
-
logger$
|
|
3722
|
+
logger$5.log("\n✅ Docker deployment ready!");
|
|
3723
|
+
logger$5.log(`\n📋 Deployment details:`);
|
|
3724
|
+
logger$5.log(` Image: ${imageRef}`);
|
|
3725
|
+
logger$5.log(` Stage: ${stage}`);
|
|
3700
3726
|
if (masterKey) {
|
|
3701
|
-
logger$
|
|
3702
|
-
logger$
|
|
3703
|
-
logger$
|
|
3704
|
-
logger$
|
|
3727
|
+
logger$5.log(`\n🔐 Deploy with this environment variable:`);
|
|
3728
|
+
logger$5.log(` GKM_MASTER_KEY=${masterKey}`);
|
|
3729
|
+
logger$5.log("\n Example docker run:");
|
|
3730
|
+
logger$5.log(` docker run -e GKM_MASTER_KEY=${masterKey} ${imageRef}`);
|
|
3705
3731
|
}
|
|
3706
3732
|
return {
|
|
3707
3733
|
imageRef,
|
|
@@ -3728,7 +3754,7 @@ function resolveDockerConfig(config) {
|
|
|
3728
3754
|
|
|
3729
3755
|
//#endregion
|
|
3730
3756
|
//#region src/deploy/dokploy.ts
|
|
3731
|
-
const logger$
|
|
3757
|
+
const logger$4 = console;
|
|
3732
3758
|
/**
|
|
3733
3759
|
* Get the Dokploy API token from stored credentials or environment
|
|
3734
3760
|
*/
|
|
@@ -3752,25 +3778,25 @@ async function createApi$1(endpoint) {
|
|
|
3752
3778
|
*/
|
|
3753
3779
|
async function deployDokploy(options) {
|
|
3754
3780
|
const { stage, imageRef, masterKey, config } = options;
|
|
3755
|
-
logger$
|
|
3756
|
-
logger$
|
|
3757
|
-
logger$
|
|
3781
|
+
logger$4.log(`\n🎯 Deploying to Dokploy...`);
|
|
3782
|
+
logger$4.log(` Endpoint: ${config.endpoint}`);
|
|
3783
|
+
logger$4.log(` Application: ${config.applicationId}`);
|
|
3758
3784
|
const api = await createApi$1(config.endpoint);
|
|
3759
|
-
logger$
|
|
3785
|
+
logger$4.log(` Configuring Docker image: ${imageRef}`);
|
|
3760
3786
|
const registryOptions = {};
|
|
3761
3787
|
if (config.registryId) {
|
|
3762
3788
|
registryOptions.registryId = config.registryId;
|
|
3763
|
-
logger$
|
|
3789
|
+
logger$4.log(` Using Dokploy registry: ${config.registryId}`);
|
|
3764
3790
|
} else {
|
|
3765
3791
|
const storedRegistryId = await getDokployRegistryId();
|
|
3766
3792
|
if (storedRegistryId) {
|
|
3767
3793
|
registryOptions.registryId = storedRegistryId;
|
|
3768
|
-
logger$
|
|
3794
|
+
logger$4.log(` Using stored Dokploy registry: ${storedRegistryId}`);
|
|
3769
3795
|
} else if (config.registryCredentials) {
|
|
3770
3796
|
registryOptions.username = config.registryCredentials.username;
|
|
3771
3797
|
registryOptions.password = config.registryCredentials.password;
|
|
3772
3798
|
registryOptions.registryUrl = config.registryCredentials.registryUrl;
|
|
3773
|
-
logger$
|
|
3799
|
+
logger$4.log(` Using registry credentials for: ${config.registryCredentials.registryUrl}`);
|
|
3774
3800
|
} else {
|
|
3775
3801
|
const username = process.env.DOCKER_REGISTRY_USERNAME;
|
|
3776
3802
|
const password = process.env.DOCKER_REGISTRY_PASSWORD;
|
|
@@ -3779,31 +3805,31 @@ async function deployDokploy(options) {
|
|
|
3779
3805
|
registryOptions.username = username;
|
|
3780
3806
|
registryOptions.password = password;
|
|
3781
3807
|
registryOptions.registryUrl = registryUrl;
|
|
3782
|
-
logger$
|
|
3808
|
+
logger$4.log(` Using registry credentials from environment`);
|
|
3783
3809
|
}
|
|
3784
3810
|
}
|
|
3785
3811
|
}
|
|
3786
3812
|
await api.saveDockerProvider(config.applicationId, imageRef, registryOptions);
|
|
3787
|
-
logger$
|
|
3813
|
+
logger$4.log(" ✓ Docker provider configured");
|
|
3788
3814
|
const envVars = {};
|
|
3789
3815
|
if (masterKey) envVars.GKM_MASTER_KEY = masterKey;
|
|
3790
3816
|
if (Object.keys(envVars).length > 0) {
|
|
3791
|
-
logger$
|
|
3817
|
+
logger$4.log(" Updating environment variables...");
|
|
3792
3818
|
const envString = Object.entries(envVars).map(([key, value]) => `${key}=${value}`).join("\n");
|
|
3793
3819
|
await api.saveApplicationEnv(config.applicationId, envString);
|
|
3794
|
-
logger$
|
|
3820
|
+
logger$4.log(" ✓ Environment variables updated");
|
|
3795
3821
|
}
|
|
3796
|
-
logger$
|
|
3822
|
+
logger$4.log(" Triggering deployment...");
|
|
3797
3823
|
await api.deployApplication(config.applicationId);
|
|
3798
|
-
logger$
|
|
3799
|
-
logger$
|
|
3800
|
-
logger$
|
|
3801
|
-
logger$
|
|
3802
|
-
logger$
|
|
3803
|
-
logger$
|
|
3804
|
-
if (masterKey) logger$
|
|
3824
|
+
logger$4.log(" ✓ Deployment triggered");
|
|
3825
|
+
logger$4.log("\n✅ Dokploy deployment initiated!");
|
|
3826
|
+
logger$4.log(`\n📋 Deployment details:`);
|
|
3827
|
+
logger$4.log(` Image: ${imageRef}`);
|
|
3828
|
+
logger$4.log(` Stage: ${stage}`);
|
|
3829
|
+
logger$4.log(` Application ID: ${config.applicationId}`);
|
|
3830
|
+
if (masterKey) logger$4.log(`\n🔐 GKM_MASTER_KEY has been set in Dokploy environment`);
|
|
3805
3831
|
const deploymentUrl = `${config.endpoint}/project/${config.projectId}`;
|
|
3806
|
-
logger$
|
|
3832
|
+
logger$4.log(`\n🔗 View deployment: ${deploymentUrl}`);
|
|
3807
3833
|
return {
|
|
3808
3834
|
imageRef,
|
|
3809
3835
|
masterKey,
|
|
@@ -3811,6 +3837,473 @@ async function deployDokploy(options) {
|
|
|
3811
3837
|
};
|
|
3812
3838
|
}
|
|
3813
3839
|
|
|
3840
|
+
//#endregion
|
|
3841
|
+
//#region src/deploy/state.ts
|
|
3842
|
+
/**
|
|
3843
|
+
* Get the state file path for a stage
|
|
3844
|
+
*/
|
|
3845
|
+
function getStateFilePath(workspaceRoot, stage) {
|
|
3846
|
+
return (0, node_path.join)(workspaceRoot, ".gkm", `deploy-${stage}.json`);
|
|
3847
|
+
}
|
|
3848
|
+
/**
|
|
3849
|
+
* Read the deploy state for a stage
|
|
3850
|
+
* Returns null if state file doesn't exist
|
|
3851
|
+
*/
|
|
3852
|
+
async function readStageState(workspaceRoot, stage) {
|
|
3853
|
+
const filePath = getStateFilePath(workspaceRoot, stage);
|
|
3854
|
+
try {
|
|
3855
|
+
const content = await (0, node_fs_promises.readFile)(filePath, "utf-8");
|
|
3856
|
+
return JSON.parse(content);
|
|
3857
|
+
} catch (error) {
|
|
3858
|
+
if (error.code === "ENOENT") return null;
|
|
3859
|
+
console.warn(`Warning: Could not read deploy state: ${error}`);
|
|
3860
|
+
return null;
|
|
3861
|
+
}
|
|
3862
|
+
}
|
|
3863
|
+
/**
|
|
3864
|
+
* Write the deploy state for a stage
|
|
3865
|
+
*/
|
|
3866
|
+
async function writeStageState(workspaceRoot, stage, state) {
|
|
3867
|
+
const filePath = getStateFilePath(workspaceRoot, stage);
|
|
3868
|
+
const dir = (0, node_path.join)(workspaceRoot, ".gkm");
|
|
3869
|
+
await (0, node_fs_promises.mkdir)(dir, { recursive: true });
|
|
3870
|
+
state.lastDeployedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
3871
|
+
await (0, node_fs_promises.writeFile)(filePath, JSON.stringify(state, null, 2));
|
|
3872
|
+
}
|
|
3873
|
+
/**
|
|
3874
|
+
* Create a new empty state for a stage
|
|
3875
|
+
*/
|
|
3876
|
+
function createEmptyState(stage, environmentId) {
|
|
3877
|
+
return {
|
|
3878
|
+
provider: "dokploy",
|
|
3879
|
+
stage,
|
|
3880
|
+
environmentId,
|
|
3881
|
+
applications: {},
|
|
3882
|
+
services: {},
|
|
3883
|
+
lastDeployedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3884
|
+
};
|
|
3885
|
+
}
|
|
3886
|
+
/**
|
|
3887
|
+
* Get application ID from state
|
|
3888
|
+
*/
|
|
3889
|
+
function getApplicationId(state, appName) {
|
|
3890
|
+
return state?.applications[appName];
|
|
3891
|
+
}
|
|
3892
|
+
/**
|
|
3893
|
+
* Set application ID in state (mutates state)
|
|
3894
|
+
*/
|
|
3895
|
+
function setApplicationId(state, appName, applicationId) {
|
|
3896
|
+
state.applications[appName] = applicationId;
|
|
3897
|
+
}
|
|
3898
|
+
/**
|
|
3899
|
+
* Get postgres ID from state
|
|
3900
|
+
*/
|
|
3901
|
+
function getPostgresId(state) {
|
|
3902
|
+
return state?.services.postgresId;
|
|
3903
|
+
}
|
|
3904
|
+
/**
|
|
3905
|
+
* Set postgres ID in state (mutates state)
|
|
3906
|
+
*/
|
|
3907
|
+
function setPostgresId(state, postgresId) {
|
|
3908
|
+
state.services.postgresId = postgresId;
|
|
3909
|
+
}
|
|
3910
|
+
/**
|
|
3911
|
+
* Get redis ID from state
|
|
3912
|
+
*/
|
|
3913
|
+
function getRedisId(state) {
|
|
3914
|
+
return state?.services.redisId;
|
|
3915
|
+
}
|
|
3916
|
+
/**
|
|
3917
|
+
* Set redis ID in state (mutates state)
|
|
3918
|
+
*/
|
|
3919
|
+
function setRedisId(state, redisId) {
|
|
3920
|
+
state.services.redisId = redisId;
|
|
3921
|
+
}
|
|
3922
|
+
|
|
3923
|
+
//#endregion
|
|
3924
|
+
//#region src/deploy/dns/hostinger-api.ts
|
|
3925
|
+
/**
|
|
3926
|
+
* Hostinger DNS API client
|
|
3927
|
+
*
|
|
3928
|
+
* API Documentation: https://developers.hostinger.com/
|
|
3929
|
+
* Authentication: Bearer token from hpanel.hostinger.com/profile/api
|
|
3930
|
+
*/
|
|
3931
|
+
const HOSTINGER_API_BASE = "https://api.hostinger.com";
|
|
3932
|
+
/**
|
|
3933
|
+
* Hostinger API error
|
|
3934
|
+
*/
|
|
3935
|
+
var HostingerApiError = class extends Error {
|
|
3936
|
+
constructor(message, status, statusText, errors) {
|
|
3937
|
+
super(message);
|
|
3938
|
+
this.status = status;
|
|
3939
|
+
this.statusText = statusText;
|
|
3940
|
+
this.errors = errors;
|
|
3941
|
+
this.name = "HostingerApiError";
|
|
3942
|
+
}
|
|
3943
|
+
};
|
|
3944
|
+
/**
|
|
3945
|
+
* Hostinger DNS API client
|
|
3946
|
+
*
|
|
3947
|
+
* @example
|
|
3948
|
+
* ```ts
|
|
3949
|
+
* const api = new HostingerApi(token);
|
|
3950
|
+
*
|
|
3951
|
+
* // Get all records for a domain
|
|
3952
|
+
* const records = await api.getRecords('traflabs.io');
|
|
3953
|
+
*
|
|
3954
|
+
* // Create/update records
|
|
3955
|
+
* await api.upsertRecords('traflabs.io', [
|
|
3956
|
+
* { name: 'api.joemoer', type: 'A', ttl: 300, records: ['1.2.3.4'] }
|
|
3957
|
+
* ]);
|
|
3958
|
+
* ```
|
|
3959
|
+
*/
|
|
3960
|
+
var HostingerApi = class {
|
|
3961
|
+
token;
|
|
3962
|
+
constructor(token) {
|
|
3963
|
+
this.token = token;
|
|
3964
|
+
}
|
|
3965
|
+
/**
|
|
3966
|
+
* Make a request to the Hostinger API
|
|
3967
|
+
*/
|
|
3968
|
+
async request(method, endpoint, body) {
|
|
3969
|
+
const url = `${HOSTINGER_API_BASE}${endpoint}`;
|
|
3970
|
+
const response = await fetch(url, {
|
|
3971
|
+
method,
|
|
3972
|
+
headers: {
|
|
3973
|
+
"Content-Type": "application/json",
|
|
3974
|
+
Authorization: `Bearer ${this.token}`
|
|
3975
|
+
},
|
|
3976
|
+
body: body ? JSON.stringify(body) : void 0
|
|
3977
|
+
});
|
|
3978
|
+
if (!response.ok) {
|
|
3979
|
+
let errorMessage = `Hostinger API error: ${response.status} ${response.statusText}`;
|
|
3980
|
+
let errors;
|
|
3981
|
+
try {
|
|
3982
|
+
const errorBody = await response.json();
|
|
3983
|
+
if (errorBody.message) errorMessage = `Hostinger API error: ${errorBody.message}`;
|
|
3984
|
+
errors = errorBody.errors;
|
|
3985
|
+
} catch {}
|
|
3986
|
+
throw new HostingerApiError(errorMessage, response.status, response.statusText, errors);
|
|
3987
|
+
}
|
|
3988
|
+
const text = await response.text();
|
|
3989
|
+
if (!text || text.trim() === "") return void 0;
|
|
3990
|
+
return JSON.parse(text);
|
|
3991
|
+
}
|
|
3992
|
+
/**
|
|
3993
|
+
* Get all DNS records for a domain
|
|
3994
|
+
*
|
|
3995
|
+
* @param domain - Root domain (e.g., 'traflabs.io')
|
|
3996
|
+
*/
|
|
3997
|
+
async getRecords(domain) {
|
|
3998
|
+
const response = await this.request("GET", `/api/dns/v1/zones/${domain}`);
|
|
3999
|
+
return response.data || [];
|
|
4000
|
+
}
|
|
4001
|
+
/**
|
|
4002
|
+
* Create or update DNS records
|
|
4003
|
+
*
|
|
4004
|
+
* @param domain - Root domain (e.g., 'traflabs.io')
|
|
4005
|
+
* @param records - Records to create/update
|
|
4006
|
+
* @param overwrite - If true, replaces all existing records. If false, merges with existing.
|
|
4007
|
+
*/
|
|
4008
|
+
async upsertRecords(domain, records, overwrite = false) {
|
|
4009
|
+
await this.request("PUT", `/api/dns/v1/zones/${domain}`, {
|
|
4010
|
+
overwrite,
|
|
4011
|
+
zone: records
|
|
4012
|
+
});
|
|
4013
|
+
}
|
|
4014
|
+
/**
|
|
4015
|
+
* Validate DNS records before applying
|
|
4016
|
+
*
|
|
4017
|
+
* @param domain - Root domain (e.g., 'traflabs.io')
|
|
4018
|
+
* @param records - Records to validate
|
|
4019
|
+
* @returns true if valid, throws if invalid
|
|
4020
|
+
*/
|
|
4021
|
+
async validateRecords(domain, records) {
|
|
4022
|
+
await this.request("POST", `/api/dns/v1/zones/${domain}/validate`, {
|
|
4023
|
+
overwrite: false,
|
|
4024
|
+
zone: records
|
|
4025
|
+
});
|
|
4026
|
+
return true;
|
|
4027
|
+
}
|
|
4028
|
+
/**
|
|
4029
|
+
* Delete specific DNS records
|
|
4030
|
+
*
|
|
4031
|
+
* @param domain - Root domain (e.g., 'traflabs.io')
|
|
4032
|
+
* @param filters - Filters to match records for deletion
|
|
4033
|
+
*/
|
|
4034
|
+
async deleteRecords(domain, filters) {
|
|
4035
|
+
await this.request("DELETE", `/api/dns/v1/zones/${domain}`, { filters });
|
|
4036
|
+
}
|
|
4037
|
+
/**
|
|
4038
|
+
* Check if a specific record exists
|
|
4039
|
+
*
|
|
4040
|
+
* @param domain - Root domain (e.g., 'traflabs.io')
|
|
4041
|
+
* @param name - Subdomain name (e.g., 'api.joemoer')
|
|
4042
|
+
* @param type - Record type (e.g., 'A')
|
|
4043
|
+
*/
|
|
4044
|
+
async recordExists(domain, name$1, type$1 = "A") {
|
|
4045
|
+
const records = await this.getRecords(domain);
|
|
4046
|
+
return records.some((r) => r.name === name$1 && r.type === type$1);
|
|
4047
|
+
}
|
|
4048
|
+
/**
|
|
4049
|
+
* Create a single A record if it doesn't exist
|
|
4050
|
+
*
|
|
4051
|
+
* @param domain - Root domain (e.g., 'traflabs.io')
|
|
4052
|
+
* @param subdomain - Subdomain name (e.g., 'api.joemoer')
|
|
4053
|
+
* @param ip - IP address to point to
|
|
4054
|
+
* @param ttl - TTL in seconds (default: 300)
|
|
4055
|
+
* @returns true if created, false if already exists
|
|
4056
|
+
*/
|
|
4057
|
+
async createARecordIfNotExists(domain, subdomain, ip, ttl = 300) {
|
|
4058
|
+
const exists = await this.recordExists(domain, subdomain, "A");
|
|
4059
|
+
if (exists) return false;
|
|
4060
|
+
await this.upsertRecords(domain, [{
|
|
4061
|
+
name: subdomain,
|
|
4062
|
+
type: "A",
|
|
4063
|
+
ttl,
|
|
4064
|
+
records: [ip]
|
|
4065
|
+
}]);
|
|
4066
|
+
return true;
|
|
4067
|
+
}
|
|
4068
|
+
};
|
|
4069
|
+
|
|
4070
|
+
//#endregion
|
|
4071
|
+
//#region src/deploy/dns/index.ts
|
|
4072
|
+
const logger$3 = console;
|
|
4073
|
+
/**
|
|
4074
|
+
* Resolve IP address from a hostname
|
|
4075
|
+
*/
|
|
4076
|
+
async function resolveHostnameToIp(hostname) {
|
|
4077
|
+
try {
|
|
4078
|
+
const addresses = await (0, node_dns_promises.lookup)(hostname, { family: 4 });
|
|
4079
|
+
return addresses.address;
|
|
4080
|
+
} catch (error) {
|
|
4081
|
+
throw new Error(`Failed to resolve IP for ${hostname}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
4082
|
+
}
|
|
4083
|
+
}
|
|
4084
|
+
/**
|
|
4085
|
+
* Extract subdomain from full hostname relative to root domain
|
|
4086
|
+
*
|
|
4087
|
+
* @example
|
|
4088
|
+
* extractSubdomain('api.joemoer.traflabs.io', 'traflabs.io') => 'api.joemoer'
|
|
4089
|
+
* extractSubdomain('joemoer.traflabs.io', 'traflabs.io') => 'joemoer'
|
|
4090
|
+
*/
|
|
4091
|
+
function extractSubdomain(hostname, rootDomain) {
|
|
4092
|
+
if (!hostname.endsWith(rootDomain)) throw new Error(`Hostname ${hostname} is not under root domain ${rootDomain}`);
|
|
4093
|
+
const subdomain = hostname.slice(0, -(rootDomain.length + 1));
|
|
4094
|
+
return subdomain || "@";
|
|
4095
|
+
}
|
|
4096
|
+
/**
|
|
4097
|
+
* Generate required DNS records for a deployment
|
|
4098
|
+
*/
|
|
4099
|
+
function generateRequiredRecords(appHostnames, rootDomain, serverIp) {
|
|
4100
|
+
const records = [];
|
|
4101
|
+
for (const [appName, hostname] of appHostnames) {
|
|
4102
|
+
const subdomain = extractSubdomain(hostname, rootDomain);
|
|
4103
|
+
records.push({
|
|
4104
|
+
hostname,
|
|
4105
|
+
subdomain,
|
|
4106
|
+
type: "A",
|
|
4107
|
+
value: serverIp,
|
|
4108
|
+
appName
|
|
4109
|
+
});
|
|
4110
|
+
}
|
|
4111
|
+
return records;
|
|
4112
|
+
}
|
|
4113
|
+
/**
|
|
4114
|
+
* Print DNS records table
|
|
4115
|
+
*/
|
|
4116
|
+
function printDnsRecordsTable(records, rootDomain) {
|
|
4117
|
+
logger$3.log("\n 📋 DNS Records for " + rootDomain + ":");
|
|
4118
|
+
logger$3.log(" ┌─────────────────────────────────────┬──────┬─────────────────┬────────┐");
|
|
4119
|
+
logger$3.log(" │ Subdomain │ Type │ Value │ Status │");
|
|
4120
|
+
logger$3.log(" ├─────────────────────────────────────┼──────┼─────────────────┼────────┤");
|
|
4121
|
+
for (const record of records) {
|
|
4122
|
+
const subdomain = record.subdomain.padEnd(35);
|
|
4123
|
+
const type$1 = record.type.padEnd(4);
|
|
4124
|
+
const value = record.value.padEnd(15);
|
|
4125
|
+
let status;
|
|
4126
|
+
if (record.error) status = "✗";
|
|
4127
|
+
else if (record.created) status = "✓ new";
|
|
4128
|
+
else if (record.existed) status = "✓";
|
|
4129
|
+
else status = "?";
|
|
4130
|
+
logger$3.log(` │ ${subdomain} │ ${type$1} │ ${value} │ ${status.padEnd(6)} │`);
|
|
4131
|
+
}
|
|
4132
|
+
logger$3.log(" └─────────────────────────────────────┴──────┴─────────────────┴────────┘");
|
|
4133
|
+
}
|
|
4134
|
+
/**
|
|
4135
|
+
* Print DNS records in a simple format for manual setup
|
|
4136
|
+
*/
|
|
4137
|
+
function printDnsRecordsSimple(records, rootDomain) {
|
|
4138
|
+
logger$3.log("\n 📋 Required DNS Records:");
|
|
4139
|
+
logger$3.log(` Add these A records to your DNS provider (${rootDomain}):\n`);
|
|
4140
|
+
for (const record of records) logger$3.log(` ${record.subdomain} → ${record.value} (A record)`);
|
|
4141
|
+
logger$3.log("");
|
|
4142
|
+
}
|
|
4143
|
+
/**
|
|
4144
|
+
* Prompt for input (reuse from deploy/index.ts pattern)
|
|
4145
|
+
*/
|
|
4146
|
+
async function promptForToken(message) {
|
|
4147
|
+
const { stdin, stdout } = await import("node:process");
|
|
4148
|
+
const readline = await import("node:readline/promises");
|
|
4149
|
+
if (!stdin.isTTY) throw new Error("Interactive input required for Hostinger token.");
|
|
4150
|
+
stdout.write(message);
|
|
4151
|
+
return new Promise((resolve$3) => {
|
|
4152
|
+
let value = "";
|
|
4153
|
+
const onData = (char) => {
|
|
4154
|
+
const c = char.toString();
|
|
4155
|
+
if (c === "\n" || c === "\r") {
|
|
4156
|
+
stdin.setRawMode(false);
|
|
4157
|
+
stdin.pause();
|
|
4158
|
+
stdin.removeListener("data", onData);
|
|
4159
|
+
stdout.write("\n");
|
|
4160
|
+
resolve$3(value);
|
|
4161
|
+
} else if (c === "") {
|
|
4162
|
+
stdin.setRawMode(false);
|
|
4163
|
+
stdin.pause();
|
|
4164
|
+
stdout.write("\n");
|
|
4165
|
+
process.exit(1);
|
|
4166
|
+
} else if (c === "" || c === "\b") {
|
|
4167
|
+
if (value.length > 0) value = value.slice(0, -1);
|
|
4168
|
+
} else value += c;
|
|
4169
|
+
};
|
|
4170
|
+
stdin.setRawMode(true);
|
|
4171
|
+
stdin.resume();
|
|
4172
|
+
stdin.on("data", onData);
|
|
4173
|
+
});
|
|
4174
|
+
}
|
|
4175
|
+
/**
|
|
4176
|
+
* Create DNS records using the configured provider
|
|
4177
|
+
*/
|
|
4178
|
+
async function createDnsRecords(records, dnsConfig) {
|
|
4179
|
+
const { provider, domain: rootDomain, ttl = 300 } = dnsConfig;
|
|
4180
|
+
if (provider === "manual") return records.map((r) => ({
|
|
4181
|
+
...r,
|
|
4182
|
+
created: false,
|
|
4183
|
+
existed: false
|
|
4184
|
+
}));
|
|
4185
|
+
if (provider === "hostinger") return createHostingerRecords(records, rootDomain, ttl);
|
|
4186
|
+
if (provider === "cloudflare") {
|
|
4187
|
+
logger$3.log(" ⚠ Cloudflare DNS integration not yet implemented");
|
|
4188
|
+
return records.map((r) => ({
|
|
4189
|
+
...r,
|
|
4190
|
+
error: "Cloudflare not implemented"
|
|
4191
|
+
}));
|
|
4192
|
+
}
|
|
4193
|
+
return records;
|
|
4194
|
+
}
|
|
4195
|
+
/**
|
|
4196
|
+
* Create DNS records at Hostinger
|
|
4197
|
+
*/
|
|
4198
|
+
async function createHostingerRecords(records, rootDomain, ttl) {
|
|
4199
|
+
let token = await getHostingerToken();
|
|
4200
|
+
if (!token) {
|
|
4201
|
+
logger$3.log("\n 📋 Hostinger API token not found.");
|
|
4202
|
+
logger$3.log(" Get your token from: https://hpanel.hostinger.com/profile/api\n");
|
|
4203
|
+
try {
|
|
4204
|
+
token = await promptForToken(" Hostinger API Token: ");
|
|
4205
|
+
await storeHostingerToken(token);
|
|
4206
|
+
logger$3.log(" ✓ Token saved");
|
|
4207
|
+
} catch {
|
|
4208
|
+
logger$3.log(" ⚠ Could not get token, skipping DNS creation");
|
|
4209
|
+
return records.map((r) => ({
|
|
4210
|
+
...r,
|
|
4211
|
+
error: "No API token"
|
|
4212
|
+
}));
|
|
4213
|
+
}
|
|
4214
|
+
}
|
|
4215
|
+
const api = new HostingerApi(token);
|
|
4216
|
+
const results = [];
|
|
4217
|
+
let existingRecords = [];
|
|
4218
|
+
try {
|
|
4219
|
+
existingRecords = await api.getRecords(rootDomain);
|
|
4220
|
+
} catch (error) {
|
|
4221
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4222
|
+
logger$3.log(` ⚠ Failed to fetch existing DNS records: ${message}`);
|
|
4223
|
+
return records.map((r) => ({
|
|
4224
|
+
...r,
|
|
4225
|
+
error: message
|
|
4226
|
+
}));
|
|
4227
|
+
}
|
|
4228
|
+
for (const record of records) {
|
|
4229
|
+
const existing = existingRecords.find((r) => r.name === record.subdomain && r.type === "A");
|
|
4230
|
+
if (existing) {
|
|
4231
|
+
results.push({
|
|
4232
|
+
...record,
|
|
4233
|
+
existed: true,
|
|
4234
|
+
created: false
|
|
4235
|
+
});
|
|
4236
|
+
continue;
|
|
4237
|
+
}
|
|
4238
|
+
try {
|
|
4239
|
+
await api.upsertRecords(rootDomain, [{
|
|
4240
|
+
name: record.subdomain,
|
|
4241
|
+
type: "A",
|
|
4242
|
+
ttl,
|
|
4243
|
+
records: [record.value]
|
|
4244
|
+
}]);
|
|
4245
|
+
results.push({
|
|
4246
|
+
...record,
|
|
4247
|
+
created: true,
|
|
4248
|
+
existed: false
|
|
4249
|
+
});
|
|
4250
|
+
} catch (error) {
|
|
4251
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4252
|
+
results.push({
|
|
4253
|
+
...record,
|
|
4254
|
+
error: message
|
|
4255
|
+
});
|
|
4256
|
+
}
|
|
4257
|
+
}
|
|
4258
|
+
return results;
|
|
4259
|
+
}
|
|
4260
|
+
/**
|
|
4261
|
+
* Main DNS orchestration function for deployments
|
|
4262
|
+
*/
|
|
4263
|
+
async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint) {
|
|
4264
|
+
if (!dnsConfig) return null;
|
|
4265
|
+
const { domain: rootDomain, autoCreate = true } = dnsConfig;
|
|
4266
|
+
logger$3.log("\n🌐 Setting up DNS records...");
|
|
4267
|
+
let serverIp;
|
|
4268
|
+
try {
|
|
4269
|
+
const endpointUrl = new URL(dokployEndpoint);
|
|
4270
|
+
serverIp = await resolveHostnameToIp(endpointUrl.hostname);
|
|
4271
|
+
logger$3.log(` Server IP: ${serverIp} (from ${endpointUrl.hostname})`);
|
|
4272
|
+
} catch (error) {
|
|
4273
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4274
|
+
logger$3.log(` ⚠ Failed to resolve server IP: ${message}`);
|
|
4275
|
+
return null;
|
|
4276
|
+
}
|
|
4277
|
+
const requiredRecords = generateRequiredRecords(appHostnames, rootDomain, serverIp);
|
|
4278
|
+
if (requiredRecords.length === 0) {
|
|
4279
|
+
logger$3.log(" No DNS records needed");
|
|
4280
|
+
return {
|
|
4281
|
+
records: [],
|
|
4282
|
+
success: true,
|
|
4283
|
+
serverIp
|
|
4284
|
+
};
|
|
4285
|
+
}
|
|
4286
|
+
let finalRecords;
|
|
4287
|
+
if (autoCreate && dnsConfig.provider !== "manual") {
|
|
4288
|
+
logger$3.log(` Creating DNS records at ${dnsConfig.provider}...`);
|
|
4289
|
+
finalRecords = await createDnsRecords(requiredRecords, dnsConfig);
|
|
4290
|
+
const created = finalRecords.filter((r) => r.created).length;
|
|
4291
|
+
const existed = finalRecords.filter((r) => r.existed).length;
|
|
4292
|
+
const failed = finalRecords.filter((r) => r.error).length;
|
|
4293
|
+
if (created > 0) logger$3.log(` ✓ Created ${created} DNS record(s)`);
|
|
4294
|
+
if (existed > 0) logger$3.log(` ✓ ${existed} record(s) already exist`);
|
|
4295
|
+
if (failed > 0) logger$3.log(` ⚠ ${failed} record(s) failed`);
|
|
4296
|
+
} else finalRecords = requiredRecords;
|
|
4297
|
+
printDnsRecordsTable(finalRecords, rootDomain);
|
|
4298
|
+
const hasFailures = finalRecords.some((r) => r.error);
|
|
4299
|
+
if (dnsConfig.provider === "manual" || hasFailures) printDnsRecordsSimple(finalRecords.filter((r) => !r.created && !r.existed), rootDomain);
|
|
4300
|
+
return {
|
|
4301
|
+
records: finalRecords,
|
|
4302
|
+
success: !hasFailures,
|
|
4303
|
+
serverIp
|
|
4304
|
+
};
|
|
4305
|
+
}
|
|
4306
|
+
|
|
3814
4307
|
//#endregion
|
|
3815
4308
|
//#region src/deploy/domain.ts
|
|
3816
4309
|
/**
|
|
@@ -4315,24 +4808,39 @@ async function prompt(message, hidden = false) {
|
|
|
4315
4808
|
* Provision docker compose services in Dokploy
|
|
4316
4809
|
* @internal Exported for testing
|
|
4317
4810
|
*/
|
|
4318
|
-
async function provisionServices(api, projectId, environmentId, appName, services,
|
|
4811
|
+
async function provisionServices(api, projectId, environmentId, appName, services, existingServiceIds) {
|
|
4319
4812
|
logger$1.log(`\n🔍 provisionServices called: services=${JSON.stringify(services)}, envId=${environmentId}`);
|
|
4320
4813
|
if (!services || !environmentId) {
|
|
4321
4814
|
logger$1.log(" Skipping: no services or no environmentId");
|
|
4322
4815
|
return void 0;
|
|
4323
4816
|
}
|
|
4324
4817
|
const serviceUrls = {};
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
logger$1.log("\n🐘
|
|
4328
|
-
const postgresName =
|
|
4818
|
+
const serviceIds = {};
|
|
4819
|
+
if (services.postgres) {
|
|
4820
|
+
logger$1.log("\n🐘 Checking PostgreSQL...");
|
|
4821
|
+
const postgresName = "db";
|
|
4329
4822
|
try {
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
4335
|
-
|
|
4823
|
+
let postgres = null;
|
|
4824
|
+
let created = false;
|
|
4825
|
+
if (existingServiceIds?.postgresId) {
|
|
4826
|
+
logger$1.log(` Using cached ID: ${existingServiceIds.postgresId}`);
|
|
4827
|
+
postgres = await api.getPostgres(existingServiceIds.postgresId);
|
|
4828
|
+
if (postgres) logger$1.log(` ✓ PostgreSQL found: ${postgres.postgresId}`);
|
|
4829
|
+
else logger$1.log(` ⚠ Cached ID invalid, will create new`);
|
|
4830
|
+
}
|
|
4831
|
+
if (!postgres) {
|
|
4832
|
+
const { randomBytes: randomBytes$1 } = await import("node:crypto");
|
|
4833
|
+
const databasePassword = randomBytes$1(16).toString("hex");
|
|
4834
|
+
const result = await api.findOrCreatePostgres(postgresName, projectId, environmentId, { databasePassword });
|
|
4835
|
+
postgres = result.postgres;
|
|
4836
|
+
created = result.created;
|
|
4837
|
+
if (created) {
|
|
4838
|
+
logger$1.log(` ✓ Created PostgreSQL: ${postgres.postgresId}`);
|
|
4839
|
+
await api.deployPostgres(postgres.postgresId);
|
|
4840
|
+
logger$1.log(" ✓ PostgreSQL deployed");
|
|
4841
|
+
} else logger$1.log(` ✓ PostgreSQL already exists: ${postgres.postgresId}`);
|
|
4842
|
+
}
|
|
4843
|
+
serviceIds.postgresId = postgres.postgresId;
|
|
4336
4844
|
serviceUrls.DATABASE_HOST = postgres.appName;
|
|
4337
4845
|
serviceUrls.DATABASE_PORT = "5432";
|
|
4338
4846
|
serviceUrls.DATABASE_NAME = postgres.databaseName;
|
|
@@ -4342,21 +4850,34 @@ async function provisionServices(api, projectId, environmentId, appName, service
|
|
|
4342
4850
|
logger$1.log(` ✓ Database credentials configured`);
|
|
4343
4851
|
} catch (error) {
|
|
4344
4852
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4345
|
-
|
|
4346
|
-
else logger$1.log(` ⚠ Failed to provision PostgreSQL: ${message}`);
|
|
4853
|
+
logger$1.log(` ⚠ Failed to provision PostgreSQL: ${message}`);
|
|
4347
4854
|
}
|
|
4348
4855
|
}
|
|
4349
|
-
if (services.redis)
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
const redisName = `${appName}-cache`;
|
|
4856
|
+
if (services.redis) {
|
|
4857
|
+
logger$1.log("\n🔴 Checking Redis...");
|
|
4858
|
+
const redisName = "cache";
|
|
4353
4859
|
try {
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4860
|
+
let redis = null;
|
|
4861
|
+
let created = false;
|
|
4862
|
+
if (existingServiceIds?.redisId) {
|
|
4863
|
+
logger$1.log(` Using cached ID: ${existingServiceIds.redisId}`);
|
|
4864
|
+
redis = await api.getRedis(existingServiceIds.redisId);
|
|
4865
|
+
if (redis) logger$1.log(` ✓ Redis found: ${redis.redisId}`);
|
|
4866
|
+
else logger$1.log(` ⚠ Cached ID invalid, will create new`);
|
|
4867
|
+
}
|
|
4868
|
+
if (!redis) {
|
|
4869
|
+
const { randomBytes: randomBytes$1 } = await import("node:crypto");
|
|
4870
|
+
const databasePassword = randomBytes$1(16).toString("hex");
|
|
4871
|
+
const result = await api.findOrCreateRedis(redisName, projectId, environmentId, { databasePassword });
|
|
4872
|
+
redis = result.redis;
|
|
4873
|
+
created = result.created;
|
|
4874
|
+
if (created) {
|
|
4875
|
+
logger$1.log(` ✓ Created Redis: ${redis.redisId}`);
|
|
4876
|
+
await api.deployRedis(redis.redisId);
|
|
4877
|
+
logger$1.log(" ✓ Redis deployed");
|
|
4878
|
+
} else logger$1.log(` ✓ Redis already exists: ${redis.redisId}`);
|
|
4879
|
+
}
|
|
4880
|
+
serviceIds.redisId = redis.redisId;
|
|
4360
4881
|
serviceUrls.REDIS_HOST = redis.appName;
|
|
4361
4882
|
serviceUrls.REDIS_PORT = "6379";
|
|
4362
4883
|
if (redis.databasePassword) serviceUrls.REDIS_PASSWORD = redis.databasePassword;
|
|
@@ -4365,11 +4886,13 @@ async function provisionServices(api, projectId, environmentId, appName, service
|
|
|
4365
4886
|
logger$1.log(` ✓ Redis credentials configured`);
|
|
4366
4887
|
} catch (error) {
|
|
4367
4888
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4368
|
-
|
|
4369
|
-
else logger$1.log(` ⚠ Failed to provision Redis: ${message}`);
|
|
4889
|
+
logger$1.log(` ⚠ Failed to provision Redis: ${message}`);
|
|
4370
4890
|
}
|
|
4371
4891
|
}
|
|
4372
|
-
return Object.keys(serviceUrls).length > 0 ?
|
|
4892
|
+
return Object.keys(serviceUrls).length > 0 ? {
|
|
4893
|
+
serviceUrls,
|
|
4894
|
+
serviceIds
|
|
4895
|
+
} : void 0;
|
|
4373
4896
|
}
|
|
4374
4897
|
/**
|
|
4375
4898
|
* Ensure Dokploy is fully configured, recovering/creating resources as needed
|
|
@@ -4424,7 +4947,7 @@ async function ensureDokploySetup(config, dockerConfig, stage, services) {
|
|
|
4424
4947
|
}
|
|
4425
4948
|
const environmentId$1 = environment.environmentId;
|
|
4426
4949
|
logger$1.log(` Services config: ${JSON.stringify(services)}, envId: ${environmentId$1}`);
|
|
4427
|
-
const
|
|
4950
|
+
const provisionResult$1 = await provisionServices(api, existingConfig.projectId, environmentId$1, dockerConfig.appName, services, void 0);
|
|
4428
4951
|
return {
|
|
4429
4952
|
config: {
|
|
4430
4953
|
endpoint: existingConfig.endpoint,
|
|
@@ -4433,7 +4956,7 @@ async function ensureDokploySetup(config, dockerConfig, stage, services) {
|
|
|
4433
4956
|
registry: existingConfig.registry,
|
|
4434
4957
|
registryId: storedRegistryId ?? void 0
|
|
4435
4958
|
},
|
|
4436
|
-
serviceUrls:
|
|
4959
|
+
serviceUrls: provisionResult$1?.serviceUrls
|
|
4437
4960
|
};
|
|
4438
4961
|
} catch {
|
|
4439
4962
|
logger$1.log("⚠ Project not found, will recover...");
|
|
@@ -4540,10 +5063,10 @@ async function ensureDokploySetup(config, dockerConfig, stage, services) {
|
|
|
4540
5063
|
logger$1.log(` Project: ${project.projectId}`);
|
|
4541
5064
|
logger$1.log(` Application: ${applicationId}`);
|
|
4542
5065
|
if (registryId) logger$1.log(` Registry: ${registryId}`);
|
|
4543
|
-
const
|
|
5066
|
+
const provisionResult = await provisionServices(api, project.projectId, environmentId, dockerConfig.appName, services, void 0);
|
|
4544
5067
|
return {
|
|
4545
5068
|
config: dokployConfig,
|
|
4546
|
-
serviceUrls
|
|
5069
|
+
serviceUrls: provisionResult?.serviceUrls
|
|
4547
5070
|
};
|
|
4548
5071
|
}
|
|
4549
5072
|
/**
|
|
@@ -4662,6 +5185,18 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
4662
5185
|
} else environmentId = result.environment.environmentId;
|
|
4663
5186
|
logger$1.log(` ✓ Created project: ${project.projectId}`);
|
|
4664
5187
|
}
|
|
5188
|
+
logger$1.log("\n📋 Loading deploy state...");
|
|
5189
|
+
let state = await readStageState(workspace.root, stage);
|
|
5190
|
+
if (state) {
|
|
5191
|
+
logger$1.log(` Found existing state for stage "${stage}"`);
|
|
5192
|
+
if (state.environmentId !== environmentId) {
|
|
5193
|
+
logger$1.log(` ⚠ Environment ID changed, updating state`);
|
|
5194
|
+
state.environmentId = environmentId;
|
|
5195
|
+
}
|
|
5196
|
+
} else {
|
|
5197
|
+
logger$1.log(` Creating new state for stage "${stage}"`);
|
|
5198
|
+
state = createEmptyState(stage, environmentId);
|
|
5199
|
+
}
|
|
4665
5200
|
logger$1.log("\n🐳 Checking registry...");
|
|
4666
5201
|
let registryId = await getDokployRegistryId();
|
|
4667
5202
|
const registry = workspace.deploy.dokploy?.registry;
|
|
@@ -4697,33 +5232,45 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
4697
5232
|
};
|
|
4698
5233
|
if (dockerServices.postgres || dockerServices.redis) {
|
|
4699
5234
|
logger$1.log("\n🔧 Provisioning infrastructure services...");
|
|
4700
|
-
const
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
}
|
|
4704
|
-
await provisionServices(api, project.projectId, environmentId, workspace.name, dockerServices,
|
|
5235
|
+
const existingServiceIds = {
|
|
5236
|
+
postgresId: getPostgresId(state),
|
|
5237
|
+
redisId: getRedisId(state)
|
|
5238
|
+
};
|
|
5239
|
+
const provisionResult = await provisionServices(api, project.projectId, environmentId, workspace.name, dockerServices, existingServiceIds);
|
|
5240
|
+
if (provisionResult?.serviceIds) {
|
|
5241
|
+
if (provisionResult.serviceIds.postgresId) setPostgresId(state, provisionResult.serviceIds.postgresId);
|
|
5242
|
+
if (provisionResult.serviceIds.redisId) setRedisId(state, provisionResult.serviceIds.redisId);
|
|
5243
|
+
}
|
|
4705
5244
|
}
|
|
4706
5245
|
const backendApps = appsToDeployNames.filter((name$1) => workspace.apps[name$1].type === "backend");
|
|
4707
5246
|
const frontendApps = appsToDeployNames.filter((name$1) => workspace.apps[name$1].type === "frontend");
|
|
4708
5247
|
const publicUrls = {};
|
|
4709
5248
|
const results = [];
|
|
4710
5249
|
const dokployConfig = workspace.deploy.dokploy;
|
|
5250
|
+
const appHostnames = /* @__PURE__ */ new Map();
|
|
5251
|
+
const appDomainIds = /* @__PURE__ */ new Map();
|
|
4711
5252
|
if (backendApps.length > 0) {
|
|
4712
5253
|
logger$1.log("\n📦 PHASE 1: Deploying backend applications...");
|
|
4713
5254
|
for (const appName of backendApps) {
|
|
4714
5255
|
const app = workspace.apps[appName];
|
|
4715
5256
|
logger$1.log(`\n ⚙️ Deploying ${appName}...`);
|
|
4716
5257
|
try {
|
|
4717
|
-
const dokployAppName =
|
|
4718
|
-
let application;
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
logger$1.log(`
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
else throw error;
|
|
5258
|
+
const dokployAppName = appName;
|
|
5259
|
+
let application = null;
|
|
5260
|
+
const cachedAppId = getApplicationId(state, appName);
|
|
5261
|
+
if (cachedAppId) {
|
|
5262
|
+
logger$1.log(` Using cached ID: ${cachedAppId}`);
|
|
5263
|
+
application = await api.getApplication(cachedAppId);
|
|
5264
|
+
if (application) logger$1.log(` ✓ Application found: ${application.applicationId}`);
|
|
5265
|
+
else logger$1.log(` ⚠ Cached ID invalid, will create new`);
|
|
4726
5266
|
}
|
|
5267
|
+
if (!application) {
|
|
5268
|
+
const result = await api.findOrCreateApplication(dokployAppName, project.projectId, environmentId);
|
|
5269
|
+
application = result.application;
|
|
5270
|
+
if (result.created) logger$1.log(` Created application: ${application.applicationId}`);
|
|
5271
|
+
else logger$1.log(` Found existing application: ${application.applicationId}`);
|
|
5272
|
+
}
|
|
5273
|
+
setApplicationId(state, appName, application.applicationId);
|
|
4727
5274
|
const appSecrets = encryptedSecrets.get(appName);
|
|
4728
5275
|
const buildArgs = [];
|
|
4729
5276
|
if (appSecrets && appSecrets.secretCount > 0) {
|
|
@@ -4747,47 +5294,42 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
4747
5294
|
});
|
|
4748
5295
|
const envVars = [`NODE_ENV=production`, `PORT=${app.port}`];
|
|
4749
5296
|
if (appSecrets && appSecrets.masterKey) envVars.push(`GKM_MASTER_KEY=${appSecrets.masterKey}`);
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
applicationId: application.applicationId
|
|
4763
|
-
});
|
|
4764
|
-
const publicUrl = `https://${host}`;
|
|
4765
|
-
publicUrls[appName] = publicUrl;
|
|
4766
|
-
logger$1.log(` ✓ Domain: ${publicUrl}`);
|
|
4767
|
-
} catch (domainError) {
|
|
4768
|
-
const host = resolveHost(appName, app, stage, dokployConfig, false);
|
|
4769
|
-
publicUrls[appName] = `https://${host}`;
|
|
4770
|
-
logger$1.log(` ℹ Domain already configured: https://${host}`);
|
|
4771
|
-
}
|
|
4772
|
-
results.push({
|
|
4773
|
-
appName,
|
|
4774
|
-
type: app.type,
|
|
4775
|
-
success: true,
|
|
4776
|
-
applicationId: application.applicationId,
|
|
4777
|
-
imageRef
|
|
4778
|
-
});
|
|
4779
|
-
logger$1.log(` ✓ ${appName} deployed successfully`);
|
|
4780
|
-
} else {
|
|
4781
|
-
const host = resolveHost(appName, app, stage, dokployConfig, false);
|
|
4782
|
-
publicUrls[appName] = `https://${host}`;
|
|
4783
|
-
results.push({
|
|
4784
|
-
appName,
|
|
4785
|
-
type: app.type,
|
|
4786
|
-
success: true,
|
|
4787
|
-
imageRef
|
|
5297
|
+
await api.saveDockerProvider(application.applicationId, imageRef, { registryId });
|
|
5298
|
+
await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
|
|
5299
|
+
logger$1.log(` Deploying to Dokploy...`);
|
|
5300
|
+
await api.deployApplication(application.applicationId);
|
|
5301
|
+
const backendHost = resolveHost(appName, app, stage, dokployConfig, false);
|
|
5302
|
+
try {
|
|
5303
|
+
const domain = await api.createDomain({
|
|
5304
|
+
host: backendHost,
|
|
5305
|
+
port: app.port,
|
|
5306
|
+
https: true,
|
|
5307
|
+
certificateType: "letsencrypt",
|
|
5308
|
+
applicationId: application.applicationId
|
|
4788
5309
|
});
|
|
4789
|
-
|
|
5310
|
+
appHostnames.set(appName, backendHost);
|
|
5311
|
+
appDomainIds.set(appName, domain.domainId);
|
|
5312
|
+
const publicUrl = `https://${backendHost}`;
|
|
5313
|
+
publicUrls[appName] = publicUrl;
|
|
5314
|
+
logger$1.log(` ✓ Domain: ${publicUrl}`);
|
|
5315
|
+
} catch (domainError) {
|
|
5316
|
+
appHostnames.set(appName, backendHost);
|
|
5317
|
+
try {
|
|
5318
|
+
const existingDomains = await api.getDomainsByApplicationId(application.applicationId);
|
|
5319
|
+
const matchingDomain = existingDomains.find((d) => d.host === backendHost);
|
|
5320
|
+
if (matchingDomain) appDomainIds.set(appName, matchingDomain.domainId);
|
|
5321
|
+
} catch {}
|
|
5322
|
+
publicUrls[appName] = `https://${backendHost}`;
|
|
5323
|
+
logger$1.log(` ℹ Domain already configured: https://${backendHost}`);
|
|
4790
5324
|
}
|
|
5325
|
+
results.push({
|
|
5326
|
+
appName,
|
|
5327
|
+
type: app.type,
|
|
5328
|
+
success: true,
|
|
5329
|
+
applicationId: application.applicationId,
|
|
5330
|
+
imageRef
|
|
5331
|
+
});
|
|
5332
|
+
logger$1.log(` ✓ ${appName} deployed successfully`);
|
|
4791
5333
|
} catch (error) {
|
|
4792
5334
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4793
5335
|
logger$1.log(` ✗ Failed to deploy ${appName}: ${message}`);
|
|
@@ -4807,16 +5349,22 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
4807
5349
|
const app = workspace.apps[appName];
|
|
4808
5350
|
logger$1.log(`\n 🌐 Deploying ${appName}...`);
|
|
4809
5351
|
try {
|
|
4810
|
-
const dokployAppName =
|
|
4811
|
-
let application;
|
|
4812
|
-
|
|
4813
|
-
|
|
4814
|
-
logger$1.log(`
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
else throw error;
|
|
5352
|
+
const dokployAppName = appName;
|
|
5353
|
+
let application = null;
|
|
5354
|
+
const cachedAppId = getApplicationId(state, appName);
|
|
5355
|
+
if (cachedAppId) {
|
|
5356
|
+
logger$1.log(` Using cached ID: ${cachedAppId}`);
|
|
5357
|
+
application = await api.getApplication(cachedAppId);
|
|
5358
|
+
if (application) logger$1.log(` ✓ Application found: ${application.applicationId}`);
|
|
5359
|
+
else logger$1.log(` ⚠ Cached ID invalid, will create new`);
|
|
4819
5360
|
}
|
|
5361
|
+
if (!application) {
|
|
5362
|
+
const result = await api.findOrCreateApplication(dokployAppName, project.projectId, environmentId);
|
|
5363
|
+
application = result.application;
|
|
5364
|
+
if (result.created) logger$1.log(` Created application: ${application.applicationId}`);
|
|
5365
|
+
else logger$1.log(` Found existing application: ${application.applicationId}`);
|
|
5366
|
+
}
|
|
5367
|
+
setApplicationId(state, appName, application.applicationId);
|
|
4820
5368
|
const buildArgs = generatePublicUrlBuildArgs(app, publicUrls);
|
|
4821
5369
|
if (buildArgs.length > 0) logger$1.log(` Public URLs: ${buildArgs.join(", ")}`);
|
|
4822
5370
|
const imageName = `${workspace.name}-${appName}`;
|
|
@@ -4835,49 +5383,43 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
4835
5383
|
publicUrlArgs: getPublicUrlArgNames(app)
|
|
4836
5384
|
});
|
|
4837
5385
|
const envVars = [`NODE_ENV=production`, `PORT=${app.port}`];
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
applicationId: application.applicationId
|
|
4852
|
-
});
|
|
4853
|
-
const publicUrl = `https://${host}`;
|
|
4854
|
-
publicUrls[appName] = publicUrl;
|
|
4855
|
-
logger$1.log(` ✓ Domain: ${publicUrl}`);
|
|
4856
|
-
} catch (domainError) {
|
|
4857
|
-
const host = resolveHost(appName, app, stage, dokployConfig, isMainFrontend);
|
|
4858
|
-
publicUrls[appName] = `https://${host}`;
|
|
4859
|
-
logger$1.log(` ℹ Domain already configured: https://${host}`);
|
|
4860
|
-
}
|
|
4861
|
-
results.push({
|
|
4862
|
-
appName,
|
|
4863
|
-
type: app.type,
|
|
4864
|
-
success: true,
|
|
4865
|
-
applicationId: application.applicationId,
|
|
4866
|
-
imageRef
|
|
4867
|
-
});
|
|
4868
|
-
logger$1.log(` ✓ ${appName} deployed successfully`);
|
|
4869
|
-
} else {
|
|
4870
|
-
const isMainFrontend = isMainFrontendApp(appName, app, workspace.apps);
|
|
4871
|
-
const host = resolveHost(appName, app, stage, dokployConfig, isMainFrontend);
|
|
4872
|
-
publicUrls[appName] = `https://${host}`;
|
|
4873
|
-
results.push({
|
|
4874
|
-
appName,
|
|
4875
|
-
type: app.type,
|
|
4876
|
-
success: true,
|
|
4877
|
-
imageRef
|
|
5386
|
+
await api.saveDockerProvider(application.applicationId, imageRef, { registryId });
|
|
5387
|
+
await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
|
|
5388
|
+
logger$1.log(` Deploying to Dokploy...`);
|
|
5389
|
+
await api.deployApplication(application.applicationId);
|
|
5390
|
+
const isMainFrontend = isMainFrontendApp(appName, app, workspace.apps);
|
|
5391
|
+
const frontendHost = resolveHost(appName, app, stage, dokployConfig, isMainFrontend);
|
|
5392
|
+
try {
|
|
5393
|
+
const domain = await api.createDomain({
|
|
5394
|
+
host: frontendHost,
|
|
5395
|
+
port: app.port,
|
|
5396
|
+
https: true,
|
|
5397
|
+
certificateType: "letsencrypt",
|
|
5398
|
+
applicationId: application.applicationId
|
|
4878
5399
|
});
|
|
4879
|
-
|
|
5400
|
+
appHostnames.set(appName, frontendHost);
|
|
5401
|
+
appDomainIds.set(appName, domain.domainId);
|
|
5402
|
+
const publicUrl = `https://${frontendHost}`;
|
|
5403
|
+
publicUrls[appName] = publicUrl;
|
|
5404
|
+
logger$1.log(` ✓ Domain: ${publicUrl}`);
|
|
5405
|
+
} catch (domainError) {
|
|
5406
|
+
appHostnames.set(appName, frontendHost);
|
|
5407
|
+
try {
|
|
5408
|
+
const existingDomains = await api.getDomainsByApplicationId(application.applicationId);
|
|
5409
|
+
const matchingDomain = existingDomains.find((d) => d.host === frontendHost);
|
|
5410
|
+
if (matchingDomain) appDomainIds.set(appName, matchingDomain.domainId);
|
|
5411
|
+
} catch {}
|
|
5412
|
+
publicUrls[appName] = `https://${frontendHost}`;
|
|
5413
|
+
logger$1.log(` ℹ Domain already configured: https://${frontendHost}`);
|
|
4880
5414
|
}
|
|
5415
|
+
results.push({
|
|
5416
|
+
appName,
|
|
5417
|
+
type: app.type,
|
|
5418
|
+
success: true,
|
|
5419
|
+
applicationId: application.applicationId,
|
|
5420
|
+
imageRef
|
|
5421
|
+
});
|
|
5422
|
+
logger$1.log(` ✓ ${appName} deployed successfully`);
|
|
4881
5423
|
} catch (error) {
|
|
4882
5424
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4883
5425
|
logger$1.log(` ✗ Failed to deploy ${appName}: ${message}`);
|
|
@@ -4890,6 +5432,23 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
4890
5432
|
}
|
|
4891
5433
|
}
|
|
4892
5434
|
}
|
|
5435
|
+
logger$1.log("\n📋 Saving deploy state...");
|
|
5436
|
+
await writeStageState(workspace.root, stage, state);
|
|
5437
|
+
logger$1.log(` ✓ State saved to .gkm/deploy-${stage}.json`);
|
|
5438
|
+
const dnsConfig = workspace.deploy.dns;
|
|
5439
|
+
if (dnsConfig && appHostnames.size > 0) {
|
|
5440
|
+
const dnsResult = await orchestrateDns(appHostnames, dnsConfig, creds.endpoint);
|
|
5441
|
+
if (dnsResult?.success && appDomainIds.size > 0) {
|
|
5442
|
+
logger$1.log("\n🔒 Validating domains for SSL certificates...");
|
|
5443
|
+
for (const [appName, domainId] of appDomainIds) try {
|
|
5444
|
+
await api.validateDomain(domainId);
|
|
5445
|
+
logger$1.log(` ✓ ${appName}: SSL validation triggered`);
|
|
5446
|
+
} catch (validationError) {
|
|
5447
|
+
const message = validationError instanceof Error ? validationError.message : "Unknown error";
|
|
5448
|
+
logger$1.log(` ⚠ ${appName}: SSL validation failed - ${message}`);
|
|
5449
|
+
}
|
|
5450
|
+
}
|
|
5451
|
+
}
|
|
4893
5452
|
const successCount = results.filter((r) => r.success).length;
|
|
4894
5453
|
const failedCount = results.filter((r) => !r.success).length;
|
|
4895
5454
|
logger$1.log(`\n${"─".repeat(50)}`);
|