@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.cjs
CHANGED
|
@@ -4,7 +4,7 @@ const require_workspace = require('./workspace-CaVW6j2q.cjs');
|
|
|
4
4
|
const require_config = require('./config-HYiM3iQJ.cjs');
|
|
5
5
|
const require_openapi = require('./openapi-D7WwlpPF.cjs');
|
|
6
6
|
const require_storage = require('./storage-BPRgh3DU.cjs');
|
|
7
|
-
const require_dokploy_api = require('./dokploy-api-
|
|
7
|
+
const require_dokploy_api = require('./dokploy-api-BDLu0qWi.cjs');
|
|
8
8
|
const require_encryption = require('./encryption-DaCB_NmS.cjs');
|
|
9
9
|
const require_openapi_react_query = require('./openapi-react-query-C_MxpBgF.cjs');
|
|
10
10
|
const node_fs = require_chunk.__toESM(require("node:fs"));
|
|
@@ -23,13 +23,14 @@ const __geekmidas_constructs_crons = require_chunk.__toESM(require("@geekmidas/c
|
|
|
23
23
|
const __geekmidas_constructs_functions = require_chunk.__toESM(require("@geekmidas/constructs/functions"));
|
|
24
24
|
const __geekmidas_constructs_subscribers = require_chunk.__toESM(require("@geekmidas/constructs/subscribers"));
|
|
25
25
|
const node_crypto = require_chunk.__toESM(require("node:crypto"));
|
|
26
|
+
const node_dns_promises = require_chunk.__toESM(require("node:dns/promises"));
|
|
26
27
|
const node_url = require_chunk.__toESM(require("node:url"));
|
|
27
28
|
const prompts = require_chunk.__toESM(require("prompts"));
|
|
28
29
|
const node_module = require_chunk.__toESM(require("node:module"));
|
|
29
30
|
|
|
30
31
|
//#region package.json
|
|
31
32
|
var name = "@geekmidas/cli";
|
|
32
|
-
var version = "0.
|
|
33
|
+
var version = "0.47.0";
|
|
33
34
|
var description = "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs";
|
|
34
35
|
var private$1 = false;
|
|
35
36
|
var type = "module";
|
|
@@ -220,15 +221,40 @@ async function getDokployRegistryId(options) {
|
|
|
220
221
|
const stored = await getDokployCredentials(options);
|
|
221
222
|
return stored?.registryId ?? void 0;
|
|
222
223
|
}
|
|
224
|
+
/**
|
|
225
|
+
* Store Hostinger API token
|
|
226
|
+
*
|
|
227
|
+
* @param token - API token from hpanel.hostinger.com/profile/api
|
|
228
|
+
*/
|
|
229
|
+
async function storeHostingerToken(token, options) {
|
|
230
|
+
const credentials = await readCredentials(options);
|
|
231
|
+
credentials.hostinger = {
|
|
232
|
+
token,
|
|
233
|
+
storedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
234
|
+
};
|
|
235
|
+
await writeCredentials(credentials, options);
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Get stored Hostinger API token
|
|
239
|
+
*
|
|
240
|
+
* Checks environment variable first (HOSTINGER_API_TOKEN),
|
|
241
|
+
* then falls back to stored credentials.
|
|
242
|
+
*/
|
|
243
|
+
async function getHostingerToken(options) {
|
|
244
|
+
const envToken = process.env.HOSTINGER_API_TOKEN;
|
|
245
|
+
if (envToken) return envToken;
|
|
246
|
+
const credentials = await readCredentials(options);
|
|
247
|
+
return credentials.hostinger?.token ?? null;
|
|
248
|
+
}
|
|
223
249
|
|
|
224
250
|
//#endregion
|
|
225
251
|
//#region src/auth/index.ts
|
|
226
|
-
const logger$
|
|
252
|
+
const logger$11 = console;
|
|
227
253
|
/**
|
|
228
254
|
* Validate Dokploy token by making a test API call
|
|
229
255
|
*/
|
|
230
256
|
async function validateDokployToken(endpoint, token) {
|
|
231
|
-
const { DokployApi: DokployApi$1 } = await Promise.resolve().then(() => require("./dokploy-api-
|
|
257
|
+
const { DokployApi: DokployApi$1 } = await Promise.resolve().then(() => require("./dokploy-api-BdCKjFDA.cjs"));
|
|
232
258
|
const api = new DokployApi$1({
|
|
233
259
|
baseUrl: endpoint,
|
|
234
260
|
token
|
|
@@ -291,36 +317,36 @@ async function prompt$1(message, hidden = false) {
|
|
|
291
317
|
async function loginCommand(options) {
|
|
292
318
|
const { service, token: providedToken, endpoint: providedEndpoint } = options;
|
|
293
319
|
if (service === "dokploy") {
|
|
294
|
-
logger$
|
|
320
|
+
logger$11.log("\n🔐 Logging in to Dokploy...\n");
|
|
295
321
|
let endpoint = providedEndpoint;
|
|
296
322
|
if (!endpoint) endpoint = await prompt$1("Dokploy URL (e.g., https://dokploy.example.com): ");
|
|
297
323
|
endpoint = endpoint.replace(/\/$/, "");
|
|
298
324
|
try {
|
|
299
325
|
new URL(endpoint);
|
|
300
326
|
} catch {
|
|
301
|
-
logger$
|
|
327
|
+
logger$11.error("Invalid URL format");
|
|
302
328
|
process.exit(1);
|
|
303
329
|
}
|
|
304
330
|
let token = providedToken;
|
|
305
331
|
if (!token) {
|
|
306
|
-
logger$
|
|
332
|
+
logger$11.log(`\nGenerate a token at: ${endpoint}/settings/profile\n`);
|
|
307
333
|
token = await prompt$1("API Token: ", true);
|
|
308
334
|
}
|
|
309
335
|
if (!token) {
|
|
310
|
-
logger$
|
|
336
|
+
logger$11.error("Token is required");
|
|
311
337
|
process.exit(1);
|
|
312
338
|
}
|
|
313
|
-
logger$
|
|
339
|
+
logger$11.log("\nValidating credentials...");
|
|
314
340
|
const isValid = await validateDokployToken(endpoint, token);
|
|
315
341
|
if (!isValid) {
|
|
316
|
-
logger$
|
|
342
|
+
logger$11.error("\n✗ Invalid credentials. Please check your token and try again.");
|
|
317
343
|
process.exit(1);
|
|
318
344
|
}
|
|
319
345
|
await storeDokployCredentials(token, endpoint);
|
|
320
|
-
logger$
|
|
321
|
-
logger$
|
|
322
|
-
logger$
|
|
323
|
-
logger$
|
|
346
|
+
logger$11.log("\n✓ Successfully logged in to Dokploy!");
|
|
347
|
+
logger$11.log(` Endpoint: ${endpoint}`);
|
|
348
|
+
logger$11.log(` Credentials stored in: ${getCredentialsPath()}`);
|
|
349
|
+
logger$11.log("\nYou can now use deploy commands without setting DOKPLOY_API_TOKEN.");
|
|
324
350
|
}
|
|
325
351
|
}
|
|
326
352
|
/**
|
|
@@ -330,28 +356,28 @@ async function logoutCommand(options) {
|
|
|
330
356
|
const { service = "dokploy" } = options;
|
|
331
357
|
if (service === "all") {
|
|
332
358
|
const dokployRemoved = await removeDokployCredentials();
|
|
333
|
-
if (dokployRemoved) logger$
|
|
334
|
-
else logger$
|
|
359
|
+
if (dokployRemoved) logger$11.log("\n✓ Logged out from all services");
|
|
360
|
+
else logger$11.log("\nNo stored credentials found");
|
|
335
361
|
return;
|
|
336
362
|
}
|
|
337
363
|
if (service === "dokploy") {
|
|
338
364
|
const removed = await removeDokployCredentials();
|
|
339
|
-
if (removed) logger$
|
|
340
|
-
else logger$
|
|
365
|
+
if (removed) logger$11.log("\n✓ Logged out from Dokploy");
|
|
366
|
+
else logger$11.log("\nNo Dokploy credentials found");
|
|
341
367
|
}
|
|
342
368
|
}
|
|
343
369
|
/**
|
|
344
370
|
* Show current login status
|
|
345
371
|
*/
|
|
346
372
|
async function whoamiCommand() {
|
|
347
|
-
logger$
|
|
373
|
+
logger$11.log("\n📋 Current credentials:\n");
|
|
348
374
|
const dokploy = await getDokployCredentials();
|
|
349
375
|
if (dokploy) {
|
|
350
|
-
logger$
|
|
351
|
-
logger$
|
|
352
|
-
logger$
|
|
353
|
-
} else logger$
|
|
354
|
-
logger$
|
|
376
|
+
logger$11.log(" Dokploy:");
|
|
377
|
+
logger$11.log(` Endpoint: ${dokploy.endpoint}`);
|
|
378
|
+
logger$11.log(` Token: ${maskToken(dokploy.token)}`);
|
|
379
|
+
} else logger$11.log(" Dokploy: Not logged in");
|
|
380
|
+
logger$11.log(`\n Credentials file: ${getCredentialsPath()}`);
|
|
355
381
|
}
|
|
356
382
|
/**
|
|
357
383
|
* Mask a token for display
|
|
@@ -437,7 +463,7 @@ function isEnabled(config) {
|
|
|
437
463
|
var CronGenerator = class extends require_openapi.ConstructGenerator {
|
|
438
464
|
async build(context, constructs, outputDir, options) {
|
|
439
465
|
const provider = options?.provider || "aws-lambda";
|
|
440
|
-
const logger$
|
|
466
|
+
const logger$12 = console;
|
|
441
467
|
const cronInfos = [];
|
|
442
468
|
if (constructs.length === 0 || provider !== "aws-lambda") return cronInfos;
|
|
443
469
|
const cronsDir = (0, node_path.join)(outputDir, "crons");
|
|
@@ -452,7 +478,7 @@ var CronGenerator = class extends require_openapi.ConstructGenerator {
|
|
|
452
478
|
memorySize: construct.memorySize,
|
|
453
479
|
environment: await construct.getEnvironment()
|
|
454
480
|
});
|
|
455
|
-
logger$
|
|
481
|
+
logger$12.log(`Generated cron handler: ${key}`);
|
|
456
482
|
}
|
|
457
483
|
return cronInfos;
|
|
458
484
|
}
|
|
@@ -488,7 +514,7 @@ var FunctionGenerator = class extends require_openapi.ConstructGenerator {
|
|
|
488
514
|
}
|
|
489
515
|
async build(context, constructs, outputDir, options) {
|
|
490
516
|
const provider = options?.provider || "aws-lambda";
|
|
491
|
-
const logger$
|
|
517
|
+
const logger$12 = console;
|
|
492
518
|
const functionInfos = [];
|
|
493
519
|
if (constructs.length === 0 || provider !== "aws-lambda") return functionInfos;
|
|
494
520
|
const functionsDir = (0, node_path.join)(outputDir, "functions");
|
|
@@ -502,7 +528,7 @@ var FunctionGenerator = class extends require_openapi.ConstructGenerator {
|
|
|
502
528
|
memorySize: construct.memorySize,
|
|
503
529
|
environment: await construct.getEnvironment()
|
|
504
530
|
});
|
|
505
|
-
logger$
|
|
531
|
+
logger$12.log(`Generated function handler: ${key}`);
|
|
506
532
|
}
|
|
507
533
|
return functionInfos;
|
|
508
534
|
}
|
|
@@ -535,11 +561,11 @@ var SubscriberGenerator = class extends require_openapi.ConstructGenerator {
|
|
|
535
561
|
}
|
|
536
562
|
async build(context, constructs, outputDir, options) {
|
|
537
563
|
const provider = options?.provider || "aws-lambda";
|
|
538
|
-
const logger$
|
|
564
|
+
const logger$12 = console;
|
|
539
565
|
const subscriberInfos = [];
|
|
540
566
|
if (provider === "server") {
|
|
541
567
|
await this.generateServerSubscribersFile(outputDir, constructs);
|
|
542
|
-
logger$
|
|
568
|
+
logger$12.log(`Generated server subscribers file with ${constructs.length} subscribers (polling mode)`);
|
|
543
569
|
return subscriberInfos;
|
|
544
570
|
}
|
|
545
571
|
if (constructs.length === 0) return subscriberInfos;
|
|
@@ -556,7 +582,7 @@ var SubscriberGenerator = class extends require_openapi.ConstructGenerator {
|
|
|
556
582
|
memorySize: construct.memorySize,
|
|
557
583
|
environment: await construct.getEnvironment()
|
|
558
584
|
});
|
|
559
|
-
logger$
|
|
585
|
+
logger$12.log(`Generated subscriber handler: ${key}`);
|
|
560
586
|
}
|
|
561
587
|
return subscriberInfos;
|
|
562
588
|
}
|
|
@@ -722,7 +748,7 @@ export async function setupSubscribers(
|
|
|
722
748
|
|
|
723
749
|
//#endregion
|
|
724
750
|
//#region src/workspace/client-generator.ts
|
|
725
|
-
const logger$
|
|
751
|
+
const logger$10 = console;
|
|
726
752
|
/**
|
|
727
753
|
* Cache of OpenAPI spec hashes to detect changes.
|
|
728
754
|
*/
|
|
@@ -842,7 +868,7 @@ ${spec.content}
|
|
|
842
868
|
* Generate clients for all frontend apps in the workspace.
|
|
843
869
|
*/
|
|
844
870
|
async function generateAllClients(workspace, options = {}) {
|
|
845
|
-
const log = options.silent ? () => {} : logger$
|
|
871
|
+
const log = options.silent ? () => {} : logger$10.log.bind(logger$10);
|
|
846
872
|
const allResults = [];
|
|
847
873
|
for (const [appName, app] of Object.entries(workspace.apps)) if (app.type === "frontend" && app.dependencies.length > 0) {
|
|
848
874
|
const results = await generateClientForFrontend(workspace, appName, { force: options.force });
|
|
@@ -864,7 +890,7 @@ function getDependentFrontends(workspace, backendAppName) {
|
|
|
864
890
|
|
|
865
891
|
//#endregion
|
|
866
892
|
//#region src/dev/index.ts
|
|
867
|
-
const logger$
|
|
893
|
+
const logger$9 = console;
|
|
868
894
|
/**
|
|
869
895
|
* Load environment files
|
|
870
896
|
* @internal Exported for testing
|
|
@@ -915,7 +941,7 @@ async function findAvailablePort(preferredPort, maxAttempts = 10) {
|
|
|
915
941
|
for (let i = 0; i < maxAttempts; i++) {
|
|
916
942
|
const port = preferredPort + i;
|
|
917
943
|
if (await isPortAvailable(port)) return port;
|
|
918
|
-
logger$
|
|
944
|
+
logger$9.log(`⚠️ Port ${port} is in use, trying ${port + 1}...`);
|
|
919
945
|
}
|
|
920
946
|
throw new Error(`Could not find an available port after trying ${maxAttempts} ports starting from ${preferredPort}`);
|
|
921
947
|
}
|
|
@@ -1016,7 +1042,7 @@ function getProductionConfigFromGkm(config) {
|
|
|
1016
1042
|
async function devCommand(options) {
|
|
1017
1043
|
if (options.entry) return entryDevCommand(options);
|
|
1018
1044
|
const defaultEnv = loadEnvFiles(".env");
|
|
1019
|
-
if (defaultEnv.loaded.length > 0) logger$
|
|
1045
|
+
if (defaultEnv.loaded.length > 0) logger$9.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
1020
1046
|
const appName = require_config.getAppNameFromCwd();
|
|
1021
1047
|
let config;
|
|
1022
1048
|
let appRoot = process.cwd();
|
|
@@ -1030,9 +1056,9 @@ async function devCommand(options) {
|
|
|
1030
1056
|
secretsRoot = appConfig.workspaceRoot;
|
|
1031
1057
|
workspaceAppName = appConfig.appName;
|
|
1032
1058
|
workspaceAppPort = appConfig.app.port;
|
|
1033
|
-
logger$
|
|
1059
|
+
logger$9.log(`📦 Running app: ${appConfig.appName} on port ${workspaceAppPort}`);
|
|
1034
1060
|
if (appConfig.app.entry) {
|
|
1035
|
-
logger$
|
|
1061
|
+
logger$9.log(`📄 Using entry point: ${appConfig.app.entry}`);
|
|
1036
1062
|
return entryDevCommand({
|
|
1037
1063
|
...options,
|
|
1038
1064
|
entry: appConfig.app.entry,
|
|
@@ -1043,7 +1069,7 @@ async function devCommand(options) {
|
|
|
1043
1069
|
} catch {
|
|
1044
1070
|
const loadedConfig = await require_config.loadWorkspaceConfig();
|
|
1045
1071
|
if (loadedConfig.type === "workspace") {
|
|
1046
|
-
logger$
|
|
1072
|
+
logger$9.log("📦 Detected workspace configuration");
|
|
1047
1073
|
return workspaceDevCommand(loadedConfig.workspace, options);
|
|
1048
1074
|
}
|
|
1049
1075
|
config = loadedConfig.raw;
|
|
@@ -1051,34 +1077,34 @@ async function devCommand(options) {
|
|
|
1051
1077
|
else {
|
|
1052
1078
|
const loadedConfig = await require_config.loadWorkspaceConfig();
|
|
1053
1079
|
if (loadedConfig.type === "workspace") {
|
|
1054
|
-
logger$
|
|
1080
|
+
logger$9.log("📦 Detected workspace configuration");
|
|
1055
1081
|
return workspaceDevCommand(loadedConfig.workspace, options);
|
|
1056
1082
|
}
|
|
1057
1083
|
config = loadedConfig.raw;
|
|
1058
1084
|
}
|
|
1059
1085
|
if (config.env) {
|
|
1060
1086
|
const { loaded, missing } = loadEnvFiles(config.env, appRoot);
|
|
1061
|
-
if (loaded.length > 0) logger$
|
|
1062
|
-
if (missing.length > 0) logger$
|
|
1087
|
+
if (loaded.length > 0) logger$9.log(`📦 Loaded env: ${loaded.join(", ")}`);
|
|
1088
|
+
if (missing.length > 0) logger$9.warn(`⚠️ Missing env files: ${missing.join(", ")}`);
|
|
1063
1089
|
}
|
|
1064
1090
|
const resolved = resolveProviders(config, { provider: "server" });
|
|
1065
|
-
logger$
|
|
1066
|
-
logger$
|
|
1067
|
-
if (config.functions) logger$
|
|
1068
|
-
if (config.crons) logger$
|
|
1069
|
-
if (config.subscribers) logger$
|
|
1070
|
-
logger$
|
|
1091
|
+
logger$9.log("🚀 Starting development server...");
|
|
1092
|
+
logger$9.log(`Loading routes from: ${config.routes}`);
|
|
1093
|
+
if (config.functions) logger$9.log(`Loading functions from: ${config.functions}`);
|
|
1094
|
+
if (config.crons) logger$9.log(`Loading crons from: ${config.crons}`);
|
|
1095
|
+
if (config.subscribers) logger$9.log(`Loading subscribers from: ${config.subscribers}`);
|
|
1096
|
+
logger$9.log(`Using envParser: ${config.envParser}`);
|
|
1071
1097
|
const { path: envParserPath, importPattern: envParserImportPattern } = require_config.parseModuleConfig(config.envParser, "envParser");
|
|
1072
1098
|
const { path: loggerPath, importPattern: loggerImportPattern } = require_config.parseModuleConfig(config.logger, "logger");
|
|
1073
1099
|
const telescope = normalizeTelescopeConfig(config.telescope);
|
|
1074
|
-
if (telescope) logger$
|
|
1100
|
+
if (telescope) logger$9.log(`🔭 Telescope enabled at ${telescope.path}`);
|
|
1075
1101
|
const studio = normalizeStudioConfig(config.studio);
|
|
1076
|
-
if (studio) logger$
|
|
1102
|
+
if (studio) logger$9.log(`🗄️ Studio enabled at ${studio.path}`);
|
|
1077
1103
|
const hooks = normalizeHooksConfig(config.hooks, appRoot);
|
|
1078
|
-
if (hooks) logger$
|
|
1104
|
+
if (hooks) logger$9.log(`🪝 Server hooks enabled from ${config.hooks?.server}`);
|
|
1079
1105
|
const openApiConfig = require_openapi.resolveOpenApiConfig(config);
|
|
1080
1106
|
const enableOpenApi = openApiConfig.enabled || resolved.enableOpenApi;
|
|
1081
|
-
if (enableOpenApi) logger$
|
|
1107
|
+
if (enableOpenApi) logger$9.log(`📄 OpenAPI output: ${require_openapi.OPENAPI_OUTPUT_PATH}`);
|
|
1082
1108
|
const buildContext = {
|
|
1083
1109
|
envParserPath,
|
|
1084
1110
|
envParserImportPattern,
|
|
@@ -1098,7 +1124,7 @@ async function devCommand(options) {
|
|
|
1098
1124
|
await (0, node_fs_promises.mkdir)(secretsDir, { recursive: true });
|
|
1099
1125
|
secretsJsonPath = (0, node_path.join)(secretsDir, "dev-secrets.json");
|
|
1100
1126
|
await (0, node_fs_promises.writeFile)(secretsJsonPath, JSON.stringify(appSecrets, null, 2));
|
|
1101
|
-
logger$
|
|
1127
|
+
logger$9.log(`🔐 Loaded ${Object.keys(appSecrets).length} secret(s)`);
|
|
1102
1128
|
}
|
|
1103
1129
|
const devServer = new DevServer(resolved.providers[0], options.port ?? workspaceAppPort ?? 3e3, options.portExplicit ?? false, enableOpenApi, telescope, studio, runtime, appRoot, secretsJsonPath);
|
|
1104
1130
|
await devServer.start();
|
|
@@ -1116,7 +1142,7 @@ async function devCommand(options) {
|
|
|
1116
1142
|
...hooksFile ? [hooksFile.endsWith(".ts") ? hooksFile : `${hooksFile}.ts`] : []
|
|
1117
1143
|
].flat().filter((p) => typeof p === "string");
|
|
1118
1144
|
const normalizedPatterns = watchPatterns.map((p) => p.startsWith("./") ? p.slice(2) : p);
|
|
1119
|
-
logger$
|
|
1145
|
+
logger$9.log(`👀 Watching for changes in: ${normalizedPatterns.join(", ")}`);
|
|
1120
1146
|
const resolvedFiles = await (0, fast_glob.default)(normalizedPatterns, {
|
|
1121
1147
|
cwd: appRoot,
|
|
1122
1148
|
absolute: false,
|
|
@@ -1126,7 +1152,7 @@ async function devCommand(options) {
|
|
|
1126
1152
|
const parts = f.split("/");
|
|
1127
1153
|
return parts.slice(0, -1).join("/");
|
|
1128
1154
|
}))];
|
|
1129
|
-
logger$
|
|
1155
|
+
logger$9.log(`📁 Found ${resolvedFiles.length} files in ${dirsToWatch.length} directories`);
|
|
1130
1156
|
const watcher = chokidar.default.watch([...resolvedFiles, ...dirsToWatch], {
|
|
1131
1157
|
ignored: /(^|[/\\])\../,
|
|
1132
1158
|
persistent: true,
|
|
@@ -1134,24 +1160,24 @@ async function devCommand(options) {
|
|
|
1134
1160
|
cwd: appRoot
|
|
1135
1161
|
});
|
|
1136
1162
|
watcher.on("ready", () => {
|
|
1137
|
-
logger$
|
|
1163
|
+
logger$9.log("🔍 File watcher ready");
|
|
1138
1164
|
});
|
|
1139
1165
|
watcher.on("error", (error) => {
|
|
1140
|
-
logger$
|
|
1166
|
+
logger$9.error("❌ Watcher error:", error);
|
|
1141
1167
|
});
|
|
1142
1168
|
let rebuildTimeout = null;
|
|
1143
1169
|
watcher.on("change", async (path) => {
|
|
1144
|
-
logger$
|
|
1170
|
+
logger$9.log(`📝 File changed: ${path}`);
|
|
1145
1171
|
if (rebuildTimeout) clearTimeout(rebuildTimeout);
|
|
1146
1172
|
rebuildTimeout = setTimeout(async () => {
|
|
1147
1173
|
try {
|
|
1148
|
-
logger$
|
|
1174
|
+
logger$9.log("🔄 Rebuilding...");
|
|
1149
1175
|
await buildServer(config, buildContext, resolved.providers[0], enableOpenApi, appRoot);
|
|
1150
1176
|
if (enableOpenApi) await require_openapi.generateOpenApi(config, { silent: true });
|
|
1151
|
-
logger$
|
|
1177
|
+
logger$9.log("✅ Rebuild complete, restarting server...");
|
|
1152
1178
|
await devServer.restart();
|
|
1153
1179
|
} catch (error) {
|
|
1154
|
-
logger$
|
|
1180
|
+
logger$9.error("❌ Rebuild failed:", error.message);
|
|
1155
1181
|
}
|
|
1156
1182
|
}, 300);
|
|
1157
1183
|
});
|
|
@@ -1159,9 +1185,9 @@ async function devCommand(options) {
|
|
|
1159
1185
|
const shutdown = () => {
|
|
1160
1186
|
if (isShuttingDown) return;
|
|
1161
1187
|
isShuttingDown = true;
|
|
1162
|
-
logger$
|
|
1188
|
+
logger$9.log("\n🛑 Shutting down...");
|
|
1163
1189
|
Promise.all([watcher.close(), devServer.stop()]).catch((err) => {
|
|
1164
|
-
logger$
|
|
1190
|
+
logger$9.error("Error during shutdown:", err);
|
|
1165
1191
|
}).finally(() => {
|
|
1166
1192
|
process.exit(0);
|
|
1167
1193
|
});
|
|
@@ -1264,11 +1290,11 @@ async function loadDevSecrets(workspace) {
|
|
|
1264
1290
|
for (const stage of stages) if (require_storage.secretsExist(stage, workspace.root)) {
|
|
1265
1291
|
const secrets = await require_storage.readStageSecrets(stage, workspace.root);
|
|
1266
1292
|
if (secrets) {
|
|
1267
|
-
logger$
|
|
1293
|
+
logger$9.log(`🔐 Loading secrets from stage: ${stage}`);
|
|
1268
1294
|
return require_storage.toEmbeddableSecrets(secrets);
|
|
1269
1295
|
}
|
|
1270
1296
|
}
|
|
1271
|
-
logger$
|
|
1297
|
+
logger$9.warn("⚠️ Secrets enabled but no dev/development secrets found. Run \"gkm secrets:init --stage dev\"");
|
|
1272
1298
|
return {};
|
|
1273
1299
|
}
|
|
1274
1300
|
/**
|
|
@@ -1283,7 +1309,7 @@ async function loadSecretsForApp(secretsRoot, appName) {
|
|
|
1283
1309
|
for (const stage of stages) if (require_storage.secretsExist(stage, secretsRoot)) {
|
|
1284
1310
|
const stageSecrets = await require_storage.readStageSecrets(stage, secretsRoot);
|
|
1285
1311
|
if (stageSecrets) {
|
|
1286
|
-
logger$
|
|
1312
|
+
logger$9.log(`🔐 Loading secrets from stage: ${stage}`);
|
|
1287
1313
|
secrets = require_storage.toEmbeddableSecrets(stageSecrets);
|
|
1288
1314
|
break;
|
|
1289
1315
|
}
|
|
@@ -1308,20 +1334,20 @@ async function startWorkspaceServices(workspace) {
|
|
|
1308
1334
|
if (services.cache) servicesToStart.push("redis");
|
|
1309
1335
|
if (services.mail) servicesToStart.push("mailpit");
|
|
1310
1336
|
if (servicesToStart.length === 0) return;
|
|
1311
|
-
logger$
|
|
1337
|
+
logger$9.log(`🐳 Starting services: ${servicesToStart.join(", ")}`);
|
|
1312
1338
|
try {
|
|
1313
1339
|
const composeFile = (0, node_path.join)(workspace.root, "docker-compose.yml");
|
|
1314
1340
|
if (!(0, node_fs.existsSync)(composeFile)) {
|
|
1315
|
-
logger$
|
|
1341
|
+
logger$9.warn("⚠️ No docker-compose.yml found. Services will not be started.");
|
|
1316
1342
|
return;
|
|
1317
1343
|
}
|
|
1318
1344
|
(0, node_child_process.execSync)(`docker compose up -d ${servicesToStart.join(" ")}`, {
|
|
1319
1345
|
cwd: workspace.root,
|
|
1320
1346
|
stdio: "inherit"
|
|
1321
1347
|
});
|
|
1322
|
-
logger$
|
|
1348
|
+
logger$9.log("✅ Services started");
|
|
1323
1349
|
} catch (error) {
|
|
1324
|
-
logger$
|
|
1350
|
+
logger$9.error("❌ Failed to start services:", error.message);
|
|
1325
1351
|
throw error;
|
|
1326
1352
|
}
|
|
1327
1353
|
}
|
|
@@ -1338,40 +1364,40 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1338
1364
|
const appCount = Object.keys(workspace.apps).length;
|
|
1339
1365
|
const backendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "backend");
|
|
1340
1366
|
const frontendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "frontend");
|
|
1341
|
-
logger$
|
|
1342
|
-
logger$
|
|
1367
|
+
logger$9.log(`\n🚀 Starting workspace: ${workspace.name}`);
|
|
1368
|
+
logger$9.log(` ${backendApps.length} backend app(s), ${frontendApps.length} frontend app(s)`);
|
|
1343
1369
|
const conflicts = checkPortConflicts(workspace);
|
|
1344
1370
|
if (conflicts.length > 0) {
|
|
1345
|
-
for (const conflict of conflicts) logger$
|
|
1371
|
+
for (const conflict of conflicts) logger$9.error(`❌ Port conflict: Apps "${conflict.app1}" and "${conflict.app2}" both use port ${conflict.port}`);
|
|
1346
1372
|
throw new Error("Port conflicts detected. Please assign unique ports to each app.");
|
|
1347
1373
|
}
|
|
1348
1374
|
if (frontendApps.length > 0) {
|
|
1349
|
-
logger$
|
|
1375
|
+
logger$9.log("\n🔍 Validating frontend apps...");
|
|
1350
1376
|
const validationResults = await validateFrontendApps(workspace);
|
|
1351
1377
|
let hasErrors = false;
|
|
1352
1378
|
for (const result of validationResults) {
|
|
1353
1379
|
if (!result.valid) {
|
|
1354
1380
|
hasErrors = true;
|
|
1355
|
-
logger$
|
|
1356
|
-
for (const error of result.errors) logger$
|
|
1381
|
+
logger$9.error(`\n❌ Frontend app "${result.appName}" validation failed:`);
|
|
1382
|
+
for (const error of result.errors) logger$9.error(` • ${error}`);
|
|
1357
1383
|
}
|
|
1358
|
-
for (const warning of result.warnings) logger$
|
|
1384
|
+
for (const warning of result.warnings) logger$9.warn(` ⚠️ ${result.appName}: ${warning}`);
|
|
1359
1385
|
}
|
|
1360
1386
|
if (hasErrors) throw new Error("Frontend app validation failed. Fix the issues above and try again.");
|
|
1361
|
-
logger$
|
|
1387
|
+
logger$9.log("✅ Frontend apps validated");
|
|
1362
1388
|
}
|
|
1363
1389
|
if (frontendApps.length > 0) {
|
|
1364
1390
|
const clientResults = await generateAllClients(workspace, { force: true });
|
|
1365
1391
|
const generatedCount = clientResults.filter((r) => r.generated).length;
|
|
1366
|
-
if (generatedCount > 0) logger$
|
|
1392
|
+
if (generatedCount > 0) logger$9.log(`\n📦 Generated ${generatedCount} API client(s)`);
|
|
1367
1393
|
}
|
|
1368
1394
|
await startWorkspaceServices(workspace);
|
|
1369
1395
|
const secretsEnv = await loadDevSecrets(workspace);
|
|
1370
|
-
if (Object.keys(secretsEnv).length > 0) logger$
|
|
1396
|
+
if (Object.keys(secretsEnv).length > 0) logger$9.log(` Loaded ${Object.keys(secretsEnv).length} secret(s)`);
|
|
1371
1397
|
const dependencyEnv = generateAllDependencyEnvVars(workspace);
|
|
1372
1398
|
if (Object.keys(dependencyEnv).length > 0) {
|
|
1373
|
-
logger$
|
|
1374
|
-
for (const [key, value] of Object.entries(dependencyEnv)) logger$
|
|
1399
|
+
logger$9.log("📡 Dependency URLs:");
|
|
1400
|
+
for (const [key, value] of Object.entries(dependencyEnv)) logger$9.log(` ${key}=${value}`);
|
|
1375
1401
|
}
|
|
1376
1402
|
let turboFilter = [];
|
|
1377
1403
|
if (options.app) {
|
|
@@ -1380,18 +1406,18 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1380
1406
|
throw new Error(`App "${options.app}" not found. Available apps: ${appNames}`);
|
|
1381
1407
|
}
|
|
1382
1408
|
turboFilter = ["--filter", options.app];
|
|
1383
|
-
logger$
|
|
1409
|
+
logger$9.log(`\n🎯 Running single app: ${options.app}`);
|
|
1384
1410
|
} else if (options.filter) {
|
|
1385
1411
|
turboFilter = ["--filter", options.filter];
|
|
1386
|
-
logger$
|
|
1387
|
-
} else logger$
|
|
1412
|
+
logger$9.log(`\n🔍 Using filter: ${options.filter}`);
|
|
1413
|
+
} else logger$9.log(`\n🎯 Running all ${appCount} apps`);
|
|
1388
1414
|
const buildOrder = require_workspace.getAppBuildOrder(workspace);
|
|
1389
|
-
logger$
|
|
1415
|
+
logger$9.log("\n📋 Apps (in dependency order):");
|
|
1390
1416
|
for (const appName of buildOrder) {
|
|
1391
1417
|
const app = workspace.apps[appName];
|
|
1392
1418
|
if (!app) continue;
|
|
1393
1419
|
const deps = app.dependencies.length > 0 ? ` (depends on: ${app.dependencies.join(", ")})` : "";
|
|
1394
|
-
logger$
|
|
1420
|
+
logger$9.log(` ${app.type === "backend" ? "🔧" : "🌐"} ${appName} → http://localhost:${app.port}${deps}`);
|
|
1395
1421
|
}
|
|
1396
1422
|
const configFiles = [
|
|
1397
1423
|
"gkm.config.ts",
|
|
@@ -1413,7 +1439,7 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1413
1439
|
NODE_ENV: "development",
|
|
1414
1440
|
...configPath ? { GKM_CONFIG_PATH: configPath } : {}
|
|
1415
1441
|
};
|
|
1416
|
-
logger$
|
|
1442
|
+
logger$9.log("\n🏃 Starting turbo run dev...\n");
|
|
1417
1443
|
const turboProcess = (0, node_child_process.spawn)("pnpm", [
|
|
1418
1444
|
"turbo",
|
|
1419
1445
|
"run",
|
|
@@ -1445,7 +1471,7 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1445
1471
|
onlyFiles: true
|
|
1446
1472
|
});
|
|
1447
1473
|
if (resolvedFiles.length > 0) {
|
|
1448
|
-
logger$
|
|
1474
|
+
logger$9.log(`\n👀 Watching ${resolvedFiles.length} endpoint file(s) for schema changes`);
|
|
1449
1475
|
endpointWatcher = chokidar.default.watch(resolvedFiles, {
|
|
1450
1476
|
ignored: /(^|[/\\])\../,
|
|
1451
1477
|
persistent: true,
|
|
@@ -1473,12 +1499,12 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1473
1499
|
for (const frontend of dependents) affectedFrontends.add(frontend);
|
|
1474
1500
|
}
|
|
1475
1501
|
if (affectedFrontends.size === 0) return;
|
|
1476
|
-
logger$
|
|
1502
|
+
logger$9.log(`\n🔄 Detected schema change in ${changedBackends.join(", ")}`);
|
|
1477
1503
|
for (const frontend of affectedFrontends) try {
|
|
1478
1504
|
const results = await generateClientForFrontend(workspace, frontend);
|
|
1479
|
-
for (const result of results) if (result.generated) logger$
|
|
1505
|
+
for (const result of results) if (result.generated) logger$9.log(` 📦 Regenerated client for ${result.frontendApp} (${result.endpointCount} endpoints)`);
|
|
1480
1506
|
} catch (error) {
|
|
1481
|
-
logger$
|
|
1507
|
+
logger$9.error(` ❌ Failed to regenerate client for ${frontend}: ${error.message}`);
|
|
1482
1508
|
}
|
|
1483
1509
|
}, 500);
|
|
1484
1510
|
});
|
|
@@ -1489,7 +1515,7 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1489
1515
|
const shutdown = () => {
|
|
1490
1516
|
if (isShuttingDown) return;
|
|
1491
1517
|
isShuttingDown = true;
|
|
1492
|
-
logger$
|
|
1518
|
+
logger$9.log("\n🛑 Shutting down workspace...");
|
|
1493
1519
|
if (endpointWatcher) endpointWatcher.close().catch(() => {});
|
|
1494
1520
|
if (turboProcess.pid) try {
|
|
1495
1521
|
process.kill(-turboProcess.pid, "SIGTERM");
|
|
@@ -1504,7 +1530,7 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1504
1530
|
process.on("SIGTERM", shutdown);
|
|
1505
1531
|
return new Promise((resolve$3, reject) => {
|
|
1506
1532
|
turboProcess.on("error", (error) => {
|
|
1507
|
-
logger$
|
|
1533
|
+
logger$9.error("❌ Turbo error:", error);
|
|
1508
1534
|
reject(error);
|
|
1509
1535
|
});
|
|
1510
1536
|
turboProcess.on("exit", (code) => {
|
|
@@ -1616,7 +1642,7 @@ async function prepareEntryCredentials(options) {
|
|
|
1616
1642
|
secretsRoot = appConfig.workspaceRoot;
|
|
1617
1643
|
appName = appConfig.appName;
|
|
1618
1644
|
} catch (error) {
|
|
1619
|
-
logger$
|
|
1645
|
+
logger$9.log(`⚠️ Could not load workspace config: ${error.message}`);
|
|
1620
1646
|
secretsRoot = findSecretsRoot(cwd);
|
|
1621
1647
|
appName = require_config.getAppNameFromCwd(cwd) ?? void 0;
|
|
1622
1648
|
}
|
|
@@ -1646,11 +1672,11 @@ async function entryDevCommand(options) {
|
|
|
1646
1672
|
const entryPath = (0, node_path.resolve)(process.cwd(), entry);
|
|
1647
1673
|
if (!(0, node_fs.existsSync)(entryPath)) throw new Error(`Entry file not found: ${entryPath}`);
|
|
1648
1674
|
const defaultEnv = loadEnvFiles(".env");
|
|
1649
|
-
if (defaultEnv.loaded.length > 0) logger$
|
|
1675
|
+
if (defaultEnv.loaded.length > 0) logger$9.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
1650
1676
|
const { credentials, resolvedPort, secretsJsonPath, appName } = await prepareEntryCredentials({ explicitPort: options.portExplicit ? options.port : void 0 });
|
|
1651
|
-
if (appName) logger$
|
|
1652
|
-
logger$
|
|
1653
|
-
if (Object.keys(credentials).length > 1) logger$
|
|
1677
|
+
if (appName) logger$9.log(`📦 App: ${appName} (port ${resolvedPort})`);
|
|
1678
|
+
logger$9.log(`🚀 Starting entry file: ${entry} on port ${resolvedPort}`);
|
|
1679
|
+
if (Object.keys(credentials).length > 1) logger$9.log(`🔐 Loaded ${Object.keys(credentials).length - 1} secret(s) + PORT`);
|
|
1654
1680
|
const wrapperDir = (0, node_path.join)(process.cwd(), ".gkm");
|
|
1655
1681
|
await (0, node_fs_promises.mkdir)(wrapperDir, { recursive: true });
|
|
1656
1682
|
const wrapperPath = (0, node_path.join)(wrapperDir, "entry-wrapper.ts");
|
|
@@ -1661,7 +1687,7 @@ async function entryDevCommand(options) {
|
|
|
1661
1687
|
const shutdown = () => {
|
|
1662
1688
|
if (isShuttingDown) return;
|
|
1663
1689
|
isShuttingDown = true;
|
|
1664
|
-
logger$
|
|
1690
|
+
logger$9.log("\n🛑 Shutting down...");
|
|
1665
1691
|
runner.stop();
|
|
1666
1692
|
process.exit(0);
|
|
1667
1693
|
};
|
|
@@ -1693,14 +1719,14 @@ var EntryRunner = class {
|
|
|
1693
1719
|
});
|
|
1694
1720
|
let restartTimeout = null;
|
|
1695
1721
|
this.watcher.on("change", (path) => {
|
|
1696
|
-
logger$
|
|
1722
|
+
logger$9.log(`📝 File changed: ${path}`);
|
|
1697
1723
|
if (restartTimeout) clearTimeout(restartTimeout);
|
|
1698
1724
|
restartTimeout = setTimeout(async () => {
|
|
1699
|
-
logger$
|
|
1725
|
+
logger$9.log("🔄 Restarting...");
|
|
1700
1726
|
await this.restart();
|
|
1701
1727
|
}, 300);
|
|
1702
1728
|
});
|
|
1703
|
-
logger$
|
|
1729
|
+
logger$9.log(`👀 Watching for changes in: ${watchDir}`);
|
|
1704
1730
|
}
|
|
1705
1731
|
}
|
|
1706
1732
|
async runProcess() {
|
|
@@ -1715,14 +1741,14 @@ var EntryRunner = class {
|
|
|
1715
1741
|
});
|
|
1716
1742
|
this.isRunning = true;
|
|
1717
1743
|
this.childProcess.on("error", (error) => {
|
|
1718
|
-
logger$
|
|
1744
|
+
logger$9.error("❌ Process error:", error);
|
|
1719
1745
|
});
|
|
1720
1746
|
this.childProcess.on("exit", (code) => {
|
|
1721
|
-
if (code !== null && code !== 0 && code !== 143) logger$
|
|
1747
|
+
if (code !== null && code !== 0 && code !== 143) logger$9.error(`❌ Process exited with code ${code}`);
|
|
1722
1748
|
this.isRunning = false;
|
|
1723
1749
|
});
|
|
1724
1750
|
await new Promise((resolve$3) => setTimeout(resolve$3, 500));
|
|
1725
|
-
if (this.isRunning) logger$
|
|
1751
|
+
if (this.isRunning) logger$9.log(`\n🎉 Running at http://localhost:${this.port}`);
|
|
1726
1752
|
}
|
|
1727
1753
|
async restart() {
|
|
1728
1754
|
this.stopProcess();
|
|
@@ -1772,11 +1798,11 @@ var DevServer = class {
|
|
|
1772
1798
|
this.actualPort = this.requestedPort;
|
|
1773
1799
|
} else {
|
|
1774
1800
|
this.actualPort = await findAvailablePort(this.requestedPort);
|
|
1775
|
-
if (this.actualPort !== this.requestedPort) logger$
|
|
1801
|
+
if (this.actualPort !== this.requestedPort) logger$9.log(`ℹ️ Port ${this.requestedPort} was in use, using port ${this.actualPort} instead`);
|
|
1776
1802
|
}
|
|
1777
1803
|
const serverEntryPath = (0, node_path.join)(this.appRoot, ".gkm", this.provider, "server.ts");
|
|
1778
1804
|
await this.createServerEntry();
|
|
1779
|
-
logger$
|
|
1805
|
+
logger$9.log(`\n✨ Starting server on port ${this.actualPort}...`);
|
|
1780
1806
|
this.serverProcess = (0, node_child_process.spawn)("npx", [
|
|
1781
1807
|
"tsx",
|
|
1782
1808
|
serverEntryPath,
|
|
@@ -1792,18 +1818,18 @@ var DevServer = class {
|
|
|
1792
1818
|
});
|
|
1793
1819
|
this.isRunning = true;
|
|
1794
1820
|
this.serverProcess.on("error", (error) => {
|
|
1795
|
-
logger$
|
|
1821
|
+
logger$9.error("❌ Server error:", error);
|
|
1796
1822
|
});
|
|
1797
1823
|
this.serverProcess.on("exit", (code, signal) => {
|
|
1798
|
-
if (code !== null && code !== 0 && signal !== "SIGTERM") logger$
|
|
1824
|
+
if (code !== null && code !== 0 && signal !== "SIGTERM") logger$9.error(`❌ Server exited with code ${code}`);
|
|
1799
1825
|
this.isRunning = false;
|
|
1800
1826
|
});
|
|
1801
1827
|
await new Promise((resolve$3) => setTimeout(resolve$3, 1e3));
|
|
1802
1828
|
if (this.isRunning) {
|
|
1803
|
-
logger$
|
|
1804
|
-
if (this.enableOpenApi) logger$
|
|
1805
|
-
if (this.telescope) logger$
|
|
1806
|
-
if (this.studio) logger$
|
|
1829
|
+
logger$9.log(`\n🎉 Server running at http://localhost:${this.actualPort}`);
|
|
1830
|
+
if (this.enableOpenApi) logger$9.log(`📚 API Docs available at http://localhost:${this.actualPort}/__docs`);
|
|
1831
|
+
if (this.telescope) logger$9.log(`🔭 Telescope available at http://localhost:${this.actualPort}${this.telescope.path}`);
|
|
1832
|
+
if (this.studio) logger$9.log(`🗄️ Studio available at http://localhost:${this.actualPort}${this.studio.path}`);
|
|
1807
1833
|
}
|
|
1808
1834
|
}
|
|
1809
1835
|
async stop() {
|
|
@@ -1911,18 +1937,18 @@ async function execCommand(commandArgs, options = {}) {
|
|
|
1911
1937
|
const cwd = options.cwd ?? process.cwd();
|
|
1912
1938
|
if (commandArgs.length === 0) throw new Error("No command specified. Usage: gkm exec -- <command>");
|
|
1913
1939
|
const defaultEnv = loadEnvFiles(".env");
|
|
1914
|
-
if (defaultEnv.loaded.length > 0) logger$
|
|
1940
|
+
if (defaultEnv.loaded.length > 0) logger$9.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
1915
1941
|
const { credentials, secretsJsonPath, appName } = await prepareEntryCredentials({ cwd });
|
|
1916
|
-
if (appName) logger$
|
|
1942
|
+
if (appName) logger$9.log(`📦 App: ${appName}`);
|
|
1917
1943
|
const secretCount = Object.keys(credentials).filter((k) => k !== "PORT").length;
|
|
1918
|
-
if (secretCount > 0) logger$
|
|
1944
|
+
if (secretCount > 0) logger$9.log(`🔐 Loaded ${secretCount} secret(s)`);
|
|
1919
1945
|
const preloadDir = (0, node_path.join)(cwd, ".gkm");
|
|
1920
1946
|
await (0, node_fs_promises.mkdir)(preloadDir, { recursive: true });
|
|
1921
1947
|
const preloadPath = (0, node_path.join)(preloadDir, "credentials-preload.ts");
|
|
1922
1948
|
await createCredentialsPreload(preloadPath, secretsJsonPath);
|
|
1923
1949
|
const [cmd, ...args] = commandArgs;
|
|
1924
1950
|
if (!cmd) throw new Error("No command specified");
|
|
1925
|
-
logger$
|
|
1951
|
+
logger$9.log(`🚀 Running: ${commandArgs.join(" ")}`);
|
|
1926
1952
|
const existingNodeOptions = process.env.NODE_OPTIONS ?? "";
|
|
1927
1953
|
const tsxImport = "--import tsx";
|
|
1928
1954
|
const preloadImport = `--import ${preloadPath}`;
|
|
@@ -1943,7 +1969,7 @@ async function execCommand(commandArgs, options = {}) {
|
|
|
1943
1969
|
const exitCode = await new Promise((resolve$3) => {
|
|
1944
1970
|
child.on("close", (code) => resolve$3(code ?? 0));
|
|
1945
1971
|
child.on("error", (error) => {
|
|
1946
|
-
logger$
|
|
1972
|
+
logger$9.error(`Failed to run command: ${error.message}`);
|
|
1947
1973
|
resolve$3(1);
|
|
1948
1974
|
});
|
|
1949
1975
|
});
|
|
@@ -1952,7 +1978,7 @@ async function execCommand(commandArgs, options = {}) {
|
|
|
1952
1978
|
|
|
1953
1979
|
//#endregion
|
|
1954
1980
|
//#region src/build/manifests.ts
|
|
1955
|
-
const logger$
|
|
1981
|
+
const logger$8 = console;
|
|
1956
1982
|
async function generateAwsManifest(outputDir, routes, functions, crons, subscribers) {
|
|
1957
1983
|
const manifestDir = (0, node_path.join)(outputDir, "manifest");
|
|
1958
1984
|
await (0, node_fs_promises.mkdir)(manifestDir, { recursive: true });
|
|
@@ -1977,8 +2003,8 @@ export type RoutePath = Route['path'];
|
|
|
1977
2003
|
`;
|
|
1978
2004
|
const manifestPath = (0, node_path.join)(manifestDir, "aws.ts");
|
|
1979
2005
|
await (0, node_fs_promises.writeFile)(manifestPath, content);
|
|
1980
|
-
logger$
|
|
1981
|
-
logger$
|
|
2006
|
+
logger$8.log(`Generated AWS manifest with ${awsRoutes.length} routes, ${functions.length} functions, ${crons.length} crons, ${subscribers.length} subscribers`);
|
|
2007
|
+
logger$8.log(`Manifest: ${(0, node_path.relative)(process.cwd(), manifestPath)}`);
|
|
1982
2008
|
}
|
|
1983
2009
|
async function generateServerManifest(outputDir, appInfo, routes, subscribers) {
|
|
1984
2010
|
const manifestDir = (0, node_path.join)(outputDir, "manifest");
|
|
@@ -2009,13 +2035,13 @@ export type RoutePath = Route['path'];
|
|
|
2009
2035
|
`;
|
|
2010
2036
|
const manifestPath = (0, node_path.join)(manifestDir, "server.ts");
|
|
2011
2037
|
await (0, node_fs_promises.writeFile)(manifestPath, content);
|
|
2012
|
-
logger$
|
|
2013
|
-
logger$
|
|
2038
|
+
logger$8.log(`Generated server manifest with ${serverRoutes.length} routes, ${serverSubscribers.length} subscribers`);
|
|
2039
|
+
logger$8.log(`Manifest: ${(0, node_path.relative)(process.cwd(), manifestPath)}`);
|
|
2014
2040
|
}
|
|
2015
2041
|
|
|
2016
2042
|
//#endregion
|
|
2017
2043
|
//#region src/build/index.ts
|
|
2018
|
-
const logger$
|
|
2044
|
+
const logger$7 = console;
|
|
2019
2045
|
async function buildCommand(options) {
|
|
2020
2046
|
const loadedConfig = await require_config.loadWorkspaceConfig();
|
|
2021
2047
|
if (loadedConfig.type === "workspace") {
|
|
@@ -2023,7 +2049,7 @@ async function buildCommand(options) {
|
|
|
2023
2049
|
const workspaceRoot = (0, node_path.resolve)(loadedConfig.workspace.root);
|
|
2024
2050
|
const isAtWorkspaceRoot = cwd === workspaceRoot;
|
|
2025
2051
|
if (isAtWorkspaceRoot) {
|
|
2026
|
-
logger$
|
|
2052
|
+
logger$7.log("📦 Detected workspace configuration");
|
|
2027
2053
|
return workspaceBuildCommand(loadedConfig.workspace, options);
|
|
2028
2054
|
}
|
|
2029
2055
|
}
|
|
@@ -2031,21 +2057,21 @@ async function buildCommand(options) {
|
|
|
2031
2057
|
const resolved = resolveProviders(config, options);
|
|
2032
2058
|
const productionConfigFromGkm = getProductionConfigFromGkm(config);
|
|
2033
2059
|
const production = normalizeProductionConfig(options.production ?? false, productionConfigFromGkm);
|
|
2034
|
-
if (production) logger$
|
|
2035
|
-
logger$
|
|
2036
|
-
logger$
|
|
2037
|
-
if (config.functions) logger$
|
|
2038
|
-
if (config.crons) logger$
|
|
2039
|
-
if (config.subscribers) logger$
|
|
2040
|
-
logger$
|
|
2060
|
+
if (production) logger$7.log(`🏭 Building for PRODUCTION`);
|
|
2061
|
+
logger$7.log(`Building with providers: ${resolved.providers.join(", ")}`);
|
|
2062
|
+
logger$7.log(`Loading routes from: ${config.routes}`);
|
|
2063
|
+
if (config.functions) logger$7.log(`Loading functions from: ${config.functions}`);
|
|
2064
|
+
if (config.crons) logger$7.log(`Loading crons from: ${config.crons}`);
|
|
2065
|
+
if (config.subscribers) logger$7.log(`Loading subscribers from: ${config.subscribers}`);
|
|
2066
|
+
logger$7.log(`Using envParser: ${config.envParser}`);
|
|
2041
2067
|
const { path: envParserPath, importPattern: envParserImportPattern } = require_config.parseModuleConfig(config.envParser, "envParser");
|
|
2042
2068
|
const { path: loggerPath, importPattern: loggerImportPattern } = require_config.parseModuleConfig(config.logger, "logger");
|
|
2043
2069
|
const telescope = production ? void 0 : normalizeTelescopeConfig(config.telescope);
|
|
2044
|
-
if (telescope) logger$
|
|
2070
|
+
if (telescope) logger$7.log(`🔭 Telescope enabled at ${telescope.path}`);
|
|
2045
2071
|
const studio = production ? void 0 : normalizeStudioConfig(config.studio);
|
|
2046
|
-
if (studio) logger$
|
|
2072
|
+
if (studio) logger$7.log(`🗄️ Studio enabled at ${studio.path}`);
|
|
2047
2073
|
const hooks = normalizeHooksConfig(config.hooks);
|
|
2048
|
-
if (hooks) logger$
|
|
2074
|
+
if (hooks) logger$7.log(`🪝 Server hooks enabled`);
|
|
2049
2075
|
const services = config.docker?.compose?.services;
|
|
2050
2076
|
const dockerServices = services ? Array.isArray(services) ? {
|
|
2051
2077
|
postgres: services.includes("postgres"),
|
|
@@ -2077,12 +2103,12 @@ async function buildCommand(options) {
|
|
|
2077
2103
|
config.crons ? cronGenerator.load(config.crons) : [],
|
|
2078
2104
|
config.subscribers ? subscriberGenerator.load(config.subscribers) : []
|
|
2079
2105
|
]);
|
|
2080
|
-
logger$
|
|
2081
|
-
logger$
|
|
2082
|
-
logger$
|
|
2083
|
-
logger$
|
|
2106
|
+
logger$7.log(`Found ${allEndpoints.length} endpoints`);
|
|
2107
|
+
logger$7.log(`Found ${allFunctions.length} functions`);
|
|
2108
|
+
logger$7.log(`Found ${allCrons.length} crons`);
|
|
2109
|
+
logger$7.log(`Found ${allSubscribers.length} subscribers`);
|
|
2084
2110
|
if (allEndpoints.length === 0 && allFunctions.length === 0 && allCrons.length === 0 && allSubscribers.length === 0) {
|
|
2085
|
-
logger$
|
|
2111
|
+
logger$7.log("No endpoints, functions, crons, or subscribers found to process");
|
|
2086
2112
|
return {};
|
|
2087
2113
|
}
|
|
2088
2114
|
const rootOutputDir = (0, node_path.join)(process.cwd(), ".gkm");
|
|
@@ -2097,7 +2123,7 @@ async function buildCommand(options) {
|
|
|
2097
2123
|
async function buildForProvider(provider, context, rootOutputDir, endpointGenerator, functionGenerator, cronGenerator, subscriberGenerator, endpoints, functions, crons, subscribers, enableOpenApi, skipBundle, stage) {
|
|
2098
2124
|
const outputDir = (0, node_path.join)(process.cwd(), ".gkm", provider);
|
|
2099
2125
|
await (0, node_fs_promises.mkdir)(outputDir, { recursive: true });
|
|
2100
|
-
logger$
|
|
2126
|
+
logger$7.log(`\nGenerating handlers for provider: ${provider}`);
|
|
2101
2127
|
const [routes, functionInfos, cronInfos, subscriberInfos] = await Promise.all([
|
|
2102
2128
|
endpointGenerator.build(context, endpoints, outputDir, {
|
|
2103
2129
|
provider,
|
|
@@ -2107,7 +2133,7 @@ async function buildForProvider(provider, context, rootOutputDir, endpointGenera
|
|
|
2107
2133
|
cronGenerator.build(context, crons, outputDir, { provider }),
|
|
2108
2134
|
subscriberGenerator.build(context, subscribers, outputDir, { provider })
|
|
2109
2135
|
]);
|
|
2110
|
-
logger$
|
|
2136
|
+
logger$7.log(`Generated ${routes.length} routes, ${functionInfos.length} functions, ${cronInfos.length} crons, ${subscriberInfos.length} subscribers for ${provider}`);
|
|
2111
2137
|
if (provider === "server") {
|
|
2112
2138
|
const routeMetadata = await Promise.all(endpoints.map(async ({ construct }) => ({
|
|
2113
2139
|
path: construct._path,
|
|
@@ -2122,7 +2148,7 @@ async function buildForProvider(provider, context, rootOutputDir, endpointGenera
|
|
|
2122
2148
|
await generateServerManifest(rootOutputDir, appInfo, routeMetadata, subscriberInfos);
|
|
2123
2149
|
let masterKey;
|
|
2124
2150
|
if (context.production?.bundle && !skipBundle) {
|
|
2125
|
-
logger$
|
|
2151
|
+
logger$7.log(`\n📦 Bundling production server...`);
|
|
2126
2152
|
const { bundleServer } = await Promise.resolve().then(() => require("./bundler-BB-kETMd.cjs"));
|
|
2127
2153
|
const allConstructs = [
|
|
2128
2154
|
...endpoints.map((e) => e.construct),
|
|
@@ -2142,10 +2168,10 @@ async function buildForProvider(provider, context, rootOutputDir, endpointGenera
|
|
|
2142
2168
|
dockerServices
|
|
2143
2169
|
});
|
|
2144
2170
|
masterKey = bundleResult.masterKey;
|
|
2145
|
-
logger$
|
|
2171
|
+
logger$7.log(`✅ Bundle complete: .gkm/server/dist/server.mjs`);
|
|
2146
2172
|
if (masterKey) {
|
|
2147
|
-
logger$
|
|
2148
|
-
logger$
|
|
2173
|
+
logger$7.log(`\n🔐 Secrets encrypted for deployment`);
|
|
2174
|
+
logger$7.log(` Deploy with: GKM_MASTER_KEY=${masterKey}`);
|
|
2149
2175
|
}
|
|
2150
2176
|
}
|
|
2151
2177
|
return { masterKey };
|
|
@@ -2182,17 +2208,17 @@ async function workspaceBuildCommand(workspace, options) {
|
|
|
2182
2208
|
const apps = Object.entries(workspace.apps);
|
|
2183
2209
|
const backendApps = apps.filter(([, app]) => app.type === "backend");
|
|
2184
2210
|
const frontendApps = apps.filter(([, app]) => app.type === "frontend");
|
|
2185
|
-
logger$
|
|
2186
|
-
logger$
|
|
2187
|
-
logger$
|
|
2188
|
-
if (options.production) logger$
|
|
2211
|
+
logger$7.log(`\n🏗️ Building workspace: ${workspace.name}`);
|
|
2212
|
+
logger$7.log(` Backend apps: ${backendApps.map(([name$1]) => name$1).join(", ") || "none"}`);
|
|
2213
|
+
logger$7.log(` Frontend apps: ${frontendApps.map(([name$1]) => name$1).join(", ") || "none"}`);
|
|
2214
|
+
if (options.production) logger$7.log(` 🏭 Production mode enabled`);
|
|
2189
2215
|
const buildOrder = require_workspace.getAppBuildOrder(workspace);
|
|
2190
|
-
logger$
|
|
2216
|
+
logger$7.log(` Build order: ${buildOrder.join(" → ")}`);
|
|
2191
2217
|
const pm = detectPackageManager$2();
|
|
2192
|
-
logger$
|
|
2218
|
+
logger$7.log(`\n📦 Using ${pm} with Turbo for parallel builds...\n`);
|
|
2193
2219
|
try {
|
|
2194
2220
|
const turboCommand = getTurboCommand(pm);
|
|
2195
|
-
logger$
|
|
2221
|
+
logger$7.log(`Running: ${turboCommand}`);
|
|
2196
2222
|
await new Promise((resolve$3, reject) => {
|
|
2197
2223
|
const child = (0, node_child_process.spawn)(turboCommand, {
|
|
2198
2224
|
shell: true,
|
|
@@ -2220,15 +2246,15 @@ async function workspaceBuildCommand(workspace, options) {
|
|
|
2220
2246
|
outputPath
|
|
2221
2247
|
});
|
|
2222
2248
|
}
|
|
2223
|
-
logger$
|
|
2224
|
-
logger$
|
|
2249
|
+
logger$7.log(`\n✅ Workspace build complete!`);
|
|
2250
|
+
logger$7.log(`\n📋 Build Summary:`);
|
|
2225
2251
|
for (const result of results) {
|
|
2226
2252
|
const icon = result.type === "backend" ? "⚙️" : "🌐";
|
|
2227
|
-
logger$
|
|
2253
|
+
logger$7.log(` ${icon} ${result.appName}: ${result.outputPath || "built"}`);
|
|
2228
2254
|
}
|
|
2229
2255
|
} catch (error) {
|
|
2230
2256
|
const errorMessage = error instanceof Error ? error.message : "Build failed";
|
|
2231
|
-
logger$
|
|
2257
|
+
logger$7.log(`\n❌ Build failed: ${errorMessage}`);
|
|
2232
2258
|
for (const [appName, app] of apps) results.push({
|
|
2233
2259
|
appName,
|
|
2234
2260
|
type: app.type,
|
|
@@ -2803,8 +2829,8 @@ ENV NODE_ENV=production
|
|
|
2803
2829
|
ENV PORT=${port}
|
|
2804
2830
|
|
|
2805
2831
|
# Health check
|
|
2806
|
-
HEALTHCHECK --interval=30s --timeout=
|
|
2807
|
-
CMD wget -
|
|
2832
|
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\
|
|
2833
|
+
CMD wget -qO- http://localhost:${port}${healthCheckPath} > /dev/null 2>&1 || exit 1
|
|
2808
2834
|
|
|
2809
2835
|
# Switch to non-root user
|
|
2810
2836
|
USER hono
|
|
@@ -2886,8 +2912,8 @@ COPY --from=builder --chown=hono:nodejs /app/.gkm/server/dist/server.mjs ./
|
|
|
2886
2912
|
ENV NODE_ENV=production
|
|
2887
2913
|
ENV PORT=${port}
|
|
2888
2914
|
|
|
2889
|
-
HEALTHCHECK --interval=30s --timeout=
|
|
2890
|
-
CMD wget -
|
|
2915
|
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\
|
|
2916
|
+
CMD wget -qO- http://localhost:${port}${healthCheckPath} > /dev/null 2>&1 || exit 1
|
|
2891
2917
|
|
|
2892
2918
|
USER hono
|
|
2893
2919
|
|
|
@@ -2923,8 +2949,8 @@ ENV NODE_ENV=production
|
|
|
2923
2949
|
ENV PORT=${port}
|
|
2924
2950
|
|
|
2925
2951
|
# Health check
|
|
2926
|
-
HEALTHCHECK --interval=30s --timeout=
|
|
2927
|
-
CMD wget -
|
|
2952
|
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\
|
|
2953
|
+
CMD wget -qO- http://localhost:${port}${healthCheckPath} > /dev/null 2>&1 || exit 1
|
|
2928
2954
|
|
|
2929
2955
|
# Switch to non-root user
|
|
2930
2956
|
USER hono
|
|
@@ -3116,10 +3142,6 @@ COPY --from=builder --chown=nextjs:nodejs /app/${appPath}/.next/standalone ./
|
|
|
3116
3142
|
COPY --from=builder --chown=nextjs:nodejs /app/${appPath}/.next/static ./${appPath}/.next/static
|
|
3117
3143
|
COPY --from=builder --chown=nextjs:nodejs /app/${appPath}/public ./${appPath}/public
|
|
3118
3144
|
|
|
3119
|
-
# Health check
|
|
3120
|
-
HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \\
|
|
3121
|
-
CMD wget -q --spider http://localhost:${port}/ || exit 1
|
|
3122
|
-
|
|
3123
3145
|
USER nextjs
|
|
3124
3146
|
|
|
3125
3147
|
EXPOSE ${port}
|
|
@@ -3212,8 +3234,8 @@ COPY --from=builder --chown=hono:nodejs /app/${appPath}/.gkm/server/dist/server.
|
|
|
3212
3234
|
ENV NODE_ENV=production
|
|
3213
3235
|
ENV PORT=${port}
|
|
3214
3236
|
|
|
3215
|
-
HEALTHCHECK --interval=30s --timeout=
|
|
3216
|
-
CMD wget -
|
|
3237
|
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\
|
|
3238
|
+
CMD wget -qO- http://localhost:${port}${healthCheckPath} > /dev/null 2>&1 || exit 1
|
|
3217
3239
|
|
|
3218
3240
|
USER hono
|
|
3219
3241
|
|
|
@@ -3322,8 +3344,8 @@ COPY --from=builder --chown=app:nodejs /app/${appPath}/dist/index.mjs ./
|
|
|
3322
3344
|
ENV NODE_ENV=production
|
|
3323
3345
|
ENV PORT=${port}
|
|
3324
3346
|
|
|
3325
|
-
HEALTHCHECK --interval=30s --timeout=
|
|
3326
|
-
CMD wget -
|
|
3347
|
+
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \\
|
|
3348
|
+
CMD wget -qO- http://localhost:${port}${healthCheckPath} > /dev/null 2>&1 || exit 1
|
|
3327
3349
|
|
|
3328
3350
|
USER app
|
|
3329
3351
|
|
|
@@ -3336,7 +3358,7 @@ CMD ["node", "index.mjs"]
|
|
|
3336
3358
|
|
|
3337
3359
|
//#endregion
|
|
3338
3360
|
//#region src/docker/index.ts
|
|
3339
|
-
const logger$
|
|
3361
|
+
const logger$6 = console;
|
|
3340
3362
|
/**
|
|
3341
3363
|
* Docker command implementation
|
|
3342
3364
|
* Generates Dockerfile, docker-compose.yml, and related files
|
|
@@ -3347,7 +3369,7 @@ const logger$5 = console;
|
|
|
3347
3369
|
async function dockerCommand(options) {
|
|
3348
3370
|
const loadedConfig = await require_config.loadWorkspaceConfig();
|
|
3349
3371
|
if (loadedConfig.type === "workspace") {
|
|
3350
|
-
logger$
|
|
3372
|
+
logger$6.log("📦 Detected workspace configuration");
|
|
3351
3373
|
return workspaceDockerCommand(loadedConfig.workspace, options);
|
|
3352
3374
|
}
|
|
3353
3375
|
const config = await require_config.loadConfig();
|
|
@@ -3368,14 +3390,14 @@ async function dockerCommand(options) {
|
|
|
3368
3390
|
let useTurbo = options.turbo ?? false;
|
|
3369
3391
|
if (inMonorepo && !useSlim) if (hasTurbo) {
|
|
3370
3392
|
useTurbo = true;
|
|
3371
|
-
logger$
|
|
3393
|
+
logger$6.log(" Detected monorepo with turbo.json - using turbo prune");
|
|
3372
3394
|
} else throw new Error("Monorepo detected but turbo.json not found.\n\nDocker builds in monorepos require Turborepo for proper dependency isolation.\n\nTo fix this:\n 1. Install turbo: pnpm add -Dw turbo\n 2. Create turbo.json in your monorepo root\n 3. Run this command again\n\nSee: https://turbo.build/repo/docs/guides/tools/docker");
|
|
3373
3395
|
let turboPackage = options.turboPackage ?? dockerConfig.imageName;
|
|
3374
3396
|
if (useTurbo && !options.turboPackage) try {
|
|
3375
3397
|
const pkg$1 = require(`${process.cwd()}/package.json`);
|
|
3376
3398
|
if (pkg$1.name) {
|
|
3377
3399
|
turboPackage = pkg$1.name;
|
|
3378
|
-
logger$
|
|
3400
|
+
logger$6.log(` Turbo package: ${turboPackage}`);
|
|
3379
3401
|
}
|
|
3380
3402
|
} catch {}
|
|
3381
3403
|
const templateOptions = {
|
|
@@ -3392,7 +3414,7 @@ async function dockerCommand(options) {
|
|
|
3392
3414
|
const dockerMode = useSlim ? "slim" : useTurbo ? "turbo" : "multi-stage";
|
|
3393
3415
|
const dockerfilePath = (0, node_path.join)(dockerDir, "Dockerfile");
|
|
3394
3416
|
await (0, node_fs_promises.writeFile)(dockerfilePath, dockerfile);
|
|
3395
|
-
logger$
|
|
3417
|
+
logger$6.log(`Generated: .gkm/docker/Dockerfile (${dockerMode}, ${packageManager})`);
|
|
3396
3418
|
const composeOptions = {
|
|
3397
3419
|
imageName: dockerConfig.imageName,
|
|
3398
3420
|
registry: options.registry ?? dockerConfig.registry,
|
|
@@ -3404,15 +3426,15 @@ async function dockerCommand(options) {
|
|
|
3404
3426
|
const dockerCompose = hasServices ? generateDockerCompose(composeOptions) : generateMinimalDockerCompose(composeOptions);
|
|
3405
3427
|
const composePath = (0, node_path.join)(dockerDir, "docker-compose.yml");
|
|
3406
3428
|
await (0, node_fs_promises.writeFile)(composePath, dockerCompose);
|
|
3407
|
-
logger$
|
|
3429
|
+
logger$6.log("Generated: .gkm/docker/docker-compose.yml");
|
|
3408
3430
|
const dockerignore = generateDockerignore();
|
|
3409
3431
|
const dockerignorePath = (0, node_path.join)(process.cwd(), ".dockerignore");
|
|
3410
3432
|
await (0, node_fs_promises.writeFile)(dockerignorePath, dockerignore);
|
|
3411
|
-
logger$
|
|
3433
|
+
logger$6.log("Generated: .dockerignore (project root)");
|
|
3412
3434
|
const entrypoint = generateDockerEntrypoint();
|
|
3413
3435
|
const entrypointPath = (0, node_path.join)(dockerDir, "docker-entrypoint.sh");
|
|
3414
3436
|
await (0, node_fs_promises.writeFile)(entrypointPath, entrypoint);
|
|
3415
|
-
logger$
|
|
3437
|
+
logger$6.log("Generated: .gkm/docker/docker-entrypoint.sh");
|
|
3416
3438
|
const result = {
|
|
3417
3439
|
dockerfile: dockerfilePath,
|
|
3418
3440
|
dockerCompose: composePath,
|
|
@@ -3431,13 +3453,13 @@ async function dockerCommand(options) {
|
|
|
3431
3453
|
function ensureLockfile(cwd) {
|
|
3432
3454
|
const lockfilePath = findLockfilePath(cwd);
|
|
3433
3455
|
if (!lockfilePath) {
|
|
3434
|
-
logger$
|
|
3456
|
+
logger$6.warn("\n⚠️ No lockfile found. Docker build may fail or use stale dependencies.");
|
|
3435
3457
|
return null;
|
|
3436
3458
|
}
|
|
3437
3459
|
const lockfileName = (0, node_path.basename)(lockfilePath);
|
|
3438
3460
|
const localLockfile = (0, node_path.join)(cwd, lockfileName);
|
|
3439
3461
|
if (lockfilePath === localLockfile) return null;
|
|
3440
|
-
logger$
|
|
3462
|
+
logger$6.log(` Copying ${lockfileName} from monorepo root...`);
|
|
3441
3463
|
(0, node_fs.copyFileSync)(lockfilePath, localLockfile);
|
|
3442
3464
|
return () => {
|
|
3443
3465
|
try {
|
|
@@ -3453,7 +3475,7 @@ async function buildDockerImage(imageName, options) {
|
|
|
3453
3475
|
const tag = options.tag ?? "latest";
|
|
3454
3476
|
const registry = options.registry;
|
|
3455
3477
|
const fullImageName = registry ? `${registry}/${imageName}:${tag}` : `${imageName}:${tag}`;
|
|
3456
|
-
logger$
|
|
3478
|
+
logger$6.log(`\n🐳 Building Docker image: ${fullImageName}`);
|
|
3457
3479
|
const cwd = process.cwd();
|
|
3458
3480
|
const cleanup = ensureLockfile(cwd);
|
|
3459
3481
|
try {
|
|
@@ -3465,7 +3487,7 @@ async function buildDockerImage(imageName, options) {
|
|
|
3465
3487
|
DOCKER_BUILDKIT: "1"
|
|
3466
3488
|
}
|
|
3467
3489
|
});
|
|
3468
|
-
logger$
|
|
3490
|
+
logger$6.log(`✅ Docker image built: ${fullImageName}`);
|
|
3469
3491
|
} catch (error) {
|
|
3470
3492
|
throw new Error(`Failed to build Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3471
3493
|
} finally {
|
|
@@ -3480,13 +3502,13 @@ async function pushDockerImage(imageName, options) {
|
|
|
3480
3502
|
const registry = options.registry;
|
|
3481
3503
|
if (!registry) throw new Error("Registry is required to push Docker image. Use --registry or configure docker.registry in gkm.config.ts");
|
|
3482
3504
|
const fullImageName = `${registry}/${imageName}:${tag}`;
|
|
3483
|
-
logger$
|
|
3505
|
+
logger$6.log(`\n🚀 Pushing Docker image: ${fullImageName}`);
|
|
3484
3506
|
try {
|
|
3485
3507
|
(0, node_child_process.execSync)(`docker push ${fullImageName}`, {
|
|
3486
3508
|
cwd: process.cwd(),
|
|
3487
3509
|
stdio: "inherit"
|
|
3488
3510
|
});
|
|
3489
|
-
logger$
|
|
3511
|
+
logger$6.log(`✅ Docker image pushed: ${fullImageName}`);
|
|
3490
3512
|
} catch (error) {
|
|
3491
3513
|
throw new Error(`Failed to push Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3492
3514
|
}
|
|
@@ -3512,11 +3534,11 @@ function getAppPackageName(appPath) {
|
|
|
3512
3534
|
async function workspaceDockerCommand(workspace, options) {
|
|
3513
3535
|
const results = [];
|
|
3514
3536
|
const apps = Object.entries(workspace.apps);
|
|
3515
|
-
logger$
|
|
3537
|
+
logger$6.log(`\n🐳 Generating Dockerfiles for workspace: ${workspace.name}`);
|
|
3516
3538
|
const dockerDir = (0, node_path.join)(workspace.root, ".gkm", "docker");
|
|
3517
3539
|
await (0, node_fs_promises.mkdir)(dockerDir, { recursive: true });
|
|
3518
3540
|
const packageManager = detectPackageManager$1(workspace.root);
|
|
3519
|
-
logger$
|
|
3541
|
+
logger$6.log(` Package manager: ${packageManager}`);
|
|
3520
3542
|
for (const [appName, app] of apps) {
|
|
3521
3543
|
const appPath = app.path;
|
|
3522
3544
|
const fullAppPath = (0, node_path.join)(workspace.root, appPath);
|
|
@@ -3524,7 +3546,7 @@ async function workspaceDockerCommand(workspace, options) {
|
|
|
3524
3546
|
const imageName = appName;
|
|
3525
3547
|
const hasEntry = !!app.entry;
|
|
3526
3548
|
const buildType = hasEntry ? "entry" : app.type;
|
|
3527
|
-
logger$
|
|
3549
|
+
logger$6.log(`\n 📄 Generating Dockerfile for ${appName} (${buildType})`);
|
|
3528
3550
|
let dockerfile;
|
|
3529
3551
|
if (app.type === "frontend") dockerfile = generateNextjsDockerfile({
|
|
3530
3552
|
imageName,
|
|
@@ -3555,7 +3577,7 @@ async function workspaceDockerCommand(workspace, options) {
|
|
|
3555
3577
|
});
|
|
3556
3578
|
const dockerfilePath = (0, node_path.join)(dockerDir, `Dockerfile.${appName}`);
|
|
3557
3579
|
await (0, node_fs_promises.writeFile)(dockerfilePath, dockerfile);
|
|
3558
|
-
logger$
|
|
3580
|
+
logger$6.log(` Generated: .gkm/docker/Dockerfile.${appName}`);
|
|
3559
3581
|
results.push({
|
|
3560
3582
|
appName,
|
|
3561
3583
|
type: app.type,
|
|
@@ -3566,19 +3588,19 @@ async function workspaceDockerCommand(workspace, options) {
|
|
|
3566
3588
|
const dockerignore = generateDockerignore();
|
|
3567
3589
|
const dockerignorePath = (0, node_path.join)(workspace.root, ".dockerignore");
|
|
3568
3590
|
await (0, node_fs_promises.writeFile)(dockerignorePath, dockerignore);
|
|
3569
|
-
logger$
|
|
3591
|
+
logger$6.log(`\n Generated: .dockerignore (workspace root)`);
|
|
3570
3592
|
const dockerCompose = generateWorkspaceCompose(workspace, { registry: options.registry });
|
|
3571
3593
|
const composePath = (0, node_path.join)(dockerDir, "docker-compose.yml");
|
|
3572
3594
|
await (0, node_fs_promises.writeFile)(composePath, dockerCompose);
|
|
3573
|
-
logger$
|
|
3574
|
-
logger$
|
|
3575
|
-
logger$
|
|
3595
|
+
logger$6.log(` Generated: .gkm/docker/docker-compose.yml`);
|
|
3596
|
+
logger$6.log(`\n✅ Generated ${results.length} Dockerfile(s) + docker-compose.yml`);
|
|
3597
|
+
logger$6.log("\n📋 Build commands:");
|
|
3576
3598
|
for (const result of results) {
|
|
3577
3599
|
const icon = result.type === "backend" ? "⚙️" : "🌐";
|
|
3578
|
-
logger$
|
|
3600
|
+
logger$6.log(` ${icon} docker build -f .gkm/docker/Dockerfile.${result.appName} -t ${result.imageName} .`);
|
|
3579
3601
|
}
|
|
3580
|
-
logger$
|
|
3581
|
-
logger$
|
|
3602
|
+
logger$6.log("\n📋 Run all services:");
|
|
3603
|
+
logger$6.log(" docker compose -f .gkm/docker/docker-compose.yml up --build");
|
|
3582
3604
|
return {
|
|
3583
3605
|
apps: results,
|
|
3584
3606
|
dockerCompose: composePath,
|
|
@@ -3618,7 +3640,7 @@ function getAppNameFromPackageJson() {
|
|
|
3618
3640
|
} catch {}
|
|
3619
3641
|
return void 0;
|
|
3620
3642
|
}
|
|
3621
|
-
const logger$
|
|
3643
|
+
const logger$5 = console;
|
|
3622
3644
|
/**
|
|
3623
3645
|
* Get the full image reference
|
|
3624
3646
|
*/
|
|
@@ -3633,18 +3655,18 @@ function getImageRef(registry, imageName, tag) {
|
|
|
3633
3655
|
* @param buildArgs - Build arguments to pass to docker build
|
|
3634
3656
|
*/
|
|
3635
3657
|
async function buildImage(imageRef, appName, buildArgs) {
|
|
3636
|
-
logger$
|
|
3658
|
+
logger$5.log(`\n🔨 Building Docker image: ${imageRef}`);
|
|
3637
3659
|
const cwd = process.cwd();
|
|
3638
3660
|
const lockfilePath = findLockfilePath(cwd);
|
|
3639
3661
|
const lockfileDir = lockfilePath ? (0, node_path.dirname)(lockfilePath) : cwd;
|
|
3640
3662
|
const inMonorepo = lockfileDir !== cwd;
|
|
3641
|
-
if (appName || inMonorepo) logger$
|
|
3642
|
-
else logger$
|
|
3663
|
+
if (appName || inMonorepo) logger$5.log(" Generating Dockerfile for monorepo (turbo prune)...");
|
|
3664
|
+
else logger$5.log(" Generating Dockerfile...");
|
|
3643
3665
|
await dockerCommand({});
|
|
3644
3666
|
const dockerfileSuffix = appName ? `.${appName}` : "";
|
|
3645
3667
|
const dockerfilePath = `.gkm/docker/Dockerfile${dockerfileSuffix}`;
|
|
3646
3668
|
const buildCwd = lockfilePath && (inMonorepo || appName) ? lockfileDir : cwd;
|
|
3647
|
-
if (buildCwd !== cwd) logger$
|
|
3669
|
+
if (buildCwd !== cwd) logger$5.log(` Building from workspace root: ${buildCwd}`);
|
|
3648
3670
|
const buildArgsString = buildArgs && buildArgs.length > 0 ? buildArgs.map((arg) => `--build-arg "${arg}"`).join(" ") : "";
|
|
3649
3671
|
try {
|
|
3650
3672
|
const cmd = [
|
|
@@ -3663,7 +3685,7 @@ async function buildImage(imageRef, appName, buildArgs) {
|
|
|
3663
3685
|
DOCKER_BUILDKIT: "1"
|
|
3664
3686
|
}
|
|
3665
3687
|
});
|
|
3666
|
-
logger$
|
|
3688
|
+
logger$5.log(`✅ Image built: ${imageRef}`);
|
|
3667
3689
|
} catch (error) {
|
|
3668
3690
|
throw new Error(`Failed to build Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3669
3691
|
}
|
|
@@ -3672,13 +3694,13 @@ async function buildImage(imageRef, appName, buildArgs) {
|
|
|
3672
3694
|
* Push Docker image to registry
|
|
3673
3695
|
*/
|
|
3674
3696
|
async function pushImage(imageRef) {
|
|
3675
|
-
logger$
|
|
3697
|
+
logger$5.log(`\n☁️ Pushing image: ${imageRef}`);
|
|
3676
3698
|
try {
|
|
3677
3699
|
(0, node_child_process.execSync)(`docker push ${imageRef}`, {
|
|
3678
3700
|
cwd: process.cwd(),
|
|
3679
3701
|
stdio: "inherit"
|
|
3680
3702
|
});
|
|
3681
|
-
logger$
|
|
3703
|
+
logger$5.log(`✅ Image pushed: ${imageRef}`);
|
|
3682
3704
|
} catch (error) {
|
|
3683
3705
|
throw new Error(`Failed to push Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
3684
3706
|
}
|
|
@@ -3691,17 +3713,17 @@ async function deployDocker(options) {
|
|
|
3691
3713
|
const imageName = config.imageName;
|
|
3692
3714
|
const imageRef = getImageRef(config.registry, imageName, tag);
|
|
3693
3715
|
await buildImage(imageRef, config.appName, buildArgs);
|
|
3694
|
-
if (!skipPush) if (!config.registry) logger$
|
|
3716
|
+
if (!skipPush) if (!config.registry) logger$5.warn("\n⚠️ No registry configured. Use --skip-push or configure docker.registry in gkm.config.ts");
|
|
3695
3717
|
else await pushImage(imageRef);
|
|
3696
|
-
logger$
|
|
3697
|
-
logger$
|
|
3698
|
-
logger$
|
|
3699
|
-
logger$
|
|
3718
|
+
logger$5.log("\n✅ Docker deployment ready!");
|
|
3719
|
+
logger$5.log(`\n📋 Deployment details:`);
|
|
3720
|
+
logger$5.log(` Image: ${imageRef}`);
|
|
3721
|
+
logger$5.log(` Stage: ${stage}`);
|
|
3700
3722
|
if (masterKey) {
|
|
3701
|
-
logger$
|
|
3702
|
-
logger$
|
|
3703
|
-
logger$
|
|
3704
|
-
logger$
|
|
3723
|
+
logger$5.log(`\n🔐 Deploy with this environment variable:`);
|
|
3724
|
+
logger$5.log(` GKM_MASTER_KEY=${masterKey}`);
|
|
3725
|
+
logger$5.log("\n Example docker run:");
|
|
3726
|
+
logger$5.log(` docker run -e GKM_MASTER_KEY=${masterKey} ${imageRef}`);
|
|
3705
3727
|
}
|
|
3706
3728
|
return {
|
|
3707
3729
|
imageRef,
|
|
@@ -3728,7 +3750,7 @@ function resolveDockerConfig(config) {
|
|
|
3728
3750
|
|
|
3729
3751
|
//#endregion
|
|
3730
3752
|
//#region src/deploy/dokploy.ts
|
|
3731
|
-
const logger$
|
|
3753
|
+
const logger$4 = console;
|
|
3732
3754
|
/**
|
|
3733
3755
|
* Get the Dokploy API token from stored credentials or environment
|
|
3734
3756
|
*/
|
|
@@ -3752,25 +3774,25 @@ async function createApi$1(endpoint) {
|
|
|
3752
3774
|
*/
|
|
3753
3775
|
async function deployDokploy(options) {
|
|
3754
3776
|
const { stage, imageRef, masterKey, config } = options;
|
|
3755
|
-
logger$
|
|
3756
|
-
logger$
|
|
3757
|
-
logger$
|
|
3777
|
+
logger$4.log(`\n🎯 Deploying to Dokploy...`);
|
|
3778
|
+
logger$4.log(` Endpoint: ${config.endpoint}`);
|
|
3779
|
+
logger$4.log(` Application: ${config.applicationId}`);
|
|
3758
3780
|
const api = await createApi$1(config.endpoint);
|
|
3759
|
-
logger$
|
|
3781
|
+
logger$4.log(` Configuring Docker image: ${imageRef}`);
|
|
3760
3782
|
const registryOptions = {};
|
|
3761
3783
|
if (config.registryId) {
|
|
3762
3784
|
registryOptions.registryId = config.registryId;
|
|
3763
|
-
logger$
|
|
3785
|
+
logger$4.log(` Using Dokploy registry: ${config.registryId}`);
|
|
3764
3786
|
} else {
|
|
3765
3787
|
const storedRegistryId = await getDokployRegistryId();
|
|
3766
3788
|
if (storedRegistryId) {
|
|
3767
3789
|
registryOptions.registryId = storedRegistryId;
|
|
3768
|
-
logger$
|
|
3790
|
+
logger$4.log(` Using stored Dokploy registry: ${storedRegistryId}`);
|
|
3769
3791
|
} else if (config.registryCredentials) {
|
|
3770
3792
|
registryOptions.username = config.registryCredentials.username;
|
|
3771
3793
|
registryOptions.password = config.registryCredentials.password;
|
|
3772
3794
|
registryOptions.registryUrl = config.registryCredentials.registryUrl;
|
|
3773
|
-
logger$
|
|
3795
|
+
logger$4.log(` Using registry credentials for: ${config.registryCredentials.registryUrl}`);
|
|
3774
3796
|
} else {
|
|
3775
3797
|
const username = process.env.DOCKER_REGISTRY_USERNAME;
|
|
3776
3798
|
const password = process.env.DOCKER_REGISTRY_PASSWORD;
|
|
@@ -3779,31 +3801,31 @@ async function deployDokploy(options) {
|
|
|
3779
3801
|
registryOptions.username = username;
|
|
3780
3802
|
registryOptions.password = password;
|
|
3781
3803
|
registryOptions.registryUrl = registryUrl;
|
|
3782
|
-
logger$
|
|
3804
|
+
logger$4.log(` Using registry credentials from environment`);
|
|
3783
3805
|
}
|
|
3784
3806
|
}
|
|
3785
3807
|
}
|
|
3786
3808
|
await api.saveDockerProvider(config.applicationId, imageRef, registryOptions);
|
|
3787
|
-
logger$
|
|
3809
|
+
logger$4.log(" ✓ Docker provider configured");
|
|
3788
3810
|
const envVars = {};
|
|
3789
3811
|
if (masterKey) envVars.GKM_MASTER_KEY = masterKey;
|
|
3790
3812
|
if (Object.keys(envVars).length > 0) {
|
|
3791
|
-
logger$
|
|
3813
|
+
logger$4.log(" Updating environment variables...");
|
|
3792
3814
|
const envString = Object.entries(envVars).map(([key, value]) => `${key}=${value}`).join("\n");
|
|
3793
3815
|
await api.saveApplicationEnv(config.applicationId, envString);
|
|
3794
|
-
logger$
|
|
3816
|
+
logger$4.log(" ✓ Environment variables updated");
|
|
3795
3817
|
}
|
|
3796
|
-
logger$
|
|
3818
|
+
logger$4.log(" Triggering deployment...");
|
|
3797
3819
|
await api.deployApplication(config.applicationId);
|
|
3798
|
-
logger$
|
|
3799
|
-
logger$
|
|
3800
|
-
logger$
|
|
3801
|
-
logger$
|
|
3802
|
-
logger$
|
|
3803
|
-
logger$
|
|
3804
|
-
if (masterKey) logger$
|
|
3820
|
+
logger$4.log(" ✓ Deployment triggered");
|
|
3821
|
+
logger$4.log("\n✅ Dokploy deployment initiated!");
|
|
3822
|
+
logger$4.log(`\n📋 Deployment details:`);
|
|
3823
|
+
logger$4.log(` Image: ${imageRef}`);
|
|
3824
|
+
logger$4.log(` Stage: ${stage}`);
|
|
3825
|
+
logger$4.log(` Application ID: ${config.applicationId}`);
|
|
3826
|
+
if (masterKey) logger$4.log(`\n🔐 GKM_MASTER_KEY has been set in Dokploy environment`);
|
|
3805
3827
|
const deploymentUrl = `${config.endpoint}/project/${config.projectId}`;
|
|
3806
|
-
logger$
|
|
3828
|
+
logger$4.log(`\n🔗 View deployment: ${deploymentUrl}`);
|
|
3807
3829
|
return {
|
|
3808
3830
|
imageRef,
|
|
3809
3831
|
masterKey,
|
|
@@ -3894,6 +3916,389 @@ function setRedisId(state, redisId) {
|
|
|
3894
3916
|
state.services.redisId = redisId;
|
|
3895
3917
|
}
|
|
3896
3918
|
|
|
3919
|
+
//#endregion
|
|
3920
|
+
//#region src/deploy/dns/hostinger-api.ts
|
|
3921
|
+
/**
|
|
3922
|
+
* Hostinger DNS API client
|
|
3923
|
+
*
|
|
3924
|
+
* API Documentation: https://developers.hostinger.com/
|
|
3925
|
+
* Authentication: Bearer token from hpanel.hostinger.com/profile/api
|
|
3926
|
+
*/
|
|
3927
|
+
const HOSTINGER_API_BASE = "https://developers.hostinger.com";
|
|
3928
|
+
/**
|
|
3929
|
+
* Hostinger API error
|
|
3930
|
+
*/
|
|
3931
|
+
var HostingerApiError = class extends Error {
|
|
3932
|
+
constructor(message, status, statusText, errors) {
|
|
3933
|
+
super(message);
|
|
3934
|
+
this.status = status;
|
|
3935
|
+
this.statusText = statusText;
|
|
3936
|
+
this.errors = errors;
|
|
3937
|
+
this.name = "HostingerApiError";
|
|
3938
|
+
}
|
|
3939
|
+
};
|
|
3940
|
+
/**
|
|
3941
|
+
* Hostinger DNS API client
|
|
3942
|
+
*
|
|
3943
|
+
* @example
|
|
3944
|
+
* ```ts
|
|
3945
|
+
* const api = new HostingerApi(token);
|
|
3946
|
+
*
|
|
3947
|
+
* // Get all records for a domain
|
|
3948
|
+
* const records = await api.getRecords('traflabs.io');
|
|
3949
|
+
*
|
|
3950
|
+
* // Create/update records
|
|
3951
|
+
* await api.upsertRecords('traflabs.io', [
|
|
3952
|
+
* { name: 'api.joemoer', type: 'A', ttl: 300, records: ['1.2.3.4'] }
|
|
3953
|
+
* ]);
|
|
3954
|
+
* ```
|
|
3955
|
+
*/
|
|
3956
|
+
var HostingerApi = class {
|
|
3957
|
+
token;
|
|
3958
|
+
constructor(token) {
|
|
3959
|
+
this.token = token;
|
|
3960
|
+
}
|
|
3961
|
+
/**
|
|
3962
|
+
* Make a request to the Hostinger API
|
|
3963
|
+
*/
|
|
3964
|
+
async request(method, endpoint, body) {
|
|
3965
|
+
const url = `${HOSTINGER_API_BASE}${endpoint}`;
|
|
3966
|
+
const response = await fetch(url, {
|
|
3967
|
+
method,
|
|
3968
|
+
headers: {
|
|
3969
|
+
"Content-Type": "application/json",
|
|
3970
|
+
Authorization: `Bearer ${this.token}`
|
|
3971
|
+
},
|
|
3972
|
+
body: body ? JSON.stringify(body) : void 0
|
|
3973
|
+
});
|
|
3974
|
+
if (!response.ok) {
|
|
3975
|
+
let errorMessage = `Hostinger API error: ${response.status} ${response.statusText}`;
|
|
3976
|
+
let errors;
|
|
3977
|
+
try {
|
|
3978
|
+
const errorBody = await response.json();
|
|
3979
|
+
if (errorBody.message) errorMessage = `Hostinger API error: ${errorBody.message}`;
|
|
3980
|
+
errors = errorBody.errors;
|
|
3981
|
+
} catch {}
|
|
3982
|
+
throw new HostingerApiError(errorMessage, response.status, response.statusText, errors);
|
|
3983
|
+
}
|
|
3984
|
+
const text = await response.text();
|
|
3985
|
+
if (!text || text.trim() === "") return void 0;
|
|
3986
|
+
return JSON.parse(text);
|
|
3987
|
+
}
|
|
3988
|
+
/**
|
|
3989
|
+
* Get all DNS records for a domain
|
|
3990
|
+
*
|
|
3991
|
+
* @param domain - Root domain (e.g., 'traflabs.io')
|
|
3992
|
+
*/
|
|
3993
|
+
async getRecords(domain) {
|
|
3994
|
+
const response = await this.request("GET", `/api/dns/v1/zones/${domain}`);
|
|
3995
|
+
return response.data || [];
|
|
3996
|
+
}
|
|
3997
|
+
/**
|
|
3998
|
+
* Create or update DNS records
|
|
3999
|
+
*
|
|
4000
|
+
* @param domain - Root domain (e.g., 'traflabs.io')
|
|
4001
|
+
* @param records - Records to create/update
|
|
4002
|
+
* @param overwrite - If true, replaces all existing records. If false, merges with existing.
|
|
4003
|
+
*/
|
|
4004
|
+
async upsertRecords(domain, records, overwrite = false) {
|
|
4005
|
+
await this.request("PUT", `/api/dns/v1/zones/${domain}`, {
|
|
4006
|
+
overwrite,
|
|
4007
|
+
zone: records
|
|
4008
|
+
});
|
|
4009
|
+
}
|
|
4010
|
+
/**
|
|
4011
|
+
* Validate DNS records before applying
|
|
4012
|
+
*
|
|
4013
|
+
* @param domain - Root domain (e.g., 'traflabs.io')
|
|
4014
|
+
* @param records - Records to validate
|
|
4015
|
+
* @returns true if valid, throws if invalid
|
|
4016
|
+
*/
|
|
4017
|
+
async validateRecords(domain, records) {
|
|
4018
|
+
await this.request("POST", `/api/dns/v1/zones/${domain}/validate`, {
|
|
4019
|
+
overwrite: false,
|
|
4020
|
+
zone: records
|
|
4021
|
+
});
|
|
4022
|
+
return true;
|
|
4023
|
+
}
|
|
4024
|
+
/**
|
|
4025
|
+
* Delete specific DNS records
|
|
4026
|
+
*
|
|
4027
|
+
* @param domain - Root domain (e.g., 'traflabs.io')
|
|
4028
|
+
* @param filters - Filters to match records for deletion
|
|
4029
|
+
*/
|
|
4030
|
+
async deleteRecords(domain, filters) {
|
|
4031
|
+
await this.request("DELETE", `/api/dns/v1/zones/${domain}`, { filters });
|
|
4032
|
+
}
|
|
4033
|
+
/**
|
|
4034
|
+
* Check if a specific record exists
|
|
4035
|
+
*
|
|
4036
|
+
* @param domain - Root domain (e.g., 'traflabs.io')
|
|
4037
|
+
* @param name - Subdomain name (e.g., 'api.joemoer')
|
|
4038
|
+
* @param type - Record type (e.g., 'A')
|
|
4039
|
+
*/
|
|
4040
|
+
async recordExists(domain, name$1, type$1 = "A") {
|
|
4041
|
+
const records = await this.getRecords(domain);
|
|
4042
|
+
return records.some((r) => r.name === name$1 && r.type === type$1);
|
|
4043
|
+
}
|
|
4044
|
+
/**
|
|
4045
|
+
* Create a single A record if it doesn't exist
|
|
4046
|
+
*
|
|
4047
|
+
* @param domain - Root domain (e.g., 'traflabs.io')
|
|
4048
|
+
* @param subdomain - Subdomain name (e.g., 'api.joemoer')
|
|
4049
|
+
* @param ip - IP address to point to
|
|
4050
|
+
* @param ttl - TTL in seconds (default: 300)
|
|
4051
|
+
* @returns true if created, false if already exists
|
|
4052
|
+
*/
|
|
4053
|
+
async createARecordIfNotExists(domain, subdomain, ip, ttl = 300) {
|
|
4054
|
+
const exists = await this.recordExists(domain, subdomain, "A");
|
|
4055
|
+
if (exists) return false;
|
|
4056
|
+
await this.upsertRecords(domain, [{
|
|
4057
|
+
name: subdomain,
|
|
4058
|
+
type: "A",
|
|
4059
|
+
ttl,
|
|
4060
|
+
records: [{ content: ip }]
|
|
4061
|
+
}]);
|
|
4062
|
+
return true;
|
|
4063
|
+
}
|
|
4064
|
+
};
|
|
4065
|
+
|
|
4066
|
+
//#endregion
|
|
4067
|
+
//#region src/deploy/dns/index.ts
|
|
4068
|
+
const logger$3 = console;
|
|
4069
|
+
/**
|
|
4070
|
+
* Resolve IP address from a hostname
|
|
4071
|
+
*/
|
|
4072
|
+
async function resolveHostnameToIp(hostname) {
|
|
4073
|
+
try {
|
|
4074
|
+
const addresses = await (0, node_dns_promises.lookup)(hostname, { family: 4 });
|
|
4075
|
+
return addresses.address;
|
|
4076
|
+
} catch (error) {
|
|
4077
|
+
throw new Error(`Failed to resolve IP for ${hostname}: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
4078
|
+
}
|
|
4079
|
+
}
|
|
4080
|
+
/**
|
|
4081
|
+
* Extract subdomain from full hostname relative to root domain
|
|
4082
|
+
*
|
|
4083
|
+
* @example
|
|
4084
|
+
* extractSubdomain('api.joemoer.traflabs.io', 'traflabs.io') => 'api.joemoer'
|
|
4085
|
+
* extractSubdomain('joemoer.traflabs.io', 'traflabs.io') => 'joemoer'
|
|
4086
|
+
*/
|
|
4087
|
+
function extractSubdomain(hostname, rootDomain) {
|
|
4088
|
+
if (!hostname.endsWith(rootDomain)) throw new Error(`Hostname ${hostname} is not under root domain ${rootDomain}`);
|
|
4089
|
+
const subdomain = hostname.slice(0, -(rootDomain.length + 1));
|
|
4090
|
+
return subdomain || "@";
|
|
4091
|
+
}
|
|
4092
|
+
/**
|
|
4093
|
+
* Generate required DNS records for a deployment
|
|
4094
|
+
*/
|
|
4095
|
+
function generateRequiredRecords(appHostnames, rootDomain, serverIp) {
|
|
4096
|
+
const records = [];
|
|
4097
|
+
for (const [appName, hostname] of appHostnames) {
|
|
4098
|
+
const subdomain = extractSubdomain(hostname, rootDomain);
|
|
4099
|
+
records.push({
|
|
4100
|
+
hostname,
|
|
4101
|
+
subdomain,
|
|
4102
|
+
type: "A",
|
|
4103
|
+
value: serverIp,
|
|
4104
|
+
appName
|
|
4105
|
+
});
|
|
4106
|
+
}
|
|
4107
|
+
return records;
|
|
4108
|
+
}
|
|
4109
|
+
/**
|
|
4110
|
+
* Print DNS records table
|
|
4111
|
+
*/
|
|
4112
|
+
function printDnsRecordsTable(records, rootDomain) {
|
|
4113
|
+
logger$3.log("\n 📋 DNS Records for " + rootDomain + ":");
|
|
4114
|
+
logger$3.log(" ┌─────────────────────────────────────┬──────┬─────────────────┬────────┐");
|
|
4115
|
+
logger$3.log(" │ Subdomain │ Type │ Value │ Status │");
|
|
4116
|
+
logger$3.log(" ├─────────────────────────────────────┼──────┼─────────────────┼────────┤");
|
|
4117
|
+
for (const record of records) {
|
|
4118
|
+
const subdomain = record.subdomain.padEnd(35);
|
|
4119
|
+
const type$1 = record.type.padEnd(4);
|
|
4120
|
+
const value = record.value.padEnd(15);
|
|
4121
|
+
let status;
|
|
4122
|
+
if (record.error) status = "✗";
|
|
4123
|
+
else if (record.created) status = "✓ new";
|
|
4124
|
+
else if (record.existed) status = "✓";
|
|
4125
|
+
else status = "?";
|
|
4126
|
+
logger$3.log(` │ ${subdomain} │ ${type$1} │ ${value} │ ${status.padEnd(6)} │`);
|
|
4127
|
+
}
|
|
4128
|
+
logger$3.log(" └─────────────────────────────────────┴──────┴─────────────────┴────────┘");
|
|
4129
|
+
}
|
|
4130
|
+
/**
|
|
4131
|
+
* Print DNS records in a simple format for manual setup
|
|
4132
|
+
*/
|
|
4133
|
+
function printDnsRecordsSimple(records, rootDomain) {
|
|
4134
|
+
logger$3.log("\n 📋 Required DNS Records:");
|
|
4135
|
+
logger$3.log(` Add these A records to your DNS provider (${rootDomain}):\n`);
|
|
4136
|
+
for (const record of records) logger$3.log(` ${record.subdomain} → ${record.value} (A record)`);
|
|
4137
|
+
logger$3.log("");
|
|
4138
|
+
}
|
|
4139
|
+
/**
|
|
4140
|
+
* Prompt for input (reuse from deploy/index.ts pattern)
|
|
4141
|
+
*/
|
|
4142
|
+
async function promptForToken(message) {
|
|
4143
|
+
const { stdin, stdout } = await import("node:process");
|
|
4144
|
+
if (!stdin.isTTY) throw new Error("Interactive input required for Hostinger token.");
|
|
4145
|
+
stdout.write(message);
|
|
4146
|
+
return new Promise((resolve$3) => {
|
|
4147
|
+
let value = "";
|
|
4148
|
+
const onData = (char) => {
|
|
4149
|
+
const c = char.toString();
|
|
4150
|
+
if (c === "\n" || c === "\r") {
|
|
4151
|
+
stdin.setRawMode(false);
|
|
4152
|
+
stdin.pause();
|
|
4153
|
+
stdin.removeListener("data", onData);
|
|
4154
|
+
stdout.write("\n");
|
|
4155
|
+
resolve$3(value);
|
|
4156
|
+
} else if (c === "") {
|
|
4157
|
+
stdin.setRawMode(false);
|
|
4158
|
+
stdin.pause();
|
|
4159
|
+
stdout.write("\n");
|
|
4160
|
+
process.exit(1);
|
|
4161
|
+
} else if (c === "" || c === "\b") {
|
|
4162
|
+
if (value.length > 0) value = value.slice(0, -1);
|
|
4163
|
+
} else value += c;
|
|
4164
|
+
};
|
|
4165
|
+
stdin.setRawMode(true);
|
|
4166
|
+
stdin.resume();
|
|
4167
|
+
stdin.on("data", onData);
|
|
4168
|
+
});
|
|
4169
|
+
}
|
|
4170
|
+
/**
|
|
4171
|
+
* Create DNS records using the configured provider
|
|
4172
|
+
*/
|
|
4173
|
+
async function createDnsRecords(records, dnsConfig) {
|
|
4174
|
+
const { provider, domain: rootDomain, ttl = 300 } = dnsConfig;
|
|
4175
|
+
if (provider === "manual") return records.map((r) => ({
|
|
4176
|
+
...r,
|
|
4177
|
+
created: false,
|
|
4178
|
+
existed: false
|
|
4179
|
+
}));
|
|
4180
|
+
if (provider === "hostinger") return createHostingerRecords(records, rootDomain, ttl);
|
|
4181
|
+
if (provider === "cloudflare") {
|
|
4182
|
+
logger$3.log(" ⚠ Cloudflare DNS integration not yet implemented");
|
|
4183
|
+
return records.map((r) => ({
|
|
4184
|
+
...r,
|
|
4185
|
+
error: "Cloudflare not implemented"
|
|
4186
|
+
}));
|
|
4187
|
+
}
|
|
4188
|
+
return records;
|
|
4189
|
+
}
|
|
4190
|
+
/**
|
|
4191
|
+
* Create DNS records at Hostinger
|
|
4192
|
+
*/
|
|
4193
|
+
async function createHostingerRecords(records, rootDomain, ttl) {
|
|
4194
|
+
let token = await getHostingerToken();
|
|
4195
|
+
if (!token) {
|
|
4196
|
+
logger$3.log("\n 📋 Hostinger API token not found.");
|
|
4197
|
+
logger$3.log(" Get your token from: https://hpanel.hostinger.com/profile/api\n");
|
|
4198
|
+
try {
|
|
4199
|
+
token = await promptForToken(" Hostinger API Token: ");
|
|
4200
|
+
await storeHostingerToken(token);
|
|
4201
|
+
logger$3.log(" ✓ Token saved");
|
|
4202
|
+
} catch {
|
|
4203
|
+
logger$3.log(" ⚠ Could not get token, skipping DNS creation");
|
|
4204
|
+
return records.map((r) => ({
|
|
4205
|
+
...r,
|
|
4206
|
+
error: "No API token"
|
|
4207
|
+
}));
|
|
4208
|
+
}
|
|
4209
|
+
}
|
|
4210
|
+
const api = new HostingerApi(token);
|
|
4211
|
+
const results = [];
|
|
4212
|
+
let existingRecords = [];
|
|
4213
|
+
try {
|
|
4214
|
+
existingRecords = await api.getRecords(rootDomain);
|
|
4215
|
+
} catch (error) {
|
|
4216
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4217
|
+
logger$3.log(` ⚠ Failed to fetch existing DNS records: ${message}`);
|
|
4218
|
+
return records.map((r) => ({
|
|
4219
|
+
...r,
|
|
4220
|
+
error: message
|
|
4221
|
+
}));
|
|
4222
|
+
}
|
|
4223
|
+
for (const record of records) {
|
|
4224
|
+
const existing = existingRecords.find((r) => r.name === record.subdomain && r.type === "A");
|
|
4225
|
+
if (existing) {
|
|
4226
|
+
results.push({
|
|
4227
|
+
...record,
|
|
4228
|
+
existed: true,
|
|
4229
|
+
created: false
|
|
4230
|
+
});
|
|
4231
|
+
continue;
|
|
4232
|
+
}
|
|
4233
|
+
try {
|
|
4234
|
+
await api.upsertRecords(rootDomain, [{
|
|
4235
|
+
name: record.subdomain,
|
|
4236
|
+
type: "A",
|
|
4237
|
+
ttl,
|
|
4238
|
+
records: [{ content: record.value }]
|
|
4239
|
+
}]);
|
|
4240
|
+
results.push({
|
|
4241
|
+
...record,
|
|
4242
|
+
created: true,
|
|
4243
|
+
existed: false
|
|
4244
|
+
});
|
|
4245
|
+
} catch (error) {
|
|
4246
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4247
|
+
results.push({
|
|
4248
|
+
...record,
|
|
4249
|
+
error: message
|
|
4250
|
+
});
|
|
4251
|
+
}
|
|
4252
|
+
}
|
|
4253
|
+
return results;
|
|
4254
|
+
}
|
|
4255
|
+
/**
|
|
4256
|
+
* Main DNS orchestration function for deployments
|
|
4257
|
+
*/
|
|
4258
|
+
async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint) {
|
|
4259
|
+
if (!dnsConfig) return null;
|
|
4260
|
+
const { domain: rootDomain, autoCreate = true } = dnsConfig;
|
|
4261
|
+
logger$3.log("\n🌐 Setting up DNS records...");
|
|
4262
|
+
let serverIp;
|
|
4263
|
+
try {
|
|
4264
|
+
const endpointUrl = new URL(dokployEndpoint);
|
|
4265
|
+
serverIp = await resolveHostnameToIp(endpointUrl.hostname);
|
|
4266
|
+
logger$3.log(` Server IP: ${serverIp} (from ${endpointUrl.hostname})`);
|
|
4267
|
+
} catch (error) {
|
|
4268
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4269
|
+
logger$3.log(` ⚠ Failed to resolve server IP: ${message}`);
|
|
4270
|
+
return null;
|
|
4271
|
+
}
|
|
4272
|
+
const requiredRecords = generateRequiredRecords(appHostnames, rootDomain, serverIp);
|
|
4273
|
+
if (requiredRecords.length === 0) {
|
|
4274
|
+
logger$3.log(" No DNS records needed");
|
|
4275
|
+
return {
|
|
4276
|
+
records: [],
|
|
4277
|
+
success: true,
|
|
4278
|
+
serverIp
|
|
4279
|
+
};
|
|
4280
|
+
}
|
|
4281
|
+
let finalRecords;
|
|
4282
|
+
if (autoCreate && dnsConfig.provider !== "manual") {
|
|
4283
|
+
logger$3.log(` Creating DNS records at ${dnsConfig.provider}...`);
|
|
4284
|
+
finalRecords = await createDnsRecords(requiredRecords, dnsConfig);
|
|
4285
|
+
const created = finalRecords.filter((r) => r.created).length;
|
|
4286
|
+
const existed = finalRecords.filter((r) => r.existed).length;
|
|
4287
|
+
const failed = finalRecords.filter((r) => r.error).length;
|
|
4288
|
+
if (created > 0) logger$3.log(` ✓ Created ${created} DNS record(s)`);
|
|
4289
|
+
if (existed > 0) logger$3.log(` ✓ ${existed} record(s) already exist`);
|
|
4290
|
+
if (failed > 0) logger$3.log(` ⚠ ${failed} record(s) failed`);
|
|
4291
|
+
} else finalRecords = requiredRecords;
|
|
4292
|
+
printDnsRecordsTable(finalRecords, rootDomain);
|
|
4293
|
+
const hasFailures = finalRecords.some((r) => r.error);
|
|
4294
|
+
if (dnsConfig.provider === "manual" || hasFailures) printDnsRecordsSimple(finalRecords.filter((r) => !r.created && !r.existed), rootDomain);
|
|
4295
|
+
return {
|
|
4296
|
+
records: finalRecords,
|
|
4297
|
+
success: !hasFailures,
|
|
4298
|
+
serverIp
|
|
4299
|
+
};
|
|
4300
|
+
}
|
|
4301
|
+
|
|
3897
4302
|
//#endregion
|
|
3898
4303
|
//#region src/deploy/domain.ts
|
|
3899
4304
|
/**
|
|
@@ -4837,6 +5242,8 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
4837
5242
|
const publicUrls = {};
|
|
4838
5243
|
const results = [];
|
|
4839
5244
|
const dokployConfig = workspace.deploy.dokploy;
|
|
5245
|
+
const appHostnames = /* @__PURE__ */ new Map();
|
|
5246
|
+
const appDomainIds = /* @__PURE__ */ new Map();
|
|
4840
5247
|
if (backendApps.length > 0) {
|
|
4841
5248
|
logger$1.log("\n📦 PHASE 1: Deploying backend applications...");
|
|
4842
5249
|
for (const appName of backendApps) {
|
|
@@ -4886,22 +5293,29 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
4886
5293
|
await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
|
|
4887
5294
|
logger$1.log(` Deploying to Dokploy...`);
|
|
4888
5295
|
await api.deployApplication(application.applicationId);
|
|
5296
|
+
const backendHost = resolveHost(appName, app, stage, dokployConfig, false);
|
|
4889
5297
|
try {
|
|
4890
|
-
const
|
|
4891
|
-
|
|
4892
|
-
host,
|
|
5298
|
+
const domain = await api.createDomain({
|
|
5299
|
+
host: backendHost,
|
|
4893
5300
|
port: app.port,
|
|
4894
5301
|
https: true,
|
|
4895
5302
|
certificateType: "letsencrypt",
|
|
4896
5303
|
applicationId: application.applicationId
|
|
4897
5304
|
});
|
|
4898
|
-
|
|
5305
|
+
appHostnames.set(appName, backendHost);
|
|
5306
|
+
appDomainIds.set(appName, domain.domainId);
|
|
5307
|
+
const publicUrl = `https://${backendHost}`;
|
|
4899
5308
|
publicUrls[appName] = publicUrl;
|
|
4900
5309
|
logger$1.log(` ✓ Domain: ${publicUrl}`);
|
|
4901
5310
|
} catch (domainError) {
|
|
4902
|
-
|
|
4903
|
-
|
|
4904
|
-
|
|
5311
|
+
appHostnames.set(appName, backendHost);
|
|
5312
|
+
try {
|
|
5313
|
+
const existingDomains = await api.getDomainsByApplicationId(application.applicationId);
|
|
5314
|
+
const matchingDomain = existingDomains.find((d) => d.host === backendHost);
|
|
5315
|
+
if (matchingDomain) appDomainIds.set(appName, matchingDomain.domainId);
|
|
5316
|
+
} catch {}
|
|
5317
|
+
publicUrls[appName] = `https://${backendHost}`;
|
|
5318
|
+
logger$1.log(` ℹ Domain already configured: https://${backendHost}`);
|
|
4905
5319
|
}
|
|
4906
5320
|
results.push({
|
|
4907
5321
|
appName,
|
|
@@ -4969,22 +5383,29 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
4969
5383
|
logger$1.log(` Deploying to Dokploy...`);
|
|
4970
5384
|
await api.deployApplication(application.applicationId);
|
|
4971
5385
|
const isMainFrontend = isMainFrontendApp(appName, app, workspace.apps);
|
|
5386
|
+
const frontendHost = resolveHost(appName, app, stage, dokployConfig, isMainFrontend);
|
|
4972
5387
|
try {
|
|
4973
|
-
const
|
|
4974
|
-
|
|
4975
|
-
host,
|
|
5388
|
+
const domain = await api.createDomain({
|
|
5389
|
+
host: frontendHost,
|
|
4976
5390
|
port: app.port,
|
|
4977
5391
|
https: true,
|
|
4978
5392
|
certificateType: "letsencrypt",
|
|
4979
5393
|
applicationId: application.applicationId
|
|
4980
5394
|
});
|
|
4981
|
-
|
|
5395
|
+
appHostnames.set(appName, frontendHost);
|
|
5396
|
+
appDomainIds.set(appName, domain.domainId);
|
|
5397
|
+
const publicUrl = `https://${frontendHost}`;
|
|
4982
5398
|
publicUrls[appName] = publicUrl;
|
|
4983
5399
|
logger$1.log(` ✓ Domain: ${publicUrl}`);
|
|
4984
5400
|
} catch (domainError) {
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
5401
|
+
appHostnames.set(appName, frontendHost);
|
|
5402
|
+
try {
|
|
5403
|
+
const existingDomains = await api.getDomainsByApplicationId(application.applicationId);
|
|
5404
|
+
const matchingDomain = existingDomains.find((d) => d.host === frontendHost);
|
|
5405
|
+
if (matchingDomain) appDomainIds.set(appName, matchingDomain.domainId);
|
|
5406
|
+
} catch {}
|
|
5407
|
+
publicUrls[appName] = `https://${frontendHost}`;
|
|
5408
|
+
logger$1.log(` ℹ Domain already configured: https://${frontendHost}`);
|
|
4988
5409
|
}
|
|
4989
5410
|
results.push({
|
|
4990
5411
|
appName,
|
|
@@ -5009,6 +5430,21 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
5009
5430
|
logger$1.log("\n📋 Saving deploy state...");
|
|
5010
5431
|
await writeStageState(workspace.root, stage, state);
|
|
5011
5432
|
logger$1.log(` ✓ State saved to .gkm/deploy-${stage}.json`);
|
|
5433
|
+
const dnsConfig = workspace.deploy.dns;
|
|
5434
|
+
if (dnsConfig && appHostnames.size > 0) {
|
|
5435
|
+
const dnsResult = await orchestrateDns(appHostnames, dnsConfig, creds.endpoint);
|
|
5436
|
+
if (dnsResult?.success && appHostnames.size > 0) {
|
|
5437
|
+
logger$1.log("\n🔒 Validating domains for SSL certificates...");
|
|
5438
|
+
for (const [appName, hostname] of appHostnames) try {
|
|
5439
|
+
const result = await api.validateDomain(hostname);
|
|
5440
|
+
if (result.isValid) logger$1.log(` ✓ ${appName}: ${hostname} → ${result.resolvedIp}`);
|
|
5441
|
+
else logger$1.log(` ⚠ ${appName}: ${hostname} not valid`);
|
|
5442
|
+
} catch (validationError) {
|
|
5443
|
+
const message = validationError instanceof Error ? validationError.message : "Unknown error";
|
|
5444
|
+
logger$1.log(` ⚠ ${appName}: validation failed - ${message}`);
|
|
5445
|
+
}
|
|
5446
|
+
}
|
|
5447
|
+
}
|
|
5012
5448
|
const successCount = results.filter((r) => r.success).length;
|
|
5013
5449
|
const failedCount = results.filter((r) => !r.success).length;
|
|
5014
5450
|
logger$1.log(`\n${"─".repeat(50)}`);
|