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