@geekmidas/cli 1.10.41 → 1.12.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/CHANGELOG.md +149 -0
- package/README.md +14 -0
- package/dist/{CachedStateProvider-37pG1HBx.cjs → CachedStateProvider-BdX7klRc.cjs} +1 -1
- package/dist/{CachedStateProvider-C8A-cCm5.mjs → CachedStateProvider-CzyFct42.mjs} +1 -1
- package/dist/{CachedStateProvider-C8A-cCm5.mjs.map → CachedStateProvider-CzyFct42.mjs.map} +1 -1
- package/dist/CachedStateProvider-GcGziXh1.mjs +3 -0
- package/dist/{CachedStateProvider-CxsXOKkg.cjs → CachedStateProvider-klLWZXWF.cjs} +1 -1
- package/dist/{CachedStateProvider-CxsXOKkg.cjs.map → CachedStateProvider-klLWZXWF.cjs.map} +1 -1
- package/dist/{HostingerProvider-ANWchdiK.mjs → HostingerProvider-B2ZYz7Yc.mjs} +1 -1
- package/dist/{HostingerProvider-ANWchdiK.mjs.map → HostingerProvider-B2ZYz7Yc.mjs.map} +1 -1
- package/dist/{HostingerProvider-5KYmwoK2.cjs → HostingerProvider-CrHfsgz9.cjs} +1 -1
- package/dist/{HostingerProvider-5KYmwoK2.cjs.map → HostingerProvider-CrHfsgz9.cjs.map} +1 -1
- package/dist/{LocalStateProvider-Dp0KkRcw.mjs → LocalStateProvider-CP49rjzt.mjs} +1 -1
- package/dist/{LocalStateProvider-Dp0KkRcw.mjs.map → LocalStateProvider-CP49rjzt.mjs.map} +1 -1
- package/dist/{LocalStateProvider-CLifRC0Y.cjs → LocalStateProvider-CalzNfNA.cjs} +1 -1
- package/dist/{LocalStateProvider-CLifRC0Y.cjs.map → LocalStateProvider-CalzNfNA.cjs.map} +1 -1
- package/dist/{Route53Provider-owQQ4pn6.cjs → Route53Provider-DwgXiLxt.cjs} +1 -1
- package/dist/{Route53Provider-owQQ4pn6.cjs.map → Route53Provider-DwgXiLxt.cjs.map} +1 -1
- package/dist/{Route53Provider-QoPgcXxn.mjs → Route53Provider-tlgBzrRQ.mjs} +1 -1
- package/dist/{Route53Provider-QoPgcXxn.mjs.map → Route53Provider-tlgBzrRQ.mjs.map} +1 -1
- package/dist/{SSMStateProvider-CksOTB8M.mjs → SSMStateProvider-BlrTiV5m.mjs} +1 -1
- package/dist/{SSMStateProvider-CksOTB8M.mjs.map → SSMStateProvider-BlrTiV5m.mjs.map} +1 -1
- package/dist/{SSMStateProvider-CT8tjl9o.cjs → SSMStateProvider-Bt6r8CTL.cjs} +1 -1
- package/dist/{SSMStateProvider-CT8tjl9o.cjs.map → SSMStateProvider-Bt6r8CTL.cjs.map} +1 -1
- package/dist/{backup-provisioner-C4noe75O.cjs → backup-provisioner-B3HBN-2k.cjs} +1 -1
- package/dist/{backup-provisioner-C4noe75O.cjs.map → backup-provisioner-B3HBN-2k.cjs.map} +1 -1
- package/dist/{backup-provisioner-BEXoHTuC.mjs → backup-provisioner-Xunq-p_8.mjs} +1 -1
- package/dist/{backup-provisioner-BEXoHTuC.mjs.map → backup-provisioner-Xunq-p_8.mjs.map} +1 -1
- package/dist/{bundler-c6QvGUBM.mjs → bundler-DkA4qzUD.mjs} +2 -2
- package/dist/{bundler-c6QvGUBM.mjs.map → bundler-DkA4qzUD.mjs.map} +1 -1
- package/dist/{bundler-BG6P249p.cjs → bundler-Dufq3uvI.cjs} +2 -2
- package/dist/{bundler-BG6P249p.cjs.map → bundler-Dufq3uvI.cjs.map} +1 -1
- package/dist/config.d.cts +2 -2
- package/dist/config.d.mts +2 -2
- package/dist/dokploy-api-BPYkG-ZZ.cjs +3 -0
- package/dist/dokploy-api-BiSzWM4v.mjs +3 -0
- package/dist/{dokploy-api-Dvyq-LsM.cjs → dokploy-api-CqOIKHme.cjs} +1 -1
- package/dist/{dokploy-api-Dvyq-LsM.cjs.map → dokploy-api-CqOIKHme.cjs.map} +1 -1
- package/dist/{dokploy-api-BIT0683o.mjs → dokploy-api-sfZFlWuE.mjs} +1 -1
- package/dist/{dokploy-api-BIT0683o.mjs.map → dokploy-api-sfZFlWuE.mjs.map} +1 -1
- package/dist/{encryption-CipsS0KW.cjs → encryption-BVS7Vmzs.cjs} +1 -1
- package/dist/encryption-Bd_m4q3g.mjs +3 -0
- package/dist/{encryption-DSkIlEZ6.mjs → encryption-CO5Q4Bhf.mjs} +1 -1
- package/dist/{encryption-DSkIlEZ6.mjs.map → encryption-CO5Q4Bhf.mjs.map} +1 -1
- package/dist/{encryption-DgKS-Dv9.cjs → encryption-MfXAiFTm.cjs} +1 -1
- package/dist/{encryption-DgKS-Dv9.cjs.map → encryption-MfXAiFTm.cjs.map} +1 -1
- package/dist/{index-BBvGMG_A.d.mts → index-Bw548Bta.d.mts} +2 -2
- package/dist/{index-Dz2a7xQU.d.cts.map → index-Bw548Bta.d.mts.map} +1 -1
- package/dist/{index-Dz2a7xQU.d.cts → index-DTpkeFb8.d.cts} +2 -2
- package/dist/{index-BBvGMG_A.d.mts.map → index-DTpkeFb8.d.cts.map} +1 -1
- package/dist/index.cjs +863 -1330
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +990 -1457
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-DHp49UJV.cjs → openapi-BzNOC7_i.cjs} +14 -2
- package/dist/openapi-BzNOC7_i.cjs.map +1 -0
- package/dist/{openapi-DF9GyhyZ.mjs → openapi-Cfwi0fqF.mjs} +14 -2
- package/dist/openapi-Cfwi0fqF.mjs.map +1 -0
- package/dist/{openapi-react-query-C4UdILaI.mjs → openapi-react-query-C4iIvpPf.mjs} +1 -1
- package/dist/{openapi-react-query-C4UdILaI.mjs.map → openapi-react-query-C4iIvpPf.mjs.map} +1 -1
- package/dist/{openapi-react-query-DYbBq-WJ.cjs → openapi-react-query-CYWik72n.cjs} +1 -1
- package/dist/{openapi-react-query-DYbBq-WJ.cjs.map → openapi-react-query-CYWik72n.cjs.map} +1 -1
- package/dist/openapi-react-query.cjs +1 -1
- package/dist/openapi-react-query.mjs +1 -1
- package/dist/openapi.cjs +1 -1
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/openapi.mjs +1 -1
- package/dist/{reconcile-CYGmqXvq.mjs → reconcile-B2FoJE0a.mjs} +1 -1
- package/dist/{reconcile-CYGmqXvq.mjs.map → reconcile-B2FoJE0a.mjs.map} +1 -1
- package/dist/{reconcile-D93YG7G5.cjs → reconcile-BzqKJ2Rt.cjs} +1 -1
- package/dist/{reconcile-D93YG7G5.cjs.map → reconcile-BzqKJ2Rt.cjs.map} +1 -1
- package/dist/setup-DDXUXzzl.mjs +765 -0
- package/dist/setup-DDXUXzzl.mjs.map +1 -0
- package/dist/setup-DLwhfyt-.cjs +8 -0
- package/dist/setup-Dh-B7YTX.mjs +8 -0
- package/dist/setup-WZ-xqAQO.cjs +849 -0
- package/dist/setup-WZ-xqAQO.cjs.map +1 -0
- package/dist/{sync-COnAugP-.mjs → sync-B5UHXwRM.mjs} +1 -1
- package/dist/sync-BLA94rgC.cjs +4 -0
- package/dist/{sync-D_NowTkZ.mjs → sync-CSooqRQ8.mjs} +1 -1
- package/dist/{sync-D_NowTkZ.mjs.map → sync-CSooqRQ8.mjs.map} +1 -1
- package/dist/{sync-DGXXSk2v.cjs → sync-o_CtEbvR.cjs} +1 -1
- package/dist/{sync-DGXXSk2v.cjs.map → sync-o_CtEbvR.cjs.map} +1 -1
- package/dist/{types-DdHfUbxk.d.cts → types-B3GQUg3k.d.mts} +3 -1
- package/dist/{types-DdHfUbxk.d.cts.map → types-B3GQUg3k.d.mts.map} +1 -1
- package/dist/{types-OszPdw9m.d.mts → types-BWQDiooe.d.cts} +3 -1
- package/dist/{types-OszPdw9m.d.mts.map → types-BWQDiooe.d.cts.map} +1 -1
- package/dist/workspace/index.d.cts +2 -2
- package/dist/workspace/index.d.mts +2 -2
- package/package.json +6 -5
- package/src/build/__tests__/index-new.spec.ts +3 -1
- package/src/build/__tests__/manifests.spec.ts +2 -2
- package/src/build/index.ts +61 -21
- package/src/build/manifests.ts +62 -5
- package/src/dev/index.ts +25 -11
- package/src/generators/EndpointGenerator.ts +14 -2
- package/src/generators/QueueGenerator.ts +245 -0
- package/src/generators/SubscriberGenerator.ts +5 -0
- package/src/generators/TopicGenerator.ts +45 -0
- package/src/generators/__tests__/QueueGenerator.spec.ts +141 -0
- package/src/generators/index.ts +2 -0
- package/src/index.ts +4 -0
- package/src/init/versions.ts +6 -6
- package/src/setup/__tests__/auto-setup.spec.ts +147 -0
- package/src/setup/index.ts +46 -6
- package/src/test/index.ts +17 -0
- package/src/types.ts +14 -51
- package/dist/CachedStateProvider-CC0DyO09.mjs +0 -3
- package/dist/dokploy-api-BOx71j6F.cjs +0 -3
- package/dist/dokploy-api-BZGk-lkD.mjs +0 -3
- package/dist/encryption-Db2zvGqM.mjs +0 -3
- package/dist/openapi-DF9GyhyZ.mjs.map +0 -1
- package/dist/openapi-DHp49UJV.cjs.map +0 -1
- package/dist/sync-D1Pa30oV.cjs +0 -4
package/dist/index.mjs
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env -S npx tsx
|
|
2
2
|
import { __require } from "./chunk-Duj1WY3L.mjs";
|
|
3
3
|
import { getAppBuildOrder, getDependencyEnvVars, getDeployTargetError, getPublicEnvPrefix, isDeployTargetSupported, stripPublicPrefix } from "./workspace-D-37Ylqo.mjs";
|
|
4
|
-
import { getAppNameFromCwd, loadAppConfig, loadConfig,
|
|
4
|
+
import { getAppNameFromCwd, loadAppConfig, loadConfig, loadWorkspaceConfig, parseModuleConfig } from "./config-Bbu1JKvf.mjs";
|
|
5
5
|
import { getCredentialsPath, getDokployCredentials, getDokployRegistryId, getDokployToken, removeDokployCredentials, storeDokployCredentials, storeDokployRegistryId } from "./credentials-s1kLcIzK.mjs";
|
|
6
|
-
import { createStageSecrets,
|
|
6
|
+
import { createStageSecrets, generateDbPassword, generateDbUrl, generateFullstackCustomSecrets, rotateServicePassword } from "./fullstack-secrets-l8s3II_H.mjs";
|
|
7
7
|
import { getKeyPath, maskPassword, readStageSecrets, secretsExist, setCustomSecret, toEmbeddableSecrets, writeStageSecrets } from "./storage-dbb9RyBl.mjs";
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
8
|
+
import { createCredentialsPreload, createEntryWrapper, findAvailablePort, isPortAvailable, loadEnvFiles, loadSecretsForApp, prepareEntryCredentials, resolveServicePorts, rewriteUrlsWithPorts, setupCommand, startWorkspaceServices } from "./setup-DDXUXzzl.mjs";
|
|
9
|
+
import { ConstructGenerator, EndpointGenerator, OPENAPI_OUTPUT_PATH, generateOpenApi, isPartitionedRoutes, normalizeRoutes, openapiCommand, resolveOpenApiConfig } from "./openapi-Cfwi0fqF.mjs";
|
|
10
|
+
import { DokployApi } from "./dokploy-api-sfZFlWuE.mjs";
|
|
11
|
+
import { encryptSecrets } from "./encryption-CO5Q4Bhf.mjs";
|
|
12
|
+
import { CachedStateProvider } from "./CachedStateProvider-CzyFct42.mjs";
|
|
13
|
+
import { generateReactQueryCommand } from "./openapi-react-query-C4iIvpPf.mjs";
|
|
14
|
+
import "./sync-CSooqRQ8.mjs";
|
|
14
15
|
import { createRequire } from "node:module";
|
|
15
16
|
import { copyFileSync, existsSync, readFileSync, unlinkSync } from "node:fs";
|
|
16
17
|
import { basename, dirname, join, parse, relative, resolve } from "node:path";
|
|
@@ -21,13 +22,13 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
|
21
22
|
import { execSync, spawn } from "node:child_process";
|
|
22
23
|
import chokidar from "chokidar";
|
|
23
24
|
import fg from "fast-glob";
|
|
24
|
-
import { createServer } from "node:net";
|
|
25
|
-
import { config } from "dotenv";
|
|
26
25
|
import { parse as parse$1 } from "yaml";
|
|
27
26
|
import { randomBytes } from "node:crypto";
|
|
28
27
|
import { Cron } from "@geekmidas/constructs/crons";
|
|
29
28
|
import { Function } from "@geekmidas/constructs/functions";
|
|
29
|
+
import { Queue } from "@geekmidas/constructs/queue";
|
|
30
30
|
import { Subscriber } from "@geekmidas/constructs/subscribers";
|
|
31
|
+
import { Topic } from "@geekmidas/constructs/topic";
|
|
31
32
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
32
33
|
import { Client } from "pg";
|
|
33
34
|
import { lookup } from "node:dns/promises";
|
|
@@ -35,7 +36,7 @@ import prompts from "prompts";
|
|
|
35
36
|
|
|
36
37
|
//#region package.json
|
|
37
38
|
var name = "@geekmidas/cli";
|
|
38
|
-
var version = "1.
|
|
39
|
+
var version = "1.11.0";
|
|
39
40
|
var description = "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs";
|
|
40
41
|
var private$1 = false;
|
|
41
42
|
var type = "module";
|
|
@@ -115,6 +116,7 @@ var dependencies = {
|
|
|
115
116
|
"@geekmidas/envkit": "workspace:~",
|
|
116
117
|
"@geekmidas/errors": "workspace:~",
|
|
117
118
|
"@geekmidas/logger": "workspace:~",
|
|
119
|
+
"@geekmidas/manifest": "workspace:*",
|
|
118
120
|
"@geekmidas/schema": "workspace:~",
|
|
119
121
|
"chokidar": "~4.0.3",
|
|
120
122
|
"commander": "^12.1.0",
|
|
@@ -158,12 +160,12 @@ var package_default = {
|
|
|
158
160
|
|
|
159
161
|
//#endregion
|
|
160
162
|
//#region src/auth/index.ts
|
|
161
|
-
const logger$
|
|
163
|
+
const logger$12 = console;
|
|
162
164
|
/**
|
|
163
165
|
* Validate Dokploy token by making a test API call
|
|
164
166
|
*/
|
|
165
167
|
async function validateDokployToken(endpoint, token) {
|
|
166
|
-
const { DokployApi: DokployApi$1 } = await import("./dokploy-api-
|
|
168
|
+
const { DokployApi: DokployApi$1 } = await import("./dokploy-api-BiSzWM4v.mjs");
|
|
167
169
|
const api = new DokployApi$1({
|
|
168
170
|
baseUrl: endpoint,
|
|
169
171
|
token
|
|
@@ -226,36 +228,36 @@ async function prompt$1(message, hidden = false) {
|
|
|
226
228
|
async function loginCommand(options) {
|
|
227
229
|
const { service, token: providedToken, endpoint: providedEndpoint } = options;
|
|
228
230
|
if (service === "dokploy") {
|
|
229
|
-
logger$
|
|
231
|
+
logger$12.log("\n🔐 Logging in to Dokploy...\n");
|
|
230
232
|
let endpoint = providedEndpoint;
|
|
231
233
|
if (!endpoint) endpoint = await prompt$1("Dokploy URL (e.g., https://dokploy.example.com): ");
|
|
232
234
|
endpoint = endpoint.replace(/\/$/, "");
|
|
233
235
|
try {
|
|
234
236
|
new URL(endpoint);
|
|
235
237
|
} catch {
|
|
236
|
-
logger$
|
|
238
|
+
logger$12.error("Invalid URL format");
|
|
237
239
|
process.exit(1);
|
|
238
240
|
}
|
|
239
241
|
let token = providedToken;
|
|
240
242
|
if (!token) {
|
|
241
|
-
logger$
|
|
243
|
+
logger$12.log(`\nGenerate a token at: ${endpoint}/settings/profile\n`);
|
|
242
244
|
token = await prompt$1("API Token: ", true);
|
|
243
245
|
}
|
|
244
246
|
if (!token) {
|
|
245
|
-
logger$
|
|
247
|
+
logger$12.error("Token is required");
|
|
246
248
|
process.exit(1);
|
|
247
249
|
}
|
|
248
|
-
logger$
|
|
250
|
+
logger$12.log("\nValidating credentials...");
|
|
249
251
|
const isValid = await validateDokployToken(endpoint, token);
|
|
250
252
|
if (!isValid) {
|
|
251
|
-
logger$
|
|
253
|
+
logger$12.error("\n✗ Invalid credentials. Please check your token and try again.");
|
|
252
254
|
process.exit(1);
|
|
253
255
|
}
|
|
254
256
|
await storeDokployCredentials(token, endpoint);
|
|
255
|
-
logger$
|
|
256
|
-
logger$
|
|
257
|
-
logger$
|
|
258
|
-
logger$
|
|
257
|
+
logger$12.log("\n✓ Successfully logged in to Dokploy!");
|
|
258
|
+
logger$12.log(` Endpoint: ${endpoint}`);
|
|
259
|
+
logger$12.log(` Credentials stored in: ${getCredentialsPath()}`);
|
|
260
|
+
logger$12.log("\nYou can now use deploy commands without setting DOKPLOY_API_TOKEN.");
|
|
259
261
|
}
|
|
260
262
|
}
|
|
261
263
|
/**
|
|
@@ -265,28 +267,28 @@ async function logoutCommand(options) {
|
|
|
265
267
|
const { service = "dokploy" } = options;
|
|
266
268
|
if (service === "all") {
|
|
267
269
|
const dokployRemoved = await removeDokployCredentials();
|
|
268
|
-
if (dokployRemoved) logger$
|
|
269
|
-
else logger$
|
|
270
|
+
if (dokployRemoved) logger$12.log("\n✓ Logged out from all services");
|
|
271
|
+
else logger$12.log("\nNo stored credentials found");
|
|
270
272
|
return;
|
|
271
273
|
}
|
|
272
274
|
if (service === "dokploy") {
|
|
273
275
|
const removed = await removeDokployCredentials();
|
|
274
|
-
if (removed) logger$
|
|
275
|
-
else logger$
|
|
276
|
+
if (removed) logger$12.log("\n✓ Logged out from Dokploy");
|
|
277
|
+
else logger$12.log("\nNo Dokploy credentials found");
|
|
276
278
|
}
|
|
277
279
|
}
|
|
278
280
|
/**
|
|
279
281
|
* Show current login status
|
|
280
282
|
*/
|
|
281
283
|
async function whoamiCommand() {
|
|
282
|
-
logger$
|
|
284
|
+
logger$12.log("\n📋 Current credentials:\n");
|
|
283
285
|
const dokploy = await getDokployCredentials();
|
|
284
286
|
if (dokploy) {
|
|
285
|
-
logger$
|
|
286
|
-
logger$
|
|
287
|
-
logger$
|
|
288
|
-
} else logger$
|
|
289
|
-
logger$
|
|
287
|
+
logger$12.log(" Dokploy:");
|
|
288
|
+
logger$12.log(` Endpoint: ${dokploy.endpoint}`);
|
|
289
|
+
logger$12.log(` Token: ${maskToken(dokploy.token)}`);
|
|
290
|
+
} else logger$12.log(" Dokploy: Not logged in");
|
|
291
|
+
logger$12.log(`\n Credentials file: ${getCredentialsPath()}`);
|
|
290
292
|
}
|
|
291
293
|
/**
|
|
292
294
|
* Mask a token for display
|
|
@@ -302,7 +304,7 @@ function maskToken(token) {
|
|
|
302
304
|
* Resolves provider configuration from the new simplified system
|
|
303
305
|
* to the internal legacy format for backward compatibility
|
|
304
306
|
*/
|
|
305
|
-
function resolveProviders(config
|
|
307
|
+
function resolveProviders(config, options) {
|
|
306
308
|
const providers = [];
|
|
307
309
|
let enableOpenApi = options.enableOpenApi || false;
|
|
308
310
|
if (options.providers) return {
|
|
@@ -310,11 +312,11 @@ function resolveProviders(config$1, options) {
|
|
|
310
312
|
enableOpenApi
|
|
311
313
|
};
|
|
312
314
|
if (options.provider) {
|
|
313
|
-
const resolvedProviders = resolveMainProvider(options.provider, config
|
|
315
|
+
const resolvedProviders = resolveMainProvider(options.provider, config.providers);
|
|
314
316
|
providers.push(...resolvedProviders.providers);
|
|
315
317
|
enableOpenApi = resolvedProviders.enableOpenApi || enableOpenApi;
|
|
316
|
-
} else if (config
|
|
317
|
-
const resolvedProviders = resolveAllConfiguredProviders(config
|
|
318
|
+
} else if (config.providers) {
|
|
319
|
+
const resolvedProviders = resolveAllConfiguredProviders(config.providers);
|
|
318
320
|
providers.push(...resolvedProviders.providers);
|
|
319
321
|
enableOpenApi = resolvedProviders.enableOpenApi || enableOpenApi;
|
|
320
322
|
} else providers.push("aws-apigatewayv2", "aws-lambda");
|
|
@@ -361,475 +363,10 @@ function resolveAllConfiguredProviders(providersConfig) {
|
|
|
361
363
|
enableOpenApi
|
|
362
364
|
};
|
|
363
365
|
}
|
|
364
|
-
function isEnabled(config
|
|
365
|
-
if (config
|
|
366
|
-
if (typeof config
|
|
367
|
-
return config
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
//#endregion
|
|
371
|
-
//#region src/credentials/index.ts
|
|
372
|
-
const logger$13 = console;
|
|
373
|
-
/**
|
|
374
|
-
* Load environment files
|
|
375
|
-
* @internal Exported for testing
|
|
376
|
-
*/
|
|
377
|
-
function loadEnvFiles(envConfig, cwd = process.cwd()) {
|
|
378
|
-
const loaded = [];
|
|
379
|
-
const missing = [];
|
|
380
|
-
const envFiles = envConfig ? Array.isArray(envConfig) ? envConfig : [envConfig] : [".env"];
|
|
381
|
-
for (const envFile of envFiles) {
|
|
382
|
-
const envPath = resolve(cwd, envFile);
|
|
383
|
-
if (existsSync(envPath)) {
|
|
384
|
-
config({
|
|
385
|
-
path: envPath,
|
|
386
|
-
override: true,
|
|
387
|
-
quiet: true
|
|
388
|
-
});
|
|
389
|
-
loaded.push(envFile);
|
|
390
|
-
} else if (envConfig) missing.push(envFile);
|
|
391
|
-
}
|
|
392
|
-
return {
|
|
393
|
-
loaded,
|
|
394
|
-
missing
|
|
395
|
-
};
|
|
396
|
-
}
|
|
397
|
-
/**
|
|
398
|
-
* Check if a port is available
|
|
399
|
-
* @internal Exported for testing
|
|
400
|
-
*/
|
|
401
|
-
async function isPortAvailable(port) {
|
|
402
|
-
return new Promise((resolve$1) => {
|
|
403
|
-
const server = createServer();
|
|
404
|
-
server.once("error", (err) => {
|
|
405
|
-
if (err.code === "EADDRINUSE") resolve$1(false);
|
|
406
|
-
else resolve$1(false);
|
|
407
|
-
});
|
|
408
|
-
server.once("listening", () => {
|
|
409
|
-
server.close();
|
|
410
|
-
resolve$1(true);
|
|
411
|
-
});
|
|
412
|
-
server.listen(port);
|
|
413
|
-
});
|
|
414
|
-
}
|
|
415
|
-
/**
|
|
416
|
-
* Find an available port starting from the preferred port
|
|
417
|
-
* @internal Exported for testing
|
|
418
|
-
*/
|
|
419
|
-
async function findAvailablePort(preferredPort, maxAttempts = 10) {
|
|
420
|
-
for (let i = 0; i < maxAttempts; i++) {
|
|
421
|
-
const port = preferredPort + i;
|
|
422
|
-
if (await isPortAvailable(port)) return port;
|
|
423
|
-
logger$13.log(`⚠️ Port ${port} is in use, trying ${port + 1}...`);
|
|
424
|
-
}
|
|
425
|
-
throw new Error(`Could not find an available port after trying ${maxAttempts} ports starting from ${preferredPort}`);
|
|
426
|
-
}
|
|
427
|
-
const PORT_STATE_PATH = ".gkm/ports.json";
|
|
428
|
-
/**
|
|
429
|
-
* Parse docker-compose.yml and extract all port mappings that use env var interpolation.
|
|
430
|
-
* Entries like `'${POSTGRES_HOST_PORT:-5432}:5432'` are captured.
|
|
431
|
-
* Fixed port mappings like `'5050:80'` are skipped.
|
|
432
|
-
* @internal Exported for testing
|
|
433
|
-
*/
|
|
434
|
-
function parseComposePortMappings(composePath) {
|
|
435
|
-
if (!existsSync(composePath)) return [];
|
|
436
|
-
const content = readFileSync(composePath, "utf-8");
|
|
437
|
-
const compose = parse$1(content);
|
|
438
|
-
if (!compose?.services) return [];
|
|
439
|
-
const results = [];
|
|
440
|
-
for (const [serviceName, serviceConfig] of Object.entries(compose.services)) for (const portMapping of serviceConfig?.ports ?? []) {
|
|
441
|
-
const match = String(portMapping).match(/\$\{(\w+):-(\d+)\}:(\d+)/);
|
|
442
|
-
if (match?.[1] && match[2] && match[3]) results.push({
|
|
443
|
-
service: serviceName,
|
|
444
|
-
envVar: match[1],
|
|
445
|
-
defaultPort: Number(match[2]),
|
|
446
|
-
containerPort: Number(match[3])
|
|
447
|
-
});
|
|
448
|
-
}
|
|
449
|
-
return results;
|
|
450
|
-
}
|
|
451
|
-
/**
|
|
452
|
-
* Load saved port state from .gkm/ports.json.
|
|
453
|
-
* @internal Exported for testing
|
|
454
|
-
*/
|
|
455
|
-
async function loadPortState(workspaceRoot) {
|
|
456
|
-
try {
|
|
457
|
-
const raw = await readFile(join(workspaceRoot, PORT_STATE_PATH), "utf-8");
|
|
458
|
-
return JSON.parse(raw);
|
|
459
|
-
} catch {
|
|
460
|
-
return {};
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
/**
|
|
464
|
-
* Save port state to .gkm/ports.json.
|
|
465
|
-
* @internal Exported for testing
|
|
466
|
-
*/
|
|
467
|
-
async function savePortState(workspaceRoot, ports) {
|
|
468
|
-
const dir = join(workspaceRoot, ".gkm");
|
|
469
|
-
await mkdir(dir, { recursive: true });
|
|
470
|
-
await writeFile(join(workspaceRoot, PORT_STATE_PATH), `${JSON.stringify(ports, null, 2)}\n`);
|
|
471
|
-
}
|
|
472
|
-
/**
|
|
473
|
-
* Check if a project's own Docker container is running and return its host port.
|
|
474
|
-
* Uses `docker compose port` scoped to the project's compose file.
|
|
475
|
-
* @internal Exported for testing
|
|
476
|
-
*/
|
|
477
|
-
function getContainerHostPort(workspaceRoot, service, containerPort) {
|
|
478
|
-
try {
|
|
479
|
-
const result = execSync(`docker compose port ${service} ${containerPort}`, {
|
|
480
|
-
cwd: workspaceRoot,
|
|
481
|
-
stdio: "pipe"
|
|
482
|
-
}).toString().trim();
|
|
483
|
-
const match = result.match(/:(\d+)$/);
|
|
484
|
-
return match ? Number(match[1]) : null;
|
|
485
|
-
} catch {
|
|
486
|
-
return null;
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
/**
|
|
490
|
-
* Resolve host ports for Docker services by parsing docker-compose.yml.
|
|
491
|
-
* Priority: running container → saved state → find available port.
|
|
492
|
-
* Persists resolved ports to .gkm/ports.json.
|
|
493
|
-
* @internal Exported for testing
|
|
494
|
-
*/
|
|
495
|
-
async function resolveServicePorts(workspaceRoot) {
|
|
496
|
-
const composePath = join(workspaceRoot, "docker-compose.yml");
|
|
497
|
-
const mappings = parseComposePortMappings(composePath);
|
|
498
|
-
if (mappings.length === 0) return {
|
|
499
|
-
dockerEnv: {},
|
|
500
|
-
ports: {},
|
|
501
|
-
mappings: []
|
|
502
|
-
};
|
|
503
|
-
const savedState = await loadPortState(workspaceRoot);
|
|
504
|
-
const dockerEnv = {};
|
|
505
|
-
const ports = {};
|
|
506
|
-
const assignedPorts = /* @__PURE__ */ new Set();
|
|
507
|
-
logger$13.log("\n🔌 Resolving service ports...");
|
|
508
|
-
for (const mapping of mappings) {
|
|
509
|
-
const containerPort = getContainerHostPort(workspaceRoot, mapping.service, mapping.containerPort);
|
|
510
|
-
if (containerPort !== null) {
|
|
511
|
-
ports[mapping.envVar] = containerPort;
|
|
512
|
-
dockerEnv[mapping.envVar] = String(containerPort);
|
|
513
|
-
assignedPorts.add(containerPort);
|
|
514
|
-
logger$13.log(` 🔄 ${mapping.service}:${mapping.containerPort}: reusing existing container on port ${containerPort}`);
|
|
515
|
-
continue;
|
|
516
|
-
}
|
|
517
|
-
const savedPort = savedState[mapping.envVar];
|
|
518
|
-
if (savedPort && !assignedPorts.has(savedPort) && await isPortAvailable(savedPort)) {
|
|
519
|
-
ports[mapping.envVar] = savedPort;
|
|
520
|
-
dockerEnv[mapping.envVar] = String(savedPort);
|
|
521
|
-
assignedPorts.add(savedPort);
|
|
522
|
-
logger$13.log(` 💾 ${mapping.service}:${mapping.containerPort}: using saved port ${savedPort}`);
|
|
523
|
-
continue;
|
|
524
|
-
}
|
|
525
|
-
let resolvedPort = await findAvailablePort(mapping.defaultPort);
|
|
526
|
-
while (assignedPorts.has(resolvedPort)) resolvedPort = await findAvailablePort(resolvedPort + 1);
|
|
527
|
-
ports[mapping.envVar] = resolvedPort;
|
|
528
|
-
dockerEnv[mapping.envVar] = String(resolvedPort);
|
|
529
|
-
assignedPorts.add(resolvedPort);
|
|
530
|
-
if (resolvedPort !== mapping.defaultPort) logger$13.log(` ⚡ ${mapping.service}:${mapping.containerPort}: port ${mapping.defaultPort} occupied, using port ${resolvedPort}`);
|
|
531
|
-
else logger$13.log(` ✅ ${mapping.service}:${mapping.containerPort}: using default port ${resolvedPort}`);
|
|
532
|
-
}
|
|
533
|
-
await savePortState(workspaceRoot, ports);
|
|
534
|
-
return {
|
|
535
|
-
dockerEnv,
|
|
536
|
-
ports,
|
|
537
|
-
mappings
|
|
538
|
-
};
|
|
539
|
-
}
|
|
540
|
-
/**
|
|
541
|
-
* Replace a port in a URL string.
|
|
542
|
-
* Handles both `hostname:port` and `localhost:port` patterns.
|
|
543
|
-
* @internal Exported for testing
|
|
544
|
-
*/
|
|
545
|
-
function replacePortInUrl(url, oldPort, newPort) {
|
|
546
|
-
if (oldPort === newPort) return url;
|
|
547
|
-
let result = url.replace(new RegExp(`:${oldPort}(?=[/?#]|$)`, "g"), `:${newPort}`);
|
|
548
|
-
result = result.replace(new RegExp(`%3A${oldPort}(?=[%/?#&]|$)`, "gi"), `%3A${newPort}`);
|
|
549
|
-
return result;
|
|
550
|
-
}
|
|
551
|
-
/**
|
|
552
|
-
* Rewrite connection URLs and port vars in secrets with resolved ports.
|
|
553
|
-
* Uses the parsed compose mappings to determine which default ports to replace.
|
|
554
|
-
* Pure transform — does not modify secrets on disk.
|
|
555
|
-
* @internal Exported for testing
|
|
556
|
-
*/
|
|
557
|
-
function rewriteUrlsWithPorts(secrets, resolvedPorts) {
|
|
558
|
-
const { ports, mappings } = resolvedPorts;
|
|
559
|
-
const result = { ...secrets };
|
|
560
|
-
const portReplacements = [];
|
|
561
|
-
const serviceNames = /* @__PURE__ */ new Set();
|
|
562
|
-
for (const mapping of mappings) {
|
|
563
|
-
serviceNames.add(mapping.service);
|
|
564
|
-
const resolved = ports[mapping.envVar];
|
|
565
|
-
if (resolved !== void 0) portReplacements.push({
|
|
566
|
-
defaultPort: mapping.defaultPort,
|
|
567
|
-
resolvedPort: resolved
|
|
568
|
-
});
|
|
569
|
-
}
|
|
570
|
-
for (const [key, value] of Object.entries(result)) {
|
|
571
|
-
if (!key.endsWith("_HOST")) continue;
|
|
572
|
-
if (serviceNames.has(value)) result[key] = "localhost";
|
|
573
|
-
}
|
|
574
|
-
for (const [key, value] of Object.entries(result)) {
|
|
575
|
-
if (!key.endsWith("_PORT")) continue;
|
|
576
|
-
for (const { defaultPort, resolvedPort } of portReplacements) if (value === String(defaultPort)) result[key] = String(resolvedPort);
|
|
577
|
-
}
|
|
578
|
-
for (const [key, value] of Object.entries(result)) {
|
|
579
|
-
if (!key.endsWith("_URL") && !key.endsWith("_ENDPOINT") && !key.endsWith("_CONNECTION_STRING") && key !== "DATABASE_URL") continue;
|
|
580
|
-
let rewritten = value;
|
|
581
|
-
for (const name$1 of serviceNames) rewritten = rewritten.replace(new RegExp(`@${name$1}:`, "g"), "@localhost:");
|
|
582
|
-
for (const { defaultPort, resolvedPort } of portReplacements) rewritten = replacePortInUrl(rewritten, defaultPort, resolvedPort);
|
|
583
|
-
result[key] = rewritten;
|
|
584
|
-
}
|
|
585
|
-
return result;
|
|
586
|
-
}
|
|
587
|
-
/**
|
|
588
|
-
* Build the environment variables to pass to `docker compose up`.
|
|
589
|
-
* Merges process.env, secrets, and port mappings so that Docker Compose
|
|
590
|
-
* can interpolate variables like ${POSTGRES_USER} correctly.
|
|
591
|
-
* @internal Exported for testing
|
|
592
|
-
*/
|
|
593
|
-
function buildDockerComposeEnv(secretsEnv, portEnv) {
|
|
594
|
-
return {
|
|
595
|
-
...process.env,
|
|
596
|
-
...secretsEnv,
|
|
597
|
-
...portEnv
|
|
598
|
-
};
|
|
599
|
-
}
|
|
600
|
-
/**
|
|
601
|
-
* Parse all service names from a docker-compose.yml file.
|
|
602
|
-
* @internal Exported for testing
|
|
603
|
-
*/
|
|
604
|
-
function parseComposeServiceNames(composePath) {
|
|
605
|
-
if (!existsSync(composePath)) return [];
|
|
606
|
-
const content = readFileSync(composePath, "utf-8");
|
|
607
|
-
const compose = parse$1(content);
|
|
608
|
-
return Object.keys(compose?.services ?? {});
|
|
609
|
-
}
|
|
610
|
-
/**
|
|
611
|
-
* Start docker-compose services for a single-app project (no workspace config).
|
|
612
|
-
* Starts all services defined in docker-compose.yml.
|
|
613
|
-
*/
|
|
614
|
-
async function startComposeServices(cwd, portEnv, secretsEnv) {
|
|
615
|
-
const composeFile = join(cwd, "docker-compose.yml");
|
|
616
|
-
if (!existsSync(composeFile)) return;
|
|
617
|
-
const servicesToStart = parseComposeServiceNames(composeFile);
|
|
618
|
-
if (servicesToStart.length === 0) return;
|
|
619
|
-
logger$13.log(`🐳 Starting services: ${servicesToStart.join(", ")}`);
|
|
620
|
-
try {
|
|
621
|
-
execSync(`docker compose up -d ${servicesToStart.join(" ")}`, {
|
|
622
|
-
cwd,
|
|
623
|
-
stdio: "inherit",
|
|
624
|
-
env: buildDockerComposeEnv(secretsEnv, portEnv)
|
|
625
|
-
});
|
|
626
|
-
logger$13.log("✅ Services started");
|
|
627
|
-
} catch (error) {
|
|
628
|
-
logger$13.error("❌ Failed to start services:", error.message);
|
|
629
|
-
throw error;
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
/**
|
|
633
|
-
* Start docker-compose services for a workspace.
|
|
634
|
-
* Discovers all services from docker-compose.yml and starts everything
|
|
635
|
-
* except app services (which are managed by turbo).
|
|
636
|
-
* @internal Exported for testing
|
|
637
|
-
*/
|
|
638
|
-
async function startWorkspaceServices(workspace, portEnv, secretsEnv) {
|
|
639
|
-
const composeFile = join(workspace.root, "docker-compose.yml");
|
|
640
|
-
if (!existsSync(composeFile)) return;
|
|
641
|
-
const allServices = parseComposeServiceNames(composeFile);
|
|
642
|
-
const appNames = new Set(Object.keys(workspace.apps));
|
|
643
|
-
const servicesToStart = allServices.filter((name$1) => !appNames.has(name$1));
|
|
644
|
-
if (servicesToStart.length === 0) return;
|
|
645
|
-
logger$13.log(`🐳 Starting services: ${servicesToStart.join(", ")}`);
|
|
646
|
-
try {
|
|
647
|
-
execSync(`docker compose up -d ${servicesToStart.join(" ")}`, {
|
|
648
|
-
cwd: workspace.root,
|
|
649
|
-
stdio: "inherit",
|
|
650
|
-
env: buildDockerComposeEnv(secretsEnv, portEnv)
|
|
651
|
-
});
|
|
652
|
-
logger$13.log("✅ Services started");
|
|
653
|
-
} catch (error) {
|
|
654
|
-
logger$13.error("❌ Failed to start services:", error.message);
|
|
655
|
-
throw error;
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
/**
|
|
659
|
-
* Load and flatten secrets for an app from encrypted storage.
|
|
660
|
-
* For workspace app: maps {APP}_DATABASE_URL → DATABASE_URL.
|
|
661
|
-
* @internal Exported for testing
|
|
662
|
-
*/
|
|
663
|
-
async function loadSecretsForApp(secretsRoot, appName, stages = ["dev", "development"]) {
|
|
664
|
-
let secrets = {};
|
|
665
|
-
for (const stage of stages) if (secretsExist(stage, secretsRoot)) {
|
|
666
|
-
const stageSecrets = await readStageSecrets(stage, secretsRoot);
|
|
667
|
-
if (stageSecrets) {
|
|
668
|
-
logger$13.log(`🔐 Loading secrets from stage: ${stage}`);
|
|
669
|
-
secrets = toEmbeddableSecrets(stageSecrets);
|
|
670
|
-
break;
|
|
671
|
-
}
|
|
672
|
-
}
|
|
673
|
-
if (Object.keys(secrets).length === 0) return {};
|
|
674
|
-
if (!appName) return secrets;
|
|
675
|
-
const prefix = appName.toUpperCase();
|
|
676
|
-
const mapped = { ...secrets };
|
|
677
|
-
const appDbUrl = secrets[`${prefix}_DATABASE_URL`];
|
|
678
|
-
if (appDbUrl) mapped.DATABASE_URL = appDbUrl;
|
|
679
|
-
return mapped;
|
|
680
|
-
}
|
|
681
|
-
/**
|
|
682
|
-
* Walk up the directory tree to find the root containing .gkm/secrets/.
|
|
683
|
-
* @internal Exported for testing
|
|
684
|
-
*/
|
|
685
|
-
function findSecretsRoot(startDir) {
|
|
686
|
-
let dir = startDir;
|
|
687
|
-
while (dir !== "/") {
|
|
688
|
-
if (existsSync(join(dir, ".gkm", "secrets"))) return dir;
|
|
689
|
-
const parent = dirname(dir);
|
|
690
|
-
if (parent === dir) break;
|
|
691
|
-
dir = parent;
|
|
692
|
-
}
|
|
693
|
-
return startDir;
|
|
694
|
-
}
|
|
695
|
-
/**
|
|
696
|
-
* Generate the credentials injection code snippet.
|
|
697
|
-
* This is the common logic used by both entry wrapper and exec preload.
|
|
698
|
-
* @internal
|
|
699
|
-
*/
|
|
700
|
-
function generateCredentialsInjection(secretsJsonPath) {
|
|
701
|
-
return `import { existsSync, readFileSync } from 'node:fs';
|
|
702
|
-
|
|
703
|
-
// Inject dev secrets via globalThis and process.env
|
|
704
|
-
// Using globalThis.__gkm_credentials__ avoids CJS/ESM interop issues where
|
|
705
|
-
// Object.assign on the Credentials export only mutates one module copy.
|
|
706
|
-
const secretsPath = '${secretsJsonPath}';
|
|
707
|
-
if (existsSync(secretsPath)) {
|
|
708
|
-
const secrets = JSON.parse(readFileSync(secretsPath, 'utf-8'));
|
|
709
|
-
globalThis.__gkm_credentials__ = secrets;
|
|
710
|
-
Object.assign(process.env, secrets);
|
|
711
|
-
}
|
|
712
|
-
`;
|
|
713
|
-
}
|
|
714
|
-
/**
|
|
715
|
-
* Create a preload script that injects secrets into Credentials.
|
|
716
|
-
* Used by `gkm exec` to inject secrets before running any command.
|
|
717
|
-
* @internal Exported for testing
|
|
718
|
-
*/
|
|
719
|
-
async function createCredentialsPreload(preloadPath, secretsJsonPath) {
|
|
720
|
-
const content = `/**
|
|
721
|
-
* Credentials preload generated by 'gkm exec'
|
|
722
|
-
* This file is loaded via NODE_OPTIONS="--import <path>"
|
|
723
|
-
*/
|
|
724
|
-
${generateCredentialsInjection(secretsJsonPath)}`;
|
|
725
|
-
await writeFile(preloadPath, content);
|
|
726
|
-
}
|
|
727
|
-
/**
|
|
728
|
-
* Create a wrapper script that injects secrets before importing the entry file.
|
|
729
|
-
* @internal Exported for testing
|
|
730
|
-
*/
|
|
731
|
-
async function createEntryWrapper(wrapperPath, entryPath, secretsJsonPath) {
|
|
732
|
-
const credentialsInjection = secretsJsonPath ? `${generateCredentialsInjection(secretsJsonPath)}
|
|
733
|
-
` : "";
|
|
734
|
-
const content = `#!/usr/bin/env node
|
|
735
|
-
/**
|
|
736
|
-
* Entry wrapper generated by 'gkm dev --entry'
|
|
737
|
-
*/
|
|
738
|
-
${credentialsInjection}// Import and run the user's entry file (dynamic import ensures secrets load first)
|
|
739
|
-
await import('${entryPath}');
|
|
740
|
-
`;
|
|
741
|
-
await writeFile(wrapperPath, content);
|
|
742
|
-
}
|
|
743
|
-
/**
|
|
744
|
-
* Prepare credentials for dev/exec/test modes.
|
|
745
|
-
* Loads workspace config, secrets, resolves Docker ports, rewrites URLs,
|
|
746
|
-
* injects PORT, dependency URLs, and writes credentials JSON.
|
|
747
|
-
*
|
|
748
|
-
* @param options.resolveDockerPorts - How to resolve Docker ports:
|
|
749
|
-
* - `'full'` (default): probe running containers, saved state, then find available ports. Used by dev/test.
|
|
750
|
-
* - `'readonly'`: check running containers and saved state only, never probe for new ports. Used by exec.
|
|
751
|
-
* @param options.stages - Secret stages to try, in order. Default: ['dev', 'development'].
|
|
752
|
-
* @param options.startDocker - Start Docker Compose services after port resolution. Default: false.
|
|
753
|
-
* @param options.secretsFileName - Custom secrets JSON filename. Default: 'dev-secrets-{appName}.json' or 'dev-secrets.json'.
|
|
754
|
-
* @internal Exported for testing
|
|
755
|
-
*/
|
|
756
|
-
async function prepareEntryCredentials(options) {
|
|
757
|
-
const cwd = options.cwd ?? process.cwd();
|
|
758
|
-
const portMode = options.resolveDockerPorts ?? "full";
|
|
759
|
-
let workspaceAppPort;
|
|
760
|
-
let secretsRoot = cwd;
|
|
761
|
-
let appName;
|
|
762
|
-
let appInfo;
|
|
763
|
-
try {
|
|
764
|
-
appInfo = await loadWorkspaceAppInfo(cwd);
|
|
765
|
-
workspaceAppPort = appInfo.app.port;
|
|
766
|
-
secretsRoot = appInfo.workspaceRoot;
|
|
767
|
-
appName = appInfo.appName;
|
|
768
|
-
} catch {
|
|
769
|
-
secretsRoot = findSecretsRoot(cwd);
|
|
770
|
-
appName = getAppNameFromCwd(cwd) ?? void 0;
|
|
771
|
-
}
|
|
772
|
-
const resolvedPort = options.explicitPort ?? workspaceAppPort ?? 3e3;
|
|
773
|
-
const credentials = await loadSecretsForApp(secretsRoot, appName, options.stages);
|
|
774
|
-
credentials.PORT = String(resolvedPort);
|
|
775
|
-
const composePath = join(secretsRoot, "docker-compose.yml");
|
|
776
|
-
const mappings = parseComposePortMappings(composePath);
|
|
777
|
-
if (mappings.length > 0) {
|
|
778
|
-
let resolvedPorts;
|
|
779
|
-
if (portMode === "full") resolvedPorts = await resolveServicePorts(secretsRoot);
|
|
780
|
-
else {
|
|
781
|
-
const savedPorts = await loadPortState(secretsRoot);
|
|
782
|
-
const ports = {};
|
|
783
|
-
for (const mapping of mappings) {
|
|
784
|
-
const containerPort = getContainerHostPort(secretsRoot, mapping.service, mapping.containerPort);
|
|
785
|
-
if (containerPort !== null) ports[mapping.envVar] = containerPort;
|
|
786
|
-
else {
|
|
787
|
-
const saved = savedPorts[mapping.envVar];
|
|
788
|
-
if (saved !== void 0) ports[mapping.envVar] = saved;
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
resolvedPorts = {
|
|
792
|
-
dockerEnv: {},
|
|
793
|
-
ports,
|
|
794
|
-
mappings
|
|
795
|
-
};
|
|
796
|
-
}
|
|
797
|
-
if (options.startDocker) if (appInfo) await startWorkspaceServices(appInfo.workspace, resolvedPorts.dockerEnv, credentials);
|
|
798
|
-
else await startComposeServices(secretsRoot, resolvedPorts.dockerEnv, credentials);
|
|
799
|
-
if (Object.keys(resolvedPorts.ports).length > 0) {
|
|
800
|
-
const rewritten = rewriteUrlsWithPorts(credentials, resolvedPorts);
|
|
801
|
-
Object.assign(credentials, rewritten);
|
|
802
|
-
logger$13.log(`🔌 Applied ${Object.keys(resolvedPorts.ports).length} port mapping(s)`);
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
if (appInfo?.appName) {
|
|
806
|
-
const depEnv = getDependencyEnvVars(appInfo.workspace, appInfo.appName);
|
|
807
|
-
Object.assign(credentials, depEnv);
|
|
808
|
-
}
|
|
809
|
-
if (!credentials.EVENT_PUBLISHER_CONNECTION_STRING && !credentials.EVENT_SUBSCRIBER_CONNECTION_STRING && credentials.PGBOSS_DB_USER) {
|
|
810
|
-
const pgbossUrl = generatePgBossUrl({
|
|
811
|
-
username: credentials.PGBOSS_DB_USER,
|
|
812
|
-
password: credentials.PGBOSS_DB_PASSWORD ?? "",
|
|
813
|
-
host: credentials.POSTGRES_HOST ?? "localhost",
|
|
814
|
-
port: Number(credentials.POSTGRES_PORT ?? "5432"),
|
|
815
|
-
database: credentials.POSTGRES_DB ?? "app"
|
|
816
|
-
});
|
|
817
|
-
credentials.EVENT_PUBLISHER_CONNECTION_STRING = pgbossUrl;
|
|
818
|
-
credentials.EVENT_SUBSCRIBER_CONNECTION_STRING = pgbossUrl;
|
|
819
|
-
}
|
|
820
|
-
const secretsDir = join(secretsRoot, ".gkm");
|
|
821
|
-
await mkdir(secretsDir, { recursive: true });
|
|
822
|
-
const secretsFileName = options.secretsFileName ?? (appName ? `dev-secrets-${appName}.json` : "dev-secrets.json");
|
|
823
|
-
const secretsJsonPath = join(secretsDir, secretsFileName);
|
|
824
|
-
await writeFile(secretsJsonPath, JSON.stringify(credentials, null, 2));
|
|
825
|
-
return {
|
|
826
|
-
credentials,
|
|
827
|
-
resolvedPort,
|
|
828
|
-
secretsJsonPath,
|
|
829
|
-
appName,
|
|
830
|
-
secretsRoot,
|
|
831
|
-
appInfo
|
|
832
|
-
};
|
|
366
|
+
function isEnabled(config) {
|
|
367
|
+
if (config === void 0) return false;
|
|
368
|
+
if (typeof config === "boolean") return config;
|
|
369
|
+
return config.enabled !== false;
|
|
833
370
|
}
|
|
834
371
|
|
|
835
372
|
//#endregion
|
|
@@ -837,7 +374,7 @@ async function prepareEntryCredentials(options) {
|
|
|
837
374
|
var CronGenerator = class extends ConstructGenerator {
|
|
838
375
|
async build(context, constructs, outputDir, options) {
|
|
839
376
|
const provider = options?.provider || "aws-lambda";
|
|
840
|
-
const logger$
|
|
377
|
+
const logger$13 = console;
|
|
841
378
|
const cronInfos = [];
|
|
842
379
|
if (constructs.length === 0 || provider !== "aws-lambda") return cronInfos;
|
|
843
380
|
const cronsDir = join(outputDir, "crons");
|
|
@@ -852,7 +389,7 @@ var CronGenerator = class extends ConstructGenerator {
|
|
|
852
389
|
memorySize: construct.memorySize,
|
|
853
390
|
environment: await construct.getEnvironment({ markOptional: context.markOptional })
|
|
854
391
|
});
|
|
855
|
-
logger$
|
|
392
|
+
logger$13.log(`Generated cron handler: ${key}`);
|
|
856
393
|
}
|
|
857
394
|
return cronInfos;
|
|
858
395
|
}
|
|
@@ -888,7 +425,7 @@ var FunctionGenerator = class extends ConstructGenerator {
|
|
|
888
425
|
}
|
|
889
426
|
async build(context, constructs, outputDir, options) {
|
|
890
427
|
const provider = options?.provider || "aws-lambda";
|
|
891
|
-
const logger$
|
|
428
|
+
const logger$13 = console;
|
|
892
429
|
const functionInfos = [];
|
|
893
430
|
if (constructs.length === 0 || provider !== "aws-lambda") return functionInfos;
|
|
894
431
|
const functionsDir = join(outputDir, "functions");
|
|
@@ -902,7 +439,7 @@ var FunctionGenerator = class extends ConstructGenerator {
|
|
|
902
439
|
memorySize: construct.memorySize,
|
|
903
440
|
environment: await construct.getEnvironment({ markOptional: context.markOptional })
|
|
904
441
|
});
|
|
905
|
-
logger$
|
|
442
|
+
logger$13.log(`Generated function handler: ${key}`);
|
|
906
443
|
}
|
|
907
444
|
return functionInfos;
|
|
908
445
|
}
|
|
@@ -927,6 +464,176 @@ export const handler = adapter.handler;
|
|
|
927
464
|
}
|
|
928
465
|
};
|
|
929
466
|
|
|
467
|
+
//#endregion
|
|
468
|
+
//#region src/generators/QueueGenerator.ts
|
|
469
|
+
/**
|
|
470
|
+
* Generates the runtime for `q` queue workers.
|
|
471
|
+
*
|
|
472
|
+
* - **server** (`gkm dev`): a single `queues.ts` exposing `setupQueues()` that
|
|
473
|
+
* runs an in-process pg-boss poller alongside the Hono server — no SQS/Lambda.
|
|
474
|
+
* Each queue subscribes by its **name** on the shared event connection.
|
|
475
|
+
* - **aws-lambda**: one handler file per queue wrapping `AWSLambdaQueue`, backed
|
|
476
|
+
* by an SQS event-source mapping.
|
|
477
|
+
*/
|
|
478
|
+
var QueueGenerator = class extends ConstructGenerator {
|
|
479
|
+
isConstruct(value) {
|
|
480
|
+
return Queue.isQueue(value);
|
|
481
|
+
}
|
|
482
|
+
async build(context, constructs, outputDir, options) {
|
|
483
|
+
const provider = options?.provider || "aws-lambda";
|
|
484
|
+
const logger$13 = console;
|
|
485
|
+
const queueInfos = [];
|
|
486
|
+
if (provider === "server") {
|
|
487
|
+
await this.generateServerQueuesFile(outputDir, constructs);
|
|
488
|
+
logger$13.log(`Generated server queues file with ${constructs.length} queues (polling mode)`);
|
|
489
|
+
return queueInfos;
|
|
490
|
+
}
|
|
491
|
+
if (constructs.length === 0 || provider !== "aws-lambda") return queueInfos;
|
|
492
|
+
const queuesDir = join(outputDir, "queues");
|
|
493
|
+
await mkdir(queuesDir, { recursive: true });
|
|
494
|
+
for (const { key, construct, path } of constructs) {
|
|
495
|
+
const handlerFile = await this.generateQueueHandler(queuesDir, path.relative, key, context);
|
|
496
|
+
queueInfos.push({
|
|
497
|
+
name: construct.name,
|
|
498
|
+
handler: relative(process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
|
|
499
|
+
batchSize: construct.batchSize,
|
|
500
|
+
fifo: construct.fifo,
|
|
501
|
+
timeout: construct.timeout,
|
|
502
|
+
environment: await construct.getEnvironment({ markOptional: context.markOptional })
|
|
503
|
+
});
|
|
504
|
+
logger$13.log(`Generated queue handler: ${key}`);
|
|
505
|
+
}
|
|
506
|
+
return queueInfos;
|
|
507
|
+
}
|
|
508
|
+
async generateQueueHandler(outputDir, sourceFile, exportName, context) {
|
|
509
|
+
const handlerPath = join(outputDir, `${exportName}.ts`);
|
|
510
|
+
const importPath = relative(dirname(handlerPath), sourceFile).replace(/\.ts$/, ".js");
|
|
511
|
+
const relativeEnvParserPath = relative(dirname(handlerPath), context.envParserPath);
|
|
512
|
+
const content = `import { AWSLambdaQueue } from '@geekmidas/constructs/aws';
|
|
513
|
+
import { ${exportName} } from '${importPath}';
|
|
514
|
+
import ${context.envParserImportPattern} from '${relativeEnvParserPath}';
|
|
515
|
+
|
|
516
|
+
const adapter = new AWSLambdaQueue(envParser, ${exportName});
|
|
517
|
+
|
|
518
|
+
export const handler = adapter.handler;
|
|
519
|
+
`;
|
|
520
|
+
await writeFile(handlerPath, content);
|
|
521
|
+
return handlerPath;
|
|
522
|
+
}
|
|
523
|
+
async generateServerQueuesFile(outputDir, queues) {
|
|
524
|
+
await mkdir(outputDir, { recursive: true });
|
|
525
|
+
const queuesPath = join(outputDir, "queues.ts");
|
|
526
|
+
const importsByFile = /* @__PURE__ */ new Map();
|
|
527
|
+
for (const { path, key } of queues) {
|
|
528
|
+
const importPath = relative(dirname(queuesPath), path.relative).replace(/\.ts$/, ".js");
|
|
529
|
+
if (!importsByFile.has(importPath)) importsByFile.set(importPath, []);
|
|
530
|
+
importsByFile.get(importPath)?.push(key);
|
|
531
|
+
}
|
|
532
|
+
const imports = Array.from(importsByFile.entries()).map(([importPath, exports$1]) => `import { ${exports$1.join(", ")} } from '${importPath}';`).join("\n");
|
|
533
|
+
const allExportNames = queues.map(({ key }) => key);
|
|
534
|
+
const content = `/**
|
|
535
|
+
* Generated queue workers setup
|
|
536
|
+
*
|
|
537
|
+
* ⚠️ WARNING: This is for LOCAL DEVELOPMENT ONLY
|
|
538
|
+
* This runs an in-process pg-boss poller alongside the HTTP server. Each queue
|
|
539
|
+
* subscribes by its name on the shared event connection
|
|
540
|
+
* (EVENT_SUBSCRIBER_CONNECTION_STRING), so producers using
|
|
541
|
+
* <NAME>_PUBLISHER_CONNECTION_STRING (pgboss:// locally) reach it.
|
|
542
|
+
*
|
|
543
|
+
* For production, queues run as AWS Lambda with SQS event-source mappings.
|
|
544
|
+
*/
|
|
545
|
+
import type { EnvironmentParser } from '@geekmidas/envkit';
|
|
546
|
+
import type { Logger } from '@geekmidas/logger';
|
|
547
|
+
import { EventConnectionFactory, Subscriber } from '@geekmidas/events';
|
|
548
|
+
import type { EventConnection, EventSubscriber } from '@geekmidas/events';
|
|
549
|
+
import { ServiceDiscovery } from '@geekmidas/services';
|
|
550
|
+
${imports}
|
|
551
|
+
|
|
552
|
+
const queues = [
|
|
553
|
+
${allExportNames.join(",\n ")}
|
|
554
|
+
];
|
|
555
|
+
|
|
556
|
+
const activeSubscribers: EventSubscriber<any>[] = [];
|
|
557
|
+
|
|
558
|
+
export async function setupQueues(
|
|
559
|
+
envParser: EnvironmentParser<any>,
|
|
560
|
+
logger: Logger,
|
|
561
|
+
): Promise<void> {
|
|
562
|
+
if (queues.length === 0) {
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
logger.info('Setting up queue workers in polling mode (local development)');
|
|
567
|
+
|
|
568
|
+
const config = envParser.create((get) => ({
|
|
569
|
+
connectionString: get('EVENT_SUBSCRIBER_CONNECTION_STRING').string(),
|
|
570
|
+
})).parse();
|
|
571
|
+
|
|
572
|
+
const serviceDiscovery = ServiceDiscovery.getInstance(envParser);
|
|
573
|
+
|
|
574
|
+
let connection: EventConnection;
|
|
575
|
+
try {
|
|
576
|
+
connection = await EventConnectionFactory.fromConnectionString(config.connectionString);
|
|
577
|
+
const connectionType = new URL(config.connectionString).protocol.replace(':', '');
|
|
578
|
+
logger.info({ connectionType }, 'Created shared event connection for queues');
|
|
579
|
+
} catch (error) {
|
|
580
|
+
logger.error({ error }, 'Failed to create event connection for queues');
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
for (const queue of queues) {
|
|
585
|
+
try {
|
|
586
|
+
const eventSubscriber = await Subscriber.fromConnection(connection);
|
|
587
|
+
|
|
588
|
+
const services = queue.services.length > 0
|
|
589
|
+
? await serviceDiscovery.register(queue.services)
|
|
590
|
+
: {};
|
|
591
|
+
|
|
592
|
+
// A queue subscribes to a single "type" — its own name.
|
|
593
|
+
await eventSubscriber.subscribe([queue.name], async (message) => {
|
|
594
|
+
try {
|
|
595
|
+
const validation = await queue.messageSchema['~standard'].validate(message.payload);
|
|
596
|
+
if (validation.issues) {
|
|
597
|
+
logger.error({ issues: validation.issues, queue: queue.name }, 'Queue message failed validation');
|
|
598
|
+
return;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
await queue.handler({
|
|
602
|
+
messages: [validation.value],
|
|
603
|
+
services: services,
|
|
604
|
+
logger: queue.logger,
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
logger.debug({ queue: queue.name }, 'Successfully processed queue message');
|
|
608
|
+
} catch (error) {
|
|
609
|
+
logger.error({ error, queue: queue.name }, 'Failed to process queue message');
|
|
610
|
+
// Message will become visible again for retry.
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
|
|
614
|
+
activeSubscribers.push(eventSubscriber);
|
|
615
|
+
logger.info({ queue: queue.name }, 'Queue worker started polling');
|
|
616
|
+
} catch (error) {
|
|
617
|
+
logger.error({ error, queue: queue.name }, 'Failed to setup queue worker');
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
const shutdown = () => {
|
|
622
|
+
logger.info('Stopping all queue workers');
|
|
623
|
+
for (const _ of activeSubscribers) {
|
|
624
|
+
connection.stop();
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
process.on('SIGTERM', shutdown);
|
|
629
|
+
process.on('SIGINT', shutdown);
|
|
630
|
+
}
|
|
631
|
+
`;
|
|
632
|
+
await writeFile(queuesPath, content);
|
|
633
|
+
return queuesPath;
|
|
634
|
+
}
|
|
635
|
+
};
|
|
636
|
+
|
|
930
637
|
//#endregion
|
|
931
638
|
//#region src/generators/SubscriberGenerator.ts
|
|
932
639
|
var SubscriberGenerator = class extends ConstructGenerator {
|
|
@@ -935,11 +642,11 @@ var SubscriberGenerator = class extends ConstructGenerator {
|
|
|
935
642
|
}
|
|
936
643
|
async build(context, constructs, outputDir, options) {
|
|
937
644
|
const provider = options?.provider || "aws-lambda";
|
|
938
|
-
const logger$
|
|
645
|
+
const logger$13 = console;
|
|
939
646
|
const subscriberInfos = [];
|
|
940
647
|
if (provider === "server") {
|
|
941
648
|
await this.generateServerSubscribersFile(outputDir, constructs);
|
|
942
|
-
logger$
|
|
649
|
+
logger$13.log(`Generated server subscribers file with ${constructs.length} subscribers (polling mode)`);
|
|
943
650
|
return subscriberInfos;
|
|
944
651
|
}
|
|
945
652
|
if (constructs.length === 0) return subscriberInfos;
|
|
@@ -952,11 +659,15 @@ var SubscriberGenerator = class extends ConstructGenerator {
|
|
|
952
659
|
name: key,
|
|
953
660
|
handler: relative(process.cwd(), handlerFile).replace(/\.ts$/, ".handler"),
|
|
954
661
|
subscribedEvents: construct.subscribedEvents || [],
|
|
662
|
+
...construct.topicName ? {
|
|
663
|
+
transport: "topic",
|
|
664
|
+
topic: construct.topicName
|
|
665
|
+
} : {},
|
|
955
666
|
timeout: construct.timeout,
|
|
956
667
|
memorySize: construct.memorySize,
|
|
957
668
|
environment: await construct.getEnvironment({ markOptional: context.markOptional })
|
|
958
669
|
});
|
|
959
|
-
logger$
|
|
670
|
+
logger$13.log(`Generated subscriber handler: ${key}`);
|
|
960
671
|
}
|
|
961
672
|
return subscriberInfos;
|
|
962
673
|
}
|
|
@@ -1120,9 +831,35 @@ export async function setupSubscribers(
|
|
|
1120
831
|
}
|
|
1121
832
|
};
|
|
1122
833
|
|
|
834
|
+
//#endregion
|
|
835
|
+
//#region src/generators/TopicGenerator.ts
|
|
836
|
+
/**
|
|
837
|
+
* Discovers `t` topics into `manifest.topics`. A topic is a *resource*, not a
|
|
838
|
+
* function — it has no handler to generate. Producers reach it via its derived
|
|
839
|
+
* publisher (`topic.publisher`, whose connection string is sniffed onto the
|
|
840
|
+
* producing construct) and subscribers bind via `s.topic(topic)`; this generator
|
|
841
|
+
* only records the topic + its event contract so infra can provision the SNS
|
|
842
|
+
* topic. Identical output for every provider (locally pg-boss routes by event
|
|
843
|
+
* name, so there's nothing to run).
|
|
844
|
+
*/
|
|
845
|
+
var TopicGenerator = class extends ConstructGenerator {
|
|
846
|
+
isConstruct(value) {
|
|
847
|
+
return Topic.isTopic(value);
|
|
848
|
+
}
|
|
849
|
+
async build(_context, constructs, _outputDir, _options) {
|
|
850
|
+
const logger$13 = console;
|
|
851
|
+
const topicInfos = constructs.map(({ construct }) => ({
|
|
852
|
+
name: construct.name,
|
|
853
|
+
events: construct.eventTypes
|
|
854
|
+
}));
|
|
855
|
+
for (const topic of topicInfos) logger$13.log(`Discovered topic: ${topic.name}`);
|
|
856
|
+
return topicInfos;
|
|
857
|
+
}
|
|
858
|
+
};
|
|
859
|
+
|
|
1123
860
|
//#endregion
|
|
1124
861
|
//#region src/exec/index.ts
|
|
1125
|
-
const logger$
|
|
862
|
+
const logger$11 = console;
|
|
1126
863
|
/**
|
|
1127
864
|
* Run a command with secrets injected into Credentials.
|
|
1128
865
|
* Uses Node's --import flag to preload a script that populates Credentials
|
|
@@ -1138,21 +875,21 @@ async function execCommand(commandArgs, options = {}) {
|
|
|
1138
875
|
const cwd = options.cwd ?? process.cwd();
|
|
1139
876
|
if (commandArgs.length === 0) throw new Error("No command specified. Usage: gkm exec -- <command>");
|
|
1140
877
|
const defaultEnv = loadEnvFiles(".env");
|
|
1141
|
-
if (defaultEnv.loaded.length > 0) logger$
|
|
878
|
+
if (defaultEnv.loaded.length > 0) logger$11.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
1142
879
|
const { credentials, secretsJsonPath, appName } = await prepareEntryCredentials({
|
|
1143
880
|
cwd,
|
|
1144
881
|
resolveDockerPorts: "readonly"
|
|
1145
882
|
});
|
|
1146
|
-
if (appName) logger$
|
|
883
|
+
if (appName) logger$11.log(`📦 App: ${appName}`);
|
|
1147
884
|
const secretCount = Object.keys(credentials).filter((k) => k !== "PORT").length;
|
|
1148
|
-
if (secretCount > 0) logger$
|
|
885
|
+
if (secretCount > 0) logger$11.log(`🔐 Loaded ${secretCount} secret(s)`);
|
|
1149
886
|
const preloadDir = join(cwd, ".gkm");
|
|
1150
887
|
await mkdir(preloadDir, { recursive: true });
|
|
1151
888
|
const preloadPath = join(preloadDir, "credentials-preload.mjs");
|
|
1152
889
|
await createCredentialsPreload(preloadPath, secretsJsonPath);
|
|
1153
890
|
const [cmd, ...args] = commandArgs;
|
|
1154
891
|
if (!cmd) throw new Error("No command specified");
|
|
1155
|
-
logger$
|
|
892
|
+
logger$11.log(`🚀 Running: ${[cmd, ...args].join(" ")}`);
|
|
1156
893
|
const existingNodeOptions = (process.env.NODE_OPTIONS ?? "").replace(/--import[= ]\S+/g, "").trim();
|
|
1157
894
|
const preloadImport = `--import=${preloadPath}`;
|
|
1158
895
|
const nodeOptions = [existingNodeOptions, preloadImport].filter(Boolean).join(" ");
|
|
@@ -1168,7 +905,7 @@ async function execCommand(commandArgs, options = {}) {
|
|
|
1168
905
|
const exitCode = await new Promise((resolve$1) => {
|
|
1169
906
|
child.on("close", (code) => resolve$1(code ?? 0));
|
|
1170
907
|
child.on("error", (error) => {
|
|
1171
|
-
logger$
|
|
908
|
+
logger$11.error(`Failed to run command: ${error.message}`);
|
|
1172
909
|
resolve$1(1);
|
|
1173
910
|
});
|
|
1174
911
|
});
|
|
@@ -1177,15 +914,15 @@ async function execCommand(commandArgs, options = {}) {
|
|
|
1177
914
|
|
|
1178
915
|
//#endregion
|
|
1179
916
|
//#region src/dev/index.ts
|
|
1180
|
-
const logger$
|
|
917
|
+
const logger$10 = console;
|
|
1181
918
|
/**
|
|
1182
919
|
* Normalize telescope configuration
|
|
1183
920
|
* @internal Exported for testing
|
|
1184
921
|
*/
|
|
1185
|
-
function normalizeTelescopeConfig(config
|
|
1186
|
-
if (config
|
|
1187
|
-
if (typeof config
|
|
1188
|
-
const { path: telescopePath, importPattern: telescopeImportPattern } = parseModuleConfig(config
|
|
922
|
+
function normalizeTelescopeConfig(config) {
|
|
923
|
+
if (config === false) return void 0;
|
|
924
|
+
if (typeof config === "string") {
|
|
925
|
+
const { path: telescopePath, importPattern: telescopeImportPattern } = parseModuleConfig(config, "telescope");
|
|
1189
926
|
return {
|
|
1190
927
|
enabled: true,
|
|
1191
928
|
telescopePath,
|
|
@@ -1197,9 +934,9 @@ function normalizeTelescopeConfig(config$1) {
|
|
|
1197
934
|
websocket: true
|
|
1198
935
|
};
|
|
1199
936
|
}
|
|
1200
|
-
const isEnabled$1 = config
|
|
937
|
+
const isEnabled$1 = config === true || config === void 0 || config.enabled !== false;
|
|
1201
938
|
if (!isEnabled$1) return void 0;
|
|
1202
|
-
const telescopeConfig = typeof config
|
|
939
|
+
const telescopeConfig = typeof config === "object" ? config : {};
|
|
1203
940
|
return {
|
|
1204
941
|
enabled: true,
|
|
1205
942
|
path: telescopeConfig.path ?? "/__telescope",
|
|
@@ -1213,10 +950,10 @@ function normalizeTelescopeConfig(config$1) {
|
|
|
1213
950
|
* Normalize studio configuration
|
|
1214
951
|
* @internal Exported for testing
|
|
1215
952
|
*/
|
|
1216
|
-
function normalizeStudioConfig(config
|
|
1217
|
-
if (config
|
|
1218
|
-
if (typeof config
|
|
1219
|
-
const { path: studioPath, importPattern: studioImportPattern } = parseModuleConfig(config
|
|
953
|
+
function normalizeStudioConfig(config) {
|
|
954
|
+
if (config === false) return void 0;
|
|
955
|
+
if (typeof config === "string") {
|
|
956
|
+
const { path: studioPath, importPattern: studioImportPattern } = parseModuleConfig(config, "studio");
|
|
1220
957
|
return {
|
|
1221
958
|
enabled: true,
|
|
1222
959
|
studioPath,
|
|
@@ -1225,9 +962,9 @@ function normalizeStudioConfig(config$1) {
|
|
|
1225
962
|
schema: "public"
|
|
1226
963
|
};
|
|
1227
964
|
}
|
|
1228
|
-
const isEnabled$1 = config
|
|
965
|
+
const isEnabled$1 = config === true || config === void 0 || config.enabled !== false;
|
|
1229
966
|
if (!isEnabled$1) return void 0;
|
|
1230
|
-
const studioConfig = typeof config
|
|
967
|
+
const studioConfig = typeof config === "object" ? config : {};
|
|
1231
968
|
return {
|
|
1232
969
|
enabled: true,
|
|
1233
970
|
path: studioConfig.path ?? "/__studio",
|
|
@@ -1238,9 +975,9 @@ function normalizeStudioConfig(config$1) {
|
|
|
1238
975
|
* Normalize hooks configuration
|
|
1239
976
|
* @internal Exported for testing
|
|
1240
977
|
*/
|
|
1241
|
-
function normalizeHooksConfig(config
|
|
1242
|
-
if (!config
|
|
1243
|
-
const serverPath = config
|
|
978
|
+
function normalizeHooksConfig(config, cwd = process.cwd()) {
|
|
979
|
+
if (!config?.server) return void 0;
|
|
980
|
+
const serverPath = config.server.endsWith(".ts") ? config.server : `${config.server}.ts`;
|
|
1244
981
|
const resolvedPath = resolve(cwd, serverPath);
|
|
1245
982
|
return { serverHooksPath: resolvedPath };
|
|
1246
983
|
}
|
|
@@ -1250,34 +987,34 @@ function normalizeHooksConfig(config$1, cwd = process.cwd()) {
|
|
|
1250
987
|
*/
|
|
1251
988
|
function normalizeProductionConfig(cliProduction, configProduction) {
|
|
1252
989
|
if (!cliProduction) return void 0;
|
|
1253
|
-
const config
|
|
990
|
+
const config = configProduction ?? {};
|
|
1254
991
|
return {
|
|
1255
992
|
enabled: true,
|
|
1256
|
-
bundle: config
|
|
1257
|
-
minify: config
|
|
1258
|
-
healthCheck: config
|
|
1259
|
-
gracefulShutdown: config
|
|
1260
|
-
external: config
|
|
1261
|
-
subscribers: config
|
|
1262
|
-
openapi: config
|
|
1263
|
-
optimizedHandlers: config
|
|
993
|
+
bundle: config.bundle ?? true,
|
|
994
|
+
minify: config.minify ?? true,
|
|
995
|
+
healthCheck: config.healthCheck ?? "/health",
|
|
996
|
+
gracefulShutdown: config.gracefulShutdown ?? true,
|
|
997
|
+
external: config.external ?? [],
|
|
998
|
+
subscribers: config.subscribers ?? "exclude",
|
|
999
|
+
openapi: config.openapi ?? false,
|
|
1000
|
+
optimizedHandlers: config.optimizedHandlers ?? true
|
|
1264
1001
|
};
|
|
1265
1002
|
}
|
|
1266
1003
|
/**
|
|
1267
1004
|
* Get production config from GkmConfig
|
|
1268
1005
|
* @internal
|
|
1269
1006
|
*/
|
|
1270
|
-
function getProductionConfigFromGkm(config
|
|
1271
|
-
const serverConfig = config
|
|
1007
|
+
function getProductionConfigFromGkm(config) {
|
|
1008
|
+
const serverConfig = config.providers?.server;
|
|
1272
1009
|
if (typeof serverConfig === "object") return serverConfig.production;
|
|
1273
1010
|
return void 0;
|
|
1274
1011
|
}
|
|
1275
1012
|
async function devCommand(options) {
|
|
1276
1013
|
if (options.entry) return entryDevCommand(options);
|
|
1277
1014
|
const defaultEnv = loadEnvFiles(".env");
|
|
1278
|
-
if (defaultEnv.loaded.length > 0) logger$
|
|
1015
|
+
if (defaultEnv.loaded.length > 0) logger$10.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
1279
1016
|
const appName = getAppNameFromCwd();
|
|
1280
|
-
let config
|
|
1017
|
+
let config;
|
|
1281
1018
|
let appRoot = process.cwd();
|
|
1282
1019
|
let secretsRoot = process.cwd();
|
|
1283
1020
|
let workspaceAppName;
|
|
@@ -1285,15 +1022,15 @@ async function devCommand(options) {
|
|
|
1285
1022
|
let workspace;
|
|
1286
1023
|
if (appName) try {
|
|
1287
1024
|
const appConfig = await loadAppConfig();
|
|
1288
|
-
config
|
|
1025
|
+
config = appConfig.gkmConfig;
|
|
1289
1026
|
appRoot = appConfig.appRoot;
|
|
1290
1027
|
secretsRoot = appConfig.workspaceRoot;
|
|
1291
1028
|
workspaceAppName = appConfig.appName;
|
|
1292
1029
|
workspaceAppPort = appConfig.app.port;
|
|
1293
1030
|
workspace = appConfig.workspace;
|
|
1294
|
-
logger$
|
|
1031
|
+
logger$10.log(`📦 Running app: ${appConfig.appName} on port ${workspaceAppPort}`);
|
|
1295
1032
|
if (appConfig.app.entry) {
|
|
1296
|
-
logger$
|
|
1033
|
+
logger$10.log(`📄 Using entry point: ${appConfig.app.entry}`);
|
|
1297
1034
|
return entryDevCommand({
|
|
1298
1035
|
...options,
|
|
1299
1036
|
entry: appConfig.app.entry,
|
|
@@ -1304,42 +1041,42 @@ async function devCommand(options) {
|
|
|
1304
1041
|
} catch {
|
|
1305
1042
|
const loadedConfig = await loadWorkspaceConfig();
|
|
1306
1043
|
if (loadedConfig.type === "workspace") {
|
|
1307
|
-
logger$
|
|
1044
|
+
logger$10.log("📦 Detected workspace configuration");
|
|
1308
1045
|
return workspaceDevCommand(loadedConfig.workspace, options);
|
|
1309
1046
|
}
|
|
1310
|
-
config
|
|
1047
|
+
config = loadedConfig.raw;
|
|
1311
1048
|
}
|
|
1312
1049
|
else {
|
|
1313
1050
|
const loadedConfig = await loadWorkspaceConfig();
|
|
1314
1051
|
if (loadedConfig.type === "workspace") {
|
|
1315
|
-
logger$
|
|
1052
|
+
logger$10.log("📦 Detected workspace configuration");
|
|
1316
1053
|
return workspaceDevCommand(loadedConfig.workspace, options);
|
|
1317
1054
|
}
|
|
1318
|
-
config
|
|
1319
|
-
}
|
|
1320
|
-
if (config
|
|
1321
|
-
const { loaded, missing } = loadEnvFiles(config
|
|
1322
|
-
if (loaded.length > 0) logger$
|
|
1323
|
-
if (missing.length > 0) logger$
|
|
1324
|
-
}
|
|
1325
|
-
const resolved = resolveProviders(config
|
|
1326
|
-
logger$
|
|
1327
|
-
logger$
|
|
1328
|
-
if (config
|
|
1329
|
-
if (config
|
|
1330
|
-
if (config
|
|
1331
|
-
logger$
|
|
1332
|
-
const { path: envParserPath, importPattern: envParserImportPattern } = parseModuleConfig(config
|
|
1333
|
-
const { path: loggerPath, importPattern: loggerImportPattern } = parseModuleConfig(config
|
|
1334
|
-
const telescope = normalizeTelescopeConfig(config
|
|
1335
|
-
if (telescope) logger$
|
|
1336
|
-
const studio = normalizeStudioConfig(config
|
|
1337
|
-
if (studio) logger$
|
|
1338
|
-
const hooks = normalizeHooksConfig(config
|
|
1339
|
-
if (hooks) logger$
|
|
1340
|
-
const openApiConfig = resolveOpenApiConfig(config
|
|
1055
|
+
config = loadedConfig.raw;
|
|
1056
|
+
}
|
|
1057
|
+
if (config.env) {
|
|
1058
|
+
const { loaded, missing } = loadEnvFiles(config.env, appRoot);
|
|
1059
|
+
if (loaded.length > 0) logger$10.log(`📦 Loaded env: ${loaded.join(", ")}`);
|
|
1060
|
+
if (missing.length > 0) logger$10.warn(`⚠️ Missing env files: ${missing.join(", ")}`);
|
|
1061
|
+
}
|
|
1062
|
+
const resolved = resolveProviders(config, { provider: "server" });
|
|
1063
|
+
logger$10.log("🚀 Starting development server...");
|
|
1064
|
+
logger$10.log(`Loading routes from: ${config.routes}`);
|
|
1065
|
+
if (config.functions) logger$10.log(`Loading functions from: ${config.functions}`);
|
|
1066
|
+
if (config.crons) logger$10.log(`Loading crons from: ${config.crons}`);
|
|
1067
|
+
if (config.subscribers) logger$10.log(`Loading subscribers from: ${config.subscribers}`);
|
|
1068
|
+
logger$10.log(`Using envParser: ${config.envParser}`);
|
|
1069
|
+
const { path: envParserPath, importPattern: envParserImportPattern } = parseModuleConfig(config.envParser, "envParser");
|
|
1070
|
+
const { path: loggerPath, importPattern: loggerImportPattern } = parseModuleConfig(config.logger, "logger");
|
|
1071
|
+
const telescope = normalizeTelescopeConfig(config.telescope);
|
|
1072
|
+
if (telescope) logger$10.log(`🔭 Telescope enabled at ${telescope.path}`);
|
|
1073
|
+
const studio = normalizeStudioConfig(config.studio);
|
|
1074
|
+
if (studio) logger$10.log(`🗄️ Studio enabled at ${studio.path}`);
|
|
1075
|
+
const hooks = normalizeHooksConfig(config.hooks, appRoot);
|
|
1076
|
+
if (hooks) logger$10.log(`🪝 Server hooks enabled from ${config.hooks?.server}`);
|
|
1077
|
+
const openApiConfig = resolveOpenApiConfig(config);
|
|
1341
1078
|
const enableOpenApi = openApiConfig.enabled || resolved.enableOpenApi;
|
|
1342
|
-
if (enableOpenApi) logger$
|
|
1079
|
+
if (enableOpenApi) logger$10.log(`📄 OpenAPI output: ${OPENAPI_OUTPUT_PATH}`);
|
|
1343
1080
|
const buildContext = {
|
|
1344
1081
|
envParserPath,
|
|
1345
1082
|
envParserImportPattern,
|
|
@@ -1349,16 +1086,16 @@ async function devCommand(options) {
|
|
|
1349
1086
|
studio,
|
|
1350
1087
|
hooks
|
|
1351
1088
|
};
|
|
1352
|
-
await buildServer(config
|
|
1353
|
-
if (enableOpenApi) await generateOpenApi(config
|
|
1354
|
-
const runtime = config
|
|
1089
|
+
await buildServer(config, buildContext, resolved.providers[0], enableOpenApi, appRoot);
|
|
1090
|
+
if (enableOpenApi) await generateOpenApi(config);
|
|
1091
|
+
const runtime = config.runtime ?? "node";
|
|
1355
1092
|
let secretsJsonPath;
|
|
1356
1093
|
const appSecrets = await loadSecretsForApp(secretsRoot, workspaceAppName);
|
|
1357
1094
|
const resolvedPorts = await resolveServicePorts(secretsRoot);
|
|
1358
1095
|
if (Object.keys(resolvedPorts.ports).length > 0) {
|
|
1359
1096
|
const rewritten = rewriteUrlsWithPorts(appSecrets, resolvedPorts);
|
|
1360
1097
|
Object.assign(appSecrets, rewritten);
|
|
1361
|
-
logger$
|
|
1098
|
+
logger$10.log(`🔌 Applied ${Object.keys(resolvedPorts.ports).length} port mapping(s)`);
|
|
1362
1099
|
}
|
|
1363
1100
|
if (workspace && workspaceAppName) {
|
|
1364
1101
|
const depEnv = getDependencyEnvVars(workspace, workspaceAppName);
|
|
@@ -1370,25 +1107,25 @@ async function devCommand(options) {
|
|
|
1370
1107
|
const secretsFileName = workspaceAppName ? `dev-secrets-${workspaceAppName}.json` : "dev-secrets.json";
|
|
1371
1108
|
secretsJsonPath = join(secretsDir, secretsFileName);
|
|
1372
1109
|
await writeFile(secretsJsonPath, JSON.stringify(appSecrets, null, 2));
|
|
1373
|
-
logger$
|
|
1110
|
+
logger$10.log(`🔐 Loaded ${Object.keys(appSecrets).length} secret(s)`);
|
|
1374
1111
|
}
|
|
1375
1112
|
const devServer = new DevServer(resolved.providers[0], options.port ?? workspaceAppPort ?? 3e3, options.portExplicit ?? false, enableOpenApi, telescope, studio, runtime, appRoot, secretsJsonPath);
|
|
1376
1113
|
await devServer.start();
|
|
1377
|
-
const envParserFile = config
|
|
1378
|
-
const loggerFile = config
|
|
1379
|
-
const hooksFileParts = config
|
|
1114
|
+
const envParserFile = config.envParser.split("#")[0] ?? config.envParser;
|
|
1115
|
+
const loggerFile = config.logger.split("#")[0] ?? config.logger;
|
|
1116
|
+
const hooksFileParts = config.hooks?.server?.split("#");
|
|
1380
1117
|
const hooksFile = hooksFileParts?.[0];
|
|
1381
1118
|
const watchPatterns = [
|
|
1382
|
-
config
|
|
1383
|
-
...config
|
|
1384
|
-
...config
|
|
1385
|
-
...config
|
|
1119
|
+
config.routes,
|
|
1120
|
+
...config.functions ? [config.functions] : [],
|
|
1121
|
+
...config.crons ? [config.crons] : [],
|
|
1122
|
+
...config.subscribers ? [config.subscribers] : [],
|
|
1386
1123
|
envParserFile.endsWith(".ts") ? envParserFile : `${envParserFile}.ts`,
|
|
1387
1124
|
loggerFile.endsWith(".ts") ? loggerFile : `${loggerFile}.ts`,
|
|
1388
1125
|
...hooksFile ? [hooksFile.endsWith(".ts") ? hooksFile : `${hooksFile}.ts`] : []
|
|
1389
1126
|
].flat().filter((p) => typeof p === "string");
|
|
1390
1127
|
const normalizedPatterns = watchPatterns.map((p) => p.startsWith("./") ? p.slice(2) : p);
|
|
1391
|
-
logger$
|
|
1128
|
+
logger$10.log(`👀 Watching for changes in: ${normalizedPatterns.join(", ")}`);
|
|
1392
1129
|
const resolvedFiles = await fg(normalizedPatterns, {
|
|
1393
1130
|
cwd: appRoot,
|
|
1394
1131
|
absolute: false,
|
|
@@ -1398,7 +1135,7 @@ async function devCommand(options) {
|
|
|
1398
1135
|
const parts = f.split("/");
|
|
1399
1136
|
return parts.slice(0, -1).join("/");
|
|
1400
1137
|
}))];
|
|
1401
|
-
logger$
|
|
1138
|
+
logger$10.log(`📁 Found ${resolvedFiles.length} files in ${dirsToWatch.length} directories`);
|
|
1402
1139
|
const watcher = chokidar.watch([...resolvedFiles, ...dirsToWatch], {
|
|
1403
1140
|
ignored: /(^|[/\\])\../,
|
|
1404
1141
|
persistent: true,
|
|
@@ -1406,29 +1143,29 @@ async function devCommand(options) {
|
|
|
1406
1143
|
cwd: appRoot
|
|
1407
1144
|
});
|
|
1408
1145
|
watcher.on("ready", () => {
|
|
1409
|
-
logger$
|
|
1146
|
+
logger$10.log("🔍 File watcher ready");
|
|
1410
1147
|
});
|
|
1411
1148
|
watcher.on("error", (error) => {
|
|
1412
|
-
logger$
|
|
1149
|
+
logger$10.error("❌ Watcher error:", error);
|
|
1413
1150
|
});
|
|
1414
1151
|
let rebuildTimeout = null;
|
|
1415
1152
|
watcher.on("change", async (path) => {
|
|
1416
|
-
logger$
|
|
1153
|
+
logger$10.log(`📝 File changed: ${path}`);
|
|
1417
1154
|
if (rebuildTimeout) clearTimeout(rebuildTimeout);
|
|
1418
1155
|
rebuildTimeout = setTimeout(async () => {
|
|
1419
1156
|
try {
|
|
1420
|
-
logger$
|
|
1421
|
-
await buildServer(config
|
|
1422
|
-
if (enableOpenApi) await generateOpenApi(config
|
|
1157
|
+
logger$10.log("🔄 Rebuilding...");
|
|
1158
|
+
await buildServer(config, buildContext, resolved.providers[0], enableOpenApi, appRoot, true);
|
|
1159
|
+
if (enableOpenApi) await generateOpenApi(config, {
|
|
1423
1160
|
silent: true,
|
|
1424
1161
|
bustCache: true
|
|
1425
1162
|
});
|
|
1426
|
-
logger$
|
|
1163
|
+
logger$10.log("✅ Rebuild complete, restarting server...");
|
|
1427
1164
|
await devServer.restart();
|
|
1428
1165
|
} catch (error) {
|
|
1429
1166
|
const err = error;
|
|
1430
|
-
logger$
|
|
1431
|
-
if (err.stack) logger$
|
|
1167
|
+
logger$10.error(`❌ Rebuild failed: ${err.message}`);
|
|
1168
|
+
if (err.stack) logger$10.error(err.stack);
|
|
1432
1169
|
}
|
|
1433
1170
|
}, 300);
|
|
1434
1171
|
});
|
|
@@ -1436,9 +1173,9 @@ async function devCommand(options) {
|
|
|
1436
1173
|
const shutdown = () => {
|
|
1437
1174
|
if (isShuttingDown) return;
|
|
1438
1175
|
isShuttingDown = true;
|
|
1439
|
-
logger$
|
|
1176
|
+
logger$10.log("\n🛑 Shutting down...");
|
|
1440
1177
|
Promise.all([watcher.close(), devServer.stop()]).catch((err) => {
|
|
1441
|
-
logger$
|
|
1178
|
+
logger$10.error("Error during shutdown:", err);
|
|
1442
1179
|
}).finally(() => {
|
|
1443
1180
|
process.exit(0);
|
|
1444
1181
|
});
|
|
@@ -1637,11 +1374,11 @@ async function loadDevSecrets(workspace) {
|
|
|
1637
1374
|
for (const stage of stages) if (secretsExist(stage, workspace.root)) {
|
|
1638
1375
|
const secrets = await readStageSecrets(stage, workspace.root);
|
|
1639
1376
|
if (secrets) {
|
|
1640
|
-
logger$
|
|
1377
|
+
logger$10.log(`🔐 Loading secrets from stage: ${stage}`);
|
|
1641
1378
|
return toEmbeddableSecrets(secrets);
|
|
1642
1379
|
}
|
|
1643
1380
|
}
|
|
1644
|
-
logger$
|
|
1381
|
+
logger$10.warn("⚠️ Secrets enabled but no dev/development secrets found. Run \"gkm setup\" to initialize your development environment");
|
|
1645
1382
|
return {};
|
|
1646
1383
|
}
|
|
1647
1384
|
/**
|
|
@@ -1658,39 +1395,39 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1658
1395
|
const backendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "backend");
|
|
1659
1396
|
const frontendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "web");
|
|
1660
1397
|
const mobileApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "mobile");
|
|
1661
|
-
logger$
|
|
1398
|
+
logger$10.log(`\n🚀 Starting workspace: ${workspace.name}`);
|
|
1662
1399
|
const counts = [`${backendApps.length} backend`, `${frontendApps.length} web`];
|
|
1663
1400
|
if (mobileApps.length > 0) counts.push(`${mobileApps.length} mobile`);
|
|
1664
|
-
logger$
|
|
1401
|
+
logger$10.log(` ${counts.join(", ")} app(s)`);
|
|
1665
1402
|
const conflicts = checkPortConflicts(workspace);
|
|
1666
1403
|
if (conflicts.length > 0) {
|
|
1667
|
-
for (const conflict of conflicts) logger$
|
|
1404
|
+
for (const conflict of conflicts) logger$10.error(`❌ Port conflict: Apps "${conflict.app1}" and "${conflict.app2}" both use port ${conflict.port}`);
|
|
1668
1405
|
throw new Error("Port conflicts detected. Please assign unique ports to each app.");
|
|
1669
1406
|
}
|
|
1670
1407
|
if (frontendApps.length > 0) {
|
|
1671
|
-
logger$
|
|
1408
|
+
logger$10.log("\n🔍 Validating frontend apps...");
|
|
1672
1409
|
const validationResults = await validateFrontendApps(workspace);
|
|
1673
1410
|
let hasErrors = false;
|
|
1674
1411
|
for (const result of validationResults) {
|
|
1675
1412
|
if (!result.valid) {
|
|
1676
1413
|
hasErrors = true;
|
|
1677
|
-
logger$
|
|
1678
|
-
for (const error of result.errors) logger$
|
|
1414
|
+
logger$10.error(`\n❌ Frontend app "${result.appName}" validation failed:`);
|
|
1415
|
+
for (const error of result.errors) logger$10.error(` • ${error}`);
|
|
1679
1416
|
}
|
|
1680
|
-
for (const warning of result.warnings) logger$
|
|
1417
|
+
for (const warning of result.warnings) logger$10.warn(` ⚠️ ${result.appName}: ${warning}`);
|
|
1681
1418
|
}
|
|
1682
1419
|
if (hasErrors) throw new Error("Frontend app validation failed. Fix the issues above and try again.");
|
|
1683
|
-
logger$
|
|
1420
|
+
logger$10.log("✅ Frontend apps validated");
|
|
1684
1421
|
}
|
|
1685
1422
|
const resolvedPorts = await resolveServicePorts(workspace.root);
|
|
1686
1423
|
const rawSecrets = await loadDevSecrets(workspace);
|
|
1687
1424
|
await startWorkspaceServices(workspace, resolvedPorts.dockerEnv, rawSecrets);
|
|
1688
1425
|
const secretsEnv = rewriteUrlsWithPorts(rawSecrets, resolvedPorts);
|
|
1689
|
-
if (Object.keys(secretsEnv).length > 0) logger$
|
|
1426
|
+
if (Object.keys(secretsEnv).length > 0) logger$10.log(` Loaded ${Object.keys(secretsEnv).length} secret(s)`);
|
|
1690
1427
|
const dependencyEnv = generateAllDependencyEnvVars(workspace);
|
|
1691
1428
|
if (Object.keys(dependencyEnv).length > 0) {
|
|
1692
|
-
logger$
|
|
1693
|
-
for (const [key, value] of Object.entries(dependencyEnv)) logger$
|
|
1429
|
+
logger$10.log("📡 Dependency URLs:");
|
|
1430
|
+
for (const [key, value] of Object.entries(dependencyEnv)) logger$10.log(` ${key}=${value}`);
|
|
1694
1431
|
}
|
|
1695
1432
|
let turboFilter = [];
|
|
1696
1433
|
if (options.app) {
|
|
@@ -1699,19 +1436,19 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1699
1436
|
throw new Error(`App "${options.app}" not found. Available apps: ${appNames}`);
|
|
1700
1437
|
}
|
|
1701
1438
|
turboFilter = ["--filter", options.app];
|
|
1702
|
-
logger$
|
|
1439
|
+
logger$10.log(`\n🎯 Running single app: ${options.app}`);
|
|
1703
1440
|
} else if (options.filter) {
|
|
1704
1441
|
turboFilter = ["--filter", options.filter];
|
|
1705
|
-
logger$
|
|
1706
|
-
} else logger$
|
|
1442
|
+
logger$10.log(`\n🔍 Using filter: ${options.filter}`);
|
|
1443
|
+
} else logger$10.log(`\n🎯 Running all ${appCount} apps`);
|
|
1707
1444
|
const buildOrder = getAppBuildOrder(workspace);
|
|
1708
|
-
logger$
|
|
1445
|
+
logger$10.log("\n📋 Apps (in dependency order):");
|
|
1709
1446
|
for (const appName of buildOrder) {
|
|
1710
1447
|
const app = workspace.apps[appName];
|
|
1711
1448
|
if (!app) continue;
|
|
1712
1449
|
const deps = app.dependencies.length > 0 ? ` (depends on: ${app.dependencies.join(", ")})` : "";
|
|
1713
1450
|
const icon = app.type === "backend" ? "🔧" : app.type === "mobile" ? "📱" : "🌐";
|
|
1714
|
-
logger$
|
|
1451
|
+
logger$10.log(` ${icon} ${appName} → http://localhost:${app.port}${deps}`);
|
|
1715
1452
|
}
|
|
1716
1453
|
const configFiles = [
|
|
1717
1454
|
"gkm.config.ts",
|
|
@@ -1733,7 +1470,7 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1733
1470
|
NODE_ENV: "development",
|
|
1734
1471
|
...configPath ? { GKM_CONFIG_PATH: configPath } : {}
|
|
1735
1472
|
};
|
|
1736
|
-
logger$
|
|
1473
|
+
logger$10.log("\n🏃 Starting turbo run dev...\n");
|
|
1737
1474
|
const turboProcess = spawn("pnpm", [
|
|
1738
1475
|
"turbo",
|
|
1739
1476
|
"run",
|
|
@@ -1749,7 +1486,7 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1749
1486
|
const shutdown = () => {
|
|
1750
1487
|
if (isShuttingDown) return;
|
|
1751
1488
|
isShuttingDown = true;
|
|
1752
|
-
logger$
|
|
1489
|
+
logger$10.log("\n🛑 Shutting down workspace...");
|
|
1753
1490
|
const pid = turboProcess.pid;
|
|
1754
1491
|
if (pid) try {
|
|
1755
1492
|
process.kill(-pid, "SIGTERM");
|
|
@@ -1773,7 +1510,7 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1773
1510
|
process.on("SIGTERM", shutdown);
|
|
1774
1511
|
return new Promise((resolve$1, reject) => {
|
|
1775
1512
|
turboProcess.on("error", (error) => {
|
|
1776
|
-
logger$
|
|
1513
|
+
logger$10.error("❌ Turbo error:", error);
|
|
1777
1514
|
reject(error);
|
|
1778
1515
|
});
|
|
1779
1516
|
turboProcess.on("exit", (code) => {
|
|
@@ -1782,16 +1519,20 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1782
1519
|
});
|
|
1783
1520
|
});
|
|
1784
1521
|
}
|
|
1785
|
-
async function buildServer(config
|
|
1522
|
+
async function buildServer(config, context, provider, enableOpenApi, appRoot = process.cwd(), bustCache = false) {
|
|
1786
1523
|
const endpointGenerator = new EndpointGenerator();
|
|
1787
1524
|
const functionGenerator = new FunctionGenerator();
|
|
1788
1525
|
const cronGenerator = new CronGenerator();
|
|
1789
1526
|
const subscriberGenerator = new SubscriberGenerator();
|
|
1790
|
-
const
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
config
|
|
1527
|
+
const queueGenerator = new QueueGenerator();
|
|
1528
|
+
const topicGenerator = new TopicGenerator();
|
|
1529
|
+
const [allEndpoints, allFunctions, allCrons, allSubscribers, allQueues, allTopics] = await Promise.all([
|
|
1530
|
+
endpointGenerator.load(config.routes, appRoot, bustCache),
|
|
1531
|
+
config.functions ? functionGenerator.load(config.functions, appRoot, bustCache) : [],
|
|
1532
|
+
config.crons ? cronGenerator.load(config.crons, appRoot, bustCache) : [],
|
|
1533
|
+
config.subscribers ? subscriberGenerator.load(config.subscribers, appRoot, bustCache) : [],
|
|
1534
|
+
config.queues ? queueGenerator.load(config.queues, appRoot, bustCache) : [],
|
|
1535
|
+
config.topics ? topicGenerator.load(config.topics, appRoot, bustCache) : []
|
|
1795
1536
|
]);
|
|
1796
1537
|
const outputDir = join(appRoot, ".gkm", provider);
|
|
1797
1538
|
await mkdir(outputDir, { recursive: true });
|
|
@@ -1802,7 +1543,9 @@ async function buildServer(config$1, context, provider, enableOpenApi, appRoot =
|
|
|
1802
1543
|
}),
|
|
1803
1544
|
functionGenerator.build(context, allFunctions, outputDir, { provider }),
|
|
1804
1545
|
cronGenerator.build(context, allCrons, outputDir, { provider }),
|
|
1805
|
-
subscriberGenerator.build(context, allSubscribers, outputDir, { provider })
|
|
1546
|
+
subscriberGenerator.build(context, allSubscribers, outputDir, { provider }),
|
|
1547
|
+
queueGenerator.build(context, allQueues, outputDir, { provider }),
|
|
1548
|
+
topicGenerator.build(context, allTopics, outputDir, { provider })
|
|
1806
1549
|
]);
|
|
1807
1550
|
}
|
|
1808
1551
|
/**
|
|
@@ -1815,11 +1558,11 @@ async function entryDevCommand(options) {
|
|
|
1815
1558
|
const entryPath = resolve(process.cwd(), entry);
|
|
1816
1559
|
if (!existsSync(entryPath)) throw new Error(`Entry file not found: ${entryPath}`);
|
|
1817
1560
|
const defaultEnv = loadEnvFiles(".env");
|
|
1818
|
-
if (defaultEnv.loaded.length > 0) logger$
|
|
1561
|
+
if (defaultEnv.loaded.length > 0) logger$10.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
1819
1562
|
const { credentials, resolvedPort, secretsJsonPath, appName } = await prepareEntryCredentials({ explicitPort: options.portExplicit ? options.port : void 0 });
|
|
1820
|
-
if (appName) logger$
|
|
1821
|
-
logger$
|
|
1822
|
-
if (Object.keys(credentials).length > 1) logger$
|
|
1563
|
+
if (appName) logger$10.log(`📦 App: ${appName} (port ${resolvedPort})`);
|
|
1564
|
+
logger$10.log(`🚀 Starting entry file: ${entry} on port ${resolvedPort}`);
|
|
1565
|
+
if (Object.keys(credentials).length > 1) logger$10.log(`🔐 Loaded ${Object.keys(credentials).length - 1} secret(s) + PORT`);
|
|
1823
1566
|
const wrapperDir = join(process.cwd(), ".gkm");
|
|
1824
1567
|
await mkdir(wrapperDir, { recursive: true });
|
|
1825
1568
|
const wrapperPath = join(wrapperDir, "entry-wrapper.ts");
|
|
@@ -1830,7 +1573,7 @@ async function entryDevCommand(options) {
|
|
|
1830
1573
|
const shutdown = () => {
|
|
1831
1574
|
if (isShuttingDown) return;
|
|
1832
1575
|
isShuttingDown = true;
|
|
1833
|
-
logger$
|
|
1576
|
+
logger$10.log("\n🛑 Shutting down...");
|
|
1834
1577
|
runner.stop();
|
|
1835
1578
|
process.exit(0);
|
|
1836
1579
|
};
|
|
@@ -1862,14 +1605,14 @@ var EntryRunner = class {
|
|
|
1862
1605
|
});
|
|
1863
1606
|
let restartTimeout = null;
|
|
1864
1607
|
this.watcher.on("change", (path) => {
|
|
1865
|
-
logger$
|
|
1608
|
+
logger$10.log(`📝 File changed: ${path}`);
|
|
1866
1609
|
if (restartTimeout) clearTimeout(restartTimeout);
|
|
1867
1610
|
restartTimeout = setTimeout(async () => {
|
|
1868
|
-
logger$
|
|
1611
|
+
logger$10.log("🔄 Restarting...");
|
|
1869
1612
|
await this.restart();
|
|
1870
1613
|
}, 300);
|
|
1871
1614
|
});
|
|
1872
|
-
logger$
|
|
1615
|
+
logger$10.log(`👀 Watching for changes in: ${watchDir}`);
|
|
1873
1616
|
}
|
|
1874
1617
|
}
|
|
1875
1618
|
async runProcess() {
|
|
@@ -1884,17 +1627,17 @@ var EntryRunner = class {
|
|
|
1884
1627
|
});
|
|
1885
1628
|
this.isRunning = true;
|
|
1886
1629
|
this.childProcess.on("error", (error) => {
|
|
1887
|
-
logger$
|
|
1630
|
+
logger$10.error("❌ Process error:", error);
|
|
1888
1631
|
});
|
|
1889
1632
|
this.childProcess.on("exit", (code) => {
|
|
1890
|
-
if (code !== null && code !== 0 && code !== 143) logger$
|
|
1633
|
+
if (code !== null && code !== 0 && code !== 143) logger$10.error(`❌ Process exited with code ${code}`);
|
|
1891
1634
|
this.isRunning = false;
|
|
1892
1635
|
});
|
|
1893
1636
|
await new Promise((resolve$1) => setTimeout(resolve$1, 500));
|
|
1894
1637
|
if (this.isRunning) {
|
|
1895
|
-
logger$
|
|
1896
|
-
logger$
|
|
1897
|
-
logger$
|
|
1638
|
+
logger$10.log("");
|
|
1639
|
+
logger$10.log(` \x1b[32m✓ Ready\x1b[0m at \x1b[36mhttp://localhost:${this.port}\x1b[0m`);
|
|
1640
|
+
logger$10.log("");
|
|
1898
1641
|
}
|
|
1899
1642
|
}
|
|
1900
1643
|
async restart() {
|
|
@@ -2008,11 +1751,11 @@ var DevServer = class {
|
|
|
2008
1751
|
this.actualPort = this.requestedPort;
|
|
2009
1752
|
} else {
|
|
2010
1753
|
this.actualPort = await findAvailablePort(this.requestedPort);
|
|
2011
|
-
if (this.actualPort !== this.requestedPort) logger$
|
|
1754
|
+
if (this.actualPort !== this.requestedPort) logger$10.log(`ℹ️ Port ${this.requestedPort} was in use, using port ${this.actualPort} instead`);
|
|
2012
1755
|
}
|
|
2013
1756
|
const serverEntryPath = join(this.appRoot, ".gkm", this.provider, "server.ts");
|
|
2014
1757
|
await this.createServerEntry();
|
|
2015
|
-
logger$
|
|
1758
|
+
logger$10.log(`\n⏳ Starting server...`);
|
|
2016
1759
|
this.serverProcess = spawn("npx", [
|
|
2017
1760
|
"tsx",
|
|
2018
1761
|
serverEntryPath,
|
|
@@ -2028,10 +1771,10 @@ var DevServer = class {
|
|
|
2028
1771
|
});
|
|
2029
1772
|
this.isRunning = true;
|
|
2030
1773
|
this.serverProcess.on("error", (error) => {
|
|
2031
|
-
logger$
|
|
1774
|
+
logger$10.error("❌ Server error:", error);
|
|
2032
1775
|
});
|
|
2033
1776
|
this.serverProcess.on("exit", (code, signal) => {
|
|
2034
|
-
if (code !== null && code !== 0 && signal !== "SIGTERM") logger$
|
|
1777
|
+
if (code !== null && code !== 0 && signal !== "SIGTERM") logger$10.error(`❌ Server exited with code ${code}`);
|
|
2035
1778
|
this.isRunning = false;
|
|
2036
1779
|
});
|
|
2037
1780
|
await new Promise((resolve$1) => setTimeout(resolve$1, 1e3));
|
|
@@ -2044,13 +1787,13 @@ var DevServer = class {
|
|
|
2044
1787
|
const maxLen = Math.max(...lines.map((l) => l.length));
|
|
2045
1788
|
const pad = (s) => s.padEnd(maxLen);
|
|
2046
1789
|
const border = "─".repeat(maxLen + 2);
|
|
2047
|
-
logger$
|
|
2048
|
-
logger$
|
|
2049
|
-
logger$
|
|
2050
|
-
logger$
|
|
2051
|
-
for (const line of lines) logger$
|
|
2052
|
-
logger$
|
|
2053
|
-
logger$
|
|
1790
|
+
logger$10.log("");
|
|
1791
|
+
logger$10.log(` \x1b[32m✓ Ready\x1b[0m in ${((Date.now() - this.startTime) / 1e3).toFixed(1)}s`);
|
|
1792
|
+
logger$10.log("");
|
|
1793
|
+
logger$10.log(` ┌${border}┐`);
|
|
1794
|
+
for (const line of lines) logger$10.log(` │ ${pad(line)} │`);
|
|
1795
|
+
logger$10.log(` └${border}┘`);
|
|
1796
|
+
logger$10.log("");
|
|
2054
1797
|
}
|
|
2055
1798
|
}
|
|
2056
1799
|
async stop() {
|
|
@@ -2100,7 +1843,7 @@ var DevServer = class {
|
|
|
2100
1843
|
|
|
2101
1844
|
//#endregion
|
|
2102
1845
|
//#region src/build/manifests.ts
|
|
2103
|
-
const logger$
|
|
1846
|
+
const logger$9 = console;
|
|
2104
1847
|
function isPartitioned(field) {
|
|
2105
1848
|
return !Array.isArray(field);
|
|
2106
1849
|
}
|
|
@@ -2133,7 +1876,7 @@ function generateDerivedType(fieldName, typeName, partitioned) {
|
|
|
2133
1876
|
}
|
|
2134
1877
|
return `export type ${typeName} = (typeof manifest.${fieldName})[number];`;
|
|
2135
1878
|
}
|
|
2136
|
-
async function generateAwsManifest(outputDir, routes, functions, crons, subscribers) {
|
|
1879
|
+
async function generateAwsManifest(outputDir, routes, functions, crons, subscribers, queues = [], topics = []) {
|
|
2137
1880
|
const manifestDir = join(outputDir, "manifest");
|
|
2138
1881
|
await mkdir(manifestDir, { recursive: true });
|
|
2139
1882
|
const awsRoutes = filterAllRoutes(routes);
|
|
@@ -2141,11 +1884,15 @@ async function generateAwsManifest(outputDir, routes, functions, crons, subscrib
|
|
|
2141
1884
|
const functionsPartitioned = isPartitioned(functions);
|
|
2142
1885
|
const cronsPartitioned = isPartitioned(crons);
|
|
2143
1886
|
const subscribersPartitioned = isPartitioned(subscribers);
|
|
1887
|
+
const queuesPartitioned = isPartitioned(queues);
|
|
1888
|
+
const topicsPartitioned = isPartitioned(topics);
|
|
2144
1889
|
const content = `export const manifest = {
|
|
2145
1890
|
routes: ${serializeField(awsRoutes)},
|
|
2146
1891
|
functions: ${serializeField(functions)},
|
|
2147
1892
|
crons: ${serializeField(crons)},
|
|
2148
1893
|
subscribers: ${serializeField(subscribers)},
|
|
1894
|
+
queues: ${serializeField(queues)},
|
|
1895
|
+
topics: ${serializeField(topics)},
|
|
2149
1896
|
} as const;
|
|
2150
1897
|
|
|
2151
1898
|
// Derived types
|
|
@@ -2153,6 +1900,8 @@ ${generateDerivedType("routes", "Route", routesPartitioned)}
|
|
|
2153
1900
|
${generateDerivedType("functions", "Function", functionsPartitioned)}
|
|
2154
1901
|
${generateDerivedType("crons", "Cron", cronsPartitioned)}
|
|
2155
1902
|
${generateDerivedType("subscribers", "Subscriber", subscribersPartitioned)}
|
|
1903
|
+
${generateDerivedType("queues", "Queue", queuesPartitioned)}
|
|
1904
|
+
${generateDerivedType("topics", "Topic", topicsPartitioned)}
|
|
2156
1905
|
|
|
2157
1906
|
// Useful union types
|
|
2158
1907
|
export type Authorizer = Route['authorizer'];
|
|
@@ -2161,25 +1910,33 @@ export type RoutePath = Route['path'];
|
|
|
2161
1910
|
`;
|
|
2162
1911
|
const manifestPath = join(manifestDir, "aws.ts");
|
|
2163
1912
|
await writeFile(manifestPath, content);
|
|
2164
|
-
logger$
|
|
2165
|
-
logger$
|
|
1913
|
+
logger$9.log(`Generated AWS manifest with ${countItems(awsRoutes)} routes, ${countItems(functions)} functions, ${countItems(crons)} crons, ${countItems(subscribers)} subscribers, ${countItems(queues)} queues, ${countItems(topics)} topics`);
|
|
1914
|
+
logger$9.log(`Manifest: ${relative(process.cwd(), manifestPath)}`);
|
|
2166
1915
|
}
|
|
2167
|
-
async function generateServerManifest(outputDir, appInfo, routes, subscribers) {
|
|
1916
|
+
async function generateServerManifest(outputDir, appInfo, routes, subscribers, queues = [], topics = []) {
|
|
2168
1917
|
const manifestDir = join(outputDir, "manifest");
|
|
2169
1918
|
await mkdir(manifestDir, { recursive: true });
|
|
2170
1919
|
const serverRoutes = mapRouteMetadata(filterAllRoutes(routes));
|
|
2171
1920
|
const serverSubscribers = mapSubscriberMetadata(subscribers);
|
|
1921
|
+
const serverQueues = mapQueueMetadata(queues);
|
|
1922
|
+
const serverTopics = mapTopicMetadata(topics);
|
|
2172
1923
|
const routesPartitioned = isPartitioned(serverRoutes);
|
|
2173
1924
|
const subscribersPartitioned = isPartitioned(serverSubscribers);
|
|
1925
|
+
const queuesPartitioned = isPartitioned(serverQueues);
|
|
1926
|
+
const topicsPartitioned = isPartitioned(serverTopics);
|
|
2174
1927
|
const content = `export const manifest = {
|
|
2175
1928
|
app: ${JSON.stringify(appInfo, null, 2)},
|
|
2176
1929
|
routes: ${serializeField(serverRoutes)},
|
|
2177
1930
|
subscribers: ${serializeField(serverSubscribers)},
|
|
1931
|
+
queues: ${serializeField(serverQueues)},
|
|
1932
|
+
topics: ${serializeField(serverTopics)},
|
|
2178
1933
|
} as const;
|
|
2179
1934
|
|
|
2180
1935
|
// Derived types
|
|
2181
1936
|
${generateDerivedType("routes", "Route", routesPartitioned)}
|
|
2182
1937
|
${generateDerivedType("subscribers", "Subscriber", subscribersPartitioned)}
|
|
1938
|
+
${generateDerivedType("queues", "Queue", queuesPartitioned)}
|
|
1939
|
+
${generateDerivedType("topics", "Topic", topicsPartitioned)}
|
|
2183
1940
|
|
|
2184
1941
|
// Useful union types
|
|
2185
1942
|
export type Authorizer = Route['authorizer'];
|
|
@@ -2188,8 +1945,8 @@ export type RoutePath = Route['path'];
|
|
|
2188
1945
|
`;
|
|
2189
1946
|
const manifestPath = join(manifestDir, "server.ts");
|
|
2190
1947
|
await writeFile(manifestPath, content);
|
|
2191
|
-
logger$
|
|
2192
|
-
logger$
|
|
1948
|
+
logger$9.log(`Generated server manifest with ${countItems(serverRoutes)} routes, ${countItems(serverSubscribers)} subscribers, ${countItems(serverQueues)} queues, ${countItems(serverTopics)} topics`);
|
|
1949
|
+
logger$9.log(`Manifest: ${relative(process.cwd(), manifestPath)}`);
|
|
2193
1950
|
}
|
|
2194
1951
|
/**
|
|
2195
1952
|
* Filter out 'ALL' method routes from a manifest field (flat or partitioned).
|
|
@@ -2227,6 +1984,23 @@ function mapSubscriberMetadata(subscribers) {
|
|
|
2227
1984
|
for (const [partition, partitionSubs] of Object.entries(subscribers)) result[partition] = partitionSubs.map(mapFn);
|
|
2228
1985
|
return result;
|
|
2229
1986
|
}
|
|
1987
|
+
function mapQueueMetadata(queues) {
|
|
1988
|
+
const mapFn = (q) => ({ name: q.name });
|
|
1989
|
+
if (Array.isArray(queues)) return queues.map(mapFn);
|
|
1990
|
+
const result = {};
|
|
1991
|
+
for (const [partition, partitionQueues] of Object.entries(queues)) result[partition] = partitionQueues.map(mapFn);
|
|
1992
|
+
return result;
|
|
1993
|
+
}
|
|
1994
|
+
function mapTopicMetadata(topics) {
|
|
1995
|
+
const mapFn = (t) => ({
|
|
1996
|
+
name: t.name,
|
|
1997
|
+
events: t.events
|
|
1998
|
+
});
|
|
1999
|
+
if (Array.isArray(topics)) return topics.map(mapFn);
|
|
2000
|
+
const result = {};
|
|
2001
|
+
for (const [partition, partitionTopics] of Object.entries(topics)) result[partition] = partitionTopics.map(mapFn);
|
|
2002
|
+
return result;
|
|
2003
|
+
}
|
|
2230
2004
|
|
|
2231
2005
|
//#endregion
|
|
2232
2006
|
//#region src/build/partitions.ts
|
|
@@ -2255,7 +2029,7 @@ function groupInfosByPartition(infos, constructs) {
|
|
|
2255
2029
|
|
|
2256
2030
|
//#endregion
|
|
2257
2031
|
//#region src/build/index.ts
|
|
2258
|
-
const logger$
|
|
2032
|
+
const logger$8 = console;
|
|
2259
2033
|
async function buildCommand(options) {
|
|
2260
2034
|
const loadedConfig = await loadWorkspaceConfig();
|
|
2261
2035
|
if (loadedConfig.type === "workspace") {
|
|
@@ -2263,30 +2037,30 @@ async function buildCommand(options) {
|
|
|
2263
2037
|
const workspaceRoot = resolve(loadedConfig.workspace.root);
|
|
2264
2038
|
const isAtWorkspaceRoot = cwd === workspaceRoot;
|
|
2265
2039
|
if (isAtWorkspaceRoot) {
|
|
2266
|
-
logger$
|
|
2040
|
+
logger$8.log("📦 Detected workspace configuration");
|
|
2267
2041
|
return workspaceBuildCommand(loadedConfig.workspace, options);
|
|
2268
2042
|
}
|
|
2269
2043
|
}
|
|
2270
|
-
const config
|
|
2271
|
-
const resolved = resolveProviders(config
|
|
2272
|
-
const productionConfigFromGkm = getProductionConfigFromGkm(config
|
|
2044
|
+
const config = loadedConfig.type === "workspace" ? (await loadAppConfig()).gkmConfig : await loadConfig();
|
|
2045
|
+
const resolved = resolveProviders(config, options);
|
|
2046
|
+
const productionConfigFromGkm = getProductionConfigFromGkm(config);
|
|
2273
2047
|
const production = normalizeProductionConfig(options.production ?? false, productionConfigFromGkm);
|
|
2274
|
-
if (production) logger$
|
|
2275
|
-
logger$
|
|
2276
|
-
logger$
|
|
2277
|
-
if (config
|
|
2278
|
-
if (config
|
|
2279
|
-
if (config
|
|
2280
|
-
logger$
|
|
2281
|
-
const { path: envParserPath, importPattern: envParserImportPattern } = parseModuleConfig(config
|
|
2282
|
-
const { path: loggerPath, importPattern: loggerImportPattern } = parseModuleConfig(config
|
|
2283
|
-
const telescope = production ? void 0 : normalizeTelescopeConfig(config
|
|
2284
|
-
if (telescope) logger$
|
|
2285
|
-
const studio = production ? void 0 : normalizeStudioConfig(config
|
|
2286
|
-
if (studio) logger$
|
|
2287
|
-
const hooks = normalizeHooksConfig(config
|
|
2288
|
-
if (hooks) logger$
|
|
2289
|
-
const services = config
|
|
2048
|
+
if (production) logger$8.log(`🏭 Building for PRODUCTION`);
|
|
2049
|
+
logger$8.log(`Building with providers: ${resolved.providers.join(", ")}`);
|
|
2050
|
+
logger$8.log(`Loading routes from: ${formatRoutes(config.routes)}`);
|
|
2051
|
+
if (config.functions) logger$8.log(`Loading functions from: ${formatRoutes(config.functions)}`);
|
|
2052
|
+
if (config.crons) logger$8.log(`Loading crons from: ${formatRoutes(config.crons)}`);
|
|
2053
|
+
if (config.subscribers) logger$8.log(`Loading subscribers from: ${formatRoutes(config.subscribers)}`);
|
|
2054
|
+
logger$8.log(`Using envParser: ${config.envParser}`);
|
|
2055
|
+
const { path: envParserPath, importPattern: envParserImportPattern } = parseModuleConfig(config.envParser, "envParser");
|
|
2056
|
+
const { path: loggerPath, importPattern: loggerImportPattern } = parseModuleConfig(config.logger, "logger");
|
|
2057
|
+
const telescope = production ? void 0 : normalizeTelescopeConfig(config.telescope);
|
|
2058
|
+
if (telescope) logger$8.log(`🔭 Telescope enabled at ${telescope.path}`);
|
|
2059
|
+
const studio = production ? void 0 : normalizeStudioConfig(config.studio);
|
|
2060
|
+
if (studio) logger$8.log(`🗄️ Studio enabled at ${studio.path}`);
|
|
2061
|
+
const hooks = normalizeHooksConfig(config.hooks);
|
|
2062
|
+
if (hooks) logger$8.log(`🪝 Server hooks enabled`);
|
|
2063
|
+
const services = config.docker?.compose?.services;
|
|
2290
2064
|
const dockerServices = services ? Array.isArray(services) ? {
|
|
2291
2065
|
postgres: services.includes("postgres"),
|
|
2292
2066
|
redis: services.includes("redis"),
|
|
@@ -2312,47 +2086,57 @@ async function buildCommand(options) {
|
|
|
2312
2086
|
const functionGenerator = new FunctionGenerator();
|
|
2313
2087
|
const cronGenerator = new CronGenerator();
|
|
2314
2088
|
const subscriberGenerator = new SubscriberGenerator();
|
|
2315
|
-
const
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
config
|
|
2089
|
+
const queueGenerator = new QueueGenerator();
|
|
2090
|
+
const topicGenerator = new TopicGenerator();
|
|
2091
|
+
const [allEndpoints, allFunctions, allCrons, allSubscribers, allQueues, allTopics] = await Promise.all([
|
|
2092
|
+
endpointGenerator.load(config.routes),
|
|
2093
|
+
config.functions ? functionGenerator.load(config.functions) : [],
|
|
2094
|
+
config.crons ? cronGenerator.load(config.crons) : [],
|
|
2095
|
+
config.subscribers ? subscriberGenerator.load(config.subscribers) : [],
|
|
2096
|
+
config.queues ? queueGenerator.load(config.queues) : [],
|
|
2097
|
+
config.topics ? topicGenerator.load(config.topics) : []
|
|
2320
2098
|
]);
|
|
2321
|
-
logger$
|
|
2322
|
-
logger$
|
|
2323
|
-
logger$
|
|
2324
|
-
logger$
|
|
2325
|
-
|
|
2326
|
-
|
|
2099
|
+
logger$8.log(`Found ${allEndpoints.length} endpoints`);
|
|
2100
|
+
logger$8.log(`Found ${allFunctions.length} functions`);
|
|
2101
|
+
logger$8.log(`Found ${allCrons.length} crons`);
|
|
2102
|
+
logger$8.log(`Found ${allSubscribers.length} subscribers`);
|
|
2103
|
+
logger$8.log(`Found ${allQueues.length} queues`);
|
|
2104
|
+
logger$8.log(`Found ${allTopics.length} topics`);
|
|
2105
|
+
if (allEndpoints.length === 0 && allFunctions.length === 0 && allCrons.length === 0 && allSubscribers.length === 0 && allQueues.length === 0 && allTopics.length === 0) {
|
|
2106
|
+
logger$8.log("No endpoints, functions, crons, subscribers, queues, or topics found to process");
|
|
2327
2107
|
return {};
|
|
2328
2108
|
}
|
|
2329
2109
|
const rootOutputDir = join(process.cwd(), ".gkm");
|
|
2330
2110
|
await mkdir(rootOutputDir, { recursive: true });
|
|
2331
2111
|
let result = {};
|
|
2332
2112
|
for (const provider of resolved.providers) {
|
|
2333
|
-
const providerResult = await buildForProvider(provider, buildContext, rootOutputDir, endpointGenerator, functionGenerator, cronGenerator, subscriberGenerator, allEndpoints, allFunctions, allCrons, allSubscribers, resolved.enableOpenApi, options.skipBundle ?? false, options.stage);
|
|
2113
|
+
const providerResult = await buildForProvider(provider, buildContext, rootOutputDir, endpointGenerator, functionGenerator, cronGenerator, subscriberGenerator, queueGenerator, topicGenerator, allEndpoints, allFunctions, allCrons, allSubscribers, allQueues, allTopics, resolved.enableOpenApi, options.skipBundle ?? false, options.stage);
|
|
2334
2114
|
if (providerResult.masterKey) result = providerResult;
|
|
2335
2115
|
}
|
|
2336
|
-
await generateOpenApi(config
|
|
2116
|
+
await generateOpenApi(config, { bustCache: true });
|
|
2337
2117
|
return result;
|
|
2338
2118
|
}
|
|
2339
|
-
async function buildForProvider(provider, context, rootOutputDir, endpointGenerator, functionGenerator, cronGenerator, subscriberGenerator, endpoints, functions, crons, subscribers, enableOpenApi, skipBundle, stage) {
|
|
2119
|
+
async function buildForProvider(provider, context, rootOutputDir, endpointGenerator, functionGenerator, cronGenerator, subscriberGenerator, queueGenerator, topicGenerator, endpoints, functions, crons, subscribers, queues, topics, enableOpenApi, skipBundle, stage) {
|
|
2340
2120
|
const outputDir = join(process.cwd(), ".gkm", provider);
|
|
2341
2121
|
await mkdir(outputDir, { recursive: true });
|
|
2342
|
-
logger$
|
|
2343
|
-
const [routes, functionInfos, cronInfos, subscriberInfos] = await Promise.all([
|
|
2122
|
+
logger$8.log(`\nGenerating handlers for provider: ${provider}`);
|
|
2123
|
+
const [routes, functionInfos, cronInfos, subscriberInfos, queueInfos, topicInfos] = await Promise.all([
|
|
2344
2124
|
endpointGenerator.build(context, endpoints, outputDir, {
|
|
2345
2125
|
provider,
|
|
2346
2126
|
enableOpenApi
|
|
2347
2127
|
}),
|
|
2348
2128
|
functionGenerator.build(context, functions, outputDir, { provider }),
|
|
2349
2129
|
cronGenerator.build(context, crons, outputDir, { provider }),
|
|
2350
|
-
subscriberGenerator.build(context, subscribers, outputDir, { provider })
|
|
2130
|
+
subscriberGenerator.build(context, subscribers, outputDir, { provider }),
|
|
2131
|
+
queueGenerator.build(context, queues, outputDir, { provider }),
|
|
2132
|
+
topicGenerator.build(context, topics, outputDir, { provider })
|
|
2351
2133
|
]);
|
|
2352
|
-
logger$
|
|
2134
|
+
logger$8.log(`Generated ${routes.length} routes, ${functionInfos.length} functions, ${cronInfos.length} crons, ${subscriberInfos.length} subscribers, ${queueInfos.length} queues, ${topicInfos.length} topics for ${provider}`);
|
|
2353
2135
|
const manifestRoutes = assembleManifestField(routes, endpoints);
|
|
2354
2136
|
const manifestFunctions = assembleManifestField(functionInfos, functions);
|
|
2355
2137
|
const manifestCrons = assembleManifestField(cronInfos, crons);
|
|
2138
|
+
const manifestQueues = assembleManifestField(queueInfos, queues);
|
|
2139
|
+
const manifestTopics = assembleManifestField(topicInfos, topics);
|
|
2356
2140
|
const manifestSubscribers = assembleManifestField(subscriberInfos, subscribers);
|
|
2357
2141
|
if (provider === "server") {
|
|
2358
2142
|
const routeMetadata = await Promise.all(endpoints.map(async ({ construct }) => ({
|
|
@@ -2366,16 +2150,17 @@ async function buildForProvider(provider, context, rootOutputDir, endpointGenera
|
|
|
2366
2150
|
handler: relative(process.cwd(), join(outputDir, "app.ts")),
|
|
2367
2151
|
endpoints: relative(process.cwd(), join(outputDir, "endpoints.ts"))
|
|
2368
2152
|
};
|
|
2369
|
-
await generateServerManifest(rootOutputDir, appInfo, serverRouteField, manifestSubscribers);
|
|
2153
|
+
await generateServerManifest(rootOutputDir, appInfo, serverRouteField, manifestSubscribers, manifestQueues, manifestTopics);
|
|
2370
2154
|
let masterKey;
|
|
2371
2155
|
if (context.production?.bundle && !skipBundle) {
|
|
2372
|
-
logger$
|
|
2373
|
-
const { bundleServer } = await import("./bundler-
|
|
2156
|
+
logger$8.log(`\n📦 Bundling production server...`);
|
|
2157
|
+
const { bundleServer } = await import("./bundler-DkA4qzUD.mjs");
|
|
2374
2158
|
const allConstructs = [
|
|
2375
2159
|
...endpoints.map((e) => e.construct),
|
|
2376
2160
|
...functions.map((f) => f.construct),
|
|
2377
2161
|
...crons.map((c) => c.construct),
|
|
2378
|
-
...subscribers.map((s) => s.construct)
|
|
2162
|
+
...subscribers.map((s) => s.construct),
|
|
2163
|
+
...queues.map((q) => q.construct)
|
|
2379
2164
|
];
|
|
2380
2165
|
const dockerServices = context.dockerServices;
|
|
2381
2166
|
const bundleResult = await bundleServer({
|
|
@@ -2389,14 +2174,14 @@ async function buildForProvider(provider, context, rootOutputDir, endpointGenera
|
|
|
2389
2174
|
dockerServices
|
|
2390
2175
|
});
|
|
2391
2176
|
masterKey = bundleResult.masterKey;
|
|
2392
|
-
logger$
|
|
2177
|
+
logger$8.log(`✅ Bundle complete: .gkm/server/dist/server.mjs`);
|
|
2393
2178
|
if (masterKey) {
|
|
2394
|
-
logger$
|
|
2395
|
-
logger$
|
|
2179
|
+
logger$8.log(`\n🔐 Secrets encrypted for deployment`);
|
|
2180
|
+
logger$8.log(` Deploy with: GKM_MASTER_KEY=${masterKey}`);
|
|
2396
2181
|
}
|
|
2397
2182
|
}
|
|
2398
2183
|
return { masterKey };
|
|
2399
|
-
} else await generateAwsManifest(rootOutputDir, manifestRoutes, manifestFunctions, manifestCrons, manifestSubscribers);
|
|
2184
|
+
} else await generateAwsManifest(rootOutputDir, manifestRoutes, manifestFunctions, manifestCrons, manifestSubscribers, manifestQueues, manifestTopics);
|
|
2400
2185
|
return {};
|
|
2401
2186
|
}
|
|
2402
2187
|
/**
|
|
@@ -2429,17 +2214,17 @@ async function workspaceBuildCommand(workspace, options) {
|
|
|
2429
2214
|
const apps = Object.entries(workspace.apps);
|
|
2430
2215
|
const backendApps = apps.filter(([, app]) => app.type === "backend");
|
|
2431
2216
|
const frontendApps = apps.filter(([, app]) => app.type === "web");
|
|
2432
|
-
logger$
|
|
2433
|
-
logger$
|
|
2434
|
-
logger$
|
|
2435
|
-
if (options.production) logger$
|
|
2217
|
+
logger$8.log(`\n🏗️ Building workspace: ${workspace.name}`);
|
|
2218
|
+
logger$8.log(` Backend apps: ${backendApps.map(([name$1]) => name$1).join(", ") || "none"}`);
|
|
2219
|
+
logger$8.log(` Frontend apps: ${frontendApps.map(([name$1]) => name$1).join(", ") || "none"}`);
|
|
2220
|
+
if (options.production) logger$8.log(` 🏭 Production mode enabled`);
|
|
2436
2221
|
const buildOrder = getAppBuildOrder(workspace);
|
|
2437
|
-
logger$
|
|
2222
|
+
logger$8.log(` Build order: ${buildOrder.join(" → ")}`);
|
|
2438
2223
|
const pm = detectPackageManager$2();
|
|
2439
|
-
logger$
|
|
2224
|
+
logger$8.log(`\n📦 Using ${pm} with Turbo for parallel builds...\n`);
|
|
2440
2225
|
try {
|
|
2441
2226
|
const turboCommand = getTurboCommand(pm);
|
|
2442
|
-
logger$
|
|
2227
|
+
logger$8.log(`Running: ${turboCommand}`);
|
|
2443
2228
|
await new Promise((resolve$1, reject) => {
|
|
2444
2229
|
const child = spawn(turboCommand, {
|
|
2445
2230
|
shell: true,
|
|
@@ -2467,16 +2252,16 @@ async function workspaceBuildCommand(workspace, options) {
|
|
|
2467
2252
|
outputPath
|
|
2468
2253
|
});
|
|
2469
2254
|
}
|
|
2470
|
-
logger$
|
|
2255
|
+
logger$8.log(`\n✅ Workspace build complete!`);
|
|
2471
2256
|
await openapiCommand({ cwd: workspace.root });
|
|
2472
|
-
logger$
|
|
2257
|
+
logger$8.log(`\n📋 Build Summary:`);
|
|
2473
2258
|
for (const result of results) {
|
|
2474
2259
|
const icon = result.type === "backend" ? "⚙️" : result.type === "mobile" ? "📱" : "🌐";
|
|
2475
|
-
logger$
|
|
2260
|
+
logger$8.log(` ${icon} ${result.appName}: ${result.outputPath || "built"}`);
|
|
2476
2261
|
}
|
|
2477
2262
|
} catch (error) {
|
|
2478
2263
|
const errorMessage = error instanceof Error ? error.message : "Build failed";
|
|
2479
|
-
logger$
|
|
2264
|
+
logger$8.log(`\n❌ Build failed: ${errorMessage}`);
|
|
2480
2265
|
for (const [appName, app] of apps) results.push({
|
|
2481
2266
|
appName,
|
|
2482
2267
|
type: app.type,
|
|
@@ -2698,17 +2483,17 @@ function isDnsProvider(value) {
|
|
|
2698
2483
|
* - Custom: Use provided DnsProvider implementation
|
|
2699
2484
|
*/
|
|
2700
2485
|
async function createDnsProvider(options) {
|
|
2701
|
-
const { config
|
|
2702
|
-
if (config
|
|
2703
|
-
if (isDnsProvider(config
|
|
2704
|
-
const provider = config
|
|
2486
|
+
const { config } = options;
|
|
2487
|
+
if (config.provider === "manual") return null;
|
|
2488
|
+
if (isDnsProvider(config.provider)) return config.provider;
|
|
2489
|
+
const provider = config.provider;
|
|
2705
2490
|
if (provider === "hostinger") {
|
|
2706
|
-
const { HostingerProvider } = await import("./HostingerProvider-
|
|
2491
|
+
const { HostingerProvider } = await import("./HostingerProvider-B2ZYz7Yc.mjs");
|
|
2707
2492
|
return new HostingerProvider();
|
|
2708
2493
|
}
|
|
2709
2494
|
if (provider === "route53") {
|
|
2710
|
-
const { Route53Provider } = await import("./Route53Provider-
|
|
2711
|
-
const route53Config = config
|
|
2495
|
+
const { Route53Provider } = await import("./Route53Provider-tlgBzrRQ.mjs");
|
|
2496
|
+
const route53Config = config;
|
|
2712
2497
|
return new Route53Provider({
|
|
2713
2498
|
region: route53Config.region,
|
|
2714
2499
|
profile: route53Config.profile,
|
|
@@ -2716,27 +2501,27 @@ async function createDnsProvider(options) {
|
|
|
2716
2501
|
});
|
|
2717
2502
|
}
|
|
2718
2503
|
if (provider === "cloudflare") throw new Error("Cloudflare DNS provider not yet implemented");
|
|
2719
|
-
throw new Error(`Unknown DNS provider: ${JSON.stringify(config
|
|
2504
|
+
throw new Error(`Unknown DNS provider: ${JSON.stringify(config)}`);
|
|
2720
2505
|
}
|
|
2721
2506
|
|
|
2722
2507
|
//#endregion
|
|
2723
2508
|
//#region src/deploy/dns/index.ts
|
|
2724
|
-
const logger$
|
|
2509
|
+
const logger$7 = console;
|
|
2725
2510
|
/**
|
|
2726
2511
|
* Check if DNS config is legacy format (single domain with `domain` property)
|
|
2727
2512
|
*/
|
|
2728
|
-
function isLegacyDnsConfig(config
|
|
2729
|
-
return typeof config
|
|
2513
|
+
function isLegacyDnsConfig(config) {
|
|
2514
|
+
return typeof config === "object" && config !== null && "provider" in config && "domain" in config;
|
|
2730
2515
|
}
|
|
2731
2516
|
/**
|
|
2732
2517
|
* Normalize DNS config to new multi-domain format
|
|
2733
2518
|
*/
|
|
2734
|
-
function normalizeDnsConfig(config
|
|
2735
|
-
if (isLegacyDnsConfig(config
|
|
2736
|
-
const { domain,...providerConfig } = config
|
|
2519
|
+
function normalizeDnsConfig(config) {
|
|
2520
|
+
if (isLegacyDnsConfig(config)) {
|
|
2521
|
+
const { domain,...providerConfig } = config;
|
|
2737
2522
|
return { [domain]: providerConfig };
|
|
2738
2523
|
}
|
|
2739
|
-
return config
|
|
2524
|
+
return config;
|
|
2740
2525
|
}
|
|
2741
2526
|
/**
|
|
2742
2527
|
* Find the root domain for a hostname from available DNS configs
|
|
@@ -2758,7 +2543,7 @@ function groupHostnamesByDomain(appHostnames, dnsConfig) {
|
|
|
2758
2543
|
for (const [appName, hostname] of appHostnames) {
|
|
2759
2544
|
const rootDomain = findRootDomain(hostname, dnsConfig);
|
|
2760
2545
|
if (!rootDomain) {
|
|
2761
|
-
logger$
|
|
2546
|
+
logger$7.log(` ⚠ No DNS config found for hostname: ${hostname}`);
|
|
2762
2547
|
continue;
|
|
2763
2548
|
}
|
|
2764
2549
|
if (!grouped.has(rootDomain)) grouped.set(rootDomain, /* @__PURE__ */ new Map());
|
|
@@ -2810,10 +2595,10 @@ function generateRequiredRecords(appHostnames, rootDomain, serverIp) {
|
|
|
2810
2595
|
* Print DNS records table
|
|
2811
2596
|
*/
|
|
2812
2597
|
function printDnsRecordsTable(records, rootDomain) {
|
|
2813
|
-
logger$
|
|
2814
|
-
logger$
|
|
2815
|
-
logger$
|
|
2816
|
-
logger$
|
|
2598
|
+
logger$7.log(`\n 📋 DNS Records for ${rootDomain}:`);
|
|
2599
|
+
logger$7.log(" ┌─────────────────────────────────────┬──────┬─────────────────┬────────┐");
|
|
2600
|
+
logger$7.log(" │ Subdomain │ Type │ Value │ Status │");
|
|
2601
|
+
logger$7.log(" ├─────────────────────────────────────┼──────┼─────────────────┼────────┤");
|
|
2817
2602
|
for (const record of records) {
|
|
2818
2603
|
const subdomain = record.subdomain.padEnd(35);
|
|
2819
2604
|
const type$1 = record.type.padEnd(4);
|
|
@@ -2823,18 +2608,18 @@ function printDnsRecordsTable(records, rootDomain) {
|
|
|
2823
2608
|
else if (record.created) status = "✓ new";
|
|
2824
2609
|
else if (record.existed) status = "✓";
|
|
2825
2610
|
else status = "?";
|
|
2826
|
-
logger$
|
|
2611
|
+
logger$7.log(` │ ${subdomain} │ ${type$1} │ ${value} │ ${status.padEnd(6)} │`);
|
|
2827
2612
|
}
|
|
2828
|
-
logger$
|
|
2613
|
+
logger$7.log(" └─────────────────────────────────────┴──────┴─────────────────┴────────┘");
|
|
2829
2614
|
}
|
|
2830
2615
|
/**
|
|
2831
2616
|
* Print DNS records in a simple format for manual setup
|
|
2832
2617
|
*/
|
|
2833
2618
|
function printDnsRecordsSimple(records, rootDomain) {
|
|
2834
|
-
logger$
|
|
2835
|
-
logger$
|
|
2836
|
-
for (const record of records) logger$
|
|
2837
|
-
logger$
|
|
2619
|
+
logger$7.log("\n 📋 Required DNS Records:");
|
|
2620
|
+
logger$7.log(` Add these A records to your DNS provider (${rootDomain}):\n`);
|
|
2621
|
+
for (const record of records) logger$7.log(` ${record.subdomain} → ${record.value} (A record)`);
|
|
2622
|
+
logger$7.log("");
|
|
2838
2623
|
}
|
|
2839
2624
|
/**
|
|
2840
2625
|
* Create DNS records for a single domain using its configured provider
|
|
@@ -2846,7 +2631,7 @@ async function createDnsRecordsForDomain(records, rootDomain, providerConfig) {
|
|
|
2846
2631
|
provider = await createDnsProvider({ config: providerConfig });
|
|
2847
2632
|
} catch (error) {
|
|
2848
2633
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2849
|
-
logger$
|
|
2634
|
+
logger$7.log(` ⚠ Failed to create DNS provider for ${rootDomain}: ${message}`);
|
|
2850
2635
|
return records.map((r) => ({
|
|
2851
2636
|
...r,
|
|
2852
2637
|
error: message
|
|
@@ -2900,7 +2685,7 @@ async function createDnsRecordsForDomain(records, rootDomain, providerConfig) {
|
|
|
2900
2685
|
}
|
|
2901
2686
|
} catch (error) {
|
|
2902
2687
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2903
|
-
logger$
|
|
2688
|
+
logger$7.log(` ⚠ Failed to create DNS records for ${rootDomain}: ${message}`);
|
|
2904
2689
|
return records.map((r) => ({
|
|
2905
2690
|
hostname: r.hostname,
|
|
2906
2691
|
subdomain: r.subdomain,
|
|
@@ -2927,20 +2712,20 @@ async function createDnsRecordsForDomain(records, rootDomain, providerConfig) {
|
|
|
2927
2712
|
async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint, state) {
|
|
2928
2713
|
if (!dnsConfig) return null;
|
|
2929
2714
|
const normalizedConfig = normalizeDnsConfig(dnsConfig);
|
|
2930
|
-
logger$
|
|
2715
|
+
logger$7.log("\n🌐 Setting up DNS records...");
|
|
2931
2716
|
let serverIp;
|
|
2932
2717
|
try {
|
|
2933
2718
|
const endpointUrl = new URL(dokployEndpoint);
|
|
2934
2719
|
serverIp = await resolveHostnameToIp(endpointUrl.hostname);
|
|
2935
|
-
logger$
|
|
2720
|
+
logger$7.log(` Server IP: ${serverIp} (from ${endpointUrl.hostname})`);
|
|
2936
2721
|
} catch (error) {
|
|
2937
2722
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2938
|
-
logger$
|
|
2723
|
+
logger$7.log(` ⚠ Failed to resolve server IP: ${message}`);
|
|
2939
2724
|
return null;
|
|
2940
2725
|
}
|
|
2941
2726
|
const groupedHostnames = groupHostnamesByDomain(appHostnames, normalizedConfig);
|
|
2942
2727
|
if (groupedHostnames.size === 0) {
|
|
2943
|
-
logger$
|
|
2728
|
+
logger$7.log(" No DNS records needed (no hostnames match configured domains)");
|
|
2944
2729
|
return {
|
|
2945
2730
|
records: [],
|
|
2946
2731
|
success: true,
|
|
@@ -2952,22 +2737,22 @@ async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint, state) {
|
|
|
2952
2737
|
for (const [rootDomain, domainHostnames] of groupedHostnames) {
|
|
2953
2738
|
const providerConfig = normalizedConfig[rootDomain];
|
|
2954
2739
|
if (!providerConfig) {
|
|
2955
|
-
logger$
|
|
2740
|
+
logger$7.log(` ⚠ No provider config for ${rootDomain}`);
|
|
2956
2741
|
continue;
|
|
2957
2742
|
}
|
|
2958
2743
|
const providerName = typeof providerConfig.provider === "string" ? providerConfig.provider : "custom";
|
|
2959
2744
|
const requiredRecords = generateRequiredRecords(domainHostnames, rootDomain, serverIp);
|
|
2960
2745
|
if (requiredRecords.length === 0) continue;
|
|
2961
|
-
logger$
|
|
2746
|
+
logger$7.log(` Creating DNS records for ${rootDomain} (${providerName})...`);
|
|
2962
2747
|
const domainRecords = await createDnsRecordsForDomain(requiredRecords, rootDomain, providerConfig);
|
|
2963
2748
|
allRecords.push(...domainRecords);
|
|
2964
2749
|
const created = domainRecords.filter((r) => r.created).length;
|
|
2965
2750
|
const existed = domainRecords.filter((r) => r.existed).length;
|
|
2966
2751
|
const failed = domainRecords.filter((r) => r.error).length;
|
|
2967
|
-
if (created > 0) logger$
|
|
2968
|
-
if (existed > 0) logger$
|
|
2752
|
+
if (created > 0) logger$7.log(` ✓ Created ${created} DNS record(s) for ${rootDomain}`);
|
|
2753
|
+
if (existed > 0) logger$7.log(` ✓ ${existed} record(s) already exist for ${rootDomain}`);
|
|
2969
2754
|
if (failed > 0) {
|
|
2970
|
-
logger$
|
|
2755
|
+
logger$7.log(` ⚠ ${failed} record(s) failed for ${rootDomain}`);
|
|
2971
2756
|
hasFailures = true;
|
|
2972
2757
|
}
|
|
2973
2758
|
if (state) {
|
|
@@ -3004,10 +2789,10 @@ async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint, state) {
|
|
|
3004
2789
|
*/
|
|
3005
2790
|
async function verifyDnsRecords(appHostnames, serverIp, state) {
|
|
3006
2791
|
const results = [];
|
|
3007
|
-
logger$
|
|
2792
|
+
logger$7.log("\n🔍 Verifying DNS records...");
|
|
3008
2793
|
for (const [appName, hostname] of appHostnames) {
|
|
3009
2794
|
if (isDnsVerified(state, hostname, serverIp)) {
|
|
3010
|
-
logger$
|
|
2795
|
+
logger$7.log(` ✓ ${hostname} (previously verified)`);
|
|
3011
2796
|
results.push({
|
|
3012
2797
|
hostname,
|
|
3013
2798
|
appName,
|
|
@@ -3021,7 +2806,7 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
|
|
|
3021
2806
|
const resolvedIp = await resolveHostnameToIp(hostname);
|
|
3022
2807
|
if (resolvedIp === serverIp) {
|
|
3023
2808
|
setDnsVerification(state, hostname, serverIp);
|
|
3024
|
-
logger$
|
|
2809
|
+
logger$7.log(` ✓ ${hostname} → ${resolvedIp}`);
|
|
3025
2810
|
results.push({
|
|
3026
2811
|
hostname,
|
|
3027
2812
|
appName,
|
|
@@ -3030,7 +2815,7 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
|
|
|
3030
2815
|
expectedIp: serverIp
|
|
3031
2816
|
});
|
|
3032
2817
|
} else {
|
|
3033
|
-
logger$
|
|
2818
|
+
logger$7.log(` ⚠ ${hostname} resolves to ${resolvedIp}, expected ${serverIp}`);
|
|
3034
2819
|
results.push({
|
|
3035
2820
|
hostname,
|
|
3036
2821
|
appName,
|
|
@@ -3041,7 +2826,7 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
|
|
|
3041
2826
|
}
|
|
3042
2827
|
} catch (error) {
|
|
3043
2828
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
3044
|
-
logger$
|
|
2829
|
+
logger$7.log(` ⚠ ${hostname} DNS not propagated (${message})`);
|
|
3045
2830
|
results.push({
|
|
3046
2831
|
hostname,
|
|
3047
2832
|
appName,
|
|
@@ -3055,9 +2840,9 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
|
|
|
3055
2840
|
const skipped = results.filter((r) => r.skipped).length;
|
|
3056
2841
|
const pending = results.filter((r) => !r.verified).length;
|
|
3057
2842
|
if (pending > 0) {
|
|
3058
|
-
logger$
|
|
3059
|
-
logger$
|
|
3060
|
-
} else if (skipped > 0) logger$
|
|
2843
|
+
logger$7.log(`\n ${verified} verified, ${pending} pending propagation`);
|
|
2844
|
+
logger$7.log(" DNS changes may take 5-30 minutes to propagate");
|
|
2845
|
+
} else if (skipped > 0) logger$7.log(` ${verified} verified (${skipped} from cache)`);
|
|
3061
2846
|
return results;
|
|
3062
2847
|
}
|
|
3063
2848
|
|
|
@@ -3149,11 +2934,11 @@ function getDefaultImage(serviceName) {
|
|
|
3149
2934
|
function normalizeServices(services) {
|
|
3150
2935
|
const result = /* @__PURE__ */ new Map();
|
|
3151
2936
|
if (Array.isArray(services)) for (const name$1 of services) result.set(name$1, getDefaultImage(name$1));
|
|
3152
|
-
else for (const [name$1, config
|
|
2937
|
+
else for (const [name$1, config] of Object.entries(services)) {
|
|
3153
2938
|
const serviceName = name$1;
|
|
3154
|
-
if (config
|
|
3155
|
-
else if (config
|
|
3156
|
-
const serviceConfig = config
|
|
2939
|
+
if (config === true) result.set(serviceName, getDefaultImage(serviceName));
|
|
2940
|
+
else if (config && typeof config === "object") {
|
|
2941
|
+
const serviceConfig = config;
|
|
3157
2942
|
if (serviceConfig.image) result.set(serviceName, serviceConfig.image);
|
|
3158
2943
|
else {
|
|
3159
2944
|
const version$1 = serviceConfig.version ?? DEFAULT_SERVICE_VERSIONS[serviceName];
|
|
@@ -3586,22 +3371,22 @@ networks:
|
|
|
3586
3371
|
/**
|
|
3587
3372
|
* Get infrastructure service image with version.
|
|
3588
3373
|
*/
|
|
3589
|
-
function getInfraServiceImage(serviceName, config
|
|
3374
|
+
function getInfraServiceImage(serviceName, config) {
|
|
3590
3375
|
const defaults = {
|
|
3591
3376
|
postgres: "postgres:18-alpine",
|
|
3592
3377
|
redis: "redis:7-alpine",
|
|
3593
3378
|
minio: "minio/minio:latest"
|
|
3594
3379
|
};
|
|
3595
|
-
if (!config
|
|
3596
|
-
if (typeof config
|
|
3597
|
-
if (config
|
|
3598
|
-
if (config
|
|
3380
|
+
if (!config || config === true) return defaults[serviceName];
|
|
3381
|
+
if (typeof config === "object") {
|
|
3382
|
+
if (config.image) return config.image;
|
|
3383
|
+
if (config.version) {
|
|
3599
3384
|
const baseImages = {
|
|
3600
3385
|
postgres: "postgres",
|
|
3601
3386
|
redis: "redis",
|
|
3602
3387
|
minio: "minio/minio"
|
|
3603
3388
|
};
|
|
3604
|
-
return `${baseImages[serviceName]}:${config
|
|
3389
|
+
return `${baseImages[serviceName]}:${config.version}`;
|
|
3605
3390
|
}
|
|
3606
3391
|
}
|
|
3607
3392
|
return defaults[serviceName];
|
|
@@ -4094,8 +3879,8 @@ exec "$@"
|
|
|
4094
3879
|
/**
|
|
4095
3880
|
* Resolve Docker configuration from GkmConfig with defaults
|
|
4096
3881
|
*/
|
|
4097
|
-
function resolveDockerConfig$1(config
|
|
4098
|
-
const docker = config
|
|
3882
|
+
function resolveDockerConfig$1(config) {
|
|
3883
|
+
const docker = config.docker ?? {};
|
|
4099
3884
|
let defaultImageName = "api";
|
|
4100
3885
|
try {
|
|
4101
3886
|
const pkg$1 = __require(`${process.cwd()}/package.json`);
|
|
@@ -4528,7 +4313,7 @@ CMD ["nginx", "-g", "daemon off;"]
|
|
|
4528
4313
|
|
|
4529
4314
|
//#endregion
|
|
4530
4315
|
//#region src/docker/index.ts
|
|
4531
|
-
const logger$
|
|
4316
|
+
const logger$6 = console;
|
|
4532
4317
|
/**
|
|
4533
4318
|
* Docker command implementation
|
|
4534
4319
|
* Generates Dockerfile, docker-compose.yml, and related files
|
|
@@ -4539,12 +4324,12 @@ const logger$7 = console;
|
|
|
4539
4324
|
async function dockerCommand(options) {
|
|
4540
4325
|
const loadedConfig = await loadWorkspaceConfig();
|
|
4541
4326
|
if (loadedConfig.type === "workspace") {
|
|
4542
|
-
logger$
|
|
4327
|
+
logger$6.log("📦 Detected workspace configuration");
|
|
4543
4328
|
return workspaceDockerCommand(loadedConfig.workspace, options);
|
|
4544
4329
|
}
|
|
4545
|
-
const config
|
|
4546
|
-
const dockerConfig = resolveDockerConfig$1(config
|
|
4547
|
-
const serverConfig = typeof config
|
|
4330
|
+
const config = await loadConfig();
|
|
4331
|
+
const dockerConfig = resolveDockerConfig$1(config);
|
|
4332
|
+
const serverConfig = typeof config.providers?.server === "object" ? config.providers.server : void 0;
|
|
4548
4333
|
const healthCheckPath = serverConfig?.production?.healthCheck ?? "/health";
|
|
4549
4334
|
const useSlim = options.slim === true;
|
|
4550
4335
|
if (useSlim) {
|
|
@@ -4560,14 +4345,14 @@ async function dockerCommand(options) {
|
|
|
4560
4345
|
let useTurbo = options.turbo ?? false;
|
|
4561
4346
|
if (inMonorepo && !useSlim) if (hasTurbo) {
|
|
4562
4347
|
useTurbo = true;
|
|
4563
|
-
logger$
|
|
4348
|
+
logger$6.log(" Detected monorepo with turbo.json - using turbo prune");
|
|
4564
4349
|
} 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");
|
|
4565
4350
|
let turboPackage = options.turboPackage ?? dockerConfig.imageName;
|
|
4566
4351
|
if (useTurbo && !options.turboPackage) try {
|
|
4567
4352
|
const pkg$1 = __require(`${process.cwd()}/package.json`);
|
|
4568
4353
|
if (pkg$1.name) {
|
|
4569
4354
|
turboPackage = pkg$1.name;
|
|
4570
|
-
logger$
|
|
4355
|
+
logger$6.log(` Turbo package: ${turboPackage}`);
|
|
4571
4356
|
}
|
|
4572
4357
|
} catch {}
|
|
4573
4358
|
const templateOptions = {
|
|
@@ -4584,7 +4369,7 @@ async function dockerCommand(options) {
|
|
|
4584
4369
|
const dockerMode = useSlim ? "slim" : useTurbo ? "turbo" : "multi-stage";
|
|
4585
4370
|
const dockerfilePath = join(dockerDir, "Dockerfile");
|
|
4586
4371
|
await writeFile(dockerfilePath, dockerfile);
|
|
4587
|
-
logger$
|
|
4372
|
+
logger$6.log(`Generated: .gkm/docker/Dockerfile (${dockerMode}, ${packageManager})`);
|
|
4588
4373
|
const composeOptions = {
|
|
4589
4374
|
imageName: dockerConfig.imageName,
|
|
4590
4375
|
registry: options.registry ?? dockerConfig.registry,
|
|
@@ -4596,15 +4381,15 @@ async function dockerCommand(options) {
|
|
|
4596
4381
|
const dockerCompose = hasServices ? generateDockerCompose(composeOptions) : generateMinimalDockerCompose(composeOptions);
|
|
4597
4382
|
const composePath = join(dockerDir, "docker-compose.yml");
|
|
4598
4383
|
await writeFile(composePath, dockerCompose);
|
|
4599
|
-
logger$
|
|
4384
|
+
logger$6.log("Generated: .gkm/docker/docker-compose.yml");
|
|
4600
4385
|
const dockerignore = generateDockerignore();
|
|
4601
4386
|
const dockerignorePath = join(process.cwd(), ".dockerignore");
|
|
4602
4387
|
await writeFile(dockerignorePath, dockerignore);
|
|
4603
|
-
logger$
|
|
4388
|
+
logger$6.log("Generated: .dockerignore (project root)");
|
|
4604
4389
|
const entrypoint = generateDockerEntrypoint();
|
|
4605
4390
|
const entrypointPath = join(dockerDir, "docker-entrypoint.sh");
|
|
4606
4391
|
await writeFile(entrypointPath, entrypoint);
|
|
4607
|
-
logger$
|
|
4392
|
+
logger$6.log("Generated: .gkm/docker/docker-entrypoint.sh");
|
|
4608
4393
|
const result = {
|
|
4609
4394
|
dockerfile: dockerfilePath,
|
|
4610
4395
|
dockerCompose: composePath,
|
|
@@ -4623,13 +4408,13 @@ async function dockerCommand(options) {
|
|
|
4623
4408
|
function ensureLockfile(cwd) {
|
|
4624
4409
|
const lockfilePath = findLockfilePath(cwd);
|
|
4625
4410
|
if (!lockfilePath) {
|
|
4626
|
-
logger$
|
|
4411
|
+
logger$6.warn("\n⚠️ No lockfile found. Docker build may fail or use stale dependencies.");
|
|
4627
4412
|
return null;
|
|
4628
4413
|
}
|
|
4629
4414
|
const lockfileName = basename(lockfilePath);
|
|
4630
4415
|
const localLockfile = join(cwd, lockfileName);
|
|
4631
4416
|
if (lockfilePath === localLockfile) return null;
|
|
4632
|
-
logger$
|
|
4417
|
+
logger$6.log(` Copying ${lockfileName} from monorepo root...`);
|
|
4633
4418
|
copyFileSync(lockfilePath, localLockfile);
|
|
4634
4419
|
return () => {
|
|
4635
4420
|
try {
|
|
@@ -4645,7 +4430,7 @@ async function buildDockerImage(imageName, options) {
|
|
|
4645
4430
|
const tag = options.tag ?? "latest";
|
|
4646
4431
|
const registry = options.registry;
|
|
4647
4432
|
const fullImageName = registry ? `${registry}/${imageName}:${tag}` : `${imageName}:${tag}`;
|
|
4648
|
-
logger$
|
|
4433
|
+
logger$6.log(`\n🐳 Building Docker image: ${fullImageName}`);
|
|
4649
4434
|
const cwd = process.cwd();
|
|
4650
4435
|
const cleanup = ensureLockfile(cwd);
|
|
4651
4436
|
try {
|
|
@@ -4657,7 +4442,7 @@ async function buildDockerImage(imageName, options) {
|
|
|
4657
4442
|
DOCKER_BUILDKIT: "1"
|
|
4658
4443
|
}
|
|
4659
4444
|
});
|
|
4660
|
-
logger$
|
|
4445
|
+
logger$6.log(`✅ Docker image built: ${fullImageName}`);
|
|
4661
4446
|
} catch (error) {
|
|
4662
4447
|
throw new Error(`Failed to build Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
4663
4448
|
} finally {
|
|
@@ -4672,13 +4457,13 @@ async function pushDockerImage(imageName, options) {
|
|
|
4672
4457
|
const registry = options.registry;
|
|
4673
4458
|
if (!registry) throw new Error("Registry is required to push Docker image. Use --registry or configure docker.registry in gkm.config.ts");
|
|
4674
4459
|
const fullImageName = `${registry}/${imageName}:${tag}`;
|
|
4675
|
-
logger$
|
|
4460
|
+
logger$6.log(`\n🚀 Pushing Docker image: ${fullImageName}`);
|
|
4676
4461
|
try {
|
|
4677
4462
|
execSync(`docker push ${fullImageName}`, {
|
|
4678
4463
|
cwd: process.cwd(),
|
|
4679
4464
|
stdio: "inherit"
|
|
4680
4465
|
});
|
|
4681
|
-
logger$
|
|
4466
|
+
logger$6.log(`✅ Docker image pushed: ${fullImageName}`);
|
|
4682
4467
|
} catch (error) {
|
|
4683
4468
|
throw new Error(`Failed to push Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
4684
4469
|
}
|
|
@@ -4704,14 +4489,14 @@ function getAppPackageName(appPath) {
|
|
|
4704
4489
|
async function workspaceDockerCommand(workspace, options) {
|
|
4705
4490
|
const results = [];
|
|
4706
4491
|
const apps = Object.entries(workspace.apps);
|
|
4707
|
-
logger$
|
|
4492
|
+
logger$6.log(`\n🐳 Generating Dockerfiles for workspace: ${workspace.name}`);
|
|
4708
4493
|
const dockerDir = join(workspace.root, ".gkm", "docker");
|
|
4709
4494
|
await mkdir(dockerDir, { recursive: true });
|
|
4710
4495
|
const packageManager = detectPackageManager$1(workspace.root);
|
|
4711
|
-
logger$
|
|
4496
|
+
logger$6.log(` Package manager: ${packageManager}`);
|
|
4712
4497
|
for (const [appName, app] of apps) {
|
|
4713
4498
|
if (app.type === "mobile") {
|
|
4714
|
-
logger$
|
|
4499
|
+
logger$6.log(`\n 📱 Skipping Docker for ${appName} (mobile app — deploy via framework toolchain)`);
|
|
4715
4500
|
continue;
|
|
4716
4501
|
}
|
|
4717
4502
|
const appPath = app.path;
|
|
@@ -4720,7 +4505,7 @@ async function workspaceDockerCommand(workspace, options) {
|
|
|
4720
4505
|
const imageName = appName;
|
|
4721
4506
|
const hasEntry = !!app.entry;
|
|
4722
4507
|
const buildType = hasEntry ? "entry" : app.type;
|
|
4723
|
-
logger$
|
|
4508
|
+
logger$6.log(`\n 📄 Generating Dockerfile for ${appName} (${buildType})`);
|
|
4724
4509
|
let dockerfile;
|
|
4725
4510
|
if (app.type === "web") {
|
|
4726
4511
|
const publicUrlArgs = getPublicUrlArgNames(app);
|
|
@@ -4764,7 +4549,7 @@ async function workspaceDockerCommand(workspace, options) {
|
|
|
4764
4549
|
});
|
|
4765
4550
|
const dockerfilePath = join(dockerDir, `Dockerfile.${appName}`);
|
|
4766
4551
|
await writeFile(dockerfilePath, dockerfile);
|
|
4767
|
-
logger$
|
|
4552
|
+
logger$6.log(` Generated: .gkm/docker/Dockerfile.${appName}`);
|
|
4768
4553
|
results.push({
|
|
4769
4554
|
appName,
|
|
4770
4555
|
type: app.type,
|
|
@@ -4775,19 +4560,19 @@ async function workspaceDockerCommand(workspace, options) {
|
|
|
4775
4560
|
const dockerignore = generateDockerignore();
|
|
4776
4561
|
const dockerignorePath = join(workspace.root, ".dockerignore");
|
|
4777
4562
|
await writeFile(dockerignorePath, dockerignore);
|
|
4778
|
-
logger$
|
|
4563
|
+
logger$6.log(`\n Generated: .dockerignore (workspace root)`);
|
|
4779
4564
|
const dockerCompose = generateWorkspaceCompose(workspace, { registry: options.registry });
|
|
4780
4565
|
const composePath = join(dockerDir, "docker-compose.yml");
|
|
4781
4566
|
await writeFile(composePath, dockerCompose);
|
|
4782
|
-
logger$
|
|
4783
|
-
logger$
|
|
4784
|
-
logger$
|
|
4567
|
+
logger$6.log(` Generated: .gkm/docker/docker-compose.yml`);
|
|
4568
|
+
logger$6.log(`\n✅ Generated ${results.length} Dockerfile(s) + docker-compose.yml`);
|
|
4569
|
+
logger$6.log("\n📋 Build commands:");
|
|
4785
4570
|
for (const result of results) {
|
|
4786
4571
|
const icon = result.type === "backend" ? "⚙️" : result.type === "mobile" ? "📱" : "🌐";
|
|
4787
|
-
logger$
|
|
4572
|
+
logger$6.log(` ${icon} docker build -f .gkm/docker/Dockerfile.${result.appName} -t ${result.imageName} .`);
|
|
4788
4573
|
}
|
|
4789
|
-
logger$
|
|
4790
|
-
logger$
|
|
4574
|
+
logger$6.log("\n📋 Run all services:");
|
|
4575
|
+
logger$6.log(" docker compose -f .gkm/docker/docker-compose.yml up --build");
|
|
4791
4576
|
return {
|
|
4792
4577
|
apps: results,
|
|
4793
4578
|
dockerCompose: composePath,
|
|
@@ -4827,7 +4612,7 @@ function getAppNameFromPackageJson() {
|
|
|
4827
4612
|
} catch {}
|
|
4828
4613
|
return void 0;
|
|
4829
4614
|
}
|
|
4830
|
-
const logger$
|
|
4615
|
+
const logger$5 = console;
|
|
4831
4616
|
/**
|
|
4832
4617
|
* Get the full image reference
|
|
4833
4618
|
*/
|
|
@@ -4842,18 +4627,18 @@ function getImageRef(registry, imageName, tag) {
|
|
|
4842
4627
|
* @param buildArgs - Build arguments to pass to docker build
|
|
4843
4628
|
*/
|
|
4844
4629
|
async function buildImage(imageRef, appName, buildArgs) {
|
|
4845
|
-
logger$
|
|
4630
|
+
logger$5.log(`\n🔨 Building Docker image: ${imageRef}`);
|
|
4846
4631
|
const cwd = process.cwd();
|
|
4847
4632
|
const lockfilePath = findLockfilePath(cwd);
|
|
4848
4633
|
const lockfileDir = lockfilePath ? dirname(lockfilePath) : cwd;
|
|
4849
4634
|
const inMonorepo = lockfileDir !== cwd;
|
|
4850
|
-
if (appName || inMonorepo) logger$
|
|
4851
|
-
else logger$
|
|
4635
|
+
if (appName || inMonorepo) logger$5.log(" Generating Dockerfile for monorepo (turbo prune)...");
|
|
4636
|
+
else logger$5.log(" Generating Dockerfile...");
|
|
4852
4637
|
await dockerCommand({});
|
|
4853
4638
|
const dockerfileSuffix = appName ? `.${appName}` : "";
|
|
4854
4639
|
const dockerfilePath = `.gkm/docker/Dockerfile${dockerfileSuffix}`;
|
|
4855
4640
|
const buildCwd = lockfilePath && (inMonorepo || appName) ? lockfileDir : cwd;
|
|
4856
|
-
if (buildCwd !== cwd) logger$
|
|
4641
|
+
if (buildCwd !== cwd) logger$5.log(` Building from workspace root: ${buildCwd}`);
|
|
4857
4642
|
const buildArgsString = buildArgs && buildArgs.length > 0 ? buildArgs.map((arg) => `--build-arg "${arg}"`).join(" ") : "";
|
|
4858
4643
|
try {
|
|
4859
4644
|
const cmd = [
|
|
@@ -4872,7 +4657,7 @@ async function buildImage(imageRef, appName, buildArgs) {
|
|
|
4872
4657
|
DOCKER_BUILDKIT: "1"
|
|
4873
4658
|
}
|
|
4874
4659
|
});
|
|
4875
|
-
logger$
|
|
4660
|
+
logger$5.log(`✅ Image built: ${imageRef}`);
|
|
4876
4661
|
} catch (error) {
|
|
4877
4662
|
throw new Error(`Failed to build Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
4878
4663
|
}
|
|
@@ -4881,13 +4666,13 @@ async function buildImage(imageRef, appName, buildArgs) {
|
|
|
4881
4666
|
* Push Docker image to registry
|
|
4882
4667
|
*/
|
|
4883
4668
|
async function pushImage(imageRef) {
|
|
4884
|
-
logger$
|
|
4669
|
+
logger$5.log(`\n☁️ Pushing image: ${imageRef}`);
|
|
4885
4670
|
try {
|
|
4886
4671
|
execSync(`docker push ${imageRef}`, {
|
|
4887
4672
|
cwd: process.cwd(),
|
|
4888
4673
|
stdio: "inherit"
|
|
4889
4674
|
});
|
|
4890
|
-
logger$
|
|
4675
|
+
logger$5.log(`✅ Image pushed: ${imageRef}`);
|
|
4891
4676
|
} catch (error) {
|
|
4892
4677
|
throw new Error(`Failed to push Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
4893
4678
|
}
|
|
@@ -4896,21 +4681,21 @@ async function pushImage(imageRef) {
|
|
|
4896
4681
|
* Deploy using Docker (build and optionally push image)
|
|
4897
4682
|
*/
|
|
4898
4683
|
async function deployDocker(options) {
|
|
4899
|
-
const { stage, tag, skipPush, masterKey, config
|
|
4900
|
-
const imageName = config
|
|
4901
|
-
const imageRef = getImageRef(config
|
|
4902
|
-
await buildImage(imageRef, config
|
|
4903
|
-
if (!skipPush) if (!config
|
|
4684
|
+
const { stage, tag, skipPush, masterKey, config, buildArgs } = options;
|
|
4685
|
+
const imageName = config.imageName;
|
|
4686
|
+
const imageRef = getImageRef(config.registry, imageName, tag);
|
|
4687
|
+
await buildImage(imageRef, config.appName, buildArgs);
|
|
4688
|
+
if (!skipPush) if (!config.registry) logger$5.warn("\n⚠️ No registry configured. Use --skip-push or configure docker.registry in gkm.config.ts");
|
|
4904
4689
|
else await pushImage(imageRef);
|
|
4905
|
-
logger$
|
|
4906
|
-
logger$
|
|
4907
|
-
logger$
|
|
4908
|
-
logger$
|
|
4690
|
+
logger$5.log("\n✅ Docker deployment ready!");
|
|
4691
|
+
logger$5.log(`\n📋 Deployment details:`);
|
|
4692
|
+
logger$5.log(` Image: ${imageRef}`);
|
|
4693
|
+
logger$5.log(` Stage: ${stage}`);
|
|
4909
4694
|
if (masterKey) {
|
|
4910
|
-
logger$
|
|
4911
|
-
logger$
|
|
4912
|
-
logger$
|
|
4913
|
-
logger$
|
|
4695
|
+
logger$5.log(`\n🔐 Deploy with this environment variable:`);
|
|
4696
|
+
logger$5.log(` GKM_MASTER_KEY=${masterKey}`);
|
|
4697
|
+
logger$5.log("\n Example docker run:");
|
|
4698
|
+
logger$5.log(` docker run -e GKM_MASTER_KEY=${masterKey} ${imageRef}`);
|
|
4914
4699
|
}
|
|
4915
4700
|
return {
|
|
4916
4701
|
imageRef,
|
|
@@ -4923,12 +4708,12 @@ async function deployDocker(options) {
|
|
|
4923
4708
|
* - projectName: from root package.json, or 'app' (for Dokploy project)
|
|
4924
4709
|
* - appName: from cwd package.json, or projectName (for Dokploy app within project)
|
|
4925
4710
|
*/
|
|
4926
|
-
function resolveDockerConfig(config
|
|
4711
|
+
function resolveDockerConfig(config) {
|
|
4927
4712
|
const projectName = getAppNameFromPackageJson() ?? "app";
|
|
4928
4713
|
const appName = getAppNameFromCwd$1() ?? projectName;
|
|
4929
|
-
const imageName = config
|
|
4714
|
+
const imageName = config.docker?.imageName ?? appName;
|
|
4930
4715
|
return {
|
|
4931
|
-
registry: config
|
|
4716
|
+
registry: config.docker?.registry,
|
|
4932
4717
|
imageName,
|
|
4933
4718
|
projectName,
|
|
4934
4719
|
appName
|
|
@@ -4937,7 +4722,7 @@ function resolveDockerConfig(config$1) {
|
|
|
4937
4722
|
|
|
4938
4723
|
//#endregion
|
|
4939
4724
|
//#region src/deploy/dokploy.ts
|
|
4940
|
-
const logger$
|
|
4725
|
+
const logger$4 = console;
|
|
4941
4726
|
/**
|
|
4942
4727
|
* Get the Dokploy API token from stored credentials or environment
|
|
4943
4728
|
*/
|
|
@@ -4960,59 +4745,59 @@ async function createApi$1(endpoint) {
|
|
|
4960
4745
|
* Deploy to Dokploy
|
|
4961
4746
|
*/
|
|
4962
4747
|
async function deployDokploy(options) {
|
|
4963
|
-
const { stage, imageRef, masterKey, config
|
|
4964
|
-
logger$
|
|
4965
|
-
logger$
|
|
4966
|
-
logger$
|
|
4967
|
-
const api = await createApi$1(config
|
|
4968
|
-
logger$
|
|
4748
|
+
const { stage, imageRef, masterKey, config } = options;
|
|
4749
|
+
logger$4.log(`\n🎯 Deploying to Dokploy...`);
|
|
4750
|
+
logger$4.log(` Endpoint: ${config.endpoint}`);
|
|
4751
|
+
logger$4.log(` Application: ${config.applicationId}`);
|
|
4752
|
+
const api = await createApi$1(config.endpoint);
|
|
4753
|
+
logger$4.log(` Configuring Docker image: ${imageRef}`);
|
|
4969
4754
|
const registryOptions = {};
|
|
4970
|
-
if (config
|
|
4971
|
-
registryOptions.registryId = config
|
|
4972
|
-
logger$
|
|
4755
|
+
if (config.registryId) {
|
|
4756
|
+
registryOptions.registryId = config.registryId;
|
|
4757
|
+
logger$4.log(` Using Dokploy registry: ${config.registryId}`);
|
|
4973
4758
|
} else {
|
|
4974
4759
|
const storedRegistryId = await getDokployRegistryId();
|
|
4975
4760
|
if (storedRegistryId) {
|
|
4976
4761
|
registryOptions.registryId = storedRegistryId;
|
|
4977
|
-
logger$
|
|
4978
|
-
} else if (config
|
|
4979
|
-
registryOptions.username = config
|
|
4980
|
-
registryOptions.password = config
|
|
4981
|
-
registryOptions.registryUrl = config
|
|
4982
|
-
logger$
|
|
4762
|
+
logger$4.log(` Using stored Dokploy registry: ${storedRegistryId}`);
|
|
4763
|
+
} else if (config.registryCredentials) {
|
|
4764
|
+
registryOptions.username = config.registryCredentials.username;
|
|
4765
|
+
registryOptions.password = config.registryCredentials.password;
|
|
4766
|
+
registryOptions.registryUrl = config.registryCredentials.registryUrl;
|
|
4767
|
+
logger$4.log(` Using registry credentials for: ${config.registryCredentials.registryUrl}`);
|
|
4983
4768
|
} else {
|
|
4984
4769
|
const username = process.env.DOCKER_REGISTRY_USERNAME;
|
|
4985
4770
|
const password = process.env.DOCKER_REGISTRY_PASSWORD;
|
|
4986
|
-
const registryUrl = process.env.DOCKER_REGISTRY_URL || config
|
|
4771
|
+
const registryUrl = process.env.DOCKER_REGISTRY_URL || config.registry;
|
|
4987
4772
|
if (username && password && registryUrl) {
|
|
4988
4773
|
registryOptions.username = username;
|
|
4989
4774
|
registryOptions.password = password;
|
|
4990
4775
|
registryOptions.registryUrl = registryUrl;
|
|
4991
|
-
logger$
|
|
4776
|
+
logger$4.log(` Using registry credentials from environment`);
|
|
4992
4777
|
}
|
|
4993
4778
|
}
|
|
4994
4779
|
}
|
|
4995
|
-
await api.saveDockerProvider(config
|
|
4996
|
-
logger$
|
|
4780
|
+
await api.saveDockerProvider(config.applicationId, imageRef, registryOptions);
|
|
4781
|
+
logger$4.log(" ✓ Docker provider configured");
|
|
4997
4782
|
const envVars = {};
|
|
4998
4783
|
if (masterKey) envVars.GKM_MASTER_KEY = masterKey;
|
|
4999
4784
|
if (Object.keys(envVars).length > 0) {
|
|
5000
|
-
logger$
|
|
4785
|
+
logger$4.log(" Updating environment variables...");
|
|
5001
4786
|
const envString = Object.entries(envVars).map(([key, value]) => `${key}=${value}`).join("\n");
|
|
5002
|
-
await api.saveApplicationEnv(config
|
|
5003
|
-
logger$
|
|
5004
|
-
}
|
|
5005
|
-
logger$
|
|
5006
|
-
await api.deployApplication(config
|
|
5007
|
-
logger$
|
|
5008
|
-
logger$
|
|
5009
|
-
logger$
|
|
5010
|
-
logger$
|
|
5011
|
-
logger$
|
|
5012
|
-
logger$
|
|
5013
|
-
if (masterKey) logger$
|
|
5014
|
-
const deploymentUrl = `${config
|
|
5015
|
-
logger$
|
|
4787
|
+
await api.saveApplicationEnv(config.applicationId, envString);
|
|
4788
|
+
logger$4.log(" ✓ Environment variables updated");
|
|
4789
|
+
}
|
|
4790
|
+
logger$4.log(" Triggering deployment...");
|
|
4791
|
+
await api.deployApplication(config.applicationId);
|
|
4792
|
+
logger$4.log(" ✓ Deployment triggered");
|
|
4793
|
+
logger$4.log("\n✅ Dokploy deployment initiated!");
|
|
4794
|
+
logger$4.log(`\n📋 Deployment details:`);
|
|
4795
|
+
logger$4.log(` Image: ${imageRef}`);
|
|
4796
|
+
logger$4.log(` Stage: ${stage}`);
|
|
4797
|
+
logger$4.log(` Application ID: ${config.applicationId}`);
|
|
4798
|
+
if (masterKey) logger$4.log(`\n🔐 GKM_MASTER_KEY has been set in Dokploy environment`);
|
|
4799
|
+
const deploymentUrl = `${config.endpoint}/project/${config.projectId}`;
|
|
4800
|
+
logger$4.log(`\n🔗 View deployment: ${deploymentUrl}`);
|
|
5016
4801
|
return {
|
|
5017
4802
|
imageRef,
|
|
5018
4803
|
masterKey,
|
|
@@ -5129,7 +4914,7 @@ function validateEnvVars(requiredVars, context) {
|
|
|
5129
4914
|
|
|
5130
4915
|
//#endregion
|
|
5131
4916
|
//#region src/deploy/init.ts
|
|
5132
|
-
const logger$
|
|
4917
|
+
const logger$3 = console;
|
|
5133
4918
|
/**
|
|
5134
4919
|
* Get the Dokploy API token from stored credentials or environment
|
|
5135
4920
|
*/
|
|
@@ -5160,29 +4945,29 @@ async function createApi(endpoint) {
|
|
|
5160
4945
|
/**
|
|
5161
4946
|
* Update gkm.config.ts with Dokploy configuration
|
|
5162
4947
|
*/
|
|
5163
|
-
async function updateConfig(config
|
|
4948
|
+
async function updateConfig(config, cwd = process.cwd()) {
|
|
5164
4949
|
const configPath = join(cwd, "gkm.config.ts");
|
|
5165
4950
|
if (!existsSync(configPath)) {
|
|
5166
|
-
logger$
|
|
5167
|
-
logger$
|
|
5168
|
-
logger$
|
|
5169
|
-
logger$
|
|
5170
|
-
logger$
|
|
5171
|
-
logger$
|
|
5172
|
-
logger$
|
|
5173
|
-
logger$
|
|
4951
|
+
logger$3.warn("\n gkm.config.ts not found. Add this configuration manually:\n");
|
|
4952
|
+
logger$3.log(` providers: {`);
|
|
4953
|
+
logger$3.log(` dokploy: {`);
|
|
4954
|
+
logger$3.log(` endpoint: '${config.endpoint}',`);
|
|
4955
|
+
logger$3.log(` projectId: '${config.projectId}',`);
|
|
4956
|
+
logger$3.log(` applicationId: '${config.applicationId}',`);
|
|
4957
|
+
logger$3.log(` },`);
|
|
4958
|
+
logger$3.log(` },`);
|
|
5174
4959
|
return;
|
|
5175
4960
|
}
|
|
5176
4961
|
const content = await readFile(configPath, "utf-8");
|
|
5177
4962
|
if (content.includes("dokploy:") && content.includes("applicationId:")) {
|
|
5178
|
-
logger$
|
|
5179
|
-
logger$
|
|
4963
|
+
logger$3.log("\n Dokploy config already exists in gkm.config.ts");
|
|
4964
|
+
logger$3.log(" Updating with new values...");
|
|
5180
4965
|
}
|
|
5181
|
-
const registryLine = config
|
|
4966
|
+
const registryLine = config.registryId ? `\n\t\t\tregistryId: '${config.registryId}',` : "";
|
|
5182
4967
|
const dokployConfigStr = `dokploy: {
|
|
5183
|
-
endpoint: '${config
|
|
5184
|
-
projectId: '${config
|
|
5185
|
-
applicationId: '${config
|
|
4968
|
+
endpoint: '${config.endpoint}',
|
|
4969
|
+
projectId: '${config.projectId}',
|
|
4970
|
+
applicationId: '${config.applicationId}',${registryLine}
|
|
5186
4971
|
}`;
|
|
5187
4972
|
let newContent;
|
|
5188
4973
|
if (content.includes("providers:")) if (content.includes("dokploy:")) newContent = content.replace(/dokploy:\s*\{[^}]*\}/s, dokployConfigStr);
|
|
@@ -5193,7 +4978,7 @@ async function updateConfig(config$1, cwd = process.cwd()) {
|
|
|
5193
4978
|
},
|
|
5194
4979
|
});`);
|
|
5195
4980
|
await writeFile(configPath, newContent);
|
|
5196
|
-
logger$
|
|
4981
|
+
logger$3.log("\n ✓ Updated gkm.config.ts with Dokploy configuration");
|
|
5197
4982
|
}
|
|
5198
4983
|
/**
|
|
5199
4984
|
* Initialize Dokploy deployment configuration
|
|
@@ -5202,24 +4987,24 @@ async function deployInitCommand(options) {
|
|
|
5202
4987
|
const { projectName, appName, projectId: existingProjectId, registryId } = options;
|
|
5203
4988
|
const endpoint = await getEndpoint(options.endpoint);
|
|
5204
4989
|
const api = await createApi(endpoint);
|
|
5205
|
-
logger$
|
|
5206
|
-
logger$
|
|
4990
|
+
logger$3.log(`\n🚀 Initializing Dokploy deployment...`);
|
|
4991
|
+
logger$3.log(` Endpoint: ${endpoint}`);
|
|
5207
4992
|
let projectId;
|
|
5208
4993
|
if (existingProjectId) {
|
|
5209
4994
|
projectId = existingProjectId;
|
|
5210
|
-
logger$
|
|
4995
|
+
logger$3.log(`\n📁 Using existing project: ${projectId}`);
|
|
5211
4996
|
} else {
|
|
5212
|
-
logger$
|
|
4997
|
+
logger$3.log(`\n📁 Looking for project: ${projectName}`);
|
|
5213
4998
|
const projects = await api.listProjects();
|
|
5214
4999
|
const existingProject = projects.find((p) => p.name.toLowerCase() === projectName.toLowerCase());
|
|
5215
5000
|
if (existingProject) {
|
|
5216
5001
|
projectId = existingProject.projectId;
|
|
5217
|
-
logger$
|
|
5002
|
+
logger$3.log(` Found existing project: ${projectId}`);
|
|
5218
5003
|
} else {
|
|
5219
|
-
logger$
|
|
5004
|
+
logger$3.log(` Creating new project...`);
|
|
5220
5005
|
const result = await api.createProject(projectName);
|
|
5221
5006
|
projectId = result.project.projectId;
|
|
5222
|
-
logger$
|
|
5007
|
+
logger$3.log(` ✓ Created project: ${projectId}`);
|
|
5223
5008
|
}
|
|
5224
5009
|
}
|
|
5225
5010
|
const project = await api.getProject(projectId);
|
|
@@ -5227,40 +5012,40 @@ async function deployInitCommand(options) {
|
|
|
5227
5012
|
const firstEnv = project.environments?.[0];
|
|
5228
5013
|
if (firstEnv) environmentId = firstEnv.environmentId;
|
|
5229
5014
|
else {
|
|
5230
|
-
logger$
|
|
5015
|
+
logger$3.log(` Creating production environment...`);
|
|
5231
5016
|
const env = await api.createEnvironment(projectId, "production");
|
|
5232
5017
|
environmentId = env.environmentId;
|
|
5233
5018
|
}
|
|
5234
|
-
logger$
|
|
5019
|
+
logger$3.log(`\n📦 Creating application: ${appName}`);
|
|
5235
5020
|
const application = await api.createApplication(appName, projectId, environmentId);
|
|
5236
|
-
logger$
|
|
5021
|
+
logger$3.log(` ✓ Created application: ${application.applicationId}`);
|
|
5237
5022
|
if (registryId) {
|
|
5238
|
-
logger$
|
|
5023
|
+
logger$3.log(`\n🔧 Configuring registry: ${registryId}`);
|
|
5239
5024
|
await api.updateApplication(application.applicationId, { registryId });
|
|
5240
|
-
logger$
|
|
5025
|
+
logger$3.log(` ✓ Registry configured`);
|
|
5241
5026
|
} else try {
|
|
5242
5027
|
const registries = await api.listRegistries();
|
|
5243
5028
|
if (registries.length > 0) {
|
|
5244
|
-
logger$
|
|
5245
|
-
for (const reg of registries) logger$
|
|
5246
|
-
logger$
|
|
5029
|
+
logger$3.log(`\n📋 Available registries:`);
|
|
5030
|
+
for (const reg of registries) logger$3.log(` - ${reg.registryName}: ${reg.registryUrl} (${reg.registryId})`);
|
|
5031
|
+
logger$3.log(`\n To use a registry, run with --registry-id <id>`);
|
|
5247
5032
|
}
|
|
5248
5033
|
} catch {}
|
|
5249
|
-
const config
|
|
5034
|
+
const config = {
|
|
5250
5035
|
endpoint,
|
|
5251
5036
|
projectId,
|
|
5252
5037
|
applicationId: application.applicationId
|
|
5253
5038
|
};
|
|
5254
|
-
await updateConfig(config
|
|
5255
|
-
logger$
|
|
5256
|
-
logger$
|
|
5257
|
-
logger$
|
|
5258
|
-
logger$
|
|
5259
|
-
logger$
|
|
5260
|
-
logger$
|
|
5261
|
-
logger$
|
|
5262
|
-
logger$
|
|
5263
|
-
return config
|
|
5039
|
+
await updateConfig(config);
|
|
5040
|
+
logger$3.log(`\n✅ Dokploy deployment initialized!`);
|
|
5041
|
+
logger$3.log(`\n📋 Configuration:`);
|
|
5042
|
+
logger$3.log(` Project ID: ${projectId}`);
|
|
5043
|
+
logger$3.log(` Application ID: ${application.applicationId}`);
|
|
5044
|
+
logger$3.log(`\n🔗 View in Dokploy: ${endpoint}/project/${projectId}`);
|
|
5045
|
+
logger$3.log(`\n📝 Next steps:`);
|
|
5046
|
+
logger$3.log(` 1. Initialize secrets: gkm secrets:init --stage production`);
|
|
5047
|
+
logger$3.log(` 2. Deploy: gkm deploy --provider dokploy --stage production`);
|
|
5048
|
+
return config;
|
|
5264
5049
|
}
|
|
5265
5050
|
/**
|
|
5266
5051
|
* List available Dokploy resources
|
|
@@ -5270,32 +5055,32 @@ async function deployListCommand(options) {
|
|
|
5270
5055
|
const api = await createApi(endpoint);
|
|
5271
5056
|
const { resource } = options;
|
|
5272
5057
|
if (resource === "projects") {
|
|
5273
|
-
logger$
|
|
5058
|
+
logger$3.log(`\n📁 Projects in ${endpoint}:`);
|
|
5274
5059
|
const projects = await api.listProjects();
|
|
5275
5060
|
if (projects.length === 0) {
|
|
5276
|
-
logger$
|
|
5061
|
+
logger$3.log(" No projects found");
|
|
5277
5062
|
return;
|
|
5278
5063
|
}
|
|
5279
5064
|
for (const project of projects) {
|
|
5280
|
-
logger$
|
|
5281
|
-
if (project.description) logger$
|
|
5065
|
+
logger$3.log(`\n ${project.name} (${project.projectId})`);
|
|
5066
|
+
if (project.description) logger$3.log(` ${project.description}`);
|
|
5282
5067
|
}
|
|
5283
5068
|
} else if (resource === "registries") {
|
|
5284
|
-
logger$
|
|
5069
|
+
logger$3.log(`\n🐳 Registries in ${endpoint}:`);
|
|
5285
5070
|
const registries = await api.listRegistries();
|
|
5286
5071
|
if (registries.length === 0) {
|
|
5287
|
-
logger$
|
|
5288
|
-
logger$
|
|
5072
|
+
logger$3.log(" No registries configured");
|
|
5073
|
+
logger$3.log(" Run \"gkm registry:setup\" to configure a registry");
|
|
5289
5074
|
return;
|
|
5290
5075
|
}
|
|
5291
5076
|
const storedRegistryId = await getDokployRegistryId();
|
|
5292
5077
|
for (const registry of registries) {
|
|
5293
5078
|
const isDefault = registry.registryId === storedRegistryId;
|
|
5294
5079
|
const marker = isDefault ? " (default)" : "";
|
|
5295
|
-
logger$
|
|
5296
|
-
logger$
|
|
5297
|
-
logger$
|
|
5298
|
-
if (registry.imagePrefix) logger$
|
|
5080
|
+
logger$3.log(`\n ${registry.registryName}${marker} (${registry.registryId})`);
|
|
5081
|
+
logger$3.log(` URL: ${registry.registryUrl}`);
|
|
5082
|
+
logger$3.log(` Username: ${registry.username}`);
|
|
5083
|
+
if (registry.imagePrefix) logger$3.log(` Prefix: ${registry.imagePrefix}`);
|
|
5299
5084
|
}
|
|
5300
5085
|
}
|
|
5301
5086
|
}
|
|
@@ -5316,23 +5101,23 @@ function isStateProvider(value) {
|
|
|
5316
5101
|
* - Custom: Use provided StateProvider implementation
|
|
5317
5102
|
*/
|
|
5318
5103
|
async function createStateProvider(options) {
|
|
5319
|
-
const { config
|
|
5320
|
-
if (!config
|
|
5321
|
-
const { LocalStateProvider } = await import("./LocalStateProvider-
|
|
5104
|
+
const { config, workspaceRoot, workspaceName } = options;
|
|
5105
|
+
if (!config) {
|
|
5106
|
+
const { LocalStateProvider } = await import("./LocalStateProvider-CP49rjzt.mjs");
|
|
5322
5107
|
return new LocalStateProvider(workspaceRoot);
|
|
5323
5108
|
}
|
|
5324
|
-
if (isStateProvider(config
|
|
5325
|
-
const provider = config
|
|
5109
|
+
if (isStateProvider(config.provider)) return config.provider;
|
|
5110
|
+
const provider = config.provider;
|
|
5326
5111
|
if (provider === "local") {
|
|
5327
|
-
const { LocalStateProvider } = await import("./LocalStateProvider-
|
|
5112
|
+
const { LocalStateProvider } = await import("./LocalStateProvider-CP49rjzt.mjs");
|
|
5328
5113
|
return new LocalStateProvider(workspaceRoot);
|
|
5329
5114
|
}
|
|
5330
5115
|
if (provider === "ssm") {
|
|
5331
5116
|
if (!workspaceName) throw new Error("Workspace name is required for SSM state provider. Set \"name\" in gkm.config.ts.");
|
|
5332
|
-
const { LocalStateProvider } = await import("./LocalStateProvider-
|
|
5333
|
-
const { SSMStateProvider } = await import("./SSMStateProvider-
|
|
5334
|
-
const { CachedStateProvider: CachedStateProvider$1 } = await import("./CachedStateProvider-
|
|
5335
|
-
const ssmConfig = config
|
|
5117
|
+
const { LocalStateProvider } = await import("./LocalStateProvider-CP49rjzt.mjs");
|
|
5118
|
+
const { SSMStateProvider } = await import("./SSMStateProvider-BlrTiV5m.mjs");
|
|
5119
|
+
const { CachedStateProvider: CachedStateProvider$1 } = await import("./CachedStateProvider-GcGziXh1.mjs");
|
|
5120
|
+
const ssmConfig = config;
|
|
5336
5121
|
const local = new LocalStateProvider(workspaceRoot);
|
|
5337
5122
|
const ssm = SSMStateProvider.create({
|
|
5338
5123
|
workspaceName,
|
|
@@ -5341,7 +5126,7 @@ async function createStateProvider(options) {
|
|
|
5341
5126
|
});
|
|
5342
5127
|
return new CachedStateProvider$1(ssm, local);
|
|
5343
5128
|
}
|
|
5344
|
-
throw new Error(`Unknown state provider: ${JSON.stringify(config
|
|
5129
|
+
throw new Error(`Unknown state provider: ${JSON.stringify(config)}`);
|
|
5345
5130
|
}
|
|
5346
5131
|
|
|
5347
5132
|
//#endregion
|
|
@@ -5791,7 +5576,7 @@ async function sniffAllApps(apps, workspacePath, options = {}) {
|
|
|
5791
5576
|
|
|
5792
5577
|
//#endregion
|
|
5793
5578
|
//#region src/deploy/index.ts
|
|
5794
|
-
const logger$
|
|
5579
|
+
const logger$2 = console;
|
|
5795
5580
|
/**
|
|
5796
5581
|
* Prompt for input
|
|
5797
5582
|
*/
|
|
@@ -5863,7 +5648,7 @@ async function waitForPostgres(host, port, user, password, database, maxRetries
|
|
|
5863
5648
|
return;
|
|
5864
5649
|
} catch {
|
|
5865
5650
|
if (i < maxRetries - 1) {
|
|
5866
|
-
logger$
|
|
5651
|
+
logger$2.log(` Waiting for Postgres... (${i + 1}/${maxRetries})`);
|
|
5867
5652
|
await new Promise((r) => setTimeout(r, retryIntervalMs));
|
|
5868
5653
|
}
|
|
5869
5654
|
}
|
|
@@ -5898,12 +5683,12 @@ async function waitForPostgres(host, port, user, password, database, maxRetries
|
|
|
5898
5683
|
* ```
|
|
5899
5684
|
*/
|
|
5900
5685
|
async function initializePostgresUsers(api, postgres, serverHostname, users) {
|
|
5901
|
-
logger$
|
|
5686
|
+
logger$2.log("\n🔧 Initializing database users...");
|
|
5902
5687
|
const externalPort = 5432;
|
|
5903
|
-
logger$
|
|
5688
|
+
logger$2.log(` Enabling external port ${externalPort}...`);
|
|
5904
5689
|
await api.savePostgresExternalPort(postgres.postgresId, externalPort);
|
|
5905
5690
|
await api.deployPostgres(postgres.postgresId);
|
|
5906
|
-
logger$
|
|
5691
|
+
logger$2.log(` Waiting for Postgres to be accessible at ${serverHostname}:${externalPort}...`);
|
|
5907
5692
|
await waitForPostgres(serverHostname, externalPort, postgres.databaseUser, postgres.databasePassword, postgres.databaseName);
|
|
5908
5693
|
const client = new Client({
|
|
5909
5694
|
host: serverHostname,
|
|
@@ -5916,7 +5701,7 @@ async function initializePostgresUsers(api, postgres, serverHostname, users) {
|
|
|
5916
5701
|
await client.connect();
|
|
5917
5702
|
for (const user of users) {
|
|
5918
5703
|
const schemaName = user.usePublicSchema ? "public" : user.name;
|
|
5919
|
-
logger$
|
|
5704
|
+
logger$2.log(` Creating user "${user.name}" with schema "${schemaName}"...`);
|
|
5920
5705
|
if (user.usePublicSchema) {
|
|
5921
5706
|
await client.query(`
|
|
5922
5707
|
DO $$ BEGIN
|
|
@@ -5951,15 +5736,15 @@ async function initializePostgresUsers(api, postgres, serverHostname, users) {
|
|
|
5951
5736
|
ALTER DEFAULT PRIVILEGES IN SCHEMA "${schemaName}" GRANT ALL ON TABLES TO "${user.name}";
|
|
5952
5737
|
`);
|
|
5953
5738
|
}
|
|
5954
|
-
logger$
|
|
5739
|
+
logger$2.log(` ✓ User "${user.name}" configured`);
|
|
5955
5740
|
}
|
|
5956
5741
|
} finally {
|
|
5957
5742
|
await client.end();
|
|
5958
5743
|
}
|
|
5959
|
-
logger$
|
|
5744
|
+
logger$2.log(" Disabling external port...");
|
|
5960
5745
|
await api.savePostgresExternalPort(postgres.postgresId, null);
|
|
5961
5746
|
await api.deployPostgres(postgres.postgresId);
|
|
5962
|
-
logger$
|
|
5747
|
+
logger$2.log(" ✓ Database users initialized");
|
|
5963
5748
|
}
|
|
5964
5749
|
/**
|
|
5965
5750
|
* Get the server hostname from the Dokploy endpoint URL
|
|
@@ -5973,24 +5758,24 @@ function getServerHostname(endpoint) {
|
|
|
5973
5758
|
* @internal Exported for testing
|
|
5974
5759
|
*/
|
|
5975
5760
|
async function provisionServices(api, projectId, environmentId, projectName, services, existingServiceIds) {
|
|
5976
|
-
logger$
|
|
5761
|
+
logger$2.log(`\n🔍 provisionServices called: services=${JSON.stringify(services)}, envId=${environmentId}`);
|
|
5977
5762
|
if (!services || !environmentId) {
|
|
5978
|
-
logger$
|
|
5763
|
+
logger$2.log(" Skipping: no services or no environmentId");
|
|
5979
5764
|
return void 0;
|
|
5980
5765
|
}
|
|
5981
5766
|
const serviceUrls = {};
|
|
5982
5767
|
const serviceIds = {};
|
|
5983
5768
|
if (services.postgres) {
|
|
5984
|
-
logger$
|
|
5769
|
+
logger$2.log("\n🐘 Checking PostgreSQL...");
|
|
5985
5770
|
const postgresName = "db";
|
|
5986
5771
|
try {
|
|
5987
5772
|
let postgres = null;
|
|
5988
5773
|
let created = false;
|
|
5989
5774
|
if (existingServiceIds?.postgresId) {
|
|
5990
|
-
logger$
|
|
5775
|
+
logger$2.log(` Using cached ID: ${existingServiceIds.postgresId}`);
|
|
5991
5776
|
postgres = await api.getPostgres(existingServiceIds.postgresId);
|
|
5992
|
-
if (postgres) logger$
|
|
5993
|
-
else logger$
|
|
5777
|
+
if (postgres) logger$2.log(` ✓ PostgreSQL found: ${postgres.postgresId}`);
|
|
5778
|
+
else logger$2.log(` ⚠ Cached ID invalid, will create new`);
|
|
5994
5779
|
}
|
|
5995
5780
|
if (!postgres) {
|
|
5996
5781
|
const databasePassword = randomBytes(16).toString("hex");
|
|
@@ -6002,10 +5787,10 @@ async function provisionServices(api, projectId, environmentId, projectName, ser
|
|
|
6002
5787
|
postgres = result.postgres;
|
|
6003
5788
|
created = result.created;
|
|
6004
5789
|
if (created) {
|
|
6005
|
-
logger$
|
|
5790
|
+
logger$2.log(` ✓ Created PostgreSQL: ${postgres.postgresId}`);
|
|
6006
5791
|
await api.deployPostgres(postgres.postgresId);
|
|
6007
|
-
logger$
|
|
6008
|
-
} else logger$
|
|
5792
|
+
logger$2.log(" ✓ PostgreSQL deployed");
|
|
5793
|
+
} else logger$2.log(` ✓ PostgreSQL already exists: ${postgres.postgresId}`);
|
|
6009
5794
|
}
|
|
6010
5795
|
serviceIds.postgresId = postgres.postgresId;
|
|
6011
5796
|
serviceUrls.DATABASE_HOST = postgres.appName;
|
|
@@ -6014,23 +5799,23 @@ async function provisionServices(api, projectId, environmentId, projectName, ser
|
|
|
6014
5799
|
serviceUrls.DATABASE_USER = postgres.databaseUser;
|
|
6015
5800
|
serviceUrls.DATABASE_PASSWORD = postgres.databasePassword;
|
|
6016
5801
|
serviceUrls.DATABASE_URL = `postgresql://${postgres.databaseUser}:${postgres.databasePassword}@${postgres.appName}:5432/${postgres.databaseName}`;
|
|
6017
|
-
logger$
|
|
5802
|
+
logger$2.log(` ✓ Database credentials configured`);
|
|
6018
5803
|
} catch (error) {
|
|
6019
5804
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
6020
|
-
logger$
|
|
5805
|
+
logger$2.log(` ⚠ Failed to provision PostgreSQL: ${message}`);
|
|
6021
5806
|
}
|
|
6022
5807
|
}
|
|
6023
5808
|
if (services.redis) {
|
|
6024
|
-
logger$
|
|
5809
|
+
logger$2.log("\n🔴 Checking Redis...");
|
|
6025
5810
|
const redisName = "cache";
|
|
6026
5811
|
try {
|
|
6027
5812
|
let redis = null;
|
|
6028
5813
|
let created = false;
|
|
6029
5814
|
if (existingServiceIds?.redisId) {
|
|
6030
|
-
logger$
|
|
5815
|
+
logger$2.log(` Using cached ID: ${existingServiceIds.redisId}`);
|
|
6031
5816
|
redis = await api.getRedis(existingServiceIds.redisId);
|
|
6032
|
-
if (redis) logger$
|
|
6033
|
-
else logger$
|
|
5817
|
+
if (redis) logger$2.log(` ✓ Redis found: ${redis.redisId}`);
|
|
5818
|
+
else logger$2.log(` ⚠ Cached ID invalid, will create new`);
|
|
6034
5819
|
}
|
|
6035
5820
|
if (!redis) {
|
|
6036
5821
|
const { randomBytes: randomBytes$1 } = await import("node:crypto");
|
|
@@ -6039,10 +5824,10 @@ async function provisionServices(api, projectId, environmentId, projectName, ser
|
|
|
6039
5824
|
redis = result.redis;
|
|
6040
5825
|
created = result.created;
|
|
6041
5826
|
if (created) {
|
|
6042
|
-
logger$
|
|
5827
|
+
logger$2.log(` ✓ Created Redis: ${redis.redisId}`);
|
|
6043
5828
|
await api.deployRedis(redis.redisId);
|
|
6044
|
-
logger$
|
|
6045
|
-
} else logger$
|
|
5829
|
+
logger$2.log(" ✓ Redis deployed");
|
|
5830
|
+
} else logger$2.log(` ✓ Redis already exists: ${redis.redisId}`);
|
|
6046
5831
|
}
|
|
6047
5832
|
serviceIds.redisId = redis.redisId;
|
|
6048
5833
|
serviceUrls.REDIS_HOST = redis.appName;
|
|
@@ -6050,10 +5835,10 @@ async function provisionServices(api, projectId, environmentId, projectName, ser
|
|
|
6050
5835
|
if (redis.databasePassword) serviceUrls.REDIS_PASSWORD = redis.databasePassword;
|
|
6051
5836
|
const password = redis.databasePassword ? `:${redis.databasePassword}@` : "";
|
|
6052
5837
|
serviceUrls.REDIS_URL = `redis://${password}${redis.appName}:6379`;
|
|
6053
|
-
logger$
|
|
5838
|
+
logger$2.log(` ✓ Redis credentials configured`);
|
|
6054
5839
|
} catch (error) {
|
|
6055
5840
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
6056
|
-
logger$
|
|
5841
|
+
logger$2.log(` ⚠ Failed to provision Redis: ${message}`);
|
|
6057
5842
|
}
|
|
6058
5843
|
}
|
|
6059
5844
|
return Object.keys(serviceUrls).length > 0 ? {
|
|
@@ -6064,11 +5849,11 @@ async function provisionServices(api, projectId, environmentId, projectName, ser
|
|
|
6064
5849
|
/**
|
|
6065
5850
|
* Ensure Dokploy is fully configured, recovering/creating resources as needed
|
|
6066
5851
|
*/
|
|
6067
|
-
async function ensureDokploySetup(config
|
|
6068
|
-
logger$
|
|
5852
|
+
async function ensureDokploySetup(config, dockerConfig, stage, services) {
|
|
5853
|
+
logger$2.log("\n🔧 Checking Dokploy setup...");
|
|
6069
5854
|
let creds = await getDokployCredentials();
|
|
6070
5855
|
if (!creds) {
|
|
6071
|
-
logger$
|
|
5856
|
+
logger$2.log("\n📋 Dokploy credentials not found. Let's set them up.");
|
|
6072
5857
|
const endpoint = await prompt("Dokploy URL (e.g., https://dokploy.example.com): ");
|
|
6073
5858
|
const normalizedEndpoint = endpoint.replace(/\/$/, "");
|
|
6074
5859
|
try {
|
|
@@ -6076,9 +5861,9 @@ async function ensureDokploySetup(config$1, dockerConfig, stage, services) {
|
|
|
6076
5861
|
} catch {
|
|
6077
5862
|
throw new Error("Invalid URL format");
|
|
6078
5863
|
}
|
|
6079
|
-
logger$
|
|
5864
|
+
logger$2.log(`\nGenerate a token at: ${normalizedEndpoint}/settings/profile\n`);
|
|
6080
5865
|
const token = await prompt("API Token: ", true);
|
|
6081
|
-
logger$
|
|
5866
|
+
logger$2.log("\nValidating credentials...");
|
|
6082
5867
|
const isValid = await validateDokployToken(normalizedEndpoint, token);
|
|
6083
5868
|
if (!isValid) throw new Error("Invalid credentials. Please check your token.");
|
|
6084
5869
|
await storeDokployCredentials(token, normalizedEndpoint);
|
|
@@ -6086,28 +5871,28 @@ async function ensureDokploySetup(config$1, dockerConfig, stage, services) {
|
|
|
6086
5871
|
token,
|
|
6087
5872
|
endpoint: normalizedEndpoint
|
|
6088
5873
|
};
|
|
6089
|
-
logger$
|
|
5874
|
+
logger$2.log("✓ Credentials saved");
|
|
6090
5875
|
}
|
|
6091
5876
|
const api = new DokployApi({
|
|
6092
5877
|
baseUrl: creds.endpoint,
|
|
6093
5878
|
token: creds.token
|
|
6094
5879
|
});
|
|
6095
|
-
const existingConfig = config
|
|
5880
|
+
const existingConfig = config.providers?.dokploy;
|
|
6096
5881
|
if (existingConfig && typeof existingConfig !== "boolean" && existingConfig.applicationId && existingConfig.projectId) {
|
|
6097
|
-
logger$
|
|
5882
|
+
logger$2.log("✓ Dokploy config found in gkm.config.ts");
|
|
6098
5883
|
try {
|
|
6099
5884
|
const projectDetails = await api.getProject(existingConfig.projectId);
|
|
6100
|
-
logger$
|
|
5885
|
+
logger$2.log("✓ Project verified");
|
|
6101
5886
|
const storedRegistryId = existingConfig.registryId ?? await getDokployRegistryId();
|
|
6102
5887
|
const environments = projectDetails.environments ?? [];
|
|
6103
5888
|
let environment = environments.find((e) => e.name.toLowerCase() === stage.toLowerCase());
|
|
6104
5889
|
if (!environment) {
|
|
6105
|
-
logger$
|
|
5890
|
+
logger$2.log(` Creating "${stage}" environment...`);
|
|
6106
5891
|
environment = await api.createEnvironment(existingConfig.projectId, stage);
|
|
6107
|
-
logger$
|
|
5892
|
+
logger$2.log(` ✓ Created environment: ${environment.environmentId}`);
|
|
6108
5893
|
}
|
|
6109
5894
|
const environmentId$1 = environment.environmentId;
|
|
6110
|
-
logger$
|
|
5895
|
+
logger$2.log(` Services config: ${JSON.stringify(services)}, envId: ${environmentId$1}`);
|
|
6111
5896
|
const provisionResult$1 = await provisionServices(api, existingConfig.projectId, environmentId$1, dockerConfig.appName, services, void 0);
|
|
6112
5897
|
return {
|
|
6113
5898
|
config: {
|
|
@@ -6120,97 +5905,97 @@ async function ensureDokploySetup(config$1, dockerConfig, stage, services) {
|
|
|
6120
5905
|
serviceUrls: provisionResult$1?.serviceUrls
|
|
6121
5906
|
};
|
|
6122
5907
|
} catch {
|
|
6123
|
-
logger$
|
|
5908
|
+
logger$2.log("⚠ Project not found, will recover...");
|
|
6124
5909
|
}
|
|
6125
5910
|
}
|
|
6126
|
-
logger$
|
|
5911
|
+
logger$2.log("\n📁 Looking for project...");
|
|
6127
5912
|
const projectName = dockerConfig.projectName;
|
|
6128
5913
|
const projects = await api.listProjects();
|
|
6129
5914
|
let project = projects.find((p) => p.name.toLowerCase() === projectName.toLowerCase());
|
|
6130
5915
|
let environmentId;
|
|
6131
5916
|
if (project) {
|
|
6132
|
-
logger$
|
|
5917
|
+
logger$2.log(` Found existing project: ${project.name} (${project.projectId})`);
|
|
6133
5918
|
const projectDetails = await api.getProject(project.projectId);
|
|
6134
5919
|
const environments = projectDetails.environments ?? [];
|
|
6135
5920
|
const matchingEnv = environments.find((e) => e.name.toLowerCase() === stage.toLowerCase());
|
|
6136
5921
|
if (matchingEnv) {
|
|
6137
5922
|
environmentId = matchingEnv.environmentId;
|
|
6138
|
-
logger$
|
|
5923
|
+
logger$2.log(` Using environment: ${matchingEnv.name}`);
|
|
6139
5924
|
} else {
|
|
6140
|
-
logger$
|
|
5925
|
+
logger$2.log(` Creating "${stage}" environment...`);
|
|
6141
5926
|
const env = await api.createEnvironment(project.projectId, stage);
|
|
6142
5927
|
environmentId = env.environmentId;
|
|
6143
|
-
logger$
|
|
5928
|
+
logger$2.log(` ✓ Created environment: ${stage}`);
|
|
6144
5929
|
}
|
|
6145
5930
|
} else {
|
|
6146
|
-
logger$
|
|
5931
|
+
logger$2.log(` Creating project: ${projectName}`);
|
|
6147
5932
|
const result = await api.createProject(projectName);
|
|
6148
5933
|
project = result.project;
|
|
6149
5934
|
if (result.environment.name.toLowerCase() !== stage.toLowerCase()) {
|
|
6150
|
-
logger$
|
|
5935
|
+
logger$2.log(` Creating "${stage}" environment...`);
|
|
6151
5936
|
const env = await api.createEnvironment(project.projectId, stage);
|
|
6152
5937
|
environmentId = env.environmentId;
|
|
6153
5938
|
} else environmentId = result.environment.environmentId;
|
|
6154
|
-
logger$
|
|
6155
|
-
logger$
|
|
5939
|
+
logger$2.log(` ✓ Created project: ${project.projectId}`);
|
|
5940
|
+
logger$2.log(` ✓ Using environment: ${stage}`);
|
|
6156
5941
|
}
|
|
6157
|
-
logger$
|
|
5942
|
+
logger$2.log("\n📦 Looking for application...");
|
|
6158
5943
|
const appName = dockerConfig.appName;
|
|
6159
5944
|
let applicationId;
|
|
6160
5945
|
if (existingConfig && typeof existingConfig !== "boolean" && existingConfig.applicationId) {
|
|
6161
5946
|
applicationId = existingConfig.applicationId;
|
|
6162
|
-
logger$
|
|
5947
|
+
logger$2.log(` Using application from config: ${applicationId}`);
|
|
6163
5948
|
} else {
|
|
6164
|
-
logger$
|
|
5949
|
+
logger$2.log(` Creating application: ${appName}`);
|
|
6165
5950
|
const app = await api.createApplication(appName, project.projectId, environmentId);
|
|
6166
5951
|
applicationId = app.applicationId;
|
|
6167
|
-
logger$
|
|
5952
|
+
logger$2.log(` ✓ Created application: ${applicationId}`);
|
|
6168
5953
|
}
|
|
6169
|
-
logger$
|
|
5954
|
+
logger$2.log("\n🐳 Checking registry...");
|
|
6170
5955
|
let registryId = await getDokployRegistryId();
|
|
6171
5956
|
if (registryId) try {
|
|
6172
5957
|
const registry = await api.getRegistry(registryId);
|
|
6173
|
-
logger$
|
|
5958
|
+
logger$2.log(` Using registry: ${registry.registryName}`);
|
|
6174
5959
|
} catch {
|
|
6175
|
-
logger$
|
|
5960
|
+
logger$2.log(" ⚠ Stored registry not found, clearing...");
|
|
6176
5961
|
registryId = void 0;
|
|
6177
5962
|
await storeDokployRegistryId("");
|
|
6178
5963
|
}
|
|
6179
5964
|
if (!registryId) {
|
|
6180
5965
|
const registries = await api.listRegistries();
|
|
6181
5966
|
if (registries.length === 0) if (dockerConfig.registry) {
|
|
6182
|
-
logger$
|
|
6183
|
-
logger$
|
|
5967
|
+
logger$2.log(" No registries found in Dokploy. Let's create one.");
|
|
5968
|
+
logger$2.log(` Registry URL: ${dockerConfig.registry}`);
|
|
6184
5969
|
const username = await prompt("Registry username: ");
|
|
6185
5970
|
const password = await prompt("Registry password/token: ", true);
|
|
6186
5971
|
const registry = await api.createRegistry("Default Registry", dockerConfig.registry, username, password);
|
|
6187
5972
|
registryId = registry.registryId;
|
|
6188
5973
|
await storeDokployRegistryId(registryId);
|
|
6189
|
-
logger$
|
|
6190
|
-
} else logger$
|
|
5974
|
+
logger$2.log(` ✓ Registry created: ${registryId}`);
|
|
5975
|
+
} else logger$2.log(" ⚠ No registry configured. Set docker.registry in gkm.config.ts");
|
|
6191
5976
|
else {
|
|
6192
|
-
logger$
|
|
5977
|
+
logger$2.log(" Available registries:");
|
|
6193
5978
|
registries.forEach((reg, i) => {
|
|
6194
|
-
logger$
|
|
5979
|
+
logger$2.log(` ${i + 1}. ${reg.registryName} (${reg.registryUrl})`);
|
|
6195
5980
|
});
|
|
6196
|
-
if (dockerConfig.registry) logger$
|
|
5981
|
+
if (dockerConfig.registry) logger$2.log(` ${registries.length + 1}. Create new registry`);
|
|
6197
5982
|
const maxOption = dockerConfig.registry ? registries.length + 1 : registries.length;
|
|
6198
5983
|
const selection = await prompt(` Select registry (1-${maxOption}): `);
|
|
6199
5984
|
const index = parseInt(selection, 10) - 1;
|
|
6200
5985
|
if (index >= 0 && index < registries.length) {
|
|
6201
5986
|
registryId = registries[index].registryId;
|
|
6202
5987
|
await storeDokployRegistryId(registryId);
|
|
6203
|
-
logger$
|
|
5988
|
+
logger$2.log(` ✓ Selected: ${registries[index].registryName}`);
|
|
6204
5989
|
} else if (dockerConfig.registry && index === registries.length) {
|
|
6205
|
-
logger$
|
|
6206
|
-
logger$
|
|
5990
|
+
logger$2.log(`\n Creating new registry...`);
|
|
5991
|
+
logger$2.log(` Registry URL: ${dockerConfig.registry}`);
|
|
6207
5992
|
const username = await prompt(" Registry username: ");
|
|
6208
5993
|
const password = await prompt(" Registry password/token: ", true);
|
|
6209
5994
|
const registry = await api.createRegistry(dockerConfig.registry.replace(/^https?:\/\//, ""), dockerConfig.registry, username, password);
|
|
6210
5995
|
registryId = registry.registryId;
|
|
6211
5996
|
await storeDokployRegistryId(registryId);
|
|
6212
|
-
logger$
|
|
6213
|
-
} else logger$
|
|
5997
|
+
logger$2.log(` ✓ Registry created: ${registryId}`);
|
|
5998
|
+
} else logger$2.log(" ⚠ Invalid selection, skipping registry setup");
|
|
6214
5999
|
}
|
|
6215
6000
|
}
|
|
6216
6001
|
const dokployConfig = {
|
|
@@ -6220,10 +6005,10 @@ async function ensureDokploySetup(config$1, dockerConfig, stage, services) {
|
|
|
6220
6005
|
registryId: registryId ?? void 0
|
|
6221
6006
|
};
|
|
6222
6007
|
await updateConfig(dokployConfig);
|
|
6223
|
-
logger$
|
|
6224
|
-
logger$
|
|
6225
|
-
logger$
|
|
6226
|
-
if (registryId) logger$
|
|
6008
|
+
logger$2.log("\n✅ Dokploy setup complete!");
|
|
6009
|
+
logger$2.log(` Project: ${project.projectId}`);
|
|
6010
|
+
logger$2.log(` Application: ${applicationId}`);
|
|
6011
|
+
if (registryId) logger$2.log(` Registry: ${registryId}`);
|
|
6227
6012
|
const provisionResult = await provisionServices(api, project.projectId, environmentId, dockerConfig.appName, services, void 0);
|
|
6228
6013
|
return {
|
|
6229
6014
|
config: dokployConfig,
|
|
@@ -6254,45 +6039,45 @@ function generateTag(stage) {
|
|
|
6254
6039
|
async function workspaceDeployCommand(workspace, options) {
|
|
6255
6040
|
const { provider, stage, tag, apps: selectedApps } = options;
|
|
6256
6041
|
if (provider !== "dokploy") throw new Error(`Workspace deployment only supports Dokploy. Got: ${provider}`);
|
|
6257
|
-
logger$
|
|
6258
|
-
logger$
|
|
6042
|
+
logger$2.log(`\n🚀 Deploying workspace "${workspace.name}" to Dokploy...`);
|
|
6043
|
+
logger$2.log(` Stage: ${stage}`);
|
|
6259
6044
|
const imageTag = tag ?? generateTag(stage);
|
|
6260
|
-
logger$
|
|
6045
|
+
logger$2.log(` Tag: ${imageTag}`);
|
|
6261
6046
|
const buildOrder = getAppBuildOrder(workspace);
|
|
6262
6047
|
let appsToDeployNames = buildOrder;
|
|
6263
6048
|
if (selectedApps && selectedApps.length > 0) {
|
|
6264
6049
|
const invalidApps = selectedApps.filter((name$1) => !workspace.apps[name$1]);
|
|
6265
6050
|
if (invalidApps.length > 0) throw new Error(`Unknown apps: ${invalidApps.join(", ")}\nAvailable apps: ${Object.keys(workspace.apps).join(", ")}`);
|
|
6266
6051
|
appsToDeployNames = buildOrder.filter((name$1) => selectedApps.includes(name$1));
|
|
6267
|
-
logger$
|
|
6268
|
-
} else logger$
|
|
6052
|
+
logger$2.log(` Deploying apps: ${appsToDeployNames.join(", ")}`);
|
|
6053
|
+
} else logger$2.log(` Deploying all apps: ${appsToDeployNames.join(", ")}`);
|
|
6269
6054
|
const dokployApps = appsToDeployNames.filter((name$1) => {
|
|
6270
6055
|
const app = workspace.apps[name$1];
|
|
6271
6056
|
const target = app.resolvedDeployTarget;
|
|
6272
6057
|
if (!isDeployTargetSupported(target)) {
|
|
6273
|
-
logger$
|
|
6058
|
+
logger$2.log(` ⚠️ Skipping ${name$1}: ${getDeployTargetError(target, name$1)}`);
|
|
6274
6059
|
return false;
|
|
6275
6060
|
}
|
|
6276
6061
|
return true;
|
|
6277
6062
|
});
|
|
6278
6063
|
if (dokployApps.length === 0) throw new Error("No apps to deploy. All selected apps have unsupported deploy targets.");
|
|
6279
6064
|
appsToDeployNames = dokployApps;
|
|
6280
|
-
logger$
|
|
6065
|
+
logger$2.log("\n🔐 Loading secrets and analyzing environment requirements...");
|
|
6281
6066
|
const stageSecrets = await readStageSecrets(stage, workspace.root);
|
|
6282
6067
|
if (!stageSecrets) {
|
|
6283
|
-
logger$
|
|
6284
|
-
logger$
|
|
6068
|
+
logger$2.log(` ⚠️ No secrets found for stage "${stage}"`);
|
|
6069
|
+
logger$2.log(` Run "gkm secrets:init --stage ${stage}" to create secrets`);
|
|
6285
6070
|
}
|
|
6286
6071
|
const sniffedApps = await sniffAllApps(workspace.apps, workspace.root);
|
|
6287
6072
|
const encryptedSecrets = stageSecrets ? prepareSecretsForAllApps(stageSecrets, sniffedApps) : /* @__PURE__ */ new Map();
|
|
6288
6073
|
if (stageSecrets) {
|
|
6289
6074
|
const report = generateSecretsReport(encryptedSecrets, sniffedApps);
|
|
6290
|
-
if (report.appsWithSecrets.length > 0) logger$
|
|
6291
|
-
if (report.appsWithMissingSecrets.length > 0) for (const { appName, missing } of report.appsWithMissingSecrets) logger$
|
|
6075
|
+
if (report.appsWithSecrets.length > 0) logger$2.log(` ✓ Encrypted secrets for: ${report.appsWithSecrets.join(", ")}`);
|
|
6076
|
+
if (report.appsWithMissingSecrets.length > 0) for (const { appName, missing } of report.appsWithMissingSecrets) logger$2.log(` ⚠️ ${appName}: Missing secrets: ${missing.join(", ")}`);
|
|
6292
6077
|
}
|
|
6293
6078
|
let creds = await getDokployCredentials();
|
|
6294
6079
|
if (!creds) {
|
|
6295
|
-
logger$
|
|
6080
|
+
logger$2.log("\n📋 Dokploy credentials not found. Let's set them up.");
|
|
6296
6081
|
const endpoint = await prompt("Dokploy URL (e.g., https://dokploy.example.com): ");
|
|
6297
6082
|
const normalizedEndpoint = endpoint.replace(/\/$/, "");
|
|
6298
6083
|
try {
|
|
@@ -6300,9 +6085,9 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6300
6085
|
} catch {
|
|
6301
6086
|
throw new Error("Invalid URL format");
|
|
6302
6087
|
}
|
|
6303
|
-
logger$
|
|
6088
|
+
logger$2.log(`\nGenerate a token at: ${normalizedEndpoint}/settings/profile\n`);
|
|
6304
6089
|
const token = await prompt("API Token: ", true);
|
|
6305
|
-
logger$
|
|
6090
|
+
logger$2.log("\nValidating credentials...");
|
|
6306
6091
|
const isValid = await validateDokployToken(normalizedEndpoint, token);
|
|
6307
6092
|
if (!isValid) throw new Error("Invalid credentials. Please check your token.");
|
|
6308
6093
|
await storeDokployCredentials(token, normalizedEndpoint);
|
|
@@ -6310,43 +6095,43 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6310
6095
|
token,
|
|
6311
6096
|
endpoint: normalizedEndpoint
|
|
6312
6097
|
};
|
|
6313
|
-
logger$
|
|
6098
|
+
logger$2.log("✓ Credentials saved");
|
|
6314
6099
|
}
|
|
6315
6100
|
const api = new DokployApi({
|
|
6316
6101
|
baseUrl: creds.endpoint,
|
|
6317
6102
|
token: creds.token
|
|
6318
6103
|
});
|
|
6319
|
-
logger$
|
|
6104
|
+
logger$2.log("\n📁 Setting up Dokploy project...");
|
|
6320
6105
|
const projectName = workspace.name;
|
|
6321
6106
|
const projects = await api.listProjects();
|
|
6322
6107
|
let project = projects.find((p) => p.name.toLowerCase() === projectName.toLowerCase());
|
|
6323
6108
|
let environmentId;
|
|
6324
6109
|
if (project) {
|
|
6325
|
-
logger$
|
|
6110
|
+
logger$2.log(` Found existing project: ${project.name}`);
|
|
6326
6111
|
const projectDetails = await api.getProject(project.projectId);
|
|
6327
6112
|
const environments = projectDetails.environments ?? [];
|
|
6328
6113
|
const matchingEnv = environments.find((e) => e.name.toLowerCase() === stage.toLowerCase());
|
|
6329
6114
|
if (matchingEnv) {
|
|
6330
6115
|
environmentId = matchingEnv.environmentId;
|
|
6331
|
-
logger$
|
|
6116
|
+
logger$2.log(` Using environment: ${matchingEnv.name}`);
|
|
6332
6117
|
} else {
|
|
6333
|
-
logger$
|
|
6118
|
+
logger$2.log(` Creating "${stage}" environment...`);
|
|
6334
6119
|
const env = await api.createEnvironment(project.projectId, stage);
|
|
6335
6120
|
environmentId = env.environmentId;
|
|
6336
|
-
logger$
|
|
6121
|
+
logger$2.log(` ✓ Created environment: ${stage}`);
|
|
6337
6122
|
}
|
|
6338
6123
|
} else {
|
|
6339
|
-
logger$
|
|
6124
|
+
logger$2.log(` Creating project: ${projectName}`);
|
|
6340
6125
|
const result = await api.createProject(projectName);
|
|
6341
6126
|
project = result.project;
|
|
6342
6127
|
if (result.environment.name.toLowerCase() !== stage.toLowerCase()) {
|
|
6343
|
-
logger$
|
|
6128
|
+
logger$2.log(` Creating "${stage}" environment...`);
|
|
6344
6129
|
const env = await api.createEnvironment(project.projectId, stage);
|
|
6345
6130
|
environmentId = env.environmentId;
|
|
6346
6131
|
} else environmentId = result.environment.environmentId;
|
|
6347
|
-
logger$
|
|
6132
|
+
logger$2.log(` ✓ Created project: ${project.projectId}`);
|
|
6348
6133
|
}
|
|
6349
|
-
logger$
|
|
6134
|
+
logger$2.log("\n📋 Loading deploy state...");
|
|
6350
6135
|
const stateProvider = await createStateProvider({
|
|
6351
6136
|
config: workspace.state,
|
|
6352
6137
|
workspaceRoot: workspace.root,
|
|
@@ -6354,27 +6139,27 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6354
6139
|
});
|
|
6355
6140
|
let state = await stateProvider.read(stage);
|
|
6356
6141
|
if (state) {
|
|
6357
|
-
logger$
|
|
6142
|
+
logger$2.log(` Found existing state for stage "${stage}"`);
|
|
6358
6143
|
if (state.projectId !== project.projectId) {
|
|
6359
|
-
logger$
|
|
6144
|
+
logger$2.log(` ⚠ Project ID changed, updating state`);
|
|
6360
6145
|
state.projectId = project.projectId;
|
|
6361
6146
|
}
|
|
6362
6147
|
if (state.environmentId !== environmentId) {
|
|
6363
|
-
logger$
|
|
6148
|
+
logger$2.log(` ⚠ Environment ID changed, updating state`);
|
|
6364
6149
|
state.environmentId = environmentId;
|
|
6365
6150
|
}
|
|
6366
6151
|
} else {
|
|
6367
|
-
logger$
|
|
6152
|
+
logger$2.log(` Creating new state for stage "${stage}"`);
|
|
6368
6153
|
state = createEmptyState(stage, project.projectId, environmentId);
|
|
6369
6154
|
}
|
|
6370
|
-
logger$
|
|
6155
|
+
logger$2.log("\n🐳 Checking registry...");
|
|
6371
6156
|
let registryId = await getDokployRegistryId();
|
|
6372
6157
|
const registry = workspace.deploy.dokploy?.registry;
|
|
6373
6158
|
if (registryId) try {
|
|
6374
6159
|
const reg = await api.getRegistry(registryId);
|
|
6375
|
-
logger$
|
|
6160
|
+
logger$2.log(` Using registry: ${reg.registryName}`);
|
|
6376
6161
|
} catch {
|
|
6377
|
-
logger$
|
|
6162
|
+
logger$2.log(" ⚠ Stored registry not found, clearing...");
|
|
6378
6163
|
registryId = void 0;
|
|
6379
6164
|
await storeDokployRegistryId("");
|
|
6380
6165
|
}
|
|
@@ -6383,17 +6168,17 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6383
6168
|
if (registries.length > 0) {
|
|
6384
6169
|
registryId = registries[0].registryId;
|
|
6385
6170
|
await storeDokployRegistryId(registryId);
|
|
6386
|
-
logger$
|
|
6171
|
+
logger$2.log(` Using registry: ${registries[0].registryName}`);
|
|
6387
6172
|
} else if (registry) {
|
|
6388
|
-
logger$
|
|
6389
|
-
logger$
|
|
6173
|
+
logger$2.log(" No registries found in Dokploy. Let's create one.");
|
|
6174
|
+
logger$2.log(` Registry URL: ${registry}`);
|
|
6390
6175
|
const username = await prompt("Registry username: ");
|
|
6391
6176
|
const password = await prompt("Registry password/token: ", true);
|
|
6392
6177
|
const reg = await api.createRegistry("Default Registry", registry, username, password);
|
|
6393
6178
|
registryId = reg.registryId;
|
|
6394
6179
|
await storeDokployRegistryId(registryId);
|
|
6395
|
-
logger$
|
|
6396
|
-
} else logger$
|
|
6180
|
+
logger$2.log(` ✓ Registry created: ${registryId}`);
|
|
6181
|
+
} else logger$2.log(" ⚠ No registry configured. Set deploy.dokploy.registry in workspace config");
|
|
6397
6182
|
}
|
|
6398
6183
|
const services = workspace.services;
|
|
6399
6184
|
const dockerServices = {
|
|
@@ -6403,7 +6188,7 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6403
6188
|
let provisionedPostgres = null;
|
|
6404
6189
|
let provisionedRedis = null;
|
|
6405
6190
|
if (dockerServices.postgres || dockerServices.redis) {
|
|
6406
|
-
logger$
|
|
6191
|
+
logger$2.log("\n🔧 Provisioning infrastructure services...");
|
|
6407
6192
|
const existingServiceIds = {
|
|
6408
6193
|
postgresId: getPostgresId(state),
|
|
6409
6194
|
redisId: getRedisId(state)
|
|
@@ -6421,7 +6206,7 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6421
6206
|
}
|
|
6422
6207
|
}
|
|
6423
6208
|
const skippedMobileApps = appsToDeployNames.filter((name$1) => workspace.apps[name$1].type === "mobile");
|
|
6424
|
-
if (skippedMobileApps.length > 0) logger$
|
|
6209
|
+
if (skippedMobileApps.length > 0) logger$2.log(`\n📱 Skipping ${skippedMobileApps.length} mobile app(s) — deploy via framework toolchain: ${skippedMobileApps.join(", ")}`);
|
|
6425
6210
|
const backendApps = appsToDeployNames.filter((name$1) => workspace.apps[name$1].type === "backend");
|
|
6426
6211
|
const frontendApps = appsToDeployNames.filter((name$1) => workspace.apps[name$1].type === "web");
|
|
6427
6212
|
const perAppDbCredentials = /* @__PURE__ */ new Map();
|
|
@@ -6431,13 +6216,13 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6431
6216
|
return requirements?.requiredEnvVars.includes("DATABASE_URL");
|
|
6432
6217
|
});
|
|
6433
6218
|
if (appsNeedingDb.length > 0) {
|
|
6434
|
-
logger$
|
|
6435
|
-
logger$
|
|
6219
|
+
logger$2.log(`\n🔐 Setting up per-app database credentials...`);
|
|
6220
|
+
logger$2.log(` Apps needing DATABASE_URL: ${appsNeedingDb.join(", ")}`);
|
|
6436
6221
|
const existingCredentials = getAllAppCredentials(state);
|
|
6437
6222
|
const usersToCreate = [];
|
|
6438
6223
|
for (const appName of appsNeedingDb) {
|
|
6439
6224
|
let credentials = existingCredentials[appName];
|
|
6440
|
-
if (credentials) logger$
|
|
6225
|
+
if (credentials) logger$2.log(` ${appName}: Using existing credentials from state`);
|
|
6441
6226
|
else {
|
|
6442
6227
|
const password = randomBytes(16).toString("hex");
|
|
6443
6228
|
credentials = {
|
|
@@ -6445,7 +6230,7 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6445
6230
|
dbPassword: password
|
|
6446
6231
|
};
|
|
6447
6232
|
setAppCredentials(state, appName, credentials);
|
|
6448
|
-
logger$
|
|
6233
|
+
logger$2.log(` ${appName}: Generated new credentials`);
|
|
6449
6234
|
}
|
|
6450
6235
|
perAppDbCredentials.set(appName, credentials);
|
|
6451
6236
|
usersToCreate.push({
|
|
@@ -6459,8 +6244,8 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6459
6244
|
}
|
|
6460
6245
|
}
|
|
6461
6246
|
if (workspace.deploy?.backups && provisionedPostgres) {
|
|
6462
|
-
logger$
|
|
6463
|
-
const { provisionBackupDestination } = await import("./backup-provisioner-
|
|
6247
|
+
logger$2.log("\n💾 Provisioning backup destination...");
|
|
6248
|
+
const { provisionBackupDestination } = await import("./backup-provisioner-Xunq-p_8.mjs");
|
|
6464
6249
|
const backupState = await provisionBackupDestination({
|
|
6465
6250
|
api,
|
|
6466
6251
|
projectId: project.projectId,
|
|
@@ -6468,13 +6253,13 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6468
6253
|
stage,
|
|
6469
6254
|
config: workspace.deploy.backups,
|
|
6470
6255
|
existingState: getBackupState(state),
|
|
6471
|
-
logger: logger$
|
|
6256
|
+
logger: logger$2
|
|
6472
6257
|
});
|
|
6473
6258
|
setBackupState(state, backupState);
|
|
6474
6259
|
if (!backupState.postgresBackupId) {
|
|
6475
6260
|
const backupSchedule = workspace.deploy.backups.schedule ?? "0 2 * * *";
|
|
6476
6261
|
const backupRetention = workspace.deploy.backups.retention ?? 30;
|
|
6477
|
-
logger$
|
|
6262
|
+
logger$2.log(" Creating postgres backup schedule...");
|
|
6478
6263
|
const backup = await api.createPostgresBackup({
|
|
6479
6264
|
schedule: backupSchedule,
|
|
6480
6265
|
prefix: `${stage}/postgres`,
|
|
@@ -6485,8 +6270,8 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6485
6270
|
keepLatestCount: backupRetention
|
|
6486
6271
|
});
|
|
6487
6272
|
setPostgresBackupId(state, backup.backupId);
|
|
6488
|
-
logger$
|
|
6489
|
-
} else logger$
|
|
6273
|
+
logger$2.log(` ✓ Postgres backup schedule created (${backupSchedule})`);
|
|
6274
|
+
} else logger$2.log(" ✓ Using existing postgres backup schedule");
|
|
6490
6275
|
}
|
|
6491
6276
|
const publicUrls = {};
|
|
6492
6277
|
const results = [];
|
|
@@ -6501,25 +6286,25 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6501
6286
|
frontendUrls.push(`https://${hostname}`);
|
|
6502
6287
|
}
|
|
6503
6288
|
if (backendApps.length > 0) {
|
|
6504
|
-
logger$
|
|
6289
|
+
logger$2.log("\n📦 PHASE 1: Deploying backend applications...");
|
|
6505
6290
|
for (const appName of backendApps) {
|
|
6506
6291
|
const app = workspace.apps[appName];
|
|
6507
|
-
logger$
|
|
6292
|
+
logger$2.log(`\n ⚙️ Deploying ${appName}...`);
|
|
6508
6293
|
try {
|
|
6509
6294
|
const dokployAppName = appName;
|
|
6510
6295
|
let application = null;
|
|
6511
6296
|
const cachedAppId = getApplicationId(state, appName);
|
|
6512
6297
|
if (cachedAppId) {
|
|
6513
|
-
logger$
|
|
6298
|
+
logger$2.log(` Using cached ID: ${cachedAppId}`);
|
|
6514
6299
|
application = await api.getApplication(cachedAppId);
|
|
6515
|
-
if (application) logger$
|
|
6516
|
-
else logger$
|
|
6300
|
+
if (application) logger$2.log(` ✓ Application found: ${application.applicationId}`);
|
|
6301
|
+
else logger$2.log(` ⚠ Cached ID invalid, will create new`);
|
|
6517
6302
|
}
|
|
6518
6303
|
if (!application) {
|
|
6519
6304
|
const result = await api.findOrCreateApplication(dokployAppName, project.projectId, environmentId);
|
|
6520
6305
|
application = result.application;
|
|
6521
|
-
if (result.created) logger$
|
|
6522
|
-
else logger$
|
|
6306
|
+
if (result.created) logger$2.log(` Created application: ${application.applicationId}`);
|
|
6307
|
+
else logger$2.log(` Found existing application: ${application.applicationId}`);
|
|
6523
6308
|
}
|
|
6524
6309
|
setApplicationId(state, appName, application.applicationId);
|
|
6525
6310
|
const appSecrets = encryptedSecrets.get(appName);
|
|
@@ -6527,11 +6312,11 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6527
6312
|
if (appSecrets && appSecrets.secretCount > 0) {
|
|
6528
6313
|
buildArgs.push(`GKM_ENCRYPTED_CREDENTIALS=${appSecrets.payload.encrypted}`);
|
|
6529
6314
|
buildArgs.push(`GKM_CREDENTIALS_IV=${appSecrets.payload.iv}`);
|
|
6530
|
-
logger$
|
|
6315
|
+
logger$2.log(` Encrypted ${appSecrets.secretCount} secrets`);
|
|
6531
6316
|
}
|
|
6532
6317
|
const imageName = `${workspace.name}-${appName}`;
|
|
6533
6318
|
const imageRef = registry ? `${registry}/${imageName}:${imageTag}` : `${imageName}:${imageTag}`;
|
|
6534
|
-
logger$
|
|
6319
|
+
logger$2.log(` Building Docker image: ${imageRef}`);
|
|
6535
6320
|
await deployDocker({
|
|
6536
6321
|
stage,
|
|
6537
6322
|
tag: imageTag,
|
|
@@ -6581,10 +6366,10 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6581
6366
|
const { valid, missing, resolved } = validateEnvVars(requiredVars, envContext);
|
|
6582
6367
|
if (!valid) throw new Error(formatMissingVarsError(appName, missing, stage));
|
|
6583
6368
|
const envVars = Object.entries(resolved).map(([key, value]) => `${key}=${value}`);
|
|
6584
|
-
if (Object.keys(resolved).length > 0) logger$
|
|
6369
|
+
if (Object.keys(resolved).length > 0) logger$2.log(` Resolved ${Object.keys(resolved).length} env vars: ${Object.keys(resolved).join(", ")}`);
|
|
6585
6370
|
await api.saveDockerProvider(application.applicationId, imageRef, { registryId });
|
|
6586
6371
|
await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
|
|
6587
|
-
logger$
|
|
6372
|
+
logger$2.log(` Deploying to Dokploy...`);
|
|
6588
6373
|
await api.deployApplication(application.applicationId);
|
|
6589
6374
|
const existingDomains = await api.getDomainsByApplicationId(application.applicationId);
|
|
6590
6375
|
const existingDomain = existingDomains.find((d) => d.host === backendHost);
|
|
@@ -6592,7 +6377,7 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6592
6377
|
appHostnames.set(appName, backendHost);
|
|
6593
6378
|
appDomainIds.set(appName, existingDomain.domainId);
|
|
6594
6379
|
publicUrls[appName] = `https://${backendHost}`;
|
|
6595
|
-
logger$
|
|
6380
|
+
logger$2.log(` ✓ Domain: https://${backendHost} (existing)`);
|
|
6596
6381
|
} else try {
|
|
6597
6382
|
const domain = await api.createDomain({
|
|
6598
6383
|
host: backendHost,
|
|
@@ -6604,10 +6389,10 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6604
6389
|
appHostnames.set(appName, backendHost);
|
|
6605
6390
|
appDomainIds.set(appName, domain.domainId);
|
|
6606
6391
|
publicUrls[appName] = `https://${backendHost}`;
|
|
6607
|
-
logger$
|
|
6392
|
+
logger$2.log(` ✓ Domain: https://${backendHost} (created)`);
|
|
6608
6393
|
} catch (domainError) {
|
|
6609
6394
|
const message = domainError instanceof Error ? domainError.message : "Unknown error";
|
|
6610
|
-
logger$
|
|
6395
|
+
logger$2.log(` ⚠ Domain creation failed: ${message}`);
|
|
6611
6396
|
appHostnames.set(appName, backendHost);
|
|
6612
6397
|
publicUrls[appName] = `https://${backendHost}`;
|
|
6613
6398
|
}
|
|
@@ -6618,10 +6403,10 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6618
6403
|
applicationId: application.applicationId,
|
|
6619
6404
|
imageRef
|
|
6620
6405
|
});
|
|
6621
|
-
logger$
|
|
6406
|
+
logger$2.log(` ✓ ${appName} deployed successfully`);
|
|
6622
6407
|
} catch (error) {
|
|
6623
6408
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
6624
|
-
logger$
|
|
6409
|
+
logger$2.log(` ✗ Failed to deploy ${appName}: ${message}`);
|
|
6625
6410
|
results.push({
|
|
6626
6411
|
appName,
|
|
6627
6412
|
type: app.type,
|
|
@@ -6633,25 +6418,25 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6633
6418
|
}
|
|
6634
6419
|
}
|
|
6635
6420
|
if (frontendApps.length > 0) {
|
|
6636
|
-
logger$
|
|
6421
|
+
logger$2.log("\n🌐 PHASE 2: Deploying frontend applications...");
|
|
6637
6422
|
for (const appName of frontendApps) {
|
|
6638
6423
|
const app = workspace.apps[appName];
|
|
6639
|
-
logger$
|
|
6424
|
+
logger$2.log(`\n 🌐 Deploying ${appName}...`);
|
|
6640
6425
|
try {
|
|
6641
6426
|
const dokployAppName = appName;
|
|
6642
6427
|
let application = null;
|
|
6643
6428
|
const cachedAppId = getApplicationId(state, appName);
|
|
6644
6429
|
if (cachedAppId) {
|
|
6645
|
-
logger$
|
|
6430
|
+
logger$2.log(` Using cached ID: ${cachedAppId}`);
|
|
6646
6431
|
application = await api.getApplication(cachedAppId);
|
|
6647
|
-
if (application) logger$
|
|
6648
|
-
else logger$
|
|
6432
|
+
if (application) logger$2.log(` ✓ Application found: ${application.applicationId}`);
|
|
6433
|
+
else logger$2.log(` ⚠ Cached ID invalid, will create new`);
|
|
6649
6434
|
}
|
|
6650
6435
|
if (!application) {
|
|
6651
6436
|
const result = await api.findOrCreateApplication(dokployAppName, project.projectId, environmentId);
|
|
6652
6437
|
application = result.application;
|
|
6653
|
-
if (result.created) logger$
|
|
6654
|
-
else logger$
|
|
6438
|
+
if (result.created) logger$2.log(` Created application: ${application.applicationId}`);
|
|
6439
|
+
else logger$2.log(` Found existing application: ${application.applicationId}`);
|
|
6655
6440
|
}
|
|
6656
6441
|
setApplicationId(state, appName, application.applicationId);
|
|
6657
6442
|
const dependencyUrls = {};
|
|
@@ -6673,7 +6458,7 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6673
6458
|
const sniffedVars = sniffedApps.get(appName)?.requiredEnvVars ?? [];
|
|
6674
6459
|
const { valid, missing, resolved } = validateEnvVars(sniffedVars, envContext);
|
|
6675
6460
|
if (!valid) throw new Error(formatMissingVarsError(appName, missing, stage));
|
|
6676
|
-
if (Object.keys(resolved).length > 0) logger$
|
|
6461
|
+
if (Object.keys(resolved).length > 0) logger$2.log(` Resolved ${Object.keys(resolved).length} env vars: ${Object.keys(resolved).join(", ")}`);
|
|
6677
6462
|
const publicPrefix = getPublicEnvPrefix(app.framework);
|
|
6678
6463
|
const buildArgs = [];
|
|
6679
6464
|
const publicUrlArgNames = [];
|
|
@@ -6683,10 +6468,10 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6683
6468
|
publicUrlArgNames.push(key);
|
|
6684
6469
|
}
|
|
6685
6470
|
}
|
|
6686
|
-
if (buildArgs.length > 0) logger$
|
|
6471
|
+
if (buildArgs.length > 0) logger$2.log(` Build args: ${publicUrlArgNames.join(", ")}`);
|
|
6687
6472
|
const imageName = `${workspace.name}-${appName}`;
|
|
6688
6473
|
const imageRef = registry ? `${registry}/${imageName}:${imageTag}` : `${imageName}:${imageTag}`;
|
|
6689
|
-
logger$
|
|
6474
|
+
logger$2.log(` Building Docker image: ${imageRef}`);
|
|
6690
6475
|
await deployDocker({
|
|
6691
6476
|
stage,
|
|
6692
6477
|
tag: imageTag,
|
|
@@ -6707,7 +6492,7 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6707
6492
|
for (const [key, value] of Object.entries(resolved)) envVars.push(`${key}=${value}`);
|
|
6708
6493
|
await api.saveDockerProvider(application.applicationId, imageRef, { registryId });
|
|
6709
6494
|
await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
|
|
6710
|
-
logger$
|
|
6495
|
+
logger$2.log(` Deploying to Dokploy...`);
|
|
6711
6496
|
await api.deployApplication(application.applicationId);
|
|
6712
6497
|
const existingFrontendDomains = await api.getDomainsByApplicationId(application.applicationId);
|
|
6713
6498
|
const existingFrontendDomain = existingFrontendDomains.find((d) => d.host === frontendHost);
|
|
@@ -6715,7 +6500,7 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6715
6500
|
appHostnames.set(appName, frontendHost);
|
|
6716
6501
|
appDomainIds.set(appName, existingFrontendDomain.domainId);
|
|
6717
6502
|
publicUrls[appName] = `https://${frontendHost}`;
|
|
6718
|
-
logger$
|
|
6503
|
+
logger$2.log(` ✓ Domain: https://${frontendHost} (existing)`);
|
|
6719
6504
|
} else try {
|
|
6720
6505
|
const domain = await api.createDomain({
|
|
6721
6506
|
host: frontendHost,
|
|
@@ -6727,10 +6512,10 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6727
6512
|
appHostnames.set(appName, frontendHost);
|
|
6728
6513
|
appDomainIds.set(appName, domain.domainId);
|
|
6729
6514
|
publicUrls[appName] = `https://${frontendHost}`;
|
|
6730
|
-
logger$
|
|
6515
|
+
logger$2.log(` ✓ Domain: https://${frontendHost} (created)`);
|
|
6731
6516
|
} catch (domainError) {
|
|
6732
6517
|
const message = domainError instanceof Error ? domainError.message : "Unknown error";
|
|
6733
|
-
logger$
|
|
6518
|
+
logger$2.log(` ⚠ Domain creation failed: ${message}`);
|
|
6734
6519
|
appHostnames.set(appName, frontendHost);
|
|
6735
6520
|
publicUrls[appName] = `https://${frontendHost}`;
|
|
6736
6521
|
}
|
|
@@ -6741,10 +6526,10 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6741
6526
|
applicationId: application.applicationId,
|
|
6742
6527
|
imageRef
|
|
6743
6528
|
});
|
|
6744
|
-
logger$
|
|
6529
|
+
logger$2.log(` ✓ ${appName} deployed successfully`);
|
|
6745
6530
|
} catch (error) {
|
|
6746
6531
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
6747
|
-
logger$
|
|
6532
|
+
logger$2.log(` ✗ Failed to deploy ${appName}: ${message}`);
|
|
6748
6533
|
results.push({
|
|
6749
6534
|
appName,
|
|
6750
6535
|
type: app.type,
|
|
@@ -6754,9 +6539,9 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6754
6539
|
}
|
|
6755
6540
|
}
|
|
6756
6541
|
}
|
|
6757
|
-
logger$
|
|
6542
|
+
logger$2.log("\n📋 Saving deploy state...");
|
|
6758
6543
|
await stateProvider.write(stage, state);
|
|
6759
|
-
logger$
|
|
6544
|
+
logger$2.log(" ✓ State saved");
|
|
6760
6545
|
const dnsConfig = workspace.deploy.dns;
|
|
6761
6546
|
if (dnsConfig && appHostnames.size > 0) {
|
|
6762
6547
|
const dnsResult = await orchestrateDns(appHostnames, dnsConfig, creds.endpoint);
|
|
@@ -6765,27 +6550,27 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6765
6550
|
await stateProvider.write(stage, state);
|
|
6766
6551
|
}
|
|
6767
6552
|
if (dnsResult?.success && appHostnames.size > 0) {
|
|
6768
|
-
logger$
|
|
6553
|
+
logger$2.log("\n🔒 Validating domains for SSL certificates...");
|
|
6769
6554
|
for (const [appName, hostname] of appHostnames) try {
|
|
6770
6555
|
const result = await api.validateDomain(hostname);
|
|
6771
|
-
if (result.isValid) logger$
|
|
6772
|
-
else logger$
|
|
6556
|
+
if (result.isValid) logger$2.log(` ✓ ${appName}: ${hostname} → ${result.resolvedIp}`);
|
|
6557
|
+
else logger$2.log(` ⚠ ${appName}: ${hostname} not valid`);
|
|
6773
6558
|
} catch (validationError) {
|
|
6774
6559
|
const message = validationError instanceof Error ? validationError.message : "Unknown error";
|
|
6775
|
-
logger$
|
|
6560
|
+
logger$2.log(` ⚠ ${appName}: validation failed - ${message}`);
|
|
6776
6561
|
}
|
|
6777
6562
|
}
|
|
6778
6563
|
}
|
|
6779
6564
|
const successCount = results.filter((r) => r.success).length;
|
|
6780
6565
|
const failedCount = results.filter((r) => !r.success).length;
|
|
6781
|
-
logger$
|
|
6782
|
-
logger$
|
|
6783
|
-
logger$
|
|
6784
|
-
logger$
|
|
6785
|
-
if (failedCount > 0) logger$
|
|
6566
|
+
logger$2.log(`\n${"─".repeat(50)}`);
|
|
6567
|
+
logger$2.log(`\n✅ Workspace deployment complete!`);
|
|
6568
|
+
logger$2.log(` Project: ${project.projectId}`);
|
|
6569
|
+
logger$2.log(` Successful: ${successCount}`);
|
|
6570
|
+
if (failedCount > 0) logger$2.log(` Failed: ${failedCount}`);
|
|
6786
6571
|
if (Object.keys(publicUrls).length > 0) {
|
|
6787
|
-
logger$
|
|
6788
|
-
for (const [name$1, url] of Object.entries(publicUrls)) logger$
|
|
6572
|
+
logger$2.log("\n 📡 Deployed URLs:");
|
|
6573
|
+
for (const [name$1, url] of Object.entries(publicUrls)) logger$2.log(` ${name$1}: ${url}`);
|
|
6789
6574
|
}
|
|
6790
6575
|
return {
|
|
6791
6576
|
apps: results,
|
|
@@ -6801,23 +6586,23 @@ async function deployCommand(options) {
|
|
|
6801
6586
|
const { provider, stage, tag, skipPush, skipBuild } = options;
|
|
6802
6587
|
const loadedConfig = await loadWorkspaceConfig();
|
|
6803
6588
|
if (loadedConfig.type === "workspace") {
|
|
6804
|
-
logger$
|
|
6589
|
+
logger$2.log("📦 Detected workspace configuration");
|
|
6805
6590
|
return workspaceDeployCommand(loadedConfig.workspace, options);
|
|
6806
6591
|
}
|
|
6807
|
-
logger$
|
|
6808
|
-
logger$
|
|
6809
|
-
const config
|
|
6592
|
+
logger$2.log(`\n🚀 Deploying to ${provider}...`);
|
|
6593
|
+
logger$2.log(` Stage: ${stage}`);
|
|
6594
|
+
const config = await loadConfig();
|
|
6810
6595
|
const imageTag = tag ?? generateTag(stage);
|
|
6811
|
-
logger$
|
|
6812
|
-
const dockerConfig = resolveDockerConfig(config
|
|
6596
|
+
logger$2.log(` Tag: ${imageTag}`);
|
|
6597
|
+
const dockerConfig = resolveDockerConfig(config);
|
|
6813
6598
|
const imageName = dockerConfig.imageName;
|
|
6814
6599
|
const registry = dockerConfig.registry;
|
|
6815
6600
|
const imageRef = registry ? `${registry}/${imageName}:${imageTag}` : `${imageName}:${imageTag}`;
|
|
6816
6601
|
let dokployConfig;
|
|
6817
6602
|
let finalRegistry = registry;
|
|
6818
6603
|
if (provider === "dokploy") {
|
|
6819
|
-
const composeServices = config
|
|
6820
|
-
logger$
|
|
6604
|
+
const composeServices = config.docker?.compose?.services;
|
|
6605
|
+
logger$2.log(`\n🔍 Docker compose config: ${JSON.stringify(config.docker?.compose)}`);
|
|
6821
6606
|
const dockerServices = composeServices ? Array.isArray(composeServices) ? {
|
|
6822
6607
|
postgres: composeServices.includes("postgres"),
|
|
6823
6608
|
redis: composeServices.includes("redis"),
|
|
@@ -6827,14 +6612,14 @@ async function deployCommand(options) {
|
|
|
6827
6612
|
redis: Boolean(composeServices.redis),
|
|
6828
6613
|
rabbitmq: Boolean(composeServices.rabbitmq)
|
|
6829
6614
|
} : void 0;
|
|
6830
|
-
const setupResult = await ensureDokploySetup(config
|
|
6615
|
+
const setupResult = await ensureDokploySetup(config, dockerConfig, stage, dockerServices);
|
|
6831
6616
|
dokployConfig = setupResult.config;
|
|
6832
6617
|
finalRegistry = dokployConfig.registry ?? dockerConfig.registry;
|
|
6833
6618
|
if (setupResult.serviceUrls) {
|
|
6834
6619
|
const { readStageSecrets: readStageSecrets$1, writeStageSecrets: writeStageSecrets$1, initStageSecrets } = await import("./storage-Cs4WBsc4.mjs");
|
|
6835
6620
|
let secrets = await readStageSecrets$1(stage);
|
|
6836
6621
|
if (!secrets) {
|
|
6837
|
-
logger$
|
|
6622
|
+
logger$2.log(` Creating secrets file for stage "${stage}"...`);
|
|
6838
6623
|
secrets = initStageSecrets(stage);
|
|
6839
6624
|
}
|
|
6840
6625
|
let updated = false;
|
|
@@ -6849,12 +6634,12 @@ async function deployCommand(options) {
|
|
|
6849
6634
|
const urlKey = key;
|
|
6850
6635
|
if (!secrets.urls[urlKey]) {
|
|
6851
6636
|
secrets.urls[urlKey] = value;
|
|
6852
|
-
logger$
|
|
6637
|
+
logger$2.log(` Saved ${key} to secrets.urls`);
|
|
6853
6638
|
updated = true;
|
|
6854
6639
|
}
|
|
6855
6640
|
} else if (!secrets.custom[key]) {
|
|
6856
6641
|
secrets.custom[key] = value;
|
|
6857
|
-
logger$
|
|
6642
|
+
logger$2.log(` Saved ${key} to secrets.custom`);
|
|
6858
6643
|
updated = true;
|
|
6859
6644
|
}
|
|
6860
6645
|
}
|
|
@@ -6863,14 +6648,14 @@ async function deployCommand(options) {
|
|
|
6863
6648
|
}
|
|
6864
6649
|
let masterKey;
|
|
6865
6650
|
if (!skipBuild) {
|
|
6866
|
-
logger$
|
|
6651
|
+
logger$2.log(`\n📦 Building for production...`);
|
|
6867
6652
|
const buildResult = await buildCommand({
|
|
6868
6653
|
provider: "server",
|
|
6869
6654
|
production: true,
|
|
6870
6655
|
stage
|
|
6871
6656
|
});
|
|
6872
6657
|
masterKey = buildResult.masterKey;
|
|
6873
|
-
} else logger$
|
|
6658
|
+
} else logger$2.log(`\n⏭️ Skipping build (--skip-build)`);
|
|
6874
6659
|
let result;
|
|
6875
6660
|
switch (provider) {
|
|
6876
6661
|
case "docker": {
|
|
@@ -6906,8 +6691,8 @@ async function deployCommand(options) {
|
|
|
6906
6691
|
break;
|
|
6907
6692
|
}
|
|
6908
6693
|
case "aws-lambda": {
|
|
6909
|
-
logger$
|
|
6910
|
-
logger$
|
|
6694
|
+
logger$2.log("\n⚠️ AWS Lambda deployment is not yet implemented.");
|
|
6695
|
+
logger$2.log(" Use SST or AWS CDK for Lambda deployments.");
|
|
6911
6696
|
result = {
|
|
6912
6697
|
imageRef,
|
|
6913
6698
|
masterKey
|
|
@@ -6916,7 +6701,7 @@ async function deployCommand(options) {
|
|
|
6916
6701
|
}
|
|
6917
6702
|
default: throw new Error(`Unknown deploy provider: ${provider}\nSupported providers: docker, dokploy, aws-lambda`);
|
|
6918
6703
|
}
|
|
6919
|
-
logger$
|
|
6704
|
+
logger$2.log("\n✅ Deployment complete!");
|
|
6920
6705
|
return result;
|
|
6921
6706
|
}
|
|
6922
6707
|
|
|
@@ -7113,22 +6898,22 @@ const GEEKMIDAS_VERSIONS = {
|
|
|
7113
6898
|
"@geekmidas/audit": "~2.0.1",
|
|
7114
6899
|
"@geekmidas/auth": "~2.0.1",
|
|
7115
6900
|
"@geekmidas/cache": "~1.1.1",
|
|
7116
|
-
"@geekmidas/client": "~
|
|
6901
|
+
"@geekmidas/client": "~6.0.0",
|
|
7117
6902
|
"@geekmidas/cloud": "~1.0.1",
|
|
7118
|
-
"@geekmidas/constructs": "~
|
|
6903
|
+
"@geekmidas/constructs": "~4.0.1",
|
|
7119
6904
|
"@geekmidas/db": "~1.0.2",
|
|
7120
6905
|
"@geekmidas/emailkit": "~1.0.1",
|
|
7121
6906
|
"@geekmidas/envkit": "~1.0.7",
|
|
7122
6907
|
"@geekmidas/errors": "~1.0.1",
|
|
7123
6908
|
"@geekmidas/events": "~1.1.4",
|
|
7124
6909
|
"@geekmidas/logger": "~1.0.2",
|
|
7125
|
-
"@geekmidas/rate-limit": "~
|
|
7126
|
-
"@geekmidas/schema": "~1.0.
|
|
7127
|
-
"@geekmidas/services": "~1.
|
|
6910
|
+
"@geekmidas/rate-limit": "~3.0.0",
|
|
6911
|
+
"@geekmidas/schema": "~1.0.3",
|
|
6912
|
+
"@geekmidas/services": "~1.1.1",
|
|
7128
6913
|
"@geekmidas/storage": "~2.0.3",
|
|
7129
6914
|
"@geekmidas/studio": "~1.0.1",
|
|
7130
6915
|
"@geekmidas/telescope": "~1.0.1",
|
|
7131
|
-
"@geekmidas/testkit": "~
|
|
6916
|
+
"@geekmidas/testkit": "~2.0.0",
|
|
7132
6917
|
"@geekmidas/cli": CLI_VERSION
|
|
7133
6918
|
};
|
|
7134
6919
|
|
|
@@ -8961,7 +8746,7 @@ function generateWorkspaceConfig(options) {
|
|
|
8961
8746
|
case "domain-based": return "./src/**/routes/*.ts";
|
|
8962
8747
|
}
|
|
8963
8748
|
};
|
|
8964
|
-
let config
|
|
8749
|
+
let config = `import { defineWorkspace } from '@geekmidas/cli/config';
|
|
8965
8750
|
|
|
8966
8751
|
export default defineWorkspace({
|
|
8967
8752
|
name: '${options.name}',
|
|
@@ -8974,12 +8759,12 @@ export default defineWorkspace({
|
|
|
8974
8759
|
envParser: './src/config/env#envParser',
|
|
8975
8760
|
logger: './src/config/logger#logger',
|
|
8976
8761
|
dependencies: ['auth'],`;
|
|
8977
|
-
if (telescope) config
|
|
8762
|
+
if (telescope) config += `
|
|
8978
8763
|
telescope: {
|
|
8979
8764
|
enabled: true,
|
|
8980
8765
|
path: '/__telescope',
|
|
8981
8766
|
},`;
|
|
8982
|
-
config
|
|
8767
|
+
config += `
|
|
8983
8768
|
openapi: {
|
|
8984
8769
|
enabled: true,
|
|
8985
8770
|
},
|
|
@@ -9003,33 +8788,33 @@ export default defineWorkspace({
|
|
|
9003
8788
|
},
|
|
9004
8789
|
},`;
|
|
9005
8790
|
if (services.db || services.cache || services.mail || services.storage || services.events) {
|
|
9006
|
-
config
|
|
8791
|
+
config += `
|
|
9007
8792
|
services: {`;
|
|
9008
|
-
if (services.db) config
|
|
8793
|
+
if (services.db) config += `
|
|
9009
8794
|
db: true,`;
|
|
9010
|
-
if (services.cache) config
|
|
8795
|
+
if (services.cache) config += `
|
|
9011
8796
|
cache: true,`;
|
|
9012
|
-
if (services.mail) config
|
|
8797
|
+
if (services.mail) config += `
|
|
9013
8798
|
mail: true,`;
|
|
9014
|
-
if (services.storage) config
|
|
8799
|
+
if (services.storage) config += `
|
|
9015
8800
|
storage: true,`;
|
|
9016
|
-
if (services.events) config
|
|
8801
|
+
if (services.events) config += `
|
|
9017
8802
|
events: '${services.events}',`;
|
|
9018
|
-
config
|
|
8803
|
+
config += `
|
|
9019
8804
|
},`;
|
|
9020
8805
|
}
|
|
9021
|
-
if (deployTarget === "dokploy") config
|
|
8806
|
+
if (deployTarget === "dokploy") config += `
|
|
9022
8807
|
deploy: {
|
|
9023
8808
|
default: 'dokploy',
|
|
9024
8809
|
},`;
|
|
9025
|
-
config
|
|
8810
|
+
config += `
|
|
9026
8811
|
secrets: {
|
|
9027
8812
|
enabled: true,
|
|
9028
8813
|
},`;
|
|
9029
|
-
config
|
|
8814
|
+
config += `
|
|
9030
8815
|
});
|
|
9031
8816
|
`;
|
|
9032
|
-
return config
|
|
8817
|
+
return config;
|
|
9033
8818
|
}
|
|
9034
8819
|
|
|
9035
8820
|
//#endregion
|
|
@@ -12868,14 +12653,14 @@ function printNextSteps(projectName, options, pkgManager) {
|
|
|
12868
12653
|
|
|
12869
12654
|
//#endregion
|
|
12870
12655
|
//#region src/secrets/index.ts
|
|
12871
|
-
const logger$
|
|
12656
|
+
const logger$1 = console;
|
|
12872
12657
|
/**
|
|
12873
12658
|
* Extract service names from compose config.
|
|
12874
12659
|
*/
|
|
12875
12660
|
function getServicesFromConfig(services) {
|
|
12876
12661
|
if (!services) return [];
|
|
12877
12662
|
if (Array.isArray(services)) return services;
|
|
12878
|
-
return Object.entries(services).filter(([, config
|
|
12663
|
+
return Object.entries(services).filter(([, config]) => config).map(([name$1]) => name$1);
|
|
12879
12664
|
}
|
|
12880
12665
|
/**
|
|
12881
12666
|
* Initialize secrets for a stage.
|
|
@@ -12884,12 +12669,12 @@ function getServicesFromConfig(services) {
|
|
|
12884
12669
|
async function secretsInitCommand(options) {
|
|
12885
12670
|
const { stage, force } = options;
|
|
12886
12671
|
if (!force && secretsExist(stage)) {
|
|
12887
|
-
logger$
|
|
12672
|
+
logger$1.error(`Secrets already exist for stage "${stage}". Use --force to overwrite.`);
|
|
12888
12673
|
process.exit(1);
|
|
12889
12674
|
}
|
|
12890
|
-
const config
|
|
12891
|
-
const services = getServicesFromConfig(config
|
|
12892
|
-
if (services.length === 0) logger$
|
|
12675
|
+
const config = await loadConfig();
|
|
12676
|
+
const services = getServicesFromConfig(config.docker?.compose?.services);
|
|
12677
|
+
if (services.length === 0) logger$1.warn("No services configured in docker.compose.services. Creating secrets with empty services.");
|
|
12893
12678
|
let projectName;
|
|
12894
12679
|
let workspaceSecrets;
|
|
12895
12680
|
try {
|
|
@@ -12898,23 +12683,23 @@ async function secretsInitCommand(options) {
|
|
|
12898
12683
|
const isMultiApp = Object.keys(loaded.workspace.apps).length > 1;
|
|
12899
12684
|
if (isMultiApp) {
|
|
12900
12685
|
workspaceSecrets = generateFullstackCustomSecrets(loaded.workspace);
|
|
12901
|
-
logger$
|
|
12686
|
+
logger$1.log(" Detected workspace mode — generating per-app secrets");
|
|
12902
12687
|
}
|
|
12903
12688
|
} catch {}
|
|
12904
12689
|
const secrets = createStageSecrets(stage, services, { projectName });
|
|
12905
12690
|
if (workspaceSecrets) secrets.custom = workspaceSecrets;
|
|
12906
12691
|
await writeStageSecrets(secrets);
|
|
12907
|
-
logger$
|
|
12908
|
-
logger$
|
|
12909
|
-
logger$
|
|
12910
|
-
for (const service of services) logger$
|
|
12911
|
-
if (secrets.urls.DATABASE_URL) logger$
|
|
12912
|
-
if (secrets.urls.REDIS_URL) logger$
|
|
12913
|
-
if (secrets.urls.RABBITMQ_URL) logger$
|
|
12914
|
-
if (secrets.urls.STORAGE_ENDPOINT) logger$
|
|
12915
|
-
if (Object.keys(secrets.custom).length > 0) logger$
|
|
12916
|
-
logger$
|
|
12917
|
-
logger$
|
|
12692
|
+
logger$1.log(`\n✓ Secrets initialized for stage "${stage}"`);
|
|
12693
|
+
logger$1.log(` Location: .gkm/secrets/${stage}.json`);
|
|
12694
|
+
logger$1.log("\n Generated credentials for:");
|
|
12695
|
+
for (const service of services) logger$1.log(` - ${service}`);
|
|
12696
|
+
if (secrets.urls.DATABASE_URL) logger$1.log(`\n DATABASE_URL: ${maskUrl(secrets.urls.DATABASE_URL)}`);
|
|
12697
|
+
if (secrets.urls.REDIS_URL) logger$1.log(` REDIS_URL: ${maskUrl(secrets.urls.REDIS_URL)}`);
|
|
12698
|
+
if (secrets.urls.RABBITMQ_URL) logger$1.log(` RABBITMQ_URL: ${maskUrl(secrets.urls.RABBITMQ_URL)}`);
|
|
12699
|
+
if (secrets.urls.STORAGE_ENDPOINT) logger$1.log(` STORAGE_ENDPOINT: ${secrets.urls.STORAGE_ENDPOINT}`);
|
|
12700
|
+
if (Object.keys(secrets.custom).length > 0) logger$1.log(`\n Custom secrets: ${Object.keys(secrets.custom).length}`);
|
|
12701
|
+
logger$1.log(`\n Use "gkm secrets:show --stage ${stage}" to view secrets`);
|
|
12702
|
+
logger$1.log(" Use \"gkm secrets:set <KEY> <VALUE> --stage " + stage + "\" to add custom secrets");
|
|
12918
12703
|
}
|
|
12919
12704
|
/**
|
|
12920
12705
|
* Read all data from stdin.
|
|
@@ -12933,21 +12718,21 @@ async function secretsSetCommand(key, value, options) {
|
|
|
12933
12718
|
let secretValue = value;
|
|
12934
12719
|
if (!secretValue) {
|
|
12935
12720
|
if (process.stdin.isTTY) {
|
|
12936
|
-
logger$
|
|
12937
|
-
logger$
|
|
12721
|
+
logger$1.error("No value provided. Use: gkm secrets:set KEY VALUE --stage <stage>");
|
|
12722
|
+
logger$1.error("Or pipe from stdin: echo \"value\" | gkm secrets:set KEY --stage <stage>");
|
|
12938
12723
|
process.exit(1);
|
|
12939
12724
|
}
|
|
12940
12725
|
secretValue = await readStdin();
|
|
12941
12726
|
if (!secretValue) {
|
|
12942
|
-
logger$
|
|
12727
|
+
logger$1.error("No value received from stdin");
|
|
12943
12728
|
process.exit(1);
|
|
12944
12729
|
}
|
|
12945
12730
|
}
|
|
12946
12731
|
try {
|
|
12947
12732
|
await setCustomSecret(stage, key, secretValue);
|
|
12948
|
-
logger$
|
|
12733
|
+
logger$1.log(`\n✓ Secret "${key}" set for stage "${stage}"`);
|
|
12949
12734
|
} catch (error) {
|
|
12950
|
-
logger$
|
|
12735
|
+
logger$1.error(error instanceof Error ? error.message : "Failed to set secret");
|
|
12951
12736
|
process.exit(1);
|
|
12952
12737
|
}
|
|
12953
12738
|
}
|
|
@@ -12958,34 +12743,34 @@ async function secretsShowCommand(options) {
|
|
|
12958
12743
|
const { stage, reveal } = options;
|
|
12959
12744
|
const secrets = await readStageSecrets(stage);
|
|
12960
12745
|
if (!secrets) {
|
|
12961
|
-
logger$
|
|
12746
|
+
logger$1.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
12962
12747
|
process.exit(1);
|
|
12963
12748
|
}
|
|
12964
|
-
logger$
|
|
12965
|
-
logger$
|
|
12966
|
-
logger$
|
|
12967
|
-
logger$
|
|
12749
|
+
logger$1.log(`\nSecrets for stage "${stage}":`);
|
|
12750
|
+
logger$1.log(` Created: ${secrets.createdAt}`);
|
|
12751
|
+
logger$1.log(` Updated: ${secrets.updatedAt}`);
|
|
12752
|
+
logger$1.log("\nService Credentials:");
|
|
12968
12753
|
for (const [service, creds] of Object.entries(secrets.services)) if (creds) {
|
|
12969
|
-
logger$
|
|
12970
|
-
logger$
|
|
12971
|
-
logger$
|
|
12972
|
-
logger$
|
|
12973
|
-
logger$
|
|
12974
|
-
if (creds.database) logger$
|
|
12975
|
-
if (creds.vhost) logger$
|
|
12976
|
-
if (creds.bucket) logger$
|
|
12977
|
-
}
|
|
12978
|
-
logger$
|
|
12979
|
-
if (secrets.urls.DATABASE_URL) logger$
|
|
12980
|
-
if (secrets.urls.REDIS_URL) logger$
|
|
12981
|
-
if (secrets.urls.RABBITMQ_URL) logger$
|
|
12982
|
-
if (secrets.urls.STORAGE_ENDPOINT) logger$
|
|
12754
|
+
logger$1.log(`\n ${service}:`);
|
|
12755
|
+
logger$1.log(` host: ${creds.host}`);
|
|
12756
|
+
logger$1.log(` port: ${creds.port}`);
|
|
12757
|
+
logger$1.log(` username: ${creds.username}`);
|
|
12758
|
+
logger$1.log(` password: ${reveal ? creds.password : maskPassword(creds.password)}`);
|
|
12759
|
+
if (creds.database) logger$1.log(` database: ${creds.database}`);
|
|
12760
|
+
if (creds.vhost) logger$1.log(` vhost: ${creds.vhost}`);
|
|
12761
|
+
if (creds.bucket) logger$1.log(` bucket: ${creds.bucket}`);
|
|
12762
|
+
}
|
|
12763
|
+
logger$1.log("\nConnection URLs:");
|
|
12764
|
+
if (secrets.urls.DATABASE_URL) logger$1.log(` DATABASE_URL: ${reveal ? secrets.urls.DATABASE_URL : maskUrl(secrets.urls.DATABASE_URL)}`);
|
|
12765
|
+
if (secrets.urls.REDIS_URL) logger$1.log(` REDIS_URL: ${reveal ? secrets.urls.REDIS_URL : maskUrl(secrets.urls.REDIS_URL)}`);
|
|
12766
|
+
if (secrets.urls.RABBITMQ_URL) logger$1.log(` RABBITMQ_URL: ${reveal ? secrets.urls.RABBITMQ_URL : maskUrl(secrets.urls.RABBITMQ_URL)}`);
|
|
12767
|
+
if (secrets.urls.STORAGE_ENDPOINT) logger$1.log(` STORAGE_ENDPOINT: ${secrets.urls.STORAGE_ENDPOINT}`);
|
|
12983
12768
|
const customKeys = Object.keys(secrets.custom);
|
|
12984
12769
|
if (customKeys.length > 0) {
|
|
12985
|
-
logger$
|
|
12986
|
-
for (const [key, value] of Object.entries(secrets.custom)) logger$
|
|
12770
|
+
logger$1.log("\nCustom Secrets:");
|
|
12771
|
+
for (const [key, value] of Object.entries(secrets.custom)) logger$1.log(` ${key}: ${reveal ? value : maskPassword(value)}`);
|
|
12987
12772
|
}
|
|
12988
|
-
if (!reveal) logger$
|
|
12773
|
+
if (!reveal) logger$1.log("\nUse --reveal to show actual values");
|
|
12989
12774
|
}
|
|
12990
12775
|
/**
|
|
12991
12776
|
* Rotate passwords for services.
|
|
@@ -12994,25 +12779,25 @@ async function secretsRotateCommand(options) {
|
|
|
12994
12779
|
const { stage, service } = options;
|
|
12995
12780
|
const secrets = await readStageSecrets(stage);
|
|
12996
12781
|
if (!secrets) {
|
|
12997
|
-
logger$
|
|
12782
|
+
logger$1.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
12998
12783
|
process.exit(1);
|
|
12999
12784
|
}
|
|
13000
12785
|
if (service) {
|
|
13001
12786
|
if (!secrets.services[service]) {
|
|
13002
|
-
logger$
|
|
12787
|
+
logger$1.error(`Service "${service}" not configured in stage "${stage}"`);
|
|
13003
12788
|
process.exit(1);
|
|
13004
12789
|
}
|
|
13005
12790
|
const updated = rotateServicePassword(secrets, service);
|
|
13006
12791
|
await writeStageSecrets(updated);
|
|
13007
|
-
logger$
|
|
12792
|
+
logger$1.log(`\n✓ Password rotated for ${service} in stage "${stage}"`);
|
|
13008
12793
|
} else {
|
|
13009
12794
|
let updated = secrets;
|
|
13010
12795
|
const services = Object.keys(secrets.services);
|
|
13011
12796
|
for (const svc of services) updated = rotateServicePassword(updated, svc);
|
|
13012
12797
|
await writeStageSecrets(updated);
|
|
13013
|
-
logger$
|
|
12798
|
+
logger$1.log(`\n✓ Passwords rotated for all services in stage "${stage}": ${services.join(", ")}`);
|
|
13014
12799
|
}
|
|
13015
|
-
logger$
|
|
12800
|
+
logger$1.log(`\nUse "gkm secrets:show --stage ${stage}" to view new values`);
|
|
13016
12801
|
}
|
|
13017
12802
|
/**
|
|
13018
12803
|
* Import secrets from a JSON file.
|
|
@@ -13020,7 +12805,7 @@ async function secretsRotateCommand(options) {
|
|
|
13020
12805
|
async function secretsImportCommand(file, options) {
|
|
13021
12806
|
const { stage, merge = true } = options;
|
|
13022
12807
|
if (!existsSync(file)) {
|
|
13023
|
-
logger$
|
|
12808
|
+
logger$1.error(`File not found: ${file}`);
|
|
13024
12809
|
process.exit(1);
|
|
13025
12810
|
}
|
|
13026
12811
|
let importedSecrets;
|
|
@@ -13030,12 +12815,12 @@ async function secretsImportCommand(file, options) {
|
|
|
13030
12815
|
if (typeof importedSecrets !== "object" || importedSecrets === null) throw new Error("JSON must be an object");
|
|
13031
12816
|
for (const [key, value] of Object.entries(importedSecrets)) if (typeof value !== "string") throw new Error(`Value for "${key}" must be a string, got ${typeof value}`);
|
|
13032
12817
|
} catch (error) {
|
|
13033
|
-
logger$
|
|
12818
|
+
logger$1.error(`Failed to parse JSON file: ${error instanceof Error ? error.message : "Invalid JSON"}`);
|
|
13034
12819
|
process.exit(1);
|
|
13035
12820
|
}
|
|
13036
12821
|
const secrets = await readStageSecrets(stage);
|
|
13037
12822
|
if (!secrets) {
|
|
13038
|
-
logger$
|
|
12823
|
+
logger$1.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
13039
12824
|
process.exit(1);
|
|
13040
12825
|
}
|
|
13041
12826
|
const updatedCustom = merge ? {
|
|
@@ -13050,10 +12835,10 @@ async function secretsImportCommand(file, options) {
|
|
|
13050
12835
|
await writeStageSecrets(updated);
|
|
13051
12836
|
const importedCount = Object.keys(importedSecrets).length;
|
|
13052
12837
|
const totalCount = Object.keys(updatedCustom).length;
|
|
13053
|
-
logger$
|
|
13054
|
-
if (merge && totalCount > importedCount) logger$
|
|
13055
|
-
logger$
|
|
13056
|
-
for (const key of Object.keys(importedSecrets)) logger$
|
|
12838
|
+
logger$1.log(`\n✓ Imported ${importedCount} secrets for stage "${stage}"`);
|
|
12839
|
+
if (merge && totalCount > importedCount) logger$1.log(` Total custom secrets: ${totalCount}`);
|
|
12840
|
+
logger$1.log("\n Imported keys:");
|
|
12841
|
+
for (const key of Object.keys(importedSecrets)) logger$1.log(` - ${key}`);
|
|
13057
12842
|
}
|
|
13058
12843
|
/**
|
|
13059
12844
|
* Mask password in a URL for display.
|
|
@@ -13068,264 +12853,6 @@ function maskUrl(url) {
|
|
|
13068
12853
|
}
|
|
13069
12854
|
}
|
|
13070
12855
|
|
|
13071
|
-
//#endregion
|
|
13072
|
-
//#region src/setup/index.ts
|
|
13073
|
-
const logger$1 = console;
|
|
13074
|
-
/**
|
|
13075
|
-
* Setup development environment.
|
|
13076
|
-
*
|
|
13077
|
-
* Orchestrates:
|
|
13078
|
-
* 1. Load workspace config
|
|
13079
|
-
* 2. Resolve secrets (local → SSM → generate fresh)
|
|
13080
|
-
* 3. Write docker/.env from secrets
|
|
13081
|
-
* 4. Start Docker services
|
|
13082
|
-
*/
|
|
13083
|
-
async function setupCommand(options = {}) {
|
|
13084
|
-
const stage = options.stage ?? "development";
|
|
13085
|
-
logger$1.log("\n🔧 Setting up development environment...\n");
|
|
13086
|
-
let loadedConfig;
|
|
13087
|
-
try {
|
|
13088
|
-
loadedConfig = await loadWorkspaceConfig();
|
|
13089
|
-
} catch {
|
|
13090
|
-
logger$1.error("❌ No gkm.config.ts found. Run this command from a workspace root.");
|
|
13091
|
-
process.exit(1);
|
|
13092
|
-
}
|
|
13093
|
-
const { workspace } = loadedConfig;
|
|
13094
|
-
const isMultiApp = Object.keys(workspace.apps).length > 1;
|
|
13095
|
-
logger$1.log(`📦 Workspace: ${workspace.name}`);
|
|
13096
|
-
logger$1.log(`📱 Apps: ${Object.keys(workspace.apps).join(", ")}`);
|
|
13097
|
-
logger$1.log(`🔑 Stage: ${stage}\n`);
|
|
13098
|
-
const secrets = await resolveSecrets(stage, workspace, options);
|
|
13099
|
-
if (!secrets) {
|
|
13100
|
-
logger$1.error("❌ Failed to resolve secrets. Exiting.");
|
|
13101
|
-
process.exit(1);
|
|
13102
|
-
}
|
|
13103
|
-
if (isMultiApp && workspace.services.db) {
|
|
13104
|
-
await writeDockerEnvFromSecrets(secrets, workspace.root);
|
|
13105
|
-
logger$1.log("📄 Generated docker/.env with database passwords");
|
|
13106
|
-
}
|
|
13107
|
-
if (!options.skipDocker) {
|
|
13108
|
-
const composeFile = join(workspace.root, "docker-compose.yml");
|
|
13109
|
-
if (existsSync(composeFile)) {
|
|
13110
|
-
logger$1.log("");
|
|
13111
|
-
const resolvedPorts = await resolveServicePorts(workspace.root);
|
|
13112
|
-
await startWorkspaceServices(workspace, resolvedPorts.dockerEnv);
|
|
13113
|
-
} else logger$1.log("⚠️ No docker-compose.yml found. Skipping Docker services.");
|
|
13114
|
-
}
|
|
13115
|
-
printSummary(workspace, stage);
|
|
13116
|
-
}
|
|
13117
|
-
/**
|
|
13118
|
-
* Resolve secrets with priority:
|
|
13119
|
-
* 1. Local secrets exist → use them (preserves manual additions)
|
|
13120
|
-
* 2. SSM configured and has secrets → pull and use
|
|
13121
|
-
* 3. Neither → generate fresh secrets
|
|
13122
|
-
*
|
|
13123
|
-
* --force skips checks 1 and 2 and always regenerates.
|
|
13124
|
-
*/
|
|
13125
|
-
async function resolveSecrets(stage, workspace, options) {
|
|
13126
|
-
if (options.force) {
|
|
13127
|
-
logger$1.log("🔐 Generating fresh secrets (--force)...");
|
|
13128
|
-
return generateFreshSecrets(stage, workspace, options);
|
|
13129
|
-
}
|
|
13130
|
-
if (secretsExist(stage, workspace.root)) {
|
|
13131
|
-
logger$1.log("🔐 Using existing local secrets");
|
|
13132
|
-
const secrets = await readStageSecrets(stage, workspace.root);
|
|
13133
|
-
if (secrets) {
|
|
13134
|
-
const reconciled = reconcileSecrets(secrets, workspace);
|
|
13135
|
-
if (reconciled) await writeStageSecrets(reconciled, workspace.root);
|
|
13136
|
-
return reconciled ?? secrets;
|
|
13137
|
-
}
|
|
13138
|
-
}
|
|
13139
|
-
if (isSSMConfigured(workspace)) {
|
|
13140
|
-
logger$1.log("☁️ Checking for remote secrets in SSM...");
|
|
13141
|
-
try {
|
|
13142
|
-
const remoteSecrets = await pullSecrets(stage, workspace);
|
|
13143
|
-
if (remoteSecrets) {
|
|
13144
|
-
logger$1.log("✅ Pulled secrets from SSM");
|
|
13145
|
-
await writeStageSecrets(remoteSecrets, workspace.root);
|
|
13146
|
-
return remoteSecrets;
|
|
13147
|
-
}
|
|
13148
|
-
logger$1.log(" No remote secrets found");
|
|
13149
|
-
} catch (error) {
|
|
13150
|
-
logger$1.warn(`⚠️ Failed to pull from SSM: ${error.message}`);
|
|
13151
|
-
}
|
|
13152
|
-
}
|
|
13153
|
-
logger$1.log("🔐 Generating fresh development secrets...");
|
|
13154
|
-
return generateFreshSecrets(stage, workspace, options);
|
|
13155
|
-
}
|
|
13156
|
-
/**
|
|
13157
|
-
* Reconcile existing secrets with expected workspace-derived keys.
|
|
13158
|
-
* Adds missing keys (e.g. BETTER_AUTH_*) without overwriting existing values.
|
|
13159
|
-
* Returns the updated secrets if changes were made, or null if no changes needed.
|
|
13160
|
-
* @internal Exported for testing
|
|
13161
|
-
*/
|
|
13162
|
-
function reconcileSecrets(secrets, workspace) {
|
|
13163
|
-
let changed = false;
|
|
13164
|
-
let result = { ...secrets };
|
|
13165
|
-
const serviceMap = [
|
|
13166
|
-
{
|
|
13167
|
-
key: "db",
|
|
13168
|
-
name: "postgres"
|
|
13169
|
-
},
|
|
13170
|
-
{
|
|
13171
|
-
key: "cache",
|
|
13172
|
-
name: "redis"
|
|
13173
|
-
},
|
|
13174
|
-
{
|
|
13175
|
-
key: "storage",
|
|
13176
|
-
name: "minio"
|
|
13177
|
-
},
|
|
13178
|
-
{
|
|
13179
|
-
key: "mail",
|
|
13180
|
-
name: "mailpit"
|
|
13181
|
-
}
|
|
13182
|
-
];
|
|
13183
|
-
for (const { key, name: name$1 } of serviceMap) if (workspace.services[key] && !result.services[name$1]) {
|
|
13184
|
-
const creds = generateServiceCredentials(name$1);
|
|
13185
|
-
if (name$1 === "minio") {
|
|
13186
|
-
creds.bucket = workspace.name;
|
|
13187
|
-
creds.username = workspace.name;
|
|
13188
|
-
}
|
|
13189
|
-
result = {
|
|
13190
|
-
...result,
|
|
13191
|
-
services: {
|
|
13192
|
-
...result.services,
|
|
13193
|
-
[name$1]: creds
|
|
13194
|
-
}
|
|
13195
|
-
};
|
|
13196
|
-
result.urls = generateConnectionUrls(result.services, result.eventsBackend);
|
|
13197
|
-
logger$1.log(` 🔄 Adding missing service credentials: ${name$1}`);
|
|
13198
|
-
changed = true;
|
|
13199
|
-
}
|
|
13200
|
-
if (result.services.postgres && !result.services.pgboss) {
|
|
13201
|
-
result = {
|
|
13202
|
-
...result,
|
|
13203
|
-
services: {
|
|
13204
|
-
...result.services,
|
|
13205
|
-
pgboss: {
|
|
13206
|
-
host: result.services.postgres.host,
|
|
13207
|
-
port: result.services.postgres.port,
|
|
13208
|
-
username: "pgboss",
|
|
13209
|
-
password: generateSecurePassword(),
|
|
13210
|
-
database: result.services.postgres.database ?? "app"
|
|
13211
|
-
}
|
|
13212
|
-
}
|
|
13213
|
-
};
|
|
13214
|
-
result.urls = generateConnectionUrls(result.services, result.eventsBackend);
|
|
13215
|
-
logger$1.log(" 🔄 Adding missing service credentials: pgboss");
|
|
13216
|
-
changed = true;
|
|
13217
|
-
}
|
|
13218
|
-
const eventsBackend = workspace.services.events;
|
|
13219
|
-
if (eventsBackend && result.eventsBackend !== eventsBackend) {
|
|
13220
|
-
result.eventsBackend = eventsBackend;
|
|
13221
|
-
if (eventsBackend === "sns" && !result.services.localstack) {
|
|
13222
|
-
result = {
|
|
13223
|
-
...result,
|
|
13224
|
-
services: {
|
|
13225
|
-
...result.services,
|
|
13226
|
-
localstack: generateLocalStackCredentials()
|
|
13227
|
-
}
|
|
13228
|
-
};
|
|
13229
|
-
logger$1.log(" 🔄 Adding missing service credentials: localstack");
|
|
13230
|
-
changed = true;
|
|
13231
|
-
}
|
|
13232
|
-
if (eventsBackend === "rabbitmq" && !result.services.rabbitmq) {
|
|
13233
|
-
result = {
|
|
13234
|
-
...result,
|
|
13235
|
-
services: {
|
|
13236
|
-
...result.services,
|
|
13237
|
-
rabbitmq: generateServiceCredentials("rabbitmq")
|
|
13238
|
-
}
|
|
13239
|
-
};
|
|
13240
|
-
logger$1.log(" 🔄 Adding missing service credentials: rabbitmq");
|
|
13241
|
-
changed = true;
|
|
13242
|
-
}
|
|
13243
|
-
result.urls = generateConnectionUrls(result.services, eventsBackend);
|
|
13244
|
-
changed = true;
|
|
13245
|
-
}
|
|
13246
|
-
const isMultiApp = Object.keys(workspace.apps).length > 1;
|
|
13247
|
-
if (isMultiApp) {
|
|
13248
|
-
const expected = generateFullstackCustomSecrets(workspace);
|
|
13249
|
-
const missing = {};
|
|
13250
|
-
for (const [key, value] of Object.entries(expected)) if (!(key in result.custom)) missing[key] = value;
|
|
13251
|
-
if (Object.keys(missing).length > 0) {
|
|
13252
|
-
logger$1.log(` 🔄 Adding missing secrets: ${Object.keys(missing).join(", ")}`);
|
|
13253
|
-
result = {
|
|
13254
|
-
...result,
|
|
13255
|
-
custom: {
|
|
13256
|
-
...result.custom,
|
|
13257
|
-
...missing
|
|
13258
|
-
}
|
|
13259
|
-
};
|
|
13260
|
-
changed = true;
|
|
13261
|
-
}
|
|
13262
|
-
}
|
|
13263
|
-
if (!changed) return null;
|
|
13264
|
-
return {
|
|
13265
|
-
...result,
|
|
13266
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
13267
|
-
};
|
|
13268
|
-
}
|
|
13269
|
-
/**
|
|
13270
|
-
* Generate fresh secrets for the workspace.
|
|
13271
|
-
*/
|
|
13272
|
-
async function generateFreshSecrets(stage, workspace, options) {
|
|
13273
|
-
const serviceNames = [];
|
|
13274
|
-
if (workspace.services.db) serviceNames.push("postgres");
|
|
13275
|
-
if (workspace.services.cache) serviceNames.push("redis");
|
|
13276
|
-
if (workspace.services.storage) serviceNames.push("minio");
|
|
13277
|
-
if (workspace.services.mail) serviceNames.push("mailpit");
|
|
13278
|
-
if (workspace.services.events === "sns") serviceNames.push("localstack");
|
|
13279
|
-
if (workspace.services.events === "rabbitmq") serviceNames.push("rabbitmq");
|
|
13280
|
-
const secrets = createStageSecrets(stage, serviceNames, {
|
|
13281
|
-
projectName: workspace.name,
|
|
13282
|
-
eventsBackend: workspace.services.events
|
|
13283
|
-
});
|
|
13284
|
-
const isMultiApp = Object.keys(workspace.apps).length > 1;
|
|
13285
|
-
if (isMultiApp) {
|
|
13286
|
-
const customSecrets = generateFullstackCustomSecrets(workspace);
|
|
13287
|
-
secrets.custom = customSecrets;
|
|
13288
|
-
} else secrets.custom = {
|
|
13289
|
-
NODE_ENV: "development",
|
|
13290
|
-
PORT: "3000",
|
|
13291
|
-
LOG_LEVEL: "debug",
|
|
13292
|
-
JWT_SECRET: `dev-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
13293
|
-
};
|
|
13294
|
-
await writeStageSecrets(secrets, workspace.root);
|
|
13295
|
-
logger$1.log(` Secrets written to .gkm/secrets/${stage}.json`);
|
|
13296
|
-
if (isSSMConfigured(workspace) && !options.yes) {
|
|
13297
|
-
const { shouldPush } = await prompts({
|
|
13298
|
-
type: "confirm",
|
|
13299
|
-
name: "shouldPush",
|
|
13300
|
-
message: "Push secrets to SSM for team sharing?",
|
|
13301
|
-
initial: true
|
|
13302
|
-
});
|
|
13303
|
-
if (shouldPush) try {
|
|
13304
|
-
await pushSecrets(stage, workspace);
|
|
13305
|
-
logger$1.log("☁️ Secrets pushed to SSM");
|
|
13306
|
-
} catch (error) {
|
|
13307
|
-
logger$1.warn(`⚠️ Failed to push to SSM: ${error.message}`);
|
|
13308
|
-
}
|
|
13309
|
-
}
|
|
13310
|
-
return secrets;
|
|
13311
|
-
}
|
|
13312
|
-
/**
|
|
13313
|
-
* Print setup summary with next steps.
|
|
13314
|
-
*/
|
|
13315
|
-
function printSummary(workspace, stage) {
|
|
13316
|
-
logger$1.log(`\n${"─".repeat(50)}`);
|
|
13317
|
-
logger$1.log("\n✅ Development environment ready!\n");
|
|
13318
|
-
logger$1.log("📋 Apps:");
|
|
13319
|
-
for (const [name$1, app] of Object.entries(workspace.apps)) {
|
|
13320
|
-
const icon = app.type === "web" ? "🌐" : app.type === "mobile" ? "📱" : "🔧";
|
|
13321
|
-
logger$1.log(` ${icon} ${name$1} → http://localhost:${app.port}`);
|
|
13322
|
-
}
|
|
13323
|
-
logger$1.log("\n🚀 Next steps:");
|
|
13324
|
-
logger$1.log(" gkm dev # Start all apps");
|
|
13325
|
-
logger$1.log(` gkm secrets:show --stage ${stage} # View secrets`);
|
|
13326
|
-
logger$1.log("");
|
|
13327
|
-
}
|
|
13328
|
-
|
|
13329
12856
|
//#endregion
|
|
13330
12857
|
//#region src/test/index.ts
|
|
13331
12858
|
/**
|
|
@@ -13336,6 +12863,12 @@ async function testCommand(options = {}) {
|
|
|
13336
12863
|
const stage = options.stage ?? "development";
|
|
13337
12864
|
const cwd = process.cwd();
|
|
13338
12865
|
console.log(`\n🧪 Running tests with ${stage} environment...\n`);
|
|
12866
|
+
const autoSetup = options.autoSetup || Boolean(process.env.GKM_AUTO_SETUP);
|
|
12867
|
+
if (autoSetup) {
|
|
12868
|
+
const { ensureStageSecrets } = await import("./setup-Dh-B7YTX.mjs");
|
|
12869
|
+
const generated = await ensureStageSecrets(stage, cwd);
|
|
12870
|
+
if (generated) console.log(` 🔐 Generated fresh ${stage} secrets (auto-setup)`);
|
|
12871
|
+
}
|
|
13339
12872
|
const defaultEnv = loadEnvFiles(".env");
|
|
13340
12873
|
if (defaultEnv.loaded.length > 0) console.log(` 📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
13341
12874
|
const result = await prepareEntryCredentials({
|
|
@@ -13648,7 +13181,7 @@ program.command("exec").description("Run a command with secrets injected into Cr
|
|
|
13648
13181
|
process.exit(1);
|
|
13649
13182
|
}
|
|
13650
13183
|
});
|
|
13651
|
-
program.command("test").description("Run tests with secrets loaded from environment").option("--stage <stage>", "Stage to load secrets from", "development").option("--run", "Run tests once without watch mode").option("--watch", "Enable watch mode").option("--coverage", "Generate coverage report").option("--ui", "Open Vitest UI").argument("[pattern]", "Pattern to filter tests").action(async (pattern, options) => {
|
|
13184
|
+
program.command("test").description("Run tests with secrets loaded from environment").option("--stage <stage>", "Stage to load secrets from", "development").option("--run", "Run tests once without watch mode").option("--watch", "Enable watch mode").option("--coverage", "Generate coverage report").option("--ui", "Open Vitest UI").option("--auto-setup", "Generate a fresh stage (secrets + key) from the workspace config when none exists (for CI; also via GKM_AUTO_SETUP)").argument("[pattern]", "Pattern to filter tests").action(async (pattern, options) => {
|
|
13652
13185
|
try {
|
|
13653
13186
|
const globalOptions = program.opts();
|
|
13654
13187
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
@@ -13790,8 +13323,8 @@ program.command("secrets:push").description("Push secrets to remote provider (SS
|
|
|
13790
13323
|
const globalOptions = program.opts();
|
|
13791
13324
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
13792
13325
|
const { loadWorkspaceConfig: loadWorkspaceConfig$1 } = await import("./config.mjs");
|
|
13793
|
-
const { pushSecrets
|
|
13794
|
-
const { reconcileMissingSecrets } = await import("./reconcile-
|
|
13326
|
+
const { pushSecrets } = await import("./sync-B5UHXwRM.mjs");
|
|
13327
|
+
const { reconcileMissingSecrets } = await import("./reconcile-B2FoJE0a.mjs");
|
|
13795
13328
|
const { readStageSecrets: readStageSecrets$1, writeStageSecrets: writeStageSecrets$1 } = await import("./storage-Cs4WBsc4.mjs");
|
|
13796
13329
|
const { workspace } = await loadWorkspaceConfig$1();
|
|
13797
13330
|
const secrets = await readStageSecrets$1(options.stage, workspace.root);
|
|
@@ -13803,7 +13336,7 @@ program.command("secrets:push").description("Push secrets to remote provider (SS
|
|
|
13803
13336
|
for (const key of result.addedKeys) console.log(` + ${key}`);
|
|
13804
13337
|
}
|
|
13805
13338
|
}
|
|
13806
|
-
await pushSecrets
|
|
13339
|
+
await pushSecrets(options.stage, workspace);
|
|
13807
13340
|
console.log(`\n✓ Secrets pushed for stage "${options.stage}"`);
|
|
13808
13341
|
} catch (error) {
|
|
13809
13342
|
console.error(formatError(error));
|
|
@@ -13815,11 +13348,11 @@ program.command("secrets:pull").description("Pull secrets from remote provider (
|
|
|
13815
13348
|
const globalOptions = program.opts();
|
|
13816
13349
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
13817
13350
|
const { loadWorkspaceConfig: loadWorkspaceConfig$1 } = await import("./config.mjs");
|
|
13818
|
-
const { pullSecrets
|
|
13351
|
+
const { pullSecrets } = await import("./sync-B5UHXwRM.mjs");
|
|
13819
13352
|
const { writeStageSecrets: writeStageSecrets$1 } = await import("./storage-Cs4WBsc4.mjs");
|
|
13820
|
-
const { reconcileMissingSecrets } = await import("./reconcile-
|
|
13353
|
+
const { reconcileMissingSecrets } = await import("./reconcile-B2FoJE0a.mjs");
|
|
13821
13354
|
const { workspace } = await loadWorkspaceConfig$1();
|
|
13822
|
-
let secrets = await pullSecrets
|
|
13355
|
+
let secrets = await pullSecrets(options.stage, workspace);
|
|
13823
13356
|
if (!secrets) {
|
|
13824
13357
|
console.error(`No remote secrets found for stage "${options.stage}".`);
|
|
13825
13358
|
process.exit(1);
|
|
@@ -13842,7 +13375,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
|
|
|
13842
13375
|
const globalOptions = program.opts();
|
|
13843
13376
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
13844
13377
|
const { loadWorkspaceConfig: loadWorkspaceConfig$1 } = await import("./config.mjs");
|
|
13845
|
-
const { reconcileMissingSecrets } = await import("./reconcile-
|
|
13378
|
+
const { reconcileMissingSecrets } = await import("./reconcile-B2FoJE0a.mjs");
|
|
13846
13379
|
const { readStageSecrets: readStageSecrets$1, writeStageSecrets: writeStageSecrets$1 } = await import("./storage-Cs4WBsc4.mjs");
|
|
13847
13380
|
const { workspace } = await loadWorkspaceConfig$1();
|
|
13848
13381
|
const secrets = await readStageSecrets$1(options.stage, workspace.root);
|