@geekmidas/cli 0.46.0 → 0.48.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.d.cts +1 -1
- package/dist/config.d.mts +1 -1
- package/dist/{dokploy-api-D8a0eQQB.cjs → dokploy-api-BDLu0qWi.cjs} +12 -1
- package/dist/dokploy-api-BDLu0qWi.cjs.map +1 -0
- package/dist/dokploy-api-BN3V57z1.mjs +3 -0
- package/dist/dokploy-api-BdCKjFDA.cjs +3 -0
- package/dist/{dokploy-api-b6usLLKk.mjs → dokploy-api-DvzIDxTj.mjs} +12 -1
- package/dist/dokploy-api-DvzIDxTj.mjs.map +1 -0
- package/dist/{index-BtnjoghR.d.mts → index-A70abJ1m.d.mts} +60 -2
- package/dist/index-A70abJ1m.d.mts.map +1 -0
- package/dist/{index-c89X2mi2.d.cts → index-pOA56MWT.d.cts} +60 -2
- package/dist/index-pOA56MWT.d.cts.map +1 -0
- package/dist/index.cjs +685 -249
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +685 -249
- package/dist/index.mjs.map +1 -1
- package/dist/workspace/index.d.cts +1 -1
- package/dist/workspace/index.d.mts +1 -1
- package/dist/workspace-CaVW6j2q.cjs.map +1 -1
- package/dist/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 +398 -0
- package/src/deploy/dokploy-api.ts +12 -0
- package/src/deploy/index.ts +108 -35
- package/src/docker/templates.ts +10 -14
- package/src/workspace/types.ts +64 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/dokploy-api-C1JgU9Vr.mjs +0 -3
- package/dist/dokploy-api-Cpq_tLSz.cjs +0 -3
- package/dist/dokploy-api-D8a0eQQB.cjs.map +0 -1
- package/dist/dokploy-api-b6usLLKk.mjs.map +0 -1
- package/dist/index-BtnjoghR.d.mts.map +0 -1
- package/dist/index-c89X2mi2.d.cts.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -3,7 +3,7 @@ import { __require, getAppBuildOrder, getDependencyEnvVars, getDeployTargetError
|
|
|
3
3
|
import { getAppNameFromCwd, loadAppConfig, loadConfig, loadWorkspaceConfig, parseModuleConfig } from "./config-C3LSBNSl.mjs";
|
|
4
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-DvzIDxTj.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-BN3V57z1.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,
|
|
@@ -2802,8 +2828,8 @@ ENV NODE_ENV=production
|
|
|
2802
2828
|
ENV PORT=${port}
|
|
2803
2829
|
|
|
2804
2830
|
# Health check
|
|
2805
|
-
HEALTHCHECK --interval=30s --timeout=
|
|
2806
|
-
CMD wget -
|
|
2831
|
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\
|
|
2832
|
+
CMD wget -qO- http://localhost:${port}${healthCheckPath} > /dev/null 2>&1 || exit 1
|
|
2807
2833
|
|
|
2808
2834
|
# Switch to non-root user
|
|
2809
2835
|
USER hono
|
|
@@ -2885,8 +2911,8 @@ COPY --from=builder --chown=hono:nodejs /app/.gkm/server/dist/server.mjs ./
|
|
|
2885
2911
|
ENV NODE_ENV=production
|
|
2886
2912
|
ENV PORT=${port}
|
|
2887
2913
|
|
|
2888
|
-
HEALTHCHECK --interval=30s --timeout=
|
|
2889
|
-
CMD wget -
|
|
2914
|
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\
|
|
2915
|
+
CMD wget -qO- http://localhost:${port}${healthCheckPath} > /dev/null 2>&1 || exit 1
|
|
2890
2916
|
|
|
2891
2917
|
USER hono
|
|
2892
2918
|
|
|
@@ -2922,8 +2948,8 @@ ENV NODE_ENV=production
|
|
|
2922
2948
|
ENV PORT=${port}
|
|
2923
2949
|
|
|
2924
2950
|
# Health check
|
|
2925
|
-
HEALTHCHECK --interval=30s --timeout=
|
|
2926
|
-
CMD wget -
|
|
2951
|
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\
|
|
2952
|
+
CMD wget -qO- http://localhost:${port}${healthCheckPath} > /dev/null 2>&1 || exit 1
|
|
2927
2953
|
|
|
2928
2954
|
# Switch to non-root user
|
|
2929
2955
|
USER hono
|
|
@@ -3115,10 +3141,6 @@ COPY --from=builder --chown=nextjs:nodejs /app/${appPath}/.next/standalone ./
|
|
|
3115
3141
|
COPY --from=builder --chown=nextjs:nodejs /app/${appPath}/.next/static ./${appPath}/.next/static
|
|
3116
3142
|
COPY --from=builder --chown=nextjs:nodejs /app/${appPath}/public ./${appPath}/public
|
|
3117
3143
|
|
|
3118
|
-
# Health check
|
|
3119
|
-
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \\
|
|
3120
|
-
CMD wget -q --spider http://localhost:${port}/ || exit 1
|
|
3121
|
-
|
|
3122
3144
|
USER nextjs
|
|
3123
3145
|
|
|
3124
3146
|
EXPOSE ${port}
|
|
@@ -3211,8 +3233,8 @@ COPY --from=builder --chown=hono:nodejs /app/${appPath}/.gkm/server/dist/server.
|
|
|
3211
3233
|
ENV NODE_ENV=production
|
|
3212
3234
|
ENV PORT=${port}
|
|
3213
3235
|
|
|
3214
|
-
HEALTHCHECK --interval=30s --timeout=
|
|
3215
|
-
CMD wget -
|
|
3236
|
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\
|
|
3237
|
+
CMD wget -qO- http://localhost:${port}${healthCheckPath} > /dev/null 2>&1 || exit 1
|
|
3216
3238
|
|
|
3217
3239
|
USER hono
|
|
3218
3240
|
|
|
@@ -3321,8 +3343,8 @@ COPY --from=builder --chown=app:nodejs /app/${appPath}/dist/index.mjs ./
|
|
|
3321
3343
|
ENV NODE_ENV=production
|
|
3322
3344
|
ENV PORT=${port}
|
|
3323
3345
|
|
|
3324
|
-
HEALTHCHECK --interval=30s --timeout=
|
|
3325
|
-
CMD wget -
|
|
3346
|
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\
|
|
3347
|
+
CMD wget -qO- http://localhost:${port}${healthCheckPath} > /dev/null 2>&1 || exit 1
|
|
3326
3348
|
|
|
3327
3349
|
USER app
|
|
3328
3350
|
|
|
@@ -3335,7 +3357,7 @@ CMD ["node", "index.mjs"]
|
|
|
3335
3357
|
|
|
3336
3358
|
//#endregion
|
|
3337
3359
|
//#region src/docker/index.ts
|
|
3338
|
-
const logger$
|
|
3360
|
+
const logger$6 = console;
|
|
3339
3361
|
/**
|
|
3340
3362
|
* Docker command implementation
|
|
3341
3363
|
* Generates Dockerfile, docker-compose.yml, and related files
|
|
@@ -3346,7 +3368,7 @@ const logger$5 = console;
|
|
|
3346
3368
|
async function dockerCommand(options) {
|
|
3347
3369
|
const loadedConfig = await loadWorkspaceConfig();
|
|
3348
3370
|
if (loadedConfig.type === "workspace") {
|
|
3349
|
-
logger$
|
|
3371
|
+
logger$6.log("📦 Detected workspace configuration");
|
|
3350
3372
|
return workspaceDockerCommand(loadedConfig.workspace, options);
|
|
3351
3373
|
}
|
|
3352
3374
|
const config$1 = await loadConfig();
|
|
@@ -3367,14 +3389,14 @@ async function dockerCommand(options) {
|
|
|
3367
3389
|
let useTurbo = options.turbo ?? false;
|
|
3368
3390
|
if (inMonorepo && !useSlim) if (hasTurbo) {
|
|
3369
3391
|
useTurbo = true;
|
|
3370
|
-
logger$
|
|
3392
|
+
logger$6.log(" Detected monorepo with turbo.json - using turbo prune");
|
|
3371
3393
|
} 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
3394
|
let turboPackage = options.turboPackage ?? dockerConfig.imageName;
|
|
3373
3395
|
if (useTurbo && !options.turboPackage) try {
|
|
3374
3396
|
const pkg$1 = __require(`${process.cwd()}/package.json`);
|
|
3375
3397
|
if (pkg$1.name) {
|
|
3376
3398
|
turboPackage = pkg$1.name;
|
|
3377
|
-
logger$
|
|
3399
|
+
logger$6.log(` Turbo package: ${turboPackage}`);
|
|
3378
3400
|
}
|
|
3379
3401
|
} catch {}
|
|
3380
3402
|
const templateOptions = {
|
|
@@ -3391,7 +3413,7 @@ async function dockerCommand(options) {
|
|
|
3391
3413
|
const dockerMode = useSlim ? "slim" : useTurbo ? "turbo" : "multi-stage";
|
|
3392
3414
|
const dockerfilePath = join(dockerDir, "Dockerfile");
|
|
3393
3415
|
await writeFile(dockerfilePath, dockerfile);
|
|
3394
|
-
logger$
|
|
3416
|
+
logger$6.log(`Generated: .gkm/docker/Dockerfile (${dockerMode}, ${packageManager})`);
|
|
3395
3417
|
const composeOptions = {
|
|
3396
3418
|
imageName: dockerConfig.imageName,
|
|
3397
3419
|
registry: options.registry ?? dockerConfig.registry,
|
|
@@ -3403,15 +3425,15 @@ async function dockerCommand(options) {
|
|
|
3403
3425
|
const dockerCompose = hasServices ? generateDockerCompose(composeOptions) : generateMinimalDockerCompose(composeOptions);
|
|
3404
3426
|
const composePath = join(dockerDir, "docker-compose.yml");
|
|
3405
3427
|
await writeFile(composePath, dockerCompose);
|
|
3406
|
-
logger$
|
|
3428
|
+
logger$6.log("Generated: .gkm/docker/docker-compose.yml");
|
|
3407
3429
|
const dockerignore = generateDockerignore();
|
|
3408
3430
|
const dockerignorePath = join(process.cwd(), ".dockerignore");
|
|
3409
3431
|
await writeFile(dockerignorePath, dockerignore);
|
|
3410
|
-
logger$
|
|
3432
|
+
logger$6.log("Generated: .dockerignore (project root)");
|
|
3411
3433
|
const entrypoint = generateDockerEntrypoint();
|
|
3412
3434
|
const entrypointPath = join(dockerDir, "docker-entrypoint.sh");
|
|
3413
3435
|
await writeFile(entrypointPath, entrypoint);
|
|
3414
|
-
logger$
|
|
3436
|
+
logger$6.log("Generated: .gkm/docker/docker-entrypoint.sh");
|
|
3415
3437
|
const result = {
|
|
3416
3438
|
dockerfile: dockerfilePath,
|
|
3417
3439
|
dockerCompose: composePath,
|
|
@@ -3430,13 +3452,13 @@ async function dockerCommand(options) {
|
|
|
3430
3452
|
function ensureLockfile(cwd) {
|
|
3431
3453
|
const lockfilePath = findLockfilePath(cwd);
|
|
3432
3454
|
if (!lockfilePath) {
|
|
3433
|
-
logger$
|
|
3455
|
+
logger$6.warn("\n⚠️ No lockfile found. Docker build may fail or use stale dependencies.");
|
|
3434
3456
|
return null;
|
|
3435
3457
|
}
|
|
3436
3458
|
const lockfileName = basename(lockfilePath);
|
|
3437
3459
|
const localLockfile = join(cwd, lockfileName);
|
|
3438
3460
|
if (lockfilePath === localLockfile) return null;
|
|
3439
|
-
logger$
|
|
3461
|
+
logger$6.log(` Copying ${lockfileName} from monorepo root...`);
|
|
3440
3462
|
copyFileSync(lockfilePath, localLockfile);
|
|
3441
3463
|
return () => {
|
|
3442
3464
|
try {
|
|
@@ -3452,7 +3474,7 @@ async function buildDockerImage(imageName, options) {
|
|
|
3452
3474
|
const tag = options.tag ?? "latest";
|
|
3453
3475
|
const registry = options.registry;
|
|
3454
3476
|
const fullImageName = registry ? `${registry}/${imageName}:${tag}` : `${imageName}:${tag}`;
|
|
3455
|
-
logger$
|
|
3477
|
+
logger$6.log(`\n🐳 Building Docker image: ${fullImageName}`);
|
|
3456
3478
|
const cwd = process.cwd();
|
|
3457
3479
|
const cleanup = ensureLockfile(cwd);
|
|
3458
3480
|
try {
|
|
@@ -3464,7 +3486,7 @@ async function buildDockerImage(imageName, options) {
|
|
|
3464
3486
|
DOCKER_BUILDKIT: "1"
|
|
3465
3487
|
}
|
|
3466
3488
|
});
|
|
3467
|
-
logger$
|
|
3489
|
+
logger$6.log(`✅ Docker image built: ${fullImageName}`);
|
|
3468
3490
|
} catch (error) {
|
|
3469
3491
|
throw new Error(`Failed to build Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3470
3492
|
} finally {
|
|
@@ -3479,13 +3501,13 @@ async function pushDockerImage(imageName, options) {
|
|
|
3479
3501
|
const registry = options.registry;
|
|
3480
3502
|
if (!registry) throw new Error("Registry is required to push Docker image. Use --registry or configure docker.registry in gkm.config.ts");
|
|
3481
3503
|
const fullImageName = `${registry}/${imageName}:${tag}`;
|
|
3482
|
-
logger$
|
|
3504
|
+
logger$6.log(`\n🚀 Pushing Docker image: ${fullImageName}`);
|
|
3483
3505
|
try {
|
|
3484
3506
|
execSync(`docker push ${fullImageName}`, {
|
|
3485
3507
|
cwd: process.cwd(),
|
|
3486
3508
|
stdio: "inherit"
|
|
3487
3509
|
});
|
|
3488
|
-
logger$
|
|
3510
|
+
logger$6.log(`✅ Docker image pushed: ${fullImageName}`);
|
|
3489
3511
|
} catch (error) {
|
|
3490
3512
|
throw new Error(`Failed to push Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3491
3513
|
}
|
|
@@ -3511,11 +3533,11 @@ function getAppPackageName(appPath) {
|
|
|
3511
3533
|
async function workspaceDockerCommand(workspace, options) {
|
|
3512
3534
|
const results = [];
|
|
3513
3535
|
const apps = Object.entries(workspace.apps);
|
|
3514
|
-
logger$
|
|
3536
|
+
logger$6.log(`\n🐳 Generating Dockerfiles for workspace: ${workspace.name}`);
|
|
3515
3537
|
const dockerDir = join(workspace.root, ".gkm", "docker");
|
|
3516
3538
|
await mkdir(dockerDir, { recursive: true });
|
|
3517
3539
|
const packageManager = detectPackageManager$1(workspace.root);
|
|
3518
|
-
logger$
|
|
3540
|
+
logger$6.log(` Package manager: ${packageManager}`);
|
|
3519
3541
|
for (const [appName, app] of apps) {
|
|
3520
3542
|
const appPath = app.path;
|
|
3521
3543
|
const fullAppPath = join(workspace.root, appPath);
|
|
@@ -3523,7 +3545,7 @@ async function workspaceDockerCommand(workspace, options) {
|
|
|
3523
3545
|
const imageName = appName;
|
|
3524
3546
|
const hasEntry = !!app.entry;
|
|
3525
3547
|
const buildType = hasEntry ? "entry" : app.type;
|
|
3526
|
-
logger$
|
|
3548
|
+
logger$6.log(`\n 📄 Generating Dockerfile for ${appName} (${buildType})`);
|
|
3527
3549
|
let dockerfile;
|
|
3528
3550
|
if (app.type === "frontend") dockerfile = generateNextjsDockerfile({
|
|
3529
3551
|
imageName,
|
|
@@ -3554,7 +3576,7 @@ async function workspaceDockerCommand(workspace, options) {
|
|
|
3554
3576
|
});
|
|
3555
3577
|
const dockerfilePath = join(dockerDir, `Dockerfile.${appName}`);
|
|
3556
3578
|
await writeFile(dockerfilePath, dockerfile);
|
|
3557
|
-
logger$
|
|
3579
|
+
logger$6.log(` Generated: .gkm/docker/Dockerfile.${appName}`);
|
|
3558
3580
|
results.push({
|
|
3559
3581
|
appName,
|
|
3560
3582
|
type: app.type,
|
|
@@ -3565,19 +3587,19 @@ async function workspaceDockerCommand(workspace, options) {
|
|
|
3565
3587
|
const dockerignore = generateDockerignore();
|
|
3566
3588
|
const dockerignorePath = join(workspace.root, ".dockerignore");
|
|
3567
3589
|
await writeFile(dockerignorePath, dockerignore);
|
|
3568
|
-
logger$
|
|
3590
|
+
logger$6.log(`\n Generated: .dockerignore (workspace root)`);
|
|
3569
3591
|
const dockerCompose = generateWorkspaceCompose(workspace, { registry: options.registry });
|
|
3570
3592
|
const composePath = join(dockerDir, "docker-compose.yml");
|
|
3571
3593
|
await writeFile(composePath, dockerCompose);
|
|
3572
|
-
logger$
|
|
3573
|
-
logger$
|
|
3574
|
-
logger$
|
|
3594
|
+
logger$6.log(` Generated: .gkm/docker/docker-compose.yml`);
|
|
3595
|
+
logger$6.log(`\n✅ Generated ${results.length} Dockerfile(s) + docker-compose.yml`);
|
|
3596
|
+
logger$6.log("\n📋 Build commands:");
|
|
3575
3597
|
for (const result of results) {
|
|
3576
3598
|
const icon = result.type === "backend" ? "⚙️" : "🌐";
|
|
3577
|
-
logger$
|
|
3599
|
+
logger$6.log(` ${icon} docker build -f .gkm/docker/Dockerfile.${result.appName} -t ${result.imageName} .`);
|
|
3578
3600
|
}
|
|
3579
|
-
logger$
|
|
3580
|
-
logger$
|
|
3601
|
+
logger$6.log("\n📋 Run all services:");
|
|
3602
|
+
logger$6.log(" docker compose -f .gkm/docker/docker-compose.yml up --build");
|
|
3581
3603
|
return {
|
|
3582
3604
|
apps: results,
|
|
3583
3605
|
dockerCompose: composePath,
|
|
@@ -3617,7 +3639,7 @@ function getAppNameFromPackageJson() {
|
|
|
3617
3639
|
} catch {}
|
|
3618
3640
|
return void 0;
|
|
3619
3641
|
}
|
|
3620
|
-
const logger$
|
|
3642
|
+
const logger$5 = console;
|
|
3621
3643
|
/**
|
|
3622
3644
|
* Get the full image reference
|
|
3623
3645
|
*/
|
|
@@ -3632,18 +3654,18 @@ function getImageRef(registry, imageName, tag) {
|
|
|
3632
3654
|
* @param buildArgs - Build arguments to pass to docker build
|
|
3633
3655
|
*/
|
|
3634
3656
|
async function buildImage(imageRef, appName, buildArgs) {
|
|
3635
|
-
logger$
|
|
3657
|
+
logger$5.log(`\n🔨 Building Docker image: ${imageRef}`);
|
|
3636
3658
|
const cwd = process.cwd();
|
|
3637
3659
|
const lockfilePath = findLockfilePath(cwd);
|
|
3638
3660
|
const lockfileDir = lockfilePath ? dirname(lockfilePath) : cwd;
|
|
3639
3661
|
const inMonorepo = lockfileDir !== cwd;
|
|
3640
|
-
if (appName || inMonorepo) logger$
|
|
3641
|
-
else logger$
|
|
3662
|
+
if (appName || inMonorepo) logger$5.log(" Generating Dockerfile for monorepo (turbo prune)...");
|
|
3663
|
+
else logger$5.log(" Generating Dockerfile...");
|
|
3642
3664
|
await dockerCommand({});
|
|
3643
3665
|
const dockerfileSuffix = appName ? `.${appName}` : "";
|
|
3644
3666
|
const dockerfilePath = `.gkm/docker/Dockerfile${dockerfileSuffix}`;
|
|
3645
3667
|
const buildCwd = lockfilePath && (inMonorepo || appName) ? lockfileDir : cwd;
|
|
3646
|
-
if (buildCwd !== cwd) logger$
|
|
3668
|
+
if (buildCwd !== cwd) logger$5.log(` Building from workspace root: ${buildCwd}`);
|
|
3647
3669
|
const buildArgsString = buildArgs && buildArgs.length > 0 ? buildArgs.map((arg) => `--build-arg "${arg}"`).join(" ") : "";
|
|
3648
3670
|
try {
|
|
3649
3671
|
const cmd = [
|
|
@@ -3662,7 +3684,7 @@ async function buildImage(imageRef, appName, buildArgs) {
|
|
|
3662
3684
|
DOCKER_BUILDKIT: "1"
|
|
3663
3685
|
}
|
|
3664
3686
|
});
|
|
3665
|
-
logger$
|
|
3687
|
+
logger$5.log(`✅ Image built: ${imageRef}`);
|
|
3666
3688
|
} catch (error) {
|
|
3667
3689
|
throw new Error(`Failed to build Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3668
3690
|
}
|
|
@@ -3671,13 +3693,13 @@ async function buildImage(imageRef, appName, buildArgs) {
|
|
|
3671
3693
|
* Push Docker image to registry
|
|
3672
3694
|
*/
|
|
3673
3695
|
async function pushImage(imageRef) {
|
|
3674
|
-
logger$
|
|
3696
|
+
logger$5.log(`\n☁️ Pushing image: ${imageRef}`);
|
|
3675
3697
|
try {
|
|
3676
3698
|
execSync(`docker push ${imageRef}`, {
|
|
3677
3699
|
cwd: process.cwd(),
|
|
3678
3700
|
stdio: "inherit"
|
|
3679
3701
|
});
|
|
3680
|
-
logger$
|
|
3702
|
+
logger$5.log(`✅ Image pushed: ${imageRef}`);
|
|
3681
3703
|
} catch (error) {
|
|
3682
3704
|
throw new Error(`Failed to push Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3683
3705
|
}
|
|
@@ -3690,17 +3712,17 @@ async function deployDocker(options) {
|
|
|
3690
3712
|
const imageName = config$1.imageName;
|
|
3691
3713
|
const imageRef = getImageRef(config$1.registry, imageName, tag);
|
|
3692
3714
|
await buildImage(imageRef, config$1.appName, buildArgs);
|
|
3693
|
-
if (!skipPush) if (!config$1.registry) logger$
|
|
3715
|
+
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
3716
|
else await pushImage(imageRef);
|
|
3695
|
-
logger$
|
|
3696
|
-
logger$
|
|
3697
|
-
logger$
|
|
3698
|
-
logger$
|
|
3717
|
+
logger$5.log("\n✅ Docker deployment ready!");
|
|
3718
|
+
logger$5.log(`\n📋 Deployment details:`);
|
|
3719
|
+
logger$5.log(` Image: ${imageRef}`);
|
|
3720
|
+
logger$5.log(` Stage: ${stage}`);
|
|
3699
3721
|
if (masterKey) {
|
|
3700
|
-
logger$
|
|
3701
|
-
logger$
|
|
3702
|
-
logger$
|
|
3703
|
-
logger$
|
|
3722
|
+
logger$5.log(`\n🔐 Deploy with this environment variable:`);
|
|
3723
|
+
logger$5.log(` GKM_MASTER_KEY=${masterKey}`);
|
|
3724
|
+
logger$5.log("\n Example docker run:");
|
|
3725
|
+
logger$5.log(` docker run -e GKM_MASTER_KEY=${masterKey} ${imageRef}`);
|
|
3704
3726
|
}
|
|
3705
3727
|
return {
|
|
3706
3728
|
imageRef,
|
|
@@ -3727,7 +3749,7 @@ function resolveDockerConfig(config$1) {
|
|
|
3727
3749
|
|
|
3728
3750
|
//#endregion
|
|
3729
3751
|
//#region src/deploy/dokploy.ts
|
|
3730
|
-
const logger$
|
|
3752
|
+
const logger$4 = console;
|
|
3731
3753
|
/**
|
|
3732
3754
|
* Get the Dokploy API token from stored credentials or environment
|
|
3733
3755
|
*/
|
|
@@ -3751,25 +3773,25 @@ async function createApi$1(endpoint) {
|
|
|
3751
3773
|
*/
|
|
3752
3774
|
async function deployDokploy(options) {
|
|
3753
3775
|
const { stage, imageRef, masterKey, config: config$1 } = options;
|
|
3754
|
-
logger$
|
|
3755
|
-
logger$
|
|
3756
|
-
logger$
|
|
3776
|
+
logger$4.log(`\n🎯 Deploying to Dokploy...`);
|
|
3777
|
+
logger$4.log(` Endpoint: ${config$1.endpoint}`);
|
|
3778
|
+
logger$4.log(` Application: ${config$1.applicationId}`);
|
|
3757
3779
|
const api = await createApi$1(config$1.endpoint);
|
|
3758
|
-
logger$
|
|
3780
|
+
logger$4.log(` Configuring Docker image: ${imageRef}`);
|
|
3759
3781
|
const registryOptions = {};
|
|
3760
3782
|
if (config$1.registryId) {
|
|
3761
3783
|
registryOptions.registryId = config$1.registryId;
|
|
3762
|
-
logger$
|
|
3784
|
+
logger$4.log(` Using Dokploy registry: ${config$1.registryId}`);
|
|
3763
3785
|
} else {
|
|
3764
3786
|
const storedRegistryId = await getDokployRegistryId();
|
|
3765
3787
|
if (storedRegistryId) {
|
|
3766
3788
|
registryOptions.registryId = storedRegistryId;
|
|
3767
|
-
logger$
|
|
3789
|
+
logger$4.log(` Using stored Dokploy registry: ${storedRegistryId}`);
|
|
3768
3790
|
} else if (config$1.registryCredentials) {
|
|
3769
3791
|
registryOptions.username = config$1.registryCredentials.username;
|
|
3770
3792
|
registryOptions.password = config$1.registryCredentials.password;
|
|
3771
3793
|
registryOptions.registryUrl = config$1.registryCredentials.registryUrl;
|
|
3772
|
-
logger$
|
|
3794
|
+
logger$4.log(` Using registry credentials for: ${config$1.registryCredentials.registryUrl}`);
|
|
3773
3795
|
} else {
|
|
3774
3796
|
const username = process.env.DOCKER_REGISTRY_USERNAME;
|
|
3775
3797
|
const password = process.env.DOCKER_REGISTRY_PASSWORD;
|
|
@@ -3778,31 +3800,31 @@ async function deployDokploy(options) {
|
|
|
3778
3800
|
registryOptions.username = username;
|
|
3779
3801
|
registryOptions.password = password;
|
|
3780
3802
|
registryOptions.registryUrl = registryUrl;
|
|
3781
|
-
logger$
|
|
3803
|
+
logger$4.log(` Using registry credentials from environment`);
|
|
3782
3804
|
}
|
|
3783
3805
|
}
|
|
3784
3806
|
}
|
|
3785
3807
|
await api.saveDockerProvider(config$1.applicationId, imageRef, registryOptions);
|
|
3786
|
-
logger$
|
|
3808
|
+
logger$4.log(" ✓ Docker provider configured");
|
|
3787
3809
|
const envVars = {};
|
|
3788
3810
|
if (masterKey) envVars.GKM_MASTER_KEY = masterKey;
|
|
3789
3811
|
if (Object.keys(envVars).length > 0) {
|
|
3790
|
-
logger$
|
|
3812
|
+
logger$4.log(" Updating environment variables...");
|
|
3791
3813
|
const envString = Object.entries(envVars).map(([key, value]) => `${key}=${value}`).join("\n");
|
|
3792
3814
|
await api.saveApplicationEnv(config$1.applicationId, envString);
|
|
3793
|
-
logger$
|
|
3815
|
+
logger$4.log(" ✓ Environment variables updated");
|
|
3794
3816
|
}
|
|
3795
|
-
logger$
|
|
3817
|
+
logger$4.log(" Triggering deployment...");
|
|
3796
3818
|
await api.deployApplication(config$1.applicationId);
|
|
3797
|
-
logger$
|
|
3798
|
-
logger$
|
|
3799
|
-
logger$
|
|
3800
|
-
logger$
|
|
3801
|
-
logger$
|
|
3802
|
-
logger$
|
|
3803
|
-
if (masterKey) logger$
|
|
3819
|
+
logger$4.log(" ✓ Deployment triggered");
|
|
3820
|
+
logger$4.log("\n✅ Dokploy deployment initiated!");
|
|
3821
|
+
logger$4.log(`\n📋 Deployment details:`);
|
|
3822
|
+
logger$4.log(` Image: ${imageRef}`);
|
|
3823
|
+
logger$4.log(` Stage: ${stage}`);
|
|
3824
|
+
logger$4.log(` Application ID: ${config$1.applicationId}`);
|
|
3825
|
+
if (masterKey) logger$4.log(`\n🔐 GKM_MASTER_KEY has been set in Dokploy environment`);
|
|
3804
3826
|
const deploymentUrl = `${config$1.endpoint}/project/${config$1.projectId}`;
|
|
3805
|
-
logger$
|
|
3827
|
+
logger$4.log(`\n🔗 View deployment: ${deploymentUrl}`);
|
|
3806
3828
|
return {
|
|
3807
3829
|
imageRef,
|
|
3808
3830
|
masterKey,
|
|
@@ -3893,6 +3915,389 @@ function setRedisId(state, redisId) {
|
|
|
3893
3915
|
state.services.redisId = redisId;
|
|
3894
3916
|
}
|
|
3895
3917
|
|
|
3918
|
+
//#endregion
|
|
3919
|
+
//#region src/deploy/dns/hostinger-api.ts
|
|
3920
|
+
/**
|
|
3921
|
+
* Hostinger DNS API client
|
|
3922
|
+
*
|
|
3923
|
+
* API Documentation: https://developers.hostinger.com/
|
|
3924
|
+
* Authentication: Bearer token from hpanel.hostinger.com/profile/api
|
|
3925
|
+
*/
|
|
3926
|
+
const HOSTINGER_API_BASE = "https://developers.hostinger.com";
|
|
3927
|
+
/**
|
|
3928
|
+
* Hostinger API error
|
|
3929
|
+
*/
|
|
3930
|
+
var HostingerApiError = class extends Error {
|
|
3931
|
+
constructor(message, status, statusText, errors) {
|
|
3932
|
+
super(message);
|
|
3933
|
+
this.status = status;
|
|
3934
|
+
this.statusText = statusText;
|
|
3935
|
+
this.errors = errors;
|
|
3936
|
+
this.name = "HostingerApiError";
|
|
3937
|
+
}
|
|
3938
|
+
};
|
|
3939
|
+
/**
|
|
3940
|
+
* Hostinger DNS API client
|
|
3941
|
+
*
|
|
3942
|
+
* @example
|
|
3943
|
+
* ```ts
|
|
3944
|
+
* const api = new HostingerApi(token);
|
|
3945
|
+
*
|
|
3946
|
+
* // Get all records for a domain
|
|
3947
|
+
* const records = await api.getRecords('traflabs.io');
|
|
3948
|
+
*
|
|
3949
|
+
* // Create/update records
|
|
3950
|
+
* await api.upsertRecords('traflabs.io', [
|
|
3951
|
+
* { name: 'api.joemoer', type: 'A', ttl: 300, records: ['1.2.3.4'] }
|
|
3952
|
+
* ]);
|
|
3953
|
+
* ```
|
|
3954
|
+
*/
|
|
3955
|
+
var HostingerApi = class {
|
|
3956
|
+
token;
|
|
3957
|
+
constructor(token) {
|
|
3958
|
+
this.token = token;
|
|
3959
|
+
}
|
|
3960
|
+
/**
|
|
3961
|
+
* Make a request to the Hostinger API
|
|
3962
|
+
*/
|
|
3963
|
+
async request(method, endpoint, body) {
|
|
3964
|
+
const url = `${HOSTINGER_API_BASE}${endpoint}`;
|
|
3965
|
+
const response = await fetch(url, {
|
|
3966
|
+
method,
|
|
3967
|
+
headers: {
|
|
3968
|
+
"Content-Type": "application/json",
|
|
3969
|
+
Authorization: `Bearer ${this.token}`
|
|
3970
|
+
},
|
|
3971
|
+
body: body ? JSON.stringify(body) : void 0
|
|
3972
|
+
});
|
|
3973
|
+
if (!response.ok) {
|
|
3974
|
+
let errorMessage = `Hostinger API error: ${response.status} ${response.statusText}`;
|
|
3975
|
+
let errors;
|
|
3976
|
+
try {
|
|
3977
|
+
const errorBody = await response.json();
|
|
3978
|
+
if (errorBody.message) errorMessage = `Hostinger API error: ${errorBody.message}`;
|
|
3979
|
+
errors = errorBody.errors;
|
|
3980
|
+
} catch {}
|
|
3981
|
+
throw new HostingerApiError(errorMessage, response.status, response.statusText, errors);
|
|
3982
|
+
}
|
|
3983
|
+
const text = await response.text();
|
|
3984
|
+
if (!text || text.trim() === "") return void 0;
|
|
3985
|
+
return JSON.parse(text);
|
|
3986
|
+
}
|
|
3987
|
+
/**
|
|
3988
|
+
* Get all DNS records for a domain
|
|
3989
|
+
*
|
|
3990
|
+
* @param domain - Root domain (e.g., 'traflabs.io')
|
|
3991
|
+
*/
|
|
3992
|
+
async getRecords(domain) {
|
|
3993
|
+
const response = await this.request("GET", `/api/dns/v1/zones/${domain}`);
|
|
3994
|
+
return response.data || [];
|
|
3995
|
+
}
|
|
3996
|
+
/**
|
|
3997
|
+
* Create or update DNS records
|
|
3998
|
+
*
|
|
3999
|
+
* @param domain - Root domain (e.g., 'traflabs.io')
|
|
4000
|
+
* @param records - Records to create/update
|
|
4001
|
+
* @param overwrite - If true, replaces all existing records. If false, merges with existing.
|
|
4002
|
+
*/
|
|
4003
|
+
async upsertRecords(domain, records, overwrite = false) {
|
|
4004
|
+
await this.request("PUT", `/api/dns/v1/zones/${domain}`, {
|
|
4005
|
+
overwrite,
|
|
4006
|
+
zone: records
|
|
4007
|
+
});
|
|
4008
|
+
}
|
|
4009
|
+
/**
|
|
4010
|
+
* Validate DNS records before applying
|
|
4011
|
+
*
|
|
4012
|
+
* @param domain - Root domain (e.g., 'traflabs.io')
|
|
4013
|
+
* @param records - Records to validate
|
|
4014
|
+
* @returns true if valid, throws if invalid
|
|
4015
|
+
*/
|
|
4016
|
+
async validateRecords(domain, records) {
|
|
4017
|
+
await this.request("POST", `/api/dns/v1/zones/${domain}/validate`, {
|
|
4018
|
+
overwrite: false,
|
|
4019
|
+
zone: records
|
|
4020
|
+
});
|
|
4021
|
+
return true;
|
|
4022
|
+
}
|
|
4023
|
+
/**
|
|
4024
|
+
* Delete specific DNS records
|
|
4025
|
+
*
|
|
4026
|
+
* @param domain - Root domain (e.g., 'traflabs.io')
|
|
4027
|
+
* @param filters - Filters to match records for deletion
|
|
4028
|
+
*/
|
|
4029
|
+
async deleteRecords(domain, filters) {
|
|
4030
|
+
await this.request("DELETE", `/api/dns/v1/zones/${domain}`, { filters });
|
|
4031
|
+
}
|
|
4032
|
+
/**
|
|
4033
|
+
* Check if a specific record exists
|
|
4034
|
+
*
|
|
4035
|
+
* @param domain - Root domain (e.g., 'traflabs.io')
|
|
4036
|
+
* @param name - Subdomain name (e.g., 'api.joemoer')
|
|
4037
|
+
* @param type - Record type (e.g., 'A')
|
|
4038
|
+
*/
|
|
4039
|
+
async recordExists(domain, name$1, type$1 = "A") {
|
|
4040
|
+
const records = await this.getRecords(domain);
|
|
4041
|
+
return records.some((r) => r.name === name$1 && r.type === type$1);
|
|
4042
|
+
}
|
|
4043
|
+
/**
|
|
4044
|
+
* Create a single A record if it doesn't exist
|
|
4045
|
+
*
|
|
4046
|
+
* @param domain - Root domain (e.g., 'traflabs.io')
|
|
4047
|
+
* @param subdomain - Subdomain name (e.g., 'api.joemoer')
|
|
4048
|
+
* @param ip - IP address to point to
|
|
4049
|
+
* @param ttl - TTL in seconds (default: 300)
|
|
4050
|
+
* @returns true if created, false if already exists
|
|
4051
|
+
*/
|
|
4052
|
+
async createARecordIfNotExists(domain, subdomain, ip, ttl = 300) {
|
|
4053
|
+
const exists = await this.recordExists(domain, subdomain, "A");
|
|
4054
|
+
if (exists) return false;
|
|
4055
|
+
await this.upsertRecords(domain, [{
|
|
4056
|
+
name: subdomain,
|
|
4057
|
+
type: "A",
|
|
4058
|
+
ttl,
|
|
4059
|
+
records: [{ content: ip }]
|
|
4060
|
+
}]);
|
|
4061
|
+
return true;
|
|
4062
|
+
}
|
|
4063
|
+
};
|
|
4064
|
+
|
|
4065
|
+
//#endregion
|
|
4066
|
+
//#region src/deploy/dns/index.ts
|
|
4067
|
+
const logger$3 = console;
|
|
4068
|
+
/**
|
|
4069
|
+
* Resolve IP address from a hostname
|
|
4070
|
+
*/
|
|
4071
|
+
async function resolveHostnameToIp(hostname) {
|
|
4072
|
+
try {
|
|
4073
|
+
const addresses = await lookup(hostname, { family: 4 });
|
|
4074
|
+
return addresses.address;
|
|
4075
|
+
} catch (error) {
|
|
4076
|
+
throw new Error(`Failed to resolve IP for ${hostname}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
4077
|
+
}
|
|
4078
|
+
}
|
|
4079
|
+
/**
|
|
4080
|
+
* Extract subdomain from full hostname relative to root domain
|
|
4081
|
+
*
|
|
4082
|
+
* @example
|
|
4083
|
+
* extractSubdomain('api.joemoer.traflabs.io', 'traflabs.io') => 'api.joemoer'
|
|
4084
|
+
* extractSubdomain('joemoer.traflabs.io', 'traflabs.io') => 'joemoer'
|
|
4085
|
+
*/
|
|
4086
|
+
function extractSubdomain(hostname, rootDomain) {
|
|
4087
|
+
if (!hostname.endsWith(rootDomain)) throw new Error(`Hostname ${hostname} is not under root domain ${rootDomain}`);
|
|
4088
|
+
const subdomain = hostname.slice(0, -(rootDomain.length + 1));
|
|
4089
|
+
return subdomain || "@";
|
|
4090
|
+
}
|
|
4091
|
+
/**
|
|
4092
|
+
* Generate required DNS records for a deployment
|
|
4093
|
+
*/
|
|
4094
|
+
function generateRequiredRecords(appHostnames, rootDomain, serverIp) {
|
|
4095
|
+
const records = [];
|
|
4096
|
+
for (const [appName, hostname] of appHostnames) {
|
|
4097
|
+
const subdomain = extractSubdomain(hostname, rootDomain);
|
|
4098
|
+
records.push({
|
|
4099
|
+
hostname,
|
|
4100
|
+
subdomain,
|
|
4101
|
+
type: "A",
|
|
4102
|
+
value: serverIp,
|
|
4103
|
+
appName
|
|
4104
|
+
});
|
|
4105
|
+
}
|
|
4106
|
+
return records;
|
|
4107
|
+
}
|
|
4108
|
+
/**
|
|
4109
|
+
* Print DNS records table
|
|
4110
|
+
*/
|
|
4111
|
+
function printDnsRecordsTable(records, rootDomain) {
|
|
4112
|
+
logger$3.log("\n 📋 DNS Records for " + rootDomain + ":");
|
|
4113
|
+
logger$3.log(" ┌─────────────────────────────────────┬──────┬─────────────────┬────────┐");
|
|
4114
|
+
logger$3.log(" │ Subdomain │ Type │ Value │ Status │");
|
|
4115
|
+
logger$3.log(" ├─────────────────────────────────────┼──────┼─────────────────┼────────┤");
|
|
4116
|
+
for (const record of records) {
|
|
4117
|
+
const subdomain = record.subdomain.padEnd(35);
|
|
4118
|
+
const type$1 = record.type.padEnd(4);
|
|
4119
|
+
const value = record.value.padEnd(15);
|
|
4120
|
+
let status;
|
|
4121
|
+
if (record.error) status = "✗";
|
|
4122
|
+
else if (record.created) status = "✓ new";
|
|
4123
|
+
else if (record.existed) status = "✓";
|
|
4124
|
+
else status = "?";
|
|
4125
|
+
logger$3.log(` │ ${subdomain} │ ${type$1} │ ${value} │ ${status.padEnd(6)} │`);
|
|
4126
|
+
}
|
|
4127
|
+
logger$3.log(" └─────────────────────────────────────┴──────┴─────────────────┴────────┘");
|
|
4128
|
+
}
|
|
4129
|
+
/**
|
|
4130
|
+
* Print DNS records in a simple format for manual setup
|
|
4131
|
+
*/
|
|
4132
|
+
function printDnsRecordsSimple(records, rootDomain) {
|
|
4133
|
+
logger$3.log("\n 📋 Required DNS Records:");
|
|
4134
|
+
logger$3.log(` Add these A records to your DNS provider (${rootDomain}):\n`);
|
|
4135
|
+
for (const record of records) logger$3.log(` ${record.subdomain} → ${record.value} (A record)`);
|
|
4136
|
+
logger$3.log("");
|
|
4137
|
+
}
|
|
4138
|
+
/**
|
|
4139
|
+
* Prompt for input (reuse from deploy/index.ts pattern)
|
|
4140
|
+
*/
|
|
4141
|
+
async function promptForToken(message) {
|
|
4142
|
+
const { stdin: stdin$1, stdout: stdout$1 } = await import("node:process");
|
|
4143
|
+
if (!stdin$1.isTTY) throw new Error("Interactive input required for Hostinger token.");
|
|
4144
|
+
stdout$1.write(message);
|
|
4145
|
+
return new Promise((resolve$1) => {
|
|
4146
|
+
let value = "";
|
|
4147
|
+
const onData = (char) => {
|
|
4148
|
+
const c = char.toString();
|
|
4149
|
+
if (c === "\n" || c === "\r") {
|
|
4150
|
+
stdin$1.setRawMode(false);
|
|
4151
|
+
stdin$1.pause();
|
|
4152
|
+
stdin$1.removeListener("data", onData);
|
|
4153
|
+
stdout$1.write("\n");
|
|
4154
|
+
resolve$1(value);
|
|
4155
|
+
} else if (c === "") {
|
|
4156
|
+
stdin$1.setRawMode(false);
|
|
4157
|
+
stdin$1.pause();
|
|
4158
|
+
stdout$1.write("\n");
|
|
4159
|
+
process.exit(1);
|
|
4160
|
+
} else if (c === "" || c === "\b") {
|
|
4161
|
+
if (value.length > 0) value = value.slice(0, -1);
|
|
4162
|
+
} else value += c;
|
|
4163
|
+
};
|
|
4164
|
+
stdin$1.setRawMode(true);
|
|
4165
|
+
stdin$1.resume();
|
|
4166
|
+
stdin$1.on("data", onData);
|
|
4167
|
+
});
|
|
4168
|
+
}
|
|
4169
|
+
/**
|
|
4170
|
+
* Create DNS records using the configured provider
|
|
4171
|
+
*/
|
|
4172
|
+
async function createDnsRecords(records, dnsConfig) {
|
|
4173
|
+
const { provider, domain: rootDomain, ttl = 300 } = dnsConfig;
|
|
4174
|
+
if (provider === "manual") return records.map((r) => ({
|
|
4175
|
+
...r,
|
|
4176
|
+
created: false,
|
|
4177
|
+
existed: false
|
|
4178
|
+
}));
|
|
4179
|
+
if (provider === "hostinger") return createHostingerRecords(records, rootDomain, ttl);
|
|
4180
|
+
if (provider === "cloudflare") {
|
|
4181
|
+
logger$3.log(" ⚠ Cloudflare DNS integration not yet implemented");
|
|
4182
|
+
return records.map((r) => ({
|
|
4183
|
+
...r,
|
|
4184
|
+
error: "Cloudflare not implemented"
|
|
4185
|
+
}));
|
|
4186
|
+
}
|
|
4187
|
+
return records;
|
|
4188
|
+
}
|
|
4189
|
+
/**
|
|
4190
|
+
* Create DNS records at Hostinger
|
|
4191
|
+
*/
|
|
4192
|
+
async function createHostingerRecords(records, rootDomain, ttl) {
|
|
4193
|
+
let token = await getHostingerToken();
|
|
4194
|
+
if (!token) {
|
|
4195
|
+
logger$3.log("\n 📋 Hostinger API token not found.");
|
|
4196
|
+
logger$3.log(" Get your token from: https://hpanel.hostinger.com/profile/api\n");
|
|
4197
|
+
try {
|
|
4198
|
+
token = await promptForToken(" Hostinger API Token: ");
|
|
4199
|
+
await storeHostingerToken(token);
|
|
4200
|
+
logger$3.log(" ✓ Token saved");
|
|
4201
|
+
} catch {
|
|
4202
|
+
logger$3.log(" ⚠ Could not get token, skipping DNS creation");
|
|
4203
|
+
return records.map((r) => ({
|
|
4204
|
+
...r,
|
|
4205
|
+
error: "No API token"
|
|
4206
|
+
}));
|
|
4207
|
+
}
|
|
4208
|
+
}
|
|
4209
|
+
const api = new HostingerApi(token);
|
|
4210
|
+
const results = [];
|
|
4211
|
+
let existingRecords = [];
|
|
4212
|
+
try {
|
|
4213
|
+
existingRecords = await api.getRecords(rootDomain);
|
|
4214
|
+
} catch (error) {
|
|
4215
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4216
|
+
logger$3.log(` ⚠ Failed to fetch existing DNS records: ${message}`);
|
|
4217
|
+
return records.map((r) => ({
|
|
4218
|
+
...r,
|
|
4219
|
+
error: message
|
|
4220
|
+
}));
|
|
4221
|
+
}
|
|
4222
|
+
for (const record of records) {
|
|
4223
|
+
const existing = existingRecords.find((r) => r.name === record.subdomain && r.type === "A");
|
|
4224
|
+
if (existing) {
|
|
4225
|
+
results.push({
|
|
4226
|
+
...record,
|
|
4227
|
+
existed: true,
|
|
4228
|
+
created: false
|
|
4229
|
+
});
|
|
4230
|
+
continue;
|
|
4231
|
+
}
|
|
4232
|
+
try {
|
|
4233
|
+
await api.upsertRecords(rootDomain, [{
|
|
4234
|
+
name: record.subdomain,
|
|
4235
|
+
type: "A",
|
|
4236
|
+
ttl,
|
|
4237
|
+
records: [{ content: record.value }]
|
|
4238
|
+
}]);
|
|
4239
|
+
results.push({
|
|
4240
|
+
...record,
|
|
4241
|
+
created: true,
|
|
4242
|
+
existed: false
|
|
4243
|
+
});
|
|
4244
|
+
} catch (error) {
|
|
4245
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4246
|
+
results.push({
|
|
4247
|
+
...record,
|
|
4248
|
+
error: message
|
|
4249
|
+
});
|
|
4250
|
+
}
|
|
4251
|
+
}
|
|
4252
|
+
return results;
|
|
4253
|
+
}
|
|
4254
|
+
/**
|
|
4255
|
+
* Main DNS orchestration function for deployments
|
|
4256
|
+
*/
|
|
4257
|
+
async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint) {
|
|
4258
|
+
if (!dnsConfig) return null;
|
|
4259
|
+
const { domain: rootDomain, autoCreate = true } = dnsConfig;
|
|
4260
|
+
logger$3.log("\n🌐 Setting up DNS records...");
|
|
4261
|
+
let serverIp;
|
|
4262
|
+
try {
|
|
4263
|
+
const endpointUrl = new URL(dokployEndpoint);
|
|
4264
|
+
serverIp = await resolveHostnameToIp(endpointUrl.hostname);
|
|
4265
|
+
logger$3.log(` Server IP: ${serverIp} (from ${endpointUrl.hostname})`);
|
|
4266
|
+
} catch (error) {
|
|
4267
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4268
|
+
logger$3.log(` ⚠ Failed to resolve server IP: ${message}`);
|
|
4269
|
+
return null;
|
|
4270
|
+
}
|
|
4271
|
+
const requiredRecords = generateRequiredRecords(appHostnames, rootDomain, serverIp);
|
|
4272
|
+
if (requiredRecords.length === 0) {
|
|
4273
|
+
logger$3.log(" No DNS records needed");
|
|
4274
|
+
return {
|
|
4275
|
+
records: [],
|
|
4276
|
+
success: true,
|
|
4277
|
+
serverIp
|
|
4278
|
+
};
|
|
4279
|
+
}
|
|
4280
|
+
let finalRecords;
|
|
4281
|
+
if (autoCreate && dnsConfig.provider !== "manual") {
|
|
4282
|
+
logger$3.log(` Creating DNS records at ${dnsConfig.provider}...`);
|
|
4283
|
+
finalRecords = await createDnsRecords(requiredRecords, dnsConfig);
|
|
4284
|
+
const created = finalRecords.filter((r) => r.created).length;
|
|
4285
|
+
const existed = finalRecords.filter((r) => r.existed).length;
|
|
4286
|
+
const failed = finalRecords.filter((r) => r.error).length;
|
|
4287
|
+
if (created > 0) logger$3.log(` ✓ Created ${created} DNS record(s)`);
|
|
4288
|
+
if (existed > 0) logger$3.log(` ✓ ${existed} record(s) already exist`);
|
|
4289
|
+
if (failed > 0) logger$3.log(` ⚠ ${failed} record(s) failed`);
|
|
4290
|
+
} else finalRecords = requiredRecords;
|
|
4291
|
+
printDnsRecordsTable(finalRecords, rootDomain);
|
|
4292
|
+
const hasFailures = finalRecords.some((r) => r.error);
|
|
4293
|
+
if (dnsConfig.provider === "manual" || hasFailures) printDnsRecordsSimple(finalRecords.filter((r) => !r.created && !r.existed), rootDomain);
|
|
4294
|
+
return {
|
|
4295
|
+
records: finalRecords,
|
|
4296
|
+
success: !hasFailures,
|
|
4297
|
+
serverIp
|
|
4298
|
+
};
|
|
4299
|
+
}
|
|
4300
|
+
|
|
3896
4301
|
//#endregion
|
|
3897
4302
|
//#region src/deploy/domain.ts
|
|
3898
4303
|
/**
|
|
@@ -4836,6 +5241,8 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
4836
5241
|
const publicUrls = {};
|
|
4837
5242
|
const results = [];
|
|
4838
5243
|
const dokployConfig = workspace.deploy.dokploy;
|
|
5244
|
+
const appHostnames = /* @__PURE__ */ new Map();
|
|
5245
|
+
const appDomainIds = /* @__PURE__ */ new Map();
|
|
4839
5246
|
if (backendApps.length > 0) {
|
|
4840
5247
|
logger$1.log("\n📦 PHASE 1: Deploying backend applications...");
|
|
4841
5248
|
for (const appName of backendApps) {
|
|
@@ -4885,22 +5292,29 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
4885
5292
|
await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
|
|
4886
5293
|
logger$1.log(` Deploying to Dokploy...`);
|
|
4887
5294
|
await api.deployApplication(application.applicationId);
|
|
5295
|
+
const backendHost = resolveHost(appName, app, stage, dokployConfig, false);
|
|
4888
5296
|
try {
|
|
4889
|
-
const
|
|
4890
|
-
|
|
4891
|
-
host,
|
|
5297
|
+
const domain = await api.createDomain({
|
|
5298
|
+
host: backendHost,
|
|
4892
5299
|
port: app.port,
|
|
4893
5300
|
https: true,
|
|
4894
5301
|
certificateType: "letsencrypt",
|
|
4895
5302
|
applicationId: application.applicationId
|
|
4896
5303
|
});
|
|
4897
|
-
|
|
5304
|
+
appHostnames.set(appName, backendHost);
|
|
5305
|
+
appDomainIds.set(appName, domain.domainId);
|
|
5306
|
+
const publicUrl = `https://${backendHost}`;
|
|
4898
5307
|
publicUrls[appName] = publicUrl;
|
|
4899
5308
|
logger$1.log(` ✓ Domain: ${publicUrl}`);
|
|
4900
5309
|
} catch (domainError) {
|
|
4901
|
-
|
|
4902
|
-
|
|
4903
|
-
|
|
5310
|
+
appHostnames.set(appName, backendHost);
|
|
5311
|
+
try {
|
|
5312
|
+
const existingDomains = await api.getDomainsByApplicationId(application.applicationId);
|
|
5313
|
+
const matchingDomain = existingDomains.find((d) => d.host === backendHost);
|
|
5314
|
+
if (matchingDomain) appDomainIds.set(appName, matchingDomain.domainId);
|
|
5315
|
+
} catch {}
|
|
5316
|
+
publicUrls[appName] = `https://${backendHost}`;
|
|
5317
|
+
logger$1.log(` ℹ Domain already configured: https://${backendHost}`);
|
|
4904
5318
|
}
|
|
4905
5319
|
results.push({
|
|
4906
5320
|
appName,
|
|
@@ -4968,22 +5382,29 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
4968
5382
|
logger$1.log(` Deploying to Dokploy...`);
|
|
4969
5383
|
await api.deployApplication(application.applicationId);
|
|
4970
5384
|
const isMainFrontend = isMainFrontendApp(appName, app, workspace.apps);
|
|
5385
|
+
const frontendHost = resolveHost(appName, app, stage, dokployConfig, isMainFrontend);
|
|
4971
5386
|
try {
|
|
4972
|
-
const
|
|
4973
|
-
|
|
4974
|
-
host,
|
|
5387
|
+
const domain = await api.createDomain({
|
|
5388
|
+
host: frontendHost,
|
|
4975
5389
|
port: app.port,
|
|
4976
5390
|
https: true,
|
|
4977
5391
|
certificateType: "letsencrypt",
|
|
4978
5392
|
applicationId: application.applicationId
|
|
4979
5393
|
});
|
|
4980
|
-
|
|
5394
|
+
appHostnames.set(appName, frontendHost);
|
|
5395
|
+
appDomainIds.set(appName, domain.domainId);
|
|
5396
|
+
const publicUrl = `https://${frontendHost}`;
|
|
4981
5397
|
publicUrls[appName] = publicUrl;
|
|
4982
5398
|
logger$1.log(` ✓ Domain: ${publicUrl}`);
|
|
4983
5399
|
} catch (domainError) {
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
5400
|
+
appHostnames.set(appName, frontendHost);
|
|
5401
|
+
try {
|
|
5402
|
+
const existingDomains = await api.getDomainsByApplicationId(application.applicationId);
|
|
5403
|
+
const matchingDomain = existingDomains.find((d) => d.host === frontendHost);
|
|
5404
|
+
if (matchingDomain) appDomainIds.set(appName, matchingDomain.domainId);
|
|
5405
|
+
} catch {}
|
|
5406
|
+
publicUrls[appName] = `https://${frontendHost}`;
|
|
5407
|
+
logger$1.log(` ℹ Domain already configured: https://${frontendHost}`);
|
|
4987
5408
|
}
|
|
4988
5409
|
results.push({
|
|
4989
5410
|
appName,
|
|
@@ -5008,6 +5429,21 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
5008
5429
|
logger$1.log("\n📋 Saving deploy state...");
|
|
5009
5430
|
await writeStageState(workspace.root, stage, state);
|
|
5010
5431
|
logger$1.log(` ✓ State saved to .gkm/deploy-${stage}.json`);
|
|
5432
|
+
const dnsConfig = workspace.deploy.dns;
|
|
5433
|
+
if (dnsConfig && appHostnames.size > 0) {
|
|
5434
|
+
const dnsResult = await orchestrateDns(appHostnames, dnsConfig, creds.endpoint);
|
|
5435
|
+
if (dnsResult?.success && appHostnames.size > 0) {
|
|
5436
|
+
logger$1.log("\n🔒 Validating domains for SSL certificates...");
|
|
5437
|
+
for (const [appName, hostname] of appHostnames) try {
|
|
5438
|
+
const result = await api.validateDomain(hostname);
|
|
5439
|
+
if (result.isValid) logger$1.log(` ✓ ${appName}: ${hostname} → ${result.resolvedIp}`);
|
|
5440
|
+
else logger$1.log(` ⚠ ${appName}: ${hostname} not valid`);
|
|
5441
|
+
} catch (validationError) {
|
|
5442
|
+
const message = validationError instanceof Error ? validationError.message : "Unknown error";
|
|
5443
|
+
logger$1.log(` ⚠ ${appName}: validation failed - ${message}`);
|
|
5444
|
+
}
|
|
5445
|
+
}
|
|
5446
|
+
}
|
|
5011
5447
|
const successCount = results.filter((r) => r.success).length;
|
|
5012
5448
|
const failedCount = results.filter((r) => !r.success).length;
|
|
5013
5449
|
logger$1.log(`\n${"─".repeat(50)}`);
|