@geekmidas/cli 1.10.41 → 1.11.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 +17 -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.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-Dx9Kk5bR.d.mts} +2 -2
- package/dist/{index-BBvGMG_A.d.mts.map → index-Dx9Kk5bR.d.mts.map} +1 -1
- package/dist/index.cjs +596 -1314
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +725 -1443
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-DHp49UJV.cjs → openapi-CG1LBk7s.cjs} +1 -1
- package/dist/{openapi-DHp49UJV.cjs.map → openapi-CG1LBk7s.cjs.map} +1 -1
- package/dist/{openapi-DF9GyhyZ.mjs → openapi-CtRVU6RD.mjs} +1 -1
- package/dist/{openapi-DF9GyhyZ.mjs.map → openapi-CtRVU6RD.mjs.map} +1 -1
- 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.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-OszPdw9m.d.mts → types-CqfhdmRi.d.mts} +1 -1
- package/dist/{types-OszPdw9m.d.mts.map → types-CqfhdmRi.d.mts.map} +1 -1
- package/dist/workspace/index.d.mts +2 -2
- package/package.json +4 -4
- package/src/index.ts +4 -0
- package/src/init/versions.ts +5 -5
- 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/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/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-CtRVU6RD.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,8 +22,6 @@ 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";
|
|
@@ -35,7 +34,7 @@ import prompts from "prompts";
|
|
|
35
34
|
|
|
36
35
|
//#region package.json
|
|
37
36
|
var name = "@geekmidas/cli";
|
|
38
|
-
var version = "1.10.
|
|
37
|
+
var version = "1.10.41";
|
|
39
38
|
var description = "CLI tools for building Lambda handlers, server applications, and generating OpenAPI specs";
|
|
40
39
|
var private$1 = false;
|
|
41
40
|
var type = "module";
|
|
@@ -158,12 +157,12 @@ var package_default = {
|
|
|
158
157
|
|
|
159
158
|
//#endregion
|
|
160
159
|
//#region src/auth/index.ts
|
|
161
|
-
const logger$
|
|
160
|
+
const logger$12 = console;
|
|
162
161
|
/**
|
|
163
162
|
* Validate Dokploy token by making a test API call
|
|
164
163
|
*/
|
|
165
164
|
async function validateDokployToken(endpoint, token) {
|
|
166
|
-
const { DokployApi: DokployApi$1 } = await import("./dokploy-api-
|
|
165
|
+
const { DokployApi: DokployApi$1 } = await import("./dokploy-api-BiSzWM4v.mjs");
|
|
167
166
|
const api = new DokployApi$1({
|
|
168
167
|
baseUrl: endpoint,
|
|
169
168
|
token
|
|
@@ -226,36 +225,36 @@ async function prompt$1(message, hidden = false) {
|
|
|
226
225
|
async function loginCommand(options) {
|
|
227
226
|
const { service, token: providedToken, endpoint: providedEndpoint } = options;
|
|
228
227
|
if (service === "dokploy") {
|
|
229
|
-
logger$
|
|
228
|
+
logger$12.log("\n🔐 Logging in to Dokploy...\n");
|
|
230
229
|
let endpoint = providedEndpoint;
|
|
231
230
|
if (!endpoint) endpoint = await prompt$1("Dokploy URL (e.g., https://dokploy.example.com): ");
|
|
232
231
|
endpoint = endpoint.replace(/\/$/, "");
|
|
233
232
|
try {
|
|
234
233
|
new URL(endpoint);
|
|
235
234
|
} catch {
|
|
236
|
-
logger$
|
|
235
|
+
logger$12.error("Invalid URL format");
|
|
237
236
|
process.exit(1);
|
|
238
237
|
}
|
|
239
238
|
let token = providedToken;
|
|
240
239
|
if (!token) {
|
|
241
|
-
logger$
|
|
240
|
+
logger$12.log(`\nGenerate a token at: ${endpoint}/settings/profile\n`);
|
|
242
241
|
token = await prompt$1("API Token: ", true);
|
|
243
242
|
}
|
|
244
243
|
if (!token) {
|
|
245
|
-
logger$
|
|
244
|
+
logger$12.error("Token is required");
|
|
246
245
|
process.exit(1);
|
|
247
246
|
}
|
|
248
|
-
logger$
|
|
247
|
+
logger$12.log("\nValidating credentials...");
|
|
249
248
|
const isValid = await validateDokployToken(endpoint, token);
|
|
250
249
|
if (!isValid) {
|
|
251
|
-
logger$
|
|
250
|
+
logger$12.error("\n✗ Invalid credentials. Please check your token and try again.");
|
|
252
251
|
process.exit(1);
|
|
253
252
|
}
|
|
254
253
|
await storeDokployCredentials(token, endpoint);
|
|
255
|
-
logger$
|
|
256
|
-
logger$
|
|
257
|
-
logger$
|
|
258
|
-
logger$
|
|
254
|
+
logger$12.log("\n✓ Successfully logged in to Dokploy!");
|
|
255
|
+
logger$12.log(` Endpoint: ${endpoint}`);
|
|
256
|
+
logger$12.log(` Credentials stored in: ${getCredentialsPath()}`);
|
|
257
|
+
logger$12.log("\nYou can now use deploy commands without setting DOKPLOY_API_TOKEN.");
|
|
259
258
|
}
|
|
260
259
|
}
|
|
261
260
|
/**
|
|
@@ -265,28 +264,28 @@ async function logoutCommand(options) {
|
|
|
265
264
|
const { service = "dokploy" } = options;
|
|
266
265
|
if (service === "all") {
|
|
267
266
|
const dokployRemoved = await removeDokployCredentials();
|
|
268
|
-
if (dokployRemoved) logger$
|
|
269
|
-
else logger$
|
|
267
|
+
if (dokployRemoved) logger$12.log("\n✓ Logged out from all services");
|
|
268
|
+
else logger$12.log("\nNo stored credentials found");
|
|
270
269
|
return;
|
|
271
270
|
}
|
|
272
271
|
if (service === "dokploy") {
|
|
273
272
|
const removed = await removeDokployCredentials();
|
|
274
|
-
if (removed) logger$
|
|
275
|
-
else logger$
|
|
273
|
+
if (removed) logger$12.log("\n✓ Logged out from Dokploy");
|
|
274
|
+
else logger$12.log("\nNo Dokploy credentials found");
|
|
276
275
|
}
|
|
277
276
|
}
|
|
278
277
|
/**
|
|
279
278
|
* Show current login status
|
|
280
279
|
*/
|
|
281
280
|
async function whoamiCommand() {
|
|
282
|
-
logger$
|
|
281
|
+
logger$12.log("\n📋 Current credentials:\n");
|
|
283
282
|
const dokploy = await getDokployCredentials();
|
|
284
283
|
if (dokploy) {
|
|
285
|
-
logger$
|
|
286
|
-
logger$
|
|
287
|
-
logger$
|
|
288
|
-
} else logger$
|
|
289
|
-
logger$
|
|
284
|
+
logger$12.log(" Dokploy:");
|
|
285
|
+
logger$12.log(` Endpoint: ${dokploy.endpoint}`);
|
|
286
|
+
logger$12.log(` Token: ${maskToken(dokploy.token)}`);
|
|
287
|
+
} else logger$12.log(" Dokploy: Not logged in");
|
|
288
|
+
logger$12.log(`\n Credentials file: ${getCredentialsPath()}`);
|
|
290
289
|
}
|
|
291
290
|
/**
|
|
292
291
|
* Mask a token for display
|
|
@@ -302,7 +301,7 @@ function maskToken(token) {
|
|
|
302
301
|
* Resolves provider configuration from the new simplified system
|
|
303
302
|
* to the internal legacy format for backward compatibility
|
|
304
303
|
*/
|
|
305
|
-
function resolveProviders(config
|
|
304
|
+
function resolveProviders(config, options) {
|
|
306
305
|
const providers = [];
|
|
307
306
|
let enableOpenApi = options.enableOpenApi || false;
|
|
308
307
|
if (options.providers) return {
|
|
@@ -310,11 +309,11 @@ function resolveProviders(config$1, options) {
|
|
|
310
309
|
enableOpenApi
|
|
311
310
|
};
|
|
312
311
|
if (options.provider) {
|
|
313
|
-
const resolvedProviders = resolveMainProvider(options.provider, config
|
|
312
|
+
const resolvedProviders = resolveMainProvider(options.provider, config.providers);
|
|
314
313
|
providers.push(...resolvedProviders.providers);
|
|
315
314
|
enableOpenApi = resolvedProviders.enableOpenApi || enableOpenApi;
|
|
316
|
-
} else if (config
|
|
317
|
-
const resolvedProviders = resolveAllConfiguredProviders(config
|
|
315
|
+
} else if (config.providers) {
|
|
316
|
+
const resolvedProviders = resolveAllConfiguredProviders(config.providers);
|
|
318
317
|
providers.push(...resolvedProviders.providers);
|
|
319
318
|
enableOpenApi = resolvedProviders.enableOpenApi || enableOpenApi;
|
|
320
319
|
} else providers.push("aws-apigatewayv2", "aws-lambda");
|
|
@@ -361,475 +360,10 @@ function resolveAllConfiguredProviders(providersConfig) {
|
|
|
361
360
|
enableOpenApi
|
|
362
361
|
};
|
|
363
362
|
}
|
|
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
|
-
};
|
|
363
|
+
function isEnabled(config) {
|
|
364
|
+
if (config === void 0) return false;
|
|
365
|
+
if (typeof config === "boolean") return config;
|
|
366
|
+
return config.enabled !== false;
|
|
833
367
|
}
|
|
834
368
|
|
|
835
369
|
//#endregion
|
|
@@ -837,7 +371,7 @@ async function prepareEntryCredentials(options) {
|
|
|
837
371
|
var CronGenerator = class extends ConstructGenerator {
|
|
838
372
|
async build(context, constructs, outputDir, options) {
|
|
839
373
|
const provider = options?.provider || "aws-lambda";
|
|
840
|
-
const logger$
|
|
374
|
+
const logger$13 = console;
|
|
841
375
|
const cronInfos = [];
|
|
842
376
|
if (constructs.length === 0 || provider !== "aws-lambda") return cronInfos;
|
|
843
377
|
const cronsDir = join(outputDir, "crons");
|
|
@@ -852,7 +386,7 @@ var CronGenerator = class extends ConstructGenerator {
|
|
|
852
386
|
memorySize: construct.memorySize,
|
|
853
387
|
environment: await construct.getEnvironment({ markOptional: context.markOptional })
|
|
854
388
|
});
|
|
855
|
-
logger$
|
|
389
|
+
logger$13.log(`Generated cron handler: ${key}`);
|
|
856
390
|
}
|
|
857
391
|
return cronInfos;
|
|
858
392
|
}
|
|
@@ -888,7 +422,7 @@ var FunctionGenerator = class extends ConstructGenerator {
|
|
|
888
422
|
}
|
|
889
423
|
async build(context, constructs, outputDir, options) {
|
|
890
424
|
const provider = options?.provider || "aws-lambda";
|
|
891
|
-
const logger$
|
|
425
|
+
const logger$13 = console;
|
|
892
426
|
const functionInfos = [];
|
|
893
427
|
if (constructs.length === 0 || provider !== "aws-lambda") return functionInfos;
|
|
894
428
|
const functionsDir = join(outputDir, "functions");
|
|
@@ -902,7 +436,7 @@ var FunctionGenerator = class extends ConstructGenerator {
|
|
|
902
436
|
memorySize: construct.memorySize,
|
|
903
437
|
environment: await construct.getEnvironment({ markOptional: context.markOptional })
|
|
904
438
|
});
|
|
905
|
-
logger$
|
|
439
|
+
logger$13.log(`Generated function handler: ${key}`);
|
|
906
440
|
}
|
|
907
441
|
return functionInfos;
|
|
908
442
|
}
|
|
@@ -935,11 +469,11 @@ var SubscriberGenerator = class extends ConstructGenerator {
|
|
|
935
469
|
}
|
|
936
470
|
async build(context, constructs, outputDir, options) {
|
|
937
471
|
const provider = options?.provider || "aws-lambda";
|
|
938
|
-
const logger$
|
|
472
|
+
const logger$13 = console;
|
|
939
473
|
const subscriberInfos = [];
|
|
940
474
|
if (provider === "server") {
|
|
941
475
|
await this.generateServerSubscribersFile(outputDir, constructs);
|
|
942
|
-
logger$
|
|
476
|
+
logger$13.log(`Generated server subscribers file with ${constructs.length} subscribers (polling mode)`);
|
|
943
477
|
return subscriberInfos;
|
|
944
478
|
}
|
|
945
479
|
if (constructs.length === 0) return subscriberInfos;
|
|
@@ -956,7 +490,7 @@ var SubscriberGenerator = class extends ConstructGenerator {
|
|
|
956
490
|
memorySize: construct.memorySize,
|
|
957
491
|
environment: await construct.getEnvironment({ markOptional: context.markOptional })
|
|
958
492
|
});
|
|
959
|
-
logger$
|
|
493
|
+
logger$13.log(`Generated subscriber handler: ${key}`);
|
|
960
494
|
}
|
|
961
495
|
return subscriberInfos;
|
|
962
496
|
}
|
|
@@ -1122,7 +656,7 @@ export async function setupSubscribers(
|
|
|
1122
656
|
|
|
1123
657
|
//#endregion
|
|
1124
658
|
//#region src/exec/index.ts
|
|
1125
|
-
const logger$
|
|
659
|
+
const logger$11 = console;
|
|
1126
660
|
/**
|
|
1127
661
|
* Run a command with secrets injected into Credentials.
|
|
1128
662
|
* Uses Node's --import flag to preload a script that populates Credentials
|
|
@@ -1138,21 +672,21 @@ async function execCommand(commandArgs, options = {}) {
|
|
|
1138
672
|
const cwd = options.cwd ?? process.cwd();
|
|
1139
673
|
if (commandArgs.length === 0) throw new Error("No command specified. Usage: gkm exec -- <command>");
|
|
1140
674
|
const defaultEnv = loadEnvFiles(".env");
|
|
1141
|
-
if (defaultEnv.loaded.length > 0) logger$
|
|
675
|
+
if (defaultEnv.loaded.length > 0) logger$11.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
1142
676
|
const { credentials, secretsJsonPath, appName } = await prepareEntryCredentials({
|
|
1143
677
|
cwd,
|
|
1144
678
|
resolveDockerPorts: "readonly"
|
|
1145
679
|
});
|
|
1146
|
-
if (appName) logger$
|
|
680
|
+
if (appName) logger$11.log(`📦 App: ${appName}`);
|
|
1147
681
|
const secretCount = Object.keys(credentials).filter((k) => k !== "PORT").length;
|
|
1148
|
-
if (secretCount > 0) logger$
|
|
682
|
+
if (secretCount > 0) logger$11.log(`🔐 Loaded ${secretCount} secret(s)`);
|
|
1149
683
|
const preloadDir = join(cwd, ".gkm");
|
|
1150
684
|
await mkdir(preloadDir, { recursive: true });
|
|
1151
685
|
const preloadPath = join(preloadDir, "credentials-preload.mjs");
|
|
1152
686
|
await createCredentialsPreload(preloadPath, secretsJsonPath);
|
|
1153
687
|
const [cmd, ...args] = commandArgs;
|
|
1154
688
|
if (!cmd) throw new Error("No command specified");
|
|
1155
|
-
logger$
|
|
689
|
+
logger$11.log(`🚀 Running: ${[cmd, ...args].join(" ")}`);
|
|
1156
690
|
const existingNodeOptions = (process.env.NODE_OPTIONS ?? "").replace(/--import[= ]\S+/g, "").trim();
|
|
1157
691
|
const preloadImport = `--import=${preloadPath}`;
|
|
1158
692
|
const nodeOptions = [existingNodeOptions, preloadImport].filter(Boolean).join(" ");
|
|
@@ -1168,7 +702,7 @@ async function execCommand(commandArgs, options = {}) {
|
|
|
1168
702
|
const exitCode = await new Promise((resolve$1) => {
|
|
1169
703
|
child.on("close", (code) => resolve$1(code ?? 0));
|
|
1170
704
|
child.on("error", (error) => {
|
|
1171
|
-
logger$
|
|
705
|
+
logger$11.error(`Failed to run command: ${error.message}`);
|
|
1172
706
|
resolve$1(1);
|
|
1173
707
|
});
|
|
1174
708
|
});
|
|
@@ -1177,15 +711,15 @@ async function execCommand(commandArgs, options = {}) {
|
|
|
1177
711
|
|
|
1178
712
|
//#endregion
|
|
1179
713
|
//#region src/dev/index.ts
|
|
1180
|
-
const logger$
|
|
714
|
+
const logger$10 = console;
|
|
1181
715
|
/**
|
|
1182
716
|
* Normalize telescope configuration
|
|
1183
717
|
* @internal Exported for testing
|
|
1184
718
|
*/
|
|
1185
|
-
function normalizeTelescopeConfig(config
|
|
1186
|
-
if (config
|
|
1187
|
-
if (typeof config
|
|
1188
|
-
const { path: telescopePath, importPattern: telescopeImportPattern } = parseModuleConfig(config
|
|
719
|
+
function normalizeTelescopeConfig(config) {
|
|
720
|
+
if (config === false) return void 0;
|
|
721
|
+
if (typeof config === "string") {
|
|
722
|
+
const { path: telescopePath, importPattern: telescopeImportPattern } = parseModuleConfig(config, "telescope");
|
|
1189
723
|
return {
|
|
1190
724
|
enabled: true,
|
|
1191
725
|
telescopePath,
|
|
@@ -1197,9 +731,9 @@ function normalizeTelescopeConfig(config$1) {
|
|
|
1197
731
|
websocket: true
|
|
1198
732
|
};
|
|
1199
733
|
}
|
|
1200
|
-
const isEnabled$1 = config
|
|
734
|
+
const isEnabled$1 = config === true || config === void 0 || config.enabled !== false;
|
|
1201
735
|
if (!isEnabled$1) return void 0;
|
|
1202
|
-
const telescopeConfig = typeof config
|
|
736
|
+
const telescopeConfig = typeof config === "object" ? config : {};
|
|
1203
737
|
return {
|
|
1204
738
|
enabled: true,
|
|
1205
739
|
path: telescopeConfig.path ?? "/__telescope",
|
|
@@ -1213,10 +747,10 @@ function normalizeTelescopeConfig(config$1) {
|
|
|
1213
747
|
* Normalize studio configuration
|
|
1214
748
|
* @internal Exported for testing
|
|
1215
749
|
*/
|
|
1216
|
-
function normalizeStudioConfig(config
|
|
1217
|
-
if (config
|
|
1218
|
-
if (typeof config
|
|
1219
|
-
const { path: studioPath, importPattern: studioImportPattern } = parseModuleConfig(config
|
|
750
|
+
function normalizeStudioConfig(config) {
|
|
751
|
+
if (config === false) return void 0;
|
|
752
|
+
if (typeof config === "string") {
|
|
753
|
+
const { path: studioPath, importPattern: studioImportPattern } = parseModuleConfig(config, "studio");
|
|
1220
754
|
return {
|
|
1221
755
|
enabled: true,
|
|
1222
756
|
studioPath,
|
|
@@ -1225,9 +759,9 @@ function normalizeStudioConfig(config$1) {
|
|
|
1225
759
|
schema: "public"
|
|
1226
760
|
};
|
|
1227
761
|
}
|
|
1228
|
-
const isEnabled$1 = config
|
|
762
|
+
const isEnabled$1 = config === true || config === void 0 || config.enabled !== false;
|
|
1229
763
|
if (!isEnabled$1) return void 0;
|
|
1230
|
-
const studioConfig = typeof config
|
|
764
|
+
const studioConfig = typeof config === "object" ? config : {};
|
|
1231
765
|
return {
|
|
1232
766
|
enabled: true,
|
|
1233
767
|
path: studioConfig.path ?? "/__studio",
|
|
@@ -1238,9 +772,9 @@ function normalizeStudioConfig(config$1) {
|
|
|
1238
772
|
* Normalize hooks configuration
|
|
1239
773
|
* @internal Exported for testing
|
|
1240
774
|
*/
|
|
1241
|
-
function normalizeHooksConfig(config
|
|
1242
|
-
if (!config
|
|
1243
|
-
const serverPath = config
|
|
775
|
+
function normalizeHooksConfig(config, cwd = process.cwd()) {
|
|
776
|
+
if (!config?.server) return void 0;
|
|
777
|
+
const serverPath = config.server.endsWith(".ts") ? config.server : `${config.server}.ts`;
|
|
1244
778
|
const resolvedPath = resolve(cwd, serverPath);
|
|
1245
779
|
return { serverHooksPath: resolvedPath };
|
|
1246
780
|
}
|
|
@@ -1250,34 +784,34 @@ function normalizeHooksConfig(config$1, cwd = process.cwd()) {
|
|
|
1250
784
|
*/
|
|
1251
785
|
function normalizeProductionConfig(cliProduction, configProduction) {
|
|
1252
786
|
if (!cliProduction) return void 0;
|
|
1253
|
-
const config
|
|
787
|
+
const config = configProduction ?? {};
|
|
1254
788
|
return {
|
|
1255
789
|
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
|
|
790
|
+
bundle: config.bundle ?? true,
|
|
791
|
+
minify: config.minify ?? true,
|
|
792
|
+
healthCheck: config.healthCheck ?? "/health",
|
|
793
|
+
gracefulShutdown: config.gracefulShutdown ?? true,
|
|
794
|
+
external: config.external ?? [],
|
|
795
|
+
subscribers: config.subscribers ?? "exclude",
|
|
796
|
+
openapi: config.openapi ?? false,
|
|
797
|
+
optimizedHandlers: config.optimizedHandlers ?? true
|
|
1264
798
|
};
|
|
1265
799
|
}
|
|
1266
800
|
/**
|
|
1267
801
|
* Get production config from GkmConfig
|
|
1268
802
|
* @internal
|
|
1269
803
|
*/
|
|
1270
|
-
function getProductionConfigFromGkm(config
|
|
1271
|
-
const serverConfig = config
|
|
804
|
+
function getProductionConfigFromGkm(config) {
|
|
805
|
+
const serverConfig = config.providers?.server;
|
|
1272
806
|
if (typeof serverConfig === "object") return serverConfig.production;
|
|
1273
807
|
return void 0;
|
|
1274
808
|
}
|
|
1275
809
|
async function devCommand(options) {
|
|
1276
810
|
if (options.entry) return entryDevCommand(options);
|
|
1277
811
|
const defaultEnv = loadEnvFiles(".env");
|
|
1278
|
-
if (defaultEnv.loaded.length > 0) logger$
|
|
812
|
+
if (defaultEnv.loaded.length > 0) logger$10.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
1279
813
|
const appName = getAppNameFromCwd();
|
|
1280
|
-
let config
|
|
814
|
+
let config;
|
|
1281
815
|
let appRoot = process.cwd();
|
|
1282
816
|
let secretsRoot = process.cwd();
|
|
1283
817
|
let workspaceAppName;
|
|
@@ -1285,15 +819,15 @@ async function devCommand(options) {
|
|
|
1285
819
|
let workspace;
|
|
1286
820
|
if (appName) try {
|
|
1287
821
|
const appConfig = await loadAppConfig();
|
|
1288
|
-
config
|
|
822
|
+
config = appConfig.gkmConfig;
|
|
1289
823
|
appRoot = appConfig.appRoot;
|
|
1290
824
|
secretsRoot = appConfig.workspaceRoot;
|
|
1291
825
|
workspaceAppName = appConfig.appName;
|
|
1292
826
|
workspaceAppPort = appConfig.app.port;
|
|
1293
827
|
workspace = appConfig.workspace;
|
|
1294
|
-
logger$
|
|
828
|
+
logger$10.log(`📦 Running app: ${appConfig.appName} on port ${workspaceAppPort}`);
|
|
1295
829
|
if (appConfig.app.entry) {
|
|
1296
|
-
logger$
|
|
830
|
+
logger$10.log(`📄 Using entry point: ${appConfig.app.entry}`);
|
|
1297
831
|
return entryDevCommand({
|
|
1298
832
|
...options,
|
|
1299
833
|
entry: appConfig.app.entry,
|
|
@@ -1304,42 +838,42 @@ async function devCommand(options) {
|
|
|
1304
838
|
} catch {
|
|
1305
839
|
const loadedConfig = await loadWorkspaceConfig();
|
|
1306
840
|
if (loadedConfig.type === "workspace") {
|
|
1307
|
-
logger$
|
|
841
|
+
logger$10.log("📦 Detected workspace configuration");
|
|
1308
842
|
return workspaceDevCommand(loadedConfig.workspace, options);
|
|
1309
843
|
}
|
|
1310
|
-
config
|
|
844
|
+
config = loadedConfig.raw;
|
|
1311
845
|
}
|
|
1312
846
|
else {
|
|
1313
847
|
const loadedConfig = await loadWorkspaceConfig();
|
|
1314
848
|
if (loadedConfig.type === "workspace") {
|
|
1315
|
-
logger$
|
|
849
|
+
logger$10.log("📦 Detected workspace configuration");
|
|
1316
850
|
return workspaceDevCommand(loadedConfig.workspace, options);
|
|
1317
851
|
}
|
|
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
|
|
852
|
+
config = loadedConfig.raw;
|
|
853
|
+
}
|
|
854
|
+
if (config.env) {
|
|
855
|
+
const { loaded, missing } = loadEnvFiles(config.env, appRoot);
|
|
856
|
+
if (loaded.length > 0) logger$10.log(`📦 Loaded env: ${loaded.join(", ")}`);
|
|
857
|
+
if (missing.length > 0) logger$10.warn(`⚠️ Missing env files: ${missing.join(", ")}`);
|
|
858
|
+
}
|
|
859
|
+
const resolved = resolveProviders(config, { provider: "server" });
|
|
860
|
+
logger$10.log("🚀 Starting development server...");
|
|
861
|
+
logger$10.log(`Loading routes from: ${config.routes}`);
|
|
862
|
+
if (config.functions) logger$10.log(`Loading functions from: ${config.functions}`);
|
|
863
|
+
if (config.crons) logger$10.log(`Loading crons from: ${config.crons}`);
|
|
864
|
+
if (config.subscribers) logger$10.log(`Loading subscribers from: ${config.subscribers}`);
|
|
865
|
+
logger$10.log(`Using envParser: ${config.envParser}`);
|
|
866
|
+
const { path: envParserPath, importPattern: envParserImportPattern } = parseModuleConfig(config.envParser, "envParser");
|
|
867
|
+
const { path: loggerPath, importPattern: loggerImportPattern } = parseModuleConfig(config.logger, "logger");
|
|
868
|
+
const telescope = normalizeTelescopeConfig(config.telescope);
|
|
869
|
+
if (telescope) logger$10.log(`🔭 Telescope enabled at ${telescope.path}`);
|
|
870
|
+
const studio = normalizeStudioConfig(config.studio);
|
|
871
|
+
if (studio) logger$10.log(`🗄️ Studio enabled at ${studio.path}`);
|
|
872
|
+
const hooks = normalizeHooksConfig(config.hooks, appRoot);
|
|
873
|
+
if (hooks) logger$10.log(`🪝 Server hooks enabled from ${config.hooks?.server}`);
|
|
874
|
+
const openApiConfig = resolveOpenApiConfig(config);
|
|
1341
875
|
const enableOpenApi = openApiConfig.enabled || resolved.enableOpenApi;
|
|
1342
|
-
if (enableOpenApi) logger$
|
|
876
|
+
if (enableOpenApi) logger$10.log(`📄 OpenAPI output: ${OPENAPI_OUTPUT_PATH}`);
|
|
1343
877
|
const buildContext = {
|
|
1344
878
|
envParserPath,
|
|
1345
879
|
envParserImportPattern,
|
|
@@ -1349,16 +883,16 @@ async function devCommand(options) {
|
|
|
1349
883
|
studio,
|
|
1350
884
|
hooks
|
|
1351
885
|
};
|
|
1352
|
-
await buildServer(config
|
|
1353
|
-
if (enableOpenApi) await generateOpenApi(config
|
|
1354
|
-
const runtime = config
|
|
886
|
+
await buildServer(config, buildContext, resolved.providers[0], enableOpenApi, appRoot);
|
|
887
|
+
if (enableOpenApi) await generateOpenApi(config);
|
|
888
|
+
const runtime = config.runtime ?? "node";
|
|
1355
889
|
let secretsJsonPath;
|
|
1356
890
|
const appSecrets = await loadSecretsForApp(secretsRoot, workspaceAppName);
|
|
1357
891
|
const resolvedPorts = await resolveServicePorts(secretsRoot);
|
|
1358
892
|
if (Object.keys(resolvedPorts.ports).length > 0) {
|
|
1359
893
|
const rewritten = rewriteUrlsWithPorts(appSecrets, resolvedPorts);
|
|
1360
894
|
Object.assign(appSecrets, rewritten);
|
|
1361
|
-
logger$
|
|
895
|
+
logger$10.log(`🔌 Applied ${Object.keys(resolvedPorts.ports).length} port mapping(s)`);
|
|
1362
896
|
}
|
|
1363
897
|
if (workspace && workspaceAppName) {
|
|
1364
898
|
const depEnv = getDependencyEnvVars(workspace, workspaceAppName);
|
|
@@ -1370,25 +904,25 @@ async function devCommand(options) {
|
|
|
1370
904
|
const secretsFileName = workspaceAppName ? `dev-secrets-${workspaceAppName}.json` : "dev-secrets.json";
|
|
1371
905
|
secretsJsonPath = join(secretsDir, secretsFileName);
|
|
1372
906
|
await writeFile(secretsJsonPath, JSON.stringify(appSecrets, null, 2));
|
|
1373
|
-
logger$
|
|
907
|
+
logger$10.log(`🔐 Loaded ${Object.keys(appSecrets).length} secret(s)`);
|
|
1374
908
|
}
|
|
1375
909
|
const devServer = new DevServer(resolved.providers[0], options.port ?? workspaceAppPort ?? 3e3, options.portExplicit ?? false, enableOpenApi, telescope, studio, runtime, appRoot, secretsJsonPath);
|
|
1376
910
|
await devServer.start();
|
|
1377
|
-
const envParserFile = config
|
|
1378
|
-
const loggerFile = config
|
|
1379
|
-
const hooksFileParts = config
|
|
911
|
+
const envParserFile = config.envParser.split("#")[0] ?? config.envParser;
|
|
912
|
+
const loggerFile = config.logger.split("#")[0] ?? config.logger;
|
|
913
|
+
const hooksFileParts = config.hooks?.server?.split("#");
|
|
1380
914
|
const hooksFile = hooksFileParts?.[0];
|
|
1381
915
|
const watchPatterns = [
|
|
1382
|
-
config
|
|
1383
|
-
...config
|
|
1384
|
-
...config
|
|
1385
|
-
...config
|
|
916
|
+
config.routes,
|
|
917
|
+
...config.functions ? [config.functions] : [],
|
|
918
|
+
...config.crons ? [config.crons] : [],
|
|
919
|
+
...config.subscribers ? [config.subscribers] : [],
|
|
1386
920
|
envParserFile.endsWith(".ts") ? envParserFile : `${envParserFile}.ts`,
|
|
1387
921
|
loggerFile.endsWith(".ts") ? loggerFile : `${loggerFile}.ts`,
|
|
1388
922
|
...hooksFile ? [hooksFile.endsWith(".ts") ? hooksFile : `${hooksFile}.ts`] : []
|
|
1389
923
|
].flat().filter((p) => typeof p === "string");
|
|
1390
924
|
const normalizedPatterns = watchPatterns.map((p) => p.startsWith("./") ? p.slice(2) : p);
|
|
1391
|
-
logger$
|
|
925
|
+
logger$10.log(`👀 Watching for changes in: ${normalizedPatterns.join(", ")}`);
|
|
1392
926
|
const resolvedFiles = await fg(normalizedPatterns, {
|
|
1393
927
|
cwd: appRoot,
|
|
1394
928
|
absolute: false,
|
|
@@ -1398,7 +932,7 @@ async function devCommand(options) {
|
|
|
1398
932
|
const parts = f.split("/");
|
|
1399
933
|
return parts.slice(0, -1).join("/");
|
|
1400
934
|
}))];
|
|
1401
|
-
logger$
|
|
935
|
+
logger$10.log(`📁 Found ${resolvedFiles.length} files in ${dirsToWatch.length} directories`);
|
|
1402
936
|
const watcher = chokidar.watch([...resolvedFiles, ...dirsToWatch], {
|
|
1403
937
|
ignored: /(^|[/\\])\../,
|
|
1404
938
|
persistent: true,
|
|
@@ -1406,29 +940,29 @@ async function devCommand(options) {
|
|
|
1406
940
|
cwd: appRoot
|
|
1407
941
|
});
|
|
1408
942
|
watcher.on("ready", () => {
|
|
1409
|
-
logger$
|
|
943
|
+
logger$10.log("🔍 File watcher ready");
|
|
1410
944
|
});
|
|
1411
945
|
watcher.on("error", (error) => {
|
|
1412
|
-
logger$
|
|
946
|
+
logger$10.error("❌ Watcher error:", error);
|
|
1413
947
|
});
|
|
1414
948
|
let rebuildTimeout = null;
|
|
1415
949
|
watcher.on("change", async (path) => {
|
|
1416
|
-
logger$
|
|
950
|
+
logger$10.log(`📝 File changed: ${path}`);
|
|
1417
951
|
if (rebuildTimeout) clearTimeout(rebuildTimeout);
|
|
1418
952
|
rebuildTimeout = setTimeout(async () => {
|
|
1419
953
|
try {
|
|
1420
|
-
logger$
|
|
1421
|
-
await buildServer(config
|
|
1422
|
-
if (enableOpenApi) await generateOpenApi(config
|
|
954
|
+
logger$10.log("🔄 Rebuilding...");
|
|
955
|
+
await buildServer(config, buildContext, resolved.providers[0], enableOpenApi, appRoot, true);
|
|
956
|
+
if (enableOpenApi) await generateOpenApi(config, {
|
|
1423
957
|
silent: true,
|
|
1424
958
|
bustCache: true
|
|
1425
959
|
});
|
|
1426
|
-
logger$
|
|
960
|
+
logger$10.log("✅ Rebuild complete, restarting server...");
|
|
1427
961
|
await devServer.restart();
|
|
1428
962
|
} catch (error) {
|
|
1429
963
|
const err = error;
|
|
1430
|
-
logger$
|
|
1431
|
-
if (err.stack) logger$
|
|
964
|
+
logger$10.error(`❌ Rebuild failed: ${err.message}`);
|
|
965
|
+
if (err.stack) logger$10.error(err.stack);
|
|
1432
966
|
}
|
|
1433
967
|
}, 300);
|
|
1434
968
|
});
|
|
@@ -1436,9 +970,9 @@ async function devCommand(options) {
|
|
|
1436
970
|
const shutdown = () => {
|
|
1437
971
|
if (isShuttingDown) return;
|
|
1438
972
|
isShuttingDown = true;
|
|
1439
|
-
logger$
|
|
973
|
+
logger$10.log("\n🛑 Shutting down...");
|
|
1440
974
|
Promise.all([watcher.close(), devServer.stop()]).catch((err) => {
|
|
1441
|
-
logger$
|
|
975
|
+
logger$10.error("Error during shutdown:", err);
|
|
1442
976
|
}).finally(() => {
|
|
1443
977
|
process.exit(0);
|
|
1444
978
|
});
|
|
@@ -1637,11 +1171,11 @@ async function loadDevSecrets(workspace) {
|
|
|
1637
1171
|
for (const stage of stages) if (secretsExist(stage, workspace.root)) {
|
|
1638
1172
|
const secrets = await readStageSecrets(stage, workspace.root);
|
|
1639
1173
|
if (secrets) {
|
|
1640
|
-
logger$
|
|
1174
|
+
logger$10.log(`🔐 Loading secrets from stage: ${stage}`);
|
|
1641
1175
|
return toEmbeddableSecrets(secrets);
|
|
1642
1176
|
}
|
|
1643
1177
|
}
|
|
1644
|
-
logger$
|
|
1178
|
+
logger$10.warn("⚠️ Secrets enabled but no dev/development secrets found. Run \"gkm setup\" to initialize your development environment");
|
|
1645
1179
|
return {};
|
|
1646
1180
|
}
|
|
1647
1181
|
/**
|
|
@@ -1658,39 +1192,39 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1658
1192
|
const backendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "backend");
|
|
1659
1193
|
const frontendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "web");
|
|
1660
1194
|
const mobileApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "mobile");
|
|
1661
|
-
logger$
|
|
1195
|
+
logger$10.log(`\n🚀 Starting workspace: ${workspace.name}`);
|
|
1662
1196
|
const counts = [`${backendApps.length} backend`, `${frontendApps.length} web`];
|
|
1663
1197
|
if (mobileApps.length > 0) counts.push(`${mobileApps.length} mobile`);
|
|
1664
|
-
logger$
|
|
1198
|
+
logger$10.log(` ${counts.join(", ")} app(s)`);
|
|
1665
1199
|
const conflicts = checkPortConflicts(workspace);
|
|
1666
1200
|
if (conflicts.length > 0) {
|
|
1667
|
-
for (const conflict of conflicts) logger$
|
|
1201
|
+
for (const conflict of conflicts) logger$10.error(`❌ Port conflict: Apps "${conflict.app1}" and "${conflict.app2}" both use port ${conflict.port}`);
|
|
1668
1202
|
throw new Error("Port conflicts detected. Please assign unique ports to each app.");
|
|
1669
1203
|
}
|
|
1670
1204
|
if (frontendApps.length > 0) {
|
|
1671
|
-
logger$
|
|
1205
|
+
logger$10.log("\n🔍 Validating frontend apps...");
|
|
1672
1206
|
const validationResults = await validateFrontendApps(workspace);
|
|
1673
1207
|
let hasErrors = false;
|
|
1674
1208
|
for (const result of validationResults) {
|
|
1675
1209
|
if (!result.valid) {
|
|
1676
1210
|
hasErrors = true;
|
|
1677
|
-
logger$
|
|
1678
|
-
for (const error of result.errors) logger$
|
|
1211
|
+
logger$10.error(`\n❌ Frontend app "${result.appName}" validation failed:`);
|
|
1212
|
+
for (const error of result.errors) logger$10.error(` • ${error}`);
|
|
1679
1213
|
}
|
|
1680
|
-
for (const warning of result.warnings) logger$
|
|
1214
|
+
for (const warning of result.warnings) logger$10.warn(` ⚠️ ${result.appName}: ${warning}`);
|
|
1681
1215
|
}
|
|
1682
1216
|
if (hasErrors) throw new Error("Frontend app validation failed. Fix the issues above and try again.");
|
|
1683
|
-
logger$
|
|
1217
|
+
logger$10.log("✅ Frontend apps validated");
|
|
1684
1218
|
}
|
|
1685
1219
|
const resolvedPorts = await resolveServicePorts(workspace.root);
|
|
1686
1220
|
const rawSecrets = await loadDevSecrets(workspace);
|
|
1687
1221
|
await startWorkspaceServices(workspace, resolvedPorts.dockerEnv, rawSecrets);
|
|
1688
1222
|
const secretsEnv = rewriteUrlsWithPorts(rawSecrets, resolvedPorts);
|
|
1689
|
-
if (Object.keys(secretsEnv).length > 0) logger$
|
|
1223
|
+
if (Object.keys(secretsEnv).length > 0) logger$10.log(` Loaded ${Object.keys(secretsEnv).length} secret(s)`);
|
|
1690
1224
|
const dependencyEnv = generateAllDependencyEnvVars(workspace);
|
|
1691
1225
|
if (Object.keys(dependencyEnv).length > 0) {
|
|
1692
|
-
logger$
|
|
1693
|
-
for (const [key, value] of Object.entries(dependencyEnv)) logger$
|
|
1226
|
+
logger$10.log("📡 Dependency URLs:");
|
|
1227
|
+
for (const [key, value] of Object.entries(dependencyEnv)) logger$10.log(` ${key}=${value}`);
|
|
1694
1228
|
}
|
|
1695
1229
|
let turboFilter = [];
|
|
1696
1230
|
if (options.app) {
|
|
@@ -1699,19 +1233,19 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1699
1233
|
throw new Error(`App "${options.app}" not found. Available apps: ${appNames}`);
|
|
1700
1234
|
}
|
|
1701
1235
|
turboFilter = ["--filter", options.app];
|
|
1702
|
-
logger$
|
|
1236
|
+
logger$10.log(`\n🎯 Running single app: ${options.app}`);
|
|
1703
1237
|
} else if (options.filter) {
|
|
1704
1238
|
turboFilter = ["--filter", options.filter];
|
|
1705
|
-
logger$
|
|
1706
|
-
} else logger$
|
|
1239
|
+
logger$10.log(`\n🔍 Using filter: ${options.filter}`);
|
|
1240
|
+
} else logger$10.log(`\n🎯 Running all ${appCount} apps`);
|
|
1707
1241
|
const buildOrder = getAppBuildOrder(workspace);
|
|
1708
|
-
logger$
|
|
1242
|
+
logger$10.log("\n📋 Apps (in dependency order):");
|
|
1709
1243
|
for (const appName of buildOrder) {
|
|
1710
1244
|
const app = workspace.apps[appName];
|
|
1711
1245
|
if (!app) continue;
|
|
1712
1246
|
const deps = app.dependencies.length > 0 ? ` (depends on: ${app.dependencies.join(", ")})` : "";
|
|
1713
1247
|
const icon = app.type === "backend" ? "🔧" : app.type === "mobile" ? "📱" : "🌐";
|
|
1714
|
-
logger$
|
|
1248
|
+
logger$10.log(` ${icon} ${appName} → http://localhost:${app.port}${deps}`);
|
|
1715
1249
|
}
|
|
1716
1250
|
const configFiles = [
|
|
1717
1251
|
"gkm.config.ts",
|
|
@@ -1733,7 +1267,7 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1733
1267
|
NODE_ENV: "development",
|
|
1734
1268
|
...configPath ? { GKM_CONFIG_PATH: configPath } : {}
|
|
1735
1269
|
};
|
|
1736
|
-
logger$
|
|
1270
|
+
logger$10.log("\n🏃 Starting turbo run dev...\n");
|
|
1737
1271
|
const turboProcess = spawn("pnpm", [
|
|
1738
1272
|
"turbo",
|
|
1739
1273
|
"run",
|
|
@@ -1749,7 +1283,7 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1749
1283
|
const shutdown = () => {
|
|
1750
1284
|
if (isShuttingDown) return;
|
|
1751
1285
|
isShuttingDown = true;
|
|
1752
|
-
logger$
|
|
1286
|
+
logger$10.log("\n🛑 Shutting down workspace...");
|
|
1753
1287
|
const pid = turboProcess.pid;
|
|
1754
1288
|
if (pid) try {
|
|
1755
1289
|
process.kill(-pid, "SIGTERM");
|
|
@@ -1773,7 +1307,7 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1773
1307
|
process.on("SIGTERM", shutdown);
|
|
1774
1308
|
return new Promise((resolve$1, reject) => {
|
|
1775
1309
|
turboProcess.on("error", (error) => {
|
|
1776
|
-
logger$
|
|
1310
|
+
logger$10.error("❌ Turbo error:", error);
|
|
1777
1311
|
reject(error);
|
|
1778
1312
|
});
|
|
1779
1313
|
turboProcess.on("exit", (code) => {
|
|
@@ -1782,16 +1316,16 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1782
1316
|
});
|
|
1783
1317
|
});
|
|
1784
1318
|
}
|
|
1785
|
-
async function buildServer(config
|
|
1319
|
+
async function buildServer(config, context, provider, enableOpenApi, appRoot = process.cwd(), bustCache = false) {
|
|
1786
1320
|
const endpointGenerator = new EndpointGenerator();
|
|
1787
1321
|
const functionGenerator = new FunctionGenerator();
|
|
1788
1322
|
const cronGenerator = new CronGenerator();
|
|
1789
1323
|
const subscriberGenerator = new SubscriberGenerator();
|
|
1790
1324
|
const [allEndpoints, allFunctions, allCrons, allSubscribers] = await Promise.all([
|
|
1791
|
-
endpointGenerator.load(config
|
|
1792
|
-
config
|
|
1793
|
-
config
|
|
1794
|
-
config
|
|
1325
|
+
endpointGenerator.load(config.routes, appRoot, bustCache),
|
|
1326
|
+
config.functions ? functionGenerator.load(config.functions, appRoot, bustCache) : [],
|
|
1327
|
+
config.crons ? cronGenerator.load(config.crons, appRoot, bustCache) : [],
|
|
1328
|
+
config.subscribers ? subscriberGenerator.load(config.subscribers, appRoot, bustCache) : []
|
|
1795
1329
|
]);
|
|
1796
1330
|
const outputDir = join(appRoot, ".gkm", provider);
|
|
1797
1331
|
await mkdir(outputDir, { recursive: true });
|
|
@@ -1815,11 +1349,11 @@ async function entryDevCommand(options) {
|
|
|
1815
1349
|
const entryPath = resolve(process.cwd(), entry);
|
|
1816
1350
|
if (!existsSync(entryPath)) throw new Error(`Entry file not found: ${entryPath}`);
|
|
1817
1351
|
const defaultEnv = loadEnvFiles(".env");
|
|
1818
|
-
if (defaultEnv.loaded.length > 0) logger$
|
|
1352
|
+
if (defaultEnv.loaded.length > 0) logger$10.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
1819
1353
|
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$
|
|
1354
|
+
if (appName) logger$10.log(`📦 App: ${appName} (port ${resolvedPort})`);
|
|
1355
|
+
logger$10.log(`🚀 Starting entry file: ${entry} on port ${resolvedPort}`);
|
|
1356
|
+
if (Object.keys(credentials).length > 1) logger$10.log(`🔐 Loaded ${Object.keys(credentials).length - 1} secret(s) + PORT`);
|
|
1823
1357
|
const wrapperDir = join(process.cwd(), ".gkm");
|
|
1824
1358
|
await mkdir(wrapperDir, { recursive: true });
|
|
1825
1359
|
const wrapperPath = join(wrapperDir, "entry-wrapper.ts");
|
|
@@ -1830,7 +1364,7 @@ async function entryDevCommand(options) {
|
|
|
1830
1364
|
const shutdown = () => {
|
|
1831
1365
|
if (isShuttingDown) return;
|
|
1832
1366
|
isShuttingDown = true;
|
|
1833
|
-
logger$
|
|
1367
|
+
logger$10.log("\n🛑 Shutting down...");
|
|
1834
1368
|
runner.stop();
|
|
1835
1369
|
process.exit(0);
|
|
1836
1370
|
};
|
|
@@ -1862,14 +1396,14 @@ var EntryRunner = class {
|
|
|
1862
1396
|
});
|
|
1863
1397
|
let restartTimeout = null;
|
|
1864
1398
|
this.watcher.on("change", (path) => {
|
|
1865
|
-
logger$
|
|
1399
|
+
logger$10.log(`📝 File changed: ${path}`);
|
|
1866
1400
|
if (restartTimeout) clearTimeout(restartTimeout);
|
|
1867
1401
|
restartTimeout = setTimeout(async () => {
|
|
1868
|
-
logger$
|
|
1402
|
+
logger$10.log("🔄 Restarting...");
|
|
1869
1403
|
await this.restart();
|
|
1870
1404
|
}, 300);
|
|
1871
1405
|
});
|
|
1872
|
-
logger$
|
|
1406
|
+
logger$10.log(`👀 Watching for changes in: ${watchDir}`);
|
|
1873
1407
|
}
|
|
1874
1408
|
}
|
|
1875
1409
|
async runProcess() {
|
|
@@ -1884,17 +1418,17 @@ var EntryRunner = class {
|
|
|
1884
1418
|
});
|
|
1885
1419
|
this.isRunning = true;
|
|
1886
1420
|
this.childProcess.on("error", (error) => {
|
|
1887
|
-
logger$
|
|
1421
|
+
logger$10.error("❌ Process error:", error);
|
|
1888
1422
|
});
|
|
1889
1423
|
this.childProcess.on("exit", (code) => {
|
|
1890
|
-
if (code !== null && code !== 0 && code !== 143) logger$
|
|
1424
|
+
if (code !== null && code !== 0 && code !== 143) logger$10.error(`❌ Process exited with code ${code}`);
|
|
1891
1425
|
this.isRunning = false;
|
|
1892
1426
|
});
|
|
1893
1427
|
await new Promise((resolve$1) => setTimeout(resolve$1, 500));
|
|
1894
1428
|
if (this.isRunning) {
|
|
1895
|
-
logger$
|
|
1896
|
-
logger$
|
|
1897
|
-
logger$
|
|
1429
|
+
logger$10.log("");
|
|
1430
|
+
logger$10.log(` \x1b[32m✓ Ready\x1b[0m at \x1b[36mhttp://localhost:${this.port}\x1b[0m`);
|
|
1431
|
+
logger$10.log("");
|
|
1898
1432
|
}
|
|
1899
1433
|
}
|
|
1900
1434
|
async restart() {
|
|
@@ -2008,11 +1542,11 @@ var DevServer = class {
|
|
|
2008
1542
|
this.actualPort = this.requestedPort;
|
|
2009
1543
|
} else {
|
|
2010
1544
|
this.actualPort = await findAvailablePort(this.requestedPort);
|
|
2011
|
-
if (this.actualPort !== this.requestedPort) logger$
|
|
1545
|
+
if (this.actualPort !== this.requestedPort) logger$10.log(`ℹ️ Port ${this.requestedPort} was in use, using port ${this.actualPort} instead`);
|
|
2012
1546
|
}
|
|
2013
1547
|
const serverEntryPath = join(this.appRoot, ".gkm", this.provider, "server.ts");
|
|
2014
1548
|
await this.createServerEntry();
|
|
2015
|
-
logger$
|
|
1549
|
+
logger$10.log(`\n⏳ Starting server...`);
|
|
2016
1550
|
this.serverProcess = spawn("npx", [
|
|
2017
1551
|
"tsx",
|
|
2018
1552
|
serverEntryPath,
|
|
@@ -2028,10 +1562,10 @@ var DevServer = class {
|
|
|
2028
1562
|
});
|
|
2029
1563
|
this.isRunning = true;
|
|
2030
1564
|
this.serverProcess.on("error", (error) => {
|
|
2031
|
-
logger$
|
|
1565
|
+
logger$10.error("❌ Server error:", error);
|
|
2032
1566
|
});
|
|
2033
1567
|
this.serverProcess.on("exit", (code, signal) => {
|
|
2034
|
-
if (code !== null && code !== 0 && signal !== "SIGTERM") logger$
|
|
1568
|
+
if (code !== null && code !== 0 && signal !== "SIGTERM") logger$10.error(`❌ Server exited with code ${code}`);
|
|
2035
1569
|
this.isRunning = false;
|
|
2036
1570
|
});
|
|
2037
1571
|
await new Promise((resolve$1) => setTimeout(resolve$1, 1e3));
|
|
@@ -2044,13 +1578,13 @@ var DevServer = class {
|
|
|
2044
1578
|
const maxLen = Math.max(...lines.map((l) => l.length));
|
|
2045
1579
|
const pad = (s) => s.padEnd(maxLen);
|
|
2046
1580
|
const border = "─".repeat(maxLen + 2);
|
|
2047
|
-
logger$
|
|
2048
|
-
logger$
|
|
2049
|
-
logger$
|
|
2050
|
-
logger$
|
|
2051
|
-
for (const line of lines) logger$
|
|
2052
|
-
logger$
|
|
2053
|
-
logger$
|
|
1581
|
+
logger$10.log("");
|
|
1582
|
+
logger$10.log(` \x1b[32m✓ Ready\x1b[0m in ${((Date.now() - this.startTime) / 1e3).toFixed(1)}s`);
|
|
1583
|
+
logger$10.log("");
|
|
1584
|
+
logger$10.log(` ┌${border}┐`);
|
|
1585
|
+
for (const line of lines) logger$10.log(` │ ${pad(line)} │`);
|
|
1586
|
+
logger$10.log(` └${border}┘`);
|
|
1587
|
+
logger$10.log("");
|
|
2054
1588
|
}
|
|
2055
1589
|
}
|
|
2056
1590
|
async stop() {
|
|
@@ -2100,7 +1634,7 @@ var DevServer = class {
|
|
|
2100
1634
|
|
|
2101
1635
|
//#endregion
|
|
2102
1636
|
//#region src/build/manifests.ts
|
|
2103
|
-
const logger$
|
|
1637
|
+
const logger$9 = console;
|
|
2104
1638
|
function isPartitioned(field) {
|
|
2105
1639
|
return !Array.isArray(field);
|
|
2106
1640
|
}
|
|
@@ -2161,8 +1695,8 @@ export type RoutePath = Route['path'];
|
|
|
2161
1695
|
`;
|
|
2162
1696
|
const manifestPath = join(manifestDir, "aws.ts");
|
|
2163
1697
|
await writeFile(manifestPath, content);
|
|
2164
|
-
logger$
|
|
2165
|
-
logger$
|
|
1698
|
+
logger$9.log(`Generated AWS manifest with ${countItems(awsRoutes)} routes, ${countItems(functions)} functions, ${countItems(crons)} crons, ${countItems(subscribers)} subscribers`);
|
|
1699
|
+
logger$9.log(`Manifest: ${relative(process.cwd(), manifestPath)}`);
|
|
2166
1700
|
}
|
|
2167
1701
|
async function generateServerManifest(outputDir, appInfo, routes, subscribers) {
|
|
2168
1702
|
const manifestDir = join(outputDir, "manifest");
|
|
@@ -2188,8 +1722,8 @@ export type RoutePath = Route['path'];
|
|
|
2188
1722
|
`;
|
|
2189
1723
|
const manifestPath = join(manifestDir, "server.ts");
|
|
2190
1724
|
await writeFile(manifestPath, content);
|
|
2191
|
-
logger$
|
|
2192
|
-
logger$
|
|
1725
|
+
logger$9.log(`Generated server manifest with ${countItems(serverRoutes)} routes, ${countItems(serverSubscribers)} subscribers`);
|
|
1726
|
+
logger$9.log(`Manifest: ${relative(process.cwd(), manifestPath)}`);
|
|
2193
1727
|
}
|
|
2194
1728
|
/**
|
|
2195
1729
|
* Filter out 'ALL' method routes from a manifest field (flat or partitioned).
|
|
@@ -2255,7 +1789,7 @@ function groupInfosByPartition(infos, constructs) {
|
|
|
2255
1789
|
|
|
2256
1790
|
//#endregion
|
|
2257
1791
|
//#region src/build/index.ts
|
|
2258
|
-
const logger$
|
|
1792
|
+
const logger$8 = console;
|
|
2259
1793
|
async function buildCommand(options) {
|
|
2260
1794
|
const loadedConfig = await loadWorkspaceConfig();
|
|
2261
1795
|
if (loadedConfig.type === "workspace") {
|
|
@@ -2263,30 +1797,30 @@ async function buildCommand(options) {
|
|
|
2263
1797
|
const workspaceRoot = resolve(loadedConfig.workspace.root);
|
|
2264
1798
|
const isAtWorkspaceRoot = cwd === workspaceRoot;
|
|
2265
1799
|
if (isAtWorkspaceRoot) {
|
|
2266
|
-
logger$
|
|
1800
|
+
logger$8.log("📦 Detected workspace configuration");
|
|
2267
1801
|
return workspaceBuildCommand(loadedConfig.workspace, options);
|
|
2268
1802
|
}
|
|
2269
1803
|
}
|
|
2270
|
-
const config
|
|
2271
|
-
const resolved = resolveProviders(config
|
|
2272
|
-
const productionConfigFromGkm = getProductionConfigFromGkm(config
|
|
1804
|
+
const config = loadedConfig.type === "workspace" ? (await loadAppConfig()).gkmConfig : await loadConfig();
|
|
1805
|
+
const resolved = resolveProviders(config, options);
|
|
1806
|
+
const productionConfigFromGkm = getProductionConfigFromGkm(config);
|
|
2273
1807
|
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
|
|
1808
|
+
if (production) logger$8.log(`🏭 Building for PRODUCTION`);
|
|
1809
|
+
logger$8.log(`Building with providers: ${resolved.providers.join(", ")}`);
|
|
1810
|
+
logger$8.log(`Loading routes from: ${formatRoutes(config.routes)}`);
|
|
1811
|
+
if (config.functions) logger$8.log(`Loading functions from: ${formatRoutes(config.functions)}`);
|
|
1812
|
+
if (config.crons) logger$8.log(`Loading crons from: ${formatRoutes(config.crons)}`);
|
|
1813
|
+
if (config.subscribers) logger$8.log(`Loading subscribers from: ${formatRoutes(config.subscribers)}`);
|
|
1814
|
+
logger$8.log(`Using envParser: ${config.envParser}`);
|
|
1815
|
+
const { path: envParserPath, importPattern: envParserImportPattern } = parseModuleConfig(config.envParser, "envParser");
|
|
1816
|
+
const { path: loggerPath, importPattern: loggerImportPattern } = parseModuleConfig(config.logger, "logger");
|
|
1817
|
+
const telescope = production ? void 0 : normalizeTelescopeConfig(config.telescope);
|
|
1818
|
+
if (telescope) logger$8.log(`🔭 Telescope enabled at ${telescope.path}`);
|
|
1819
|
+
const studio = production ? void 0 : normalizeStudioConfig(config.studio);
|
|
1820
|
+
if (studio) logger$8.log(`🗄️ Studio enabled at ${studio.path}`);
|
|
1821
|
+
const hooks = normalizeHooksConfig(config.hooks);
|
|
1822
|
+
if (hooks) logger$8.log(`🪝 Server hooks enabled`);
|
|
1823
|
+
const services = config.docker?.compose?.services;
|
|
2290
1824
|
const dockerServices = services ? Array.isArray(services) ? {
|
|
2291
1825
|
postgres: services.includes("postgres"),
|
|
2292
1826
|
redis: services.includes("redis"),
|
|
@@ -2313,17 +1847,17 @@ async function buildCommand(options) {
|
|
|
2313
1847
|
const cronGenerator = new CronGenerator();
|
|
2314
1848
|
const subscriberGenerator = new SubscriberGenerator();
|
|
2315
1849
|
const [allEndpoints, allFunctions, allCrons, allSubscribers] = await Promise.all([
|
|
2316
|
-
endpointGenerator.load(config
|
|
2317
|
-
config
|
|
2318
|
-
config
|
|
2319
|
-
config
|
|
1850
|
+
endpointGenerator.load(config.routes),
|
|
1851
|
+
config.functions ? functionGenerator.load(config.functions) : [],
|
|
1852
|
+
config.crons ? cronGenerator.load(config.crons) : [],
|
|
1853
|
+
config.subscribers ? subscriberGenerator.load(config.subscribers) : []
|
|
2320
1854
|
]);
|
|
2321
|
-
logger$
|
|
2322
|
-
logger$
|
|
2323
|
-
logger$
|
|
2324
|
-
logger$
|
|
1855
|
+
logger$8.log(`Found ${allEndpoints.length} endpoints`);
|
|
1856
|
+
logger$8.log(`Found ${allFunctions.length} functions`);
|
|
1857
|
+
logger$8.log(`Found ${allCrons.length} crons`);
|
|
1858
|
+
logger$8.log(`Found ${allSubscribers.length} subscribers`);
|
|
2325
1859
|
if (allEndpoints.length === 0 && allFunctions.length === 0 && allCrons.length === 0 && allSubscribers.length === 0) {
|
|
2326
|
-
logger$
|
|
1860
|
+
logger$8.log("No endpoints, functions, crons, or subscribers found to process");
|
|
2327
1861
|
return {};
|
|
2328
1862
|
}
|
|
2329
1863
|
const rootOutputDir = join(process.cwd(), ".gkm");
|
|
@@ -2333,13 +1867,13 @@ async function buildCommand(options) {
|
|
|
2333
1867
|
const providerResult = await buildForProvider(provider, buildContext, rootOutputDir, endpointGenerator, functionGenerator, cronGenerator, subscriberGenerator, allEndpoints, allFunctions, allCrons, allSubscribers, resolved.enableOpenApi, options.skipBundle ?? false, options.stage);
|
|
2334
1868
|
if (providerResult.masterKey) result = providerResult;
|
|
2335
1869
|
}
|
|
2336
|
-
await generateOpenApi(config
|
|
1870
|
+
await generateOpenApi(config, { bustCache: true });
|
|
2337
1871
|
return result;
|
|
2338
1872
|
}
|
|
2339
1873
|
async function buildForProvider(provider, context, rootOutputDir, endpointGenerator, functionGenerator, cronGenerator, subscriberGenerator, endpoints, functions, crons, subscribers, enableOpenApi, skipBundle, stage) {
|
|
2340
1874
|
const outputDir = join(process.cwd(), ".gkm", provider);
|
|
2341
1875
|
await mkdir(outputDir, { recursive: true });
|
|
2342
|
-
logger$
|
|
1876
|
+
logger$8.log(`\nGenerating handlers for provider: ${provider}`);
|
|
2343
1877
|
const [routes, functionInfos, cronInfos, subscriberInfos] = await Promise.all([
|
|
2344
1878
|
endpointGenerator.build(context, endpoints, outputDir, {
|
|
2345
1879
|
provider,
|
|
@@ -2349,7 +1883,7 @@ async function buildForProvider(provider, context, rootOutputDir, endpointGenera
|
|
|
2349
1883
|
cronGenerator.build(context, crons, outputDir, { provider }),
|
|
2350
1884
|
subscriberGenerator.build(context, subscribers, outputDir, { provider })
|
|
2351
1885
|
]);
|
|
2352
|
-
logger$
|
|
1886
|
+
logger$8.log(`Generated ${routes.length} routes, ${functionInfos.length} functions, ${cronInfos.length} crons, ${subscriberInfos.length} subscribers for ${provider}`);
|
|
2353
1887
|
const manifestRoutes = assembleManifestField(routes, endpoints);
|
|
2354
1888
|
const manifestFunctions = assembleManifestField(functionInfos, functions);
|
|
2355
1889
|
const manifestCrons = assembleManifestField(cronInfos, crons);
|
|
@@ -2369,8 +1903,8 @@ async function buildForProvider(provider, context, rootOutputDir, endpointGenera
|
|
|
2369
1903
|
await generateServerManifest(rootOutputDir, appInfo, serverRouteField, manifestSubscribers);
|
|
2370
1904
|
let masterKey;
|
|
2371
1905
|
if (context.production?.bundle && !skipBundle) {
|
|
2372
|
-
logger$
|
|
2373
|
-
const { bundleServer } = await import("./bundler-
|
|
1906
|
+
logger$8.log(`\n📦 Bundling production server...`);
|
|
1907
|
+
const { bundleServer } = await import("./bundler-DkA4qzUD.mjs");
|
|
2374
1908
|
const allConstructs = [
|
|
2375
1909
|
...endpoints.map((e) => e.construct),
|
|
2376
1910
|
...functions.map((f) => f.construct),
|
|
@@ -2389,10 +1923,10 @@ async function buildForProvider(provider, context, rootOutputDir, endpointGenera
|
|
|
2389
1923
|
dockerServices
|
|
2390
1924
|
});
|
|
2391
1925
|
masterKey = bundleResult.masterKey;
|
|
2392
|
-
logger$
|
|
1926
|
+
logger$8.log(`✅ Bundle complete: .gkm/server/dist/server.mjs`);
|
|
2393
1927
|
if (masterKey) {
|
|
2394
|
-
logger$
|
|
2395
|
-
logger$
|
|
1928
|
+
logger$8.log(`\n🔐 Secrets encrypted for deployment`);
|
|
1929
|
+
logger$8.log(` Deploy with: GKM_MASTER_KEY=${masterKey}`);
|
|
2396
1930
|
}
|
|
2397
1931
|
}
|
|
2398
1932
|
return { masterKey };
|
|
@@ -2429,17 +1963,17 @@ async function workspaceBuildCommand(workspace, options) {
|
|
|
2429
1963
|
const apps = Object.entries(workspace.apps);
|
|
2430
1964
|
const backendApps = apps.filter(([, app]) => app.type === "backend");
|
|
2431
1965
|
const frontendApps = apps.filter(([, app]) => app.type === "web");
|
|
2432
|
-
logger$
|
|
2433
|
-
logger$
|
|
2434
|
-
logger$
|
|
2435
|
-
if (options.production) logger$
|
|
1966
|
+
logger$8.log(`\n🏗️ Building workspace: ${workspace.name}`);
|
|
1967
|
+
logger$8.log(` Backend apps: ${backendApps.map(([name$1]) => name$1).join(", ") || "none"}`);
|
|
1968
|
+
logger$8.log(` Frontend apps: ${frontendApps.map(([name$1]) => name$1).join(", ") || "none"}`);
|
|
1969
|
+
if (options.production) logger$8.log(` 🏭 Production mode enabled`);
|
|
2436
1970
|
const buildOrder = getAppBuildOrder(workspace);
|
|
2437
|
-
logger$
|
|
1971
|
+
logger$8.log(` Build order: ${buildOrder.join(" → ")}`);
|
|
2438
1972
|
const pm = detectPackageManager$2();
|
|
2439
|
-
logger$
|
|
1973
|
+
logger$8.log(`\n📦 Using ${pm} with Turbo for parallel builds...\n`);
|
|
2440
1974
|
try {
|
|
2441
1975
|
const turboCommand = getTurboCommand(pm);
|
|
2442
|
-
logger$
|
|
1976
|
+
logger$8.log(`Running: ${turboCommand}`);
|
|
2443
1977
|
await new Promise((resolve$1, reject) => {
|
|
2444
1978
|
const child = spawn(turboCommand, {
|
|
2445
1979
|
shell: true,
|
|
@@ -2467,16 +2001,16 @@ async function workspaceBuildCommand(workspace, options) {
|
|
|
2467
2001
|
outputPath
|
|
2468
2002
|
});
|
|
2469
2003
|
}
|
|
2470
|
-
logger$
|
|
2004
|
+
logger$8.log(`\n✅ Workspace build complete!`);
|
|
2471
2005
|
await openapiCommand({ cwd: workspace.root });
|
|
2472
|
-
logger$
|
|
2006
|
+
logger$8.log(`\n📋 Build Summary:`);
|
|
2473
2007
|
for (const result of results) {
|
|
2474
2008
|
const icon = result.type === "backend" ? "⚙️" : result.type === "mobile" ? "📱" : "🌐";
|
|
2475
|
-
logger$
|
|
2009
|
+
logger$8.log(` ${icon} ${result.appName}: ${result.outputPath || "built"}`);
|
|
2476
2010
|
}
|
|
2477
2011
|
} catch (error) {
|
|
2478
2012
|
const errorMessage = error instanceof Error ? error.message : "Build failed";
|
|
2479
|
-
logger$
|
|
2013
|
+
logger$8.log(`\n❌ Build failed: ${errorMessage}`);
|
|
2480
2014
|
for (const [appName, app] of apps) results.push({
|
|
2481
2015
|
appName,
|
|
2482
2016
|
type: app.type,
|
|
@@ -2698,17 +2232,17 @@ function isDnsProvider(value) {
|
|
|
2698
2232
|
* - Custom: Use provided DnsProvider implementation
|
|
2699
2233
|
*/
|
|
2700
2234
|
async function createDnsProvider(options) {
|
|
2701
|
-
const { config
|
|
2702
|
-
if (config
|
|
2703
|
-
if (isDnsProvider(config
|
|
2704
|
-
const provider = config
|
|
2235
|
+
const { config } = options;
|
|
2236
|
+
if (config.provider === "manual") return null;
|
|
2237
|
+
if (isDnsProvider(config.provider)) return config.provider;
|
|
2238
|
+
const provider = config.provider;
|
|
2705
2239
|
if (provider === "hostinger") {
|
|
2706
|
-
const { HostingerProvider } = await import("./HostingerProvider-
|
|
2240
|
+
const { HostingerProvider } = await import("./HostingerProvider-B2ZYz7Yc.mjs");
|
|
2707
2241
|
return new HostingerProvider();
|
|
2708
2242
|
}
|
|
2709
2243
|
if (provider === "route53") {
|
|
2710
|
-
const { Route53Provider } = await import("./Route53Provider-
|
|
2711
|
-
const route53Config = config
|
|
2244
|
+
const { Route53Provider } = await import("./Route53Provider-tlgBzrRQ.mjs");
|
|
2245
|
+
const route53Config = config;
|
|
2712
2246
|
return new Route53Provider({
|
|
2713
2247
|
region: route53Config.region,
|
|
2714
2248
|
profile: route53Config.profile,
|
|
@@ -2716,27 +2250,27 @@ async function createDnsProvider(options) {
|
|
|
2716
2250
|
});
|
|
2717
2251
|
}
|
|
2718
2252
|
if (provider === "cloudflare") throw new Error("Cloudflare DNS provider not yet implemented");
|
|
2719
|
-
throw new Error(`Unknown DNS provider: ${JSON.stringify(config
|
|
2253
|
+
throw new Error(`Unknown DNS provider: ${JSON.stringify(config)}`);
|
|
2720
2254
|
}
|
|
2721
2255
|
|
|
2722
2256
|
//#endregion
|
|
2723
2257
|
//#region src/deploy/dns/index.ts
|
|
2724
|
-
const logger$
|
|
2258
|
+
const logger$7 = console;
|
|
2725
2259
|
/**
|
|
2726
2260
|
* Check if DNS config is legacy format (single domain with `domain` property)
|
|
2727
2261
|
*/
|
|
2728
|
-
function isLegacyDnsConfig(config
|
|
2729
|
-
return typeof config
|
|
2262
|
+
function isLegacyDnsConfig(config) {
|
|
2263
|
+
return typeof config === "object" && config !== null && "provider" in config && "domain" in config;
|
|
2730
2264
|
}
|
|
2731
2265
|
/**
|
|
2732
2266
|
* Normalize DNS config to new multi-domain format
|
|
2733
2267
|
*/
|
|
2734
|
-
function normalizeDnsConfig(config
|
|
2735
|
-
if (isLegacyDnsConfig(config
|
|
2736
|
-
const { domain,...providerConfig } = config
|
|
2268
|
+
function normalizeDnsConfig(config) {
|
|
2269
|
+
if (isLegacyDnsConfig(config)) {
|
|
2270
|
+
const { domain,...providerConfig } = config;
|
|
2737
2271
|
return { [domain]: providerConfig };
|
|
2738
2272
|
}
|
|
2739
|
-
return config
|
|
2273
|
+
return config;
|
|
2740
2274
|
}
|
|
2741
2275
|
/**
|
|
2742
2276
|
* Find the root domain for a hostname from available DNS configs
|
|
@@ -2758,7 +2292,7 @@ function groupHostnamesByDomain(appHostnames, dnsConfig) {
|
|
|
2758
2292
|
for (const [appName, hostname] of appHostnames) {
|
|
2759
2293
|
const rootDomain = findRootDomain(hostname, dnsConfig);
|
|
2760
2294
|
if (!rootDomain) {
|
|
2761
|
-
logger$
|
|
2295
|
+
logger$7.log(` ⚠ No DNS config found for hostname: ${hostname}`);
|
|
2762
2296
|
continue;
|
|
2763
2297
|
}
|
|
2764
2298
|
if (!grouped.has(rootDomain)) grouped.set(rootDomain, /* @__PURE__ */ new Map());
|
|
@@ -2810,10 +2344,10 @@ function generateRequiredRecords(appHostnames, rootDomain, serverIp) {
|
|
|
2810
2344
|
* Print DNS records table
|
|
2811
2345
|
*/
|
|
2812
2346
|
function printDnsRecordsTable(records, rootDomain) {
|
|
2813
|
-
logger$
|
|
2814
|
-
logger$
|
|
2815
|
-
logger$
|
|
2816
|
-
logger$
|
|
2347
|
+
logger$7.log(`\n 📋 DNS Records for ${rootDomain}:`);
|
|
2348
|
+
logger$7.log(" ┌─────────────────────────────────────┬──────┬─────────────────┬────────┐");
|
|
2349
|
+
logger$7.log(" │ Subdomain │ Type │ Value │ Status │");
|
|
2350
|
+
logger$7.log(" ├─────────────────────────────────────┼──────┼─────────────────┼────────┤");
|
|
2817
2351
|
for (const record of records) {
|
|
2818
2352
|
const subdomain = record.subdomain.padEnd(35);
|
|
2819
2353
|
const type$1 = record.type.padEnd(4);
|
|
@@ -2823,18 +2357,18 @@ function printDnsRecordsTable(records, rootDomain) {
|
|
|
2823
2357
|
else if (record.created) status = "✓ new";
|
|
2824
2358
|
else if (record.existed) status = "✓";
|
|
2825
2359
|
else status = "?";
|
|
2826
|
-
logger$
|
|
2360
|
+
logger$7.log(` │ ${subdomain} │ ${type$1} │ ${value} │ ${status.padEnd(6)} │`);
|
|
2827
2361
|
}
|
|
2828
|
-
logger$
|
|
2362
|
+
logger$7.log(" └─────────────────────────────────────┴──────┴─────────────────┴────────┘");
|
|
2829
2363
|
}
|
|
2830
2364
|
/**
|
|
2831
2365
|
* Print DNS records in a simple format for manual setup
|
|
2832
2366
|
*/
|
|
2833
2367
|
function printDnsRecordsSimple(records, rootDomain) {
|
|
2834
|
-
logger$
|
|
2835
|
-
logger$
|
|
2836
|
-
for (const record of records) logger$
|
|
2837
|
-
logger$
|
|
2368
|
+
logger$7.log("\n 📋 Required DNS Records:");
|
|
2369
|
+
logger$7.log(` Add these A records to your DNS provider (${rootDomain}):\n`);
|
|
2370
|
+
for (const record of records) logger$7.log(` ${record.subdomain} → ${record.value} (A record)`);
|
|
2371
|
+
logger$7.log("");
|
|
2838
2372
|
}
|
|
2839
2373
|
/**
|
|
2840
2374
|
* Create DNS records for a single domain using its configured provider
|
|
@@ -2846,7 +2380,7 @@ async function createDnsRecordsForDomain(records, rootDomain, providerConfig) {
|
|
|
2846
2380
|
provider = await createDnsProvider({ config: providerConfig });
|
|
2847
2381
|
} catch (error) {
|
|
2848
2382
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2849
|
-
logger$
|
|
2383
|
+
logger$7.log(` ⚠ Failed to create DNS provider for ${rootDomain}: ${message}`);
|
|
2850
2384
|
return records.map((r) => ({
|
|
2851
2385
|
...r,
|
|
2852
2386
|
error: message
|
|
@@ -2900,7 +2434,7 @@ async function createDnsRecordsForDomain(records, rootDomain, providerConfig) {
|
|
|
2900
2434
|
}
|
|
2901
2435
|
} catch (error) {
|
|
2902
2436
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2903
|
-
logger$
|
|
2437
|
+
logger$7.log(` ⚠ Failed to create DNS records for ${rootDomain}: ${message}`);
|
|
2904
2438
|
return records.map((r) => ({
|
|
2905
2439
|
hostname: r.hostname,
|
|
2906
2440
|
subdomain: r.subdomain,
|
|
@@ -2927,20 +2461,20 @@ async function createDnsRecordsForDomain(records, rootDomain, providerConfig) {
|
|
|
2927
2461
|
async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint, state) {
|
|
2928
2462
|
if (!dnsConfig) return null;
|
|
2929
2463
|
const normalizedConfig = normalizeDnsConfig(dnsConfig);
|
|
2930
|
-
logger$
|
|
2464
|
+
logger$7.log("\n🌐 Setting up DNS records...");
|
|
2931
2465
|
let serverIp;
|
|
2932
2466
|
try {
|
|
2933
2467
|
const endpointUrl = new URL(dokployEndpoint);
|
|
2934
2468
|
serverIp = await resolveHostnameToIp(endpointUrl.hostname);
|
|
2935
|
-
logger$
|
|
2469
|
+
logger$7.log(` Server IP: ${serverIp} (from ${endpointUrl.hostname})`);
|
|
2936
2470
|
} catch (error) {
|
|
2937
2471
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
2938
|
-
logger$
|
|
2472
|
+
logger$7.log(` ⚠ Failed to resolve server IP: ${message}`);
|
|
2939
2473
|
return null;
|
|
2940
2474
|
}
|
|
2941
2475
|
const groupedHostnames = groupHostnamesByDomain(appHostnames, normalizedConfig);
|
|
2942
2476
|
if (groupedHostnames.size === 0) {
|
|
2943
|
-
logger$
|
|
2477
|
+
logger$7.log(" No DNS records needed (no hostnames match configured domains)");
|
|
2944
2478
|
return {
|
|
2945
2479
|
records: [],
|
|
2946
2480
|
success: true,
|
|
@@ -2952,22 +2486,22 @@ async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint, state) {
|
|
|
2952
2486
|
for (const [rootDomain, domainHostnames] of groupedHostnames) {
|
|
2953
2487
|
const providerConfig = normalizedConfig[rootDomain];
|
|
2954
2488
|
if (!providerConfig) {
|
|
2955
|
-
logger$
|
|
2489
|
+
logger$7.log(` ⚠ No provider config for ${rootDomain}`);
|
|
2956
2490
|
continue;
|
|
2957
2491
|
}
|
|
2958
2492
|
const providerName = typeof providerConfig.provider === "string" ? providerConfig.provider : "custom";
|
|
2959
2493
|
const requiredRecords = generateRequiredRecords(domainHostnames, rootDomain, serverIp);
|
|
2960
2494
|
if (requiredRecords.length === 0) continue;
|
|
2961
|
-
logger$
|
|
2495
|
+
logger$7.log(` Creating DNS records for ${rootDomain} (${providerName})...`);
|
|
2962
2496
|
const domainRecords = await createDnsRecordsForDomain(requiredRecords, rootDomain, providerConfig);
|
|
2963
2497
|
allRecords.push(...domainRecords);
|
|
2964
2498
|
const created = domainRecords.filter((r) => r.created).length;
|
|
2965
2499
|
const existed = domainRecords.filter((r) => r.existed).length;
|
|
2966
2500
|
const failed = domainRecords.filter((r) => r.error).length;
|
|
2967
|
-
if (created > 0) logger$
|
|
2968
|
-
if (existed > 0) logger$
|
|
2501
|
+
if (created > 0) logger$7.log(` ✓ Created ${created} DNS record(s) for ${rootDomain}`);
|
|
2502
|
+
if (existed > 0) logger$7.log(` ✓ ${existed} record(s) already exist for ${rootDomain}`);
|
|
2969
2503
|
if (failed > 0) {
|
|
2970
|
-
logger$
|
|
2504
|
+
logger$7.log(` ⚠ ${failed} record(s) failed for ${rootDomain}`);
|
|
2971
2505
|
hasFailures = true;
|
|
2972
2506
|
}
|
|
2973
2507
|
if (state) {
|
|
@@ -3004,10 +2538,10 @@ async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint, state) {
|
|
|
3004
2538
|
*/
|
|
3005
2539
|
async function verifyDnsRecords(appHostnames, serverIp, state) {
|
|
3006
2540
|
const results = [];
|
|
3007
|
-
logger$
|
|
2541
|
+
logger$7.log("\n🔍 Verifying DNS records...");
|
|
3008
2542
|
for (const [appName, hostname] of appHostnames) {
|
|
3009
2543
|
if (isDnsVerified(state, hostname, serverIp)) {
|
|
3010
|
-
logger$
|
|
2544
|
+
logger$7.log(` ✓ ${hostname} (previously verified)`);
|
|
3011
2545
|
results.push({
|
|
3012
2546
|
hostname,
|
|
3013
2547
|
appName,
|
|
@@ -3021,7 +2555,7 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
|
|
|
3021
2555
|
const resolvedIp = await resolveHostnameToIp(hostname);
|
|
3022
2556
|
if (resolvedIp === serverIp) {
|
|
3023
2557
|
setDnsVerification(state, hostname, serverIp);
|
|
3024
|
-
logger$
|
|
2558
|
+
logger$7.log(` ✓ ${hostname} → ${resolvedIp}`);
|
|
3025
2559
|
results.push({
|
|
3026
2560
|
hostname,
|
|
3027
2561
|
appName,
|
|
@@ -3030,7 +2564,7 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
|
|
|
3030
2564
|
expectedIp: serverIp
|
|
3031
2565
|
});
|
|
3032
2566
|
} else {
|
|
3033
|
-
logger$
|
|
2567
|
+
logger$7.log(` ⚠ ${hostname} resolves to ${resolvedIp}, expected ${serverIp}`);
|
|
3034
2568
|
results.push({
|
|
3035
2569
|
hostname,
|
|
3036
2570
|
appName,
|
|
@@ -3041,7 +2575,7 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
|
|
|
3041
2575
|
}
|
|
3042
2576
|
} catch (error) {
|
|
3043
2577
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
3044
|
-
logger$
|
|
2578
|
+
logger$7.log(` ⚠ ${hostname} DNS not propagated (${message})`);
|
|
3045
2579
|
results.push({
|
|
3046
2580
|
hostname,
|
|
3047
2581
|
appName,
|
|
@@ -3055,9 +2589,9 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
|
|
|
3055
2589
|
const skipped = results.filter((r) => r.skipped).length;
|
|
3056
2590
|
const pending = results.filter((r) => !r.verified).length;
|
|
3057
2591
|
if (pending > 0) {
|
|
3058
|
-
logger$
|
|
3059
|
-
logger$
|
|
3060
|
-
} else if (skipped > 0) logger$
|
|
2592
|
+
logger$7.log(`\n ${verified} verified, ${pending} pending propagation`);
|
|
2593
|
+
logger$7.log(" DNS changes may take 5-30 minutes to propagate");
|
|
2594
|
+
} else if (skipped > 0) logger$7.log(` ${verified} verified (${skipped} from cache)`);
|
|
3061
2595
|
return results;
|
|
3062
2596
|
}
|
|
3063
2597
|
|
|
@@ -3149,11 +2683,11 @@ function getDefaultImage(serviceName) {
|
|
|
3149
2683
|
function normalizeServices(services) {
|
|
3150
2684
|
const result = /* @__PURE__ */ new Map();
|
|
3151
2685
|
if (Array.isArray(services)) for (const name$1 of services) result.set(name$1, getDefaultImage(name$1));
|
|
3152
|
-
else for (const [name$1, config
|
|
2686
|
+
else for (const [name$1, config] of Object.entries(services)) {
|
|
3153
2687
|
const serviceName = name$1;
|
|
3154
|
-
if (config
|
|
3155
|
-
else if (config
|
|
3156
|
-
const serviceConfig = config
|
|
2688
|
+
if (config === true) result.set(serviceName, getDefaultImage(serviceName));
|
|
2689
|
+
else if (config && typeof config === "object") {
|
|
2690
|
+
const serviceConfig = config;
|
|
3157
2691
|
if (serviceConfig.image) result.set(serviceName, serviceConfig.image);
|
|
3158
2692
|
else {
|
|
3159
2693
|
const version$1 = serviceConfig.version ?? DEFAULT_SERVICE_VERSIONS[serviceName];
|
|
@@ -3586,22 +3120,22 @@ networks:
|
|
|
3586
3120
|
/**
|
|
3587
3121
|
* Get infrastructure service image with version.
|
|
3588
3122
|
*/
|
|
3589
|
-
function getInfraServiceImage(serviceName, config
|
|
3123
|
+
function getInfraServiceImage(serviceName, config) {
|
|
3590
3124
|
const defaults = {
|
|
3591
3125
|
postgres: "postgres:18-alpine",
|
|
3592
3126
|
redis: "redis:7-alpine",
|
|
3593
3127
|
minio: "minio/minio:latest"
|
|
3594
3128
|
};
|
|
3595
|
-
if (!config
|
|
3596
|
-
if (typeof config
|
|
3597
|
-
if (config
|
|
3598
|
-
if (config
|
|
3129
|
+
if (!config || config === true) return defaults[serviceName];
|
|
3130
|
+
if (typeof config === "object") {
|
|
3131
|
+
if (config.image) return config.image;
|
|
3132
|
+
if (config.version) {
|
|
3599
3133
|
const baseImages = {
|
|
3600
3134
|
postgres: "postgres",
|
|
3601
3135
|
redis: "redis",
|
|
3602
3136
|
minio: "minio/minio"
|
|
3603
3137
|
};
|
|
3604
|
-
return `${baseImages[serviceName]}:${config
|
|
3138
|
+
return `${baseImages[serviceName]}:${config.version}`;
|
|
3605
3139
|
}
|
|
3606
3140
|
}
|
|
3607
3141
|
return defaults[serviceName];
|
|
@@ -4094,8 +3628,8 @@ exec "$@"
|
|
|
4094
3628
|
/**
|
|
4095
3629
|
* Resolve Docker configuration from GkmConfig with defaults
|
|
4096
3630
|
*/
|
|
4097
|
-
function resolveDockerConfig$1(config
|
|
4098
|
-
const docker = config
|
|
3631
|
+
function resolveDockerConfig$1(config) {
|
|
3632
|
+
const docker = config.docker ?? {};
|
|
4099
3633
|
let defaultImageName = "api";
|
|
4100
3634
|
try {
|
|
4101
3635
|
const pkg$1 = __require(`${process.cwd()}/package.json`);
|
|
@@ -4528,7 +4062,7 @@ CMD ["nginx", "-g", "daemon off;"]
|
|
|
4528
4062
|
|
|
4529
4063
|
//#endregion
|
|
4530
4064
|
//#region src/docker/index.ts
|
|
4531
|
-
const logger$
|
|
4065
|
+
const logger$6 = console;
|
|
4532
4066
|
/**
|
|
4533
4067
|
* Docker command implementation
|
|
4534
4068
|
* Generates Dockerfile, docker-compose.yml, and related files
|
|
@@ -4539,12 +4073,12 @@ const logger$7 = console;
|
|
|
4539
4073
|
async function dockerCommand(options) {
|
|
4540
4074
|
const loadedConfig = await loadWorkspaceConfig();
|
|
4541
4075
|
if (loadedConfig.type === "workspace") {
|
|
4542
|
-
logger$
|
|
4076
|
+
logger$6.log("📦 Detected workspace configuration");
|
|
4543
4077
|
return workspaceDockerCommand(loadedConfig.workspace, options);
|
|
4544
4078
|
}
|
|
4545
|
-
const config
|
|
4546
|
-
const dockerConfig = resolveDockerConfig$1(config
|
|
4547
|
-
const serverConfig = typeof config
|
|
4079
|
+
const config = await loadConfig();
|
|
4080
|
+
const dockerConfig = resolveDockerConfig$1(config);
|
|
4081
|
+
const serverConfig = typeof config.providers?.server === "object" ? config.providers.server : void 0;
|
|
4548
4082
|
const healthCheckPath = serverConfig?.production?.healthCheck ?? "/health";
|
|
4549
4083
|
const useSlim = options.slim === true;
|
|
4550
4084
|
if (useSlim) {
|
|
@@ -4560,14 +4094,14 @@ async function dockerCommand(options) {
|
|
|
4560
4094
|
let useTurbo = options.turbo ?? false;
|
|
4561
4095
|
if (inMonorepo && !useSlim) if (hasTurbo) {
|
|
4562
4096
|
useTurbo = true;
|
|
4563
|
-
logger$
|
|
4097
|
+
logger$6.log(" Detected monorepo with turbo.json - using turbo prune");
|
|
4564
4098
|
} 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
4099
|
let turboPackage = options.turboPackage ?? dockerConfig.imageName;
|
|
4566
4100
|
if (useTurbo && !options.turboPackage) try {
|
|
4567
4101
|
const pkg$1 = __require(`${process.cwd()}/package.json`);
|
|
4568
4102
|
if (pkg$1.name) {
|
|
4569
4103
|
turboPackage = pkg$1.name;
|
|
4570
|
-
logger$
|
|
4104
|
+
logger$6.log(` Turbo package: ${turboPackage}`);
|
|
4571
4105
|
}
|
|
4572
4106
|
} catch {}
|
|
4573
4107
|
const templateOptions = {
|
|
@@ -4584,7 +4118,7 @@ async function dockerCommand(options) {
|
|
|
4584
4118
|
const dockerMode = useSlim ? "slim" : useTurbo ? "turbo" : "multi-stage";
|
|
4585
4119
|
const dockerfilePath = join(dockerDir, "Dockerfile");
|
|
4586
4120
|
await writeFile(dockerfilePath, dockerfile);
|
|
4587
|
-
logger$
|
|
4121
|
+
logger$6.log(`Generated: .gkm/docker/Dockerfile (${dockerMode}, ${packageManager})`);
|
|
4588
4122
|
const composeOptions = {
|
|
4589
4123
|
imageName: dockerConfig.imageName,
|
|
4590
4124
|
registry: options.registry ?? dockerConfig.registry,
|
|
@@ -4596,15 +4130,15 @@ async function dockerCommand(options) {
|
|
|
4596
4130
|
const dockerCompose = hasServices ? generateDockerCompose(composeOptions) : generateMinimalDockerCompose(composeOptions);
|
|
4597
4131
|
const composePath = join(dockerDir, "docker-compose.yml");
|
|
4598
4132
|
await writeFile(composePath, dockerCompose);
|
|
4599
|
-
logger$
|
|
4133
|
+
logger$6.log("Generated: .gkm/docker/docker-compose.yml");
|
|
4600
4134
|
const dockerignore = generateDockerignore();
|
|
4601
4135
|
const dockerignorePath = join(process.cwd(), ".dockerignore");
|
|
4602
4136
|
await writeFile(dockerignorePath, dockerignore);
|
|
4603
|
-
logger$
|
|
4137
|
+
logger$6.log("Generated: .dockerignore (project root)");
|
|
4604
4138
|
const entrypoint = generateDockerEntrypoint();
|
|
4605
4139
|
const entrypointPath = join(dockerDir, "docker-entrypoint.sh");
|
|
4606
4140
|
await writeFile(entrypointPath, entrypoint);
|
|
4607
|
-
logger$
|
|
4141
|
+
logger$6.log("Generated: .gkm/docker/docker-entrypoint.sh");
|
|
4608
4142
|
const result = {
|
|
4609
4143
|
dockerfile: dockerfilePath,
|
|
4610
4144
|
dockerCompose: composePath,
|
|
@@ -4623,13 +4157,13 @@ async function dockerCommand(options) {
|
|
|
4623
4157
|
function ensureLockfile(cwd) {
|
|
4624
4158
|
const lockfilePath = findLockfilePath(cwd);
|
|
4625
4159
|
if (!lockfilePath) {
|
|
4626
|
-
logger$
|
|
4160
|
+
logger$6.warn("\n⚠️ No lockfile found. Docker build may fail or use stale dependencies.");
|
|
4627
4161
|
return null;
|
|
4628
4162
|
}
|
|
4629
4163
|
const lockfileName = basename(lockfilePath);
|
|
4630
4164
|
const localLockfile = join(cwd, lockfileName);
|
|
4631
4165
|
if (lockfilePath === localLockfile) return null;
|
|
4632
|
-
logger$
|
|
4166
|
+
logger$6.log(` Copying ${lockfileName} from monorepo root...`);
|
|
4633
4167
|
copyFileSync(lockfilePath, localLockfile);
|
|
4634
4168
|
return () => {
|
|
4635
4169
|
try {
|
|
@@ -4645,7 +4179,7 @@ async function buildDockerImage(imageName, options) {
|
|
|
4645
4179
|
const tag = options.tag ?? "latest";
|
|
4646
4180
|
const registry = options.registry;
|
|
4647
4181
|
const fullImageName = registry ? `${registry}/${imageName}:${tag}` : `${imageName}:${tag}`;
|
|
4648
|
-
logger$
|
|
4182
|
+
logger$6.log(`\n🐳 Building Docker image: ${fullImageName}`);
|
|
4649
4183
|
const cwd = process.cwd();
|
|
4650
4184
|
const cleanup = ensureLockfile(cwd);
|
|
4651
4185
|
try {
|
|
@@ -4657,7 +4191,7 @@ async function buildDockerImage(imageName, options) {
|
|
|
4657
4191
|
DOCKER_BUILDKIT: "1"
|
|
4658
4192
|
}
|
|
4659
4193
|
});
|
|
4660
|
-
logger$
|
|
4194
|
+
logger$6.log(`✅ Docker image built: ${fullImageName}`);
|
|
4661
4195
|
} catch (error) {
|
|
4662
4196
|
throw new Error(`Failed to build Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
4663
4197
|
} finally {
|
|
@@ -4672,13 +4206,13 @@ async function pushDockerImage(imageName, options) {
|
|
|
4672
4206
|
const registry = options.registry;
|
|
4673
4207
|
if (!registry) throw new Error("Registry is required to push Docker image. Use --registry or configure docker.registry in gkm.config.ts");
|
|
4674
4208
|
const fullImageName = `${registry}/${imageName}:${tag}`;
|
|
4675
|
-
logger$
|
|
4209
|
+
logger$6.log(`\n🚀 Pushing Docker image: ${fullImageName}`);
|
|
4676
4210
|
try {
|
|
4677
4211
|
execSync(`docker push ${fullImageName}`, {
|
|
4678
4212
|
cwd: process.cwd(),
|
|
4679
4213
|
stdio: "inherit"
|
|
4680
4214
|
});
|
|
4681
|
-
logger$
|
|
4215
|
+
logger$6.log(`✅ Docker image pushed: ${fullImageName}`);
|
|
4682
4216
|
} catch (error) {
|
|
4683
4217
|
throw new Error(`Failed to push Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
4684
4218
|
}
|
|
@@ -4704,14 +4238,14 @@ function getAppPackageName(appPath) {
|
|
|
4704
4238
|
async function workspaceDockerCommand(workspace, options) {
|
|
4705
4239
|
const results = [];
|
|
4706
4240
|
const apps = Object.entries(workspace.apps);
|
|
4707
|
-
logger$
|
|
4241
|
+
logger$6.log(`\n🐳 Generating Dockerfiles for workspace: ${workspace.name}`);
|
|
4708
4242
|
const dockerDir = join(workspace.root, ".gkm", "docker");
|
|
4709
4243
|
await mkdir(dockerDir, { recursive: true });
|
|
4710
4244
|
const packageManager = detectPackageManager$1(workspace.root);
|
|
4711
|
-
logger$
|
|
4245
|
+
logger$6.log(` Package manager: ${packageManager}`);
|
|
4712
4246
|
for (const [appName, app] of apps) {
|
|
4713
4247
|
if (app.type === "mobile") {
|
|
4714
|
-
logger$
|
|
4248
|
+
logger$6.log(`\n 📱 Skipping Docker for ${appName} (mobile app — deploy via framework toolchain)`);
|
|
4715
4249
|
continue;
|
|
4716
4250
|
}
|
|
4717
4251
|
const appPath = app.path;
|
|
@@ -4720,7 +4254,7 @@ async function workspaceDockerCommand(workspace, options) {
|
|
|
4720
4254
|
const imageName = appName;
|
|
4721
4255
|
const hasEntry = !!app.entry;
|
|
4722
4256
|
const buildType = hasEntry ? "entry" : app.type;
|
|
4723
|
-
logger$
|
|
4257
|
+
logger$6.log(`\n 📄 Generating Dockerfile for ${appName} (${buildType})`);
|
|
4724
4258
|
let dockerfile;
|
|
4725
4259
|
if (app.type === "web") {
|
|
4726
4260
|
const publicUrlArgs = getPublicUrlArgNames(app);
|
|
@@ -4764,7 +4298,7 @@ async function workspaceDockerCommand(workspace, options) {
|
|
|
4764
4298
|
});
|
|
4765
4299
|
const dockerfilePath = join(dockerDir, `Dockerfile.${appName}`);
|
|
4766
4300
|
await writeFile(dockerfilePath, dockerfile);
|
|
4767
|
-
logger$
|
|
4301
|
+
logger$6.log(` Generated: .gkm/docker/Dockerfile.${appName}`);
|
|
4768
4302
|
results.push({
|
|
4769
4303
|
appName,
|
|
4770
4304
|
type: app.type,
|
|
@@ -4775,19 +4309,19 @@ async function workspaceDockerCommand(workspace, options) {
|
|
|
4775
4309
|
const dockerignore = generateDockerignore();
|
|
4776
4310
|
const dockerignorePath = join(workspace.root, ".dockerignore");
|
|
4777
4311
|
await writeFile(dockerignorePath, dockerignore);
|
|
4778
|
-
logger$
|
|
4312
|
+
logger$6.log(`\n Generated: .dockerignore (workspace root)`);
|
|
4779
4313
|
const dockerCompose = generateWorkspaceCompose(workspace, { registry: options.registry });
|
|
4780
4314
|
const composePath = join(dockerDir, "docker-compose.yml");
|
|
4781
4315
|
await writeFile(composePath, dockerCompose);
|
|
4782
|
-
logger$
|
|
4783
|
-
logger$
|
|
4784
|
-
logger$
|
|
4316
|
+
logger$6.log(` Generated: .gkm/docker/docker-compose.yml`);
|
|
4317
|
+
logger$6.log(`\n✅ Generated ${results.length} Dockerfile(s) + docker-compose.yml`);
|
|
4318
|
+
logger$6.log("\n📋 Build commands:");
|
|
4785
4319
|
for (const result of results) {
|
|
4786
4320
|
const icon = result.type === "backend" ? "⚙️" : result.type === "mobile" ? "📱" : "🌐";
|
|
4787
|
-
logger$
|
|
4321
|
+
logger$6.log(` ${icon} docker build -f .gkm/docker/Dockerfile.${result.appName} -t ${result.imageName} .`);
|
|
4788
4322
|
}
|
|
4789
|
-
logger$
|
|
4790
|
-
logger$
|
|
4323
|
+
logger$6.log("\n📋 Run all services:");
|
|
4324
|
+
logger$6.log(" docker compose -f .gkm/docker/docker-compose.yml up --build");
|
|
4791
4325
|
return {
|
|
4792
4326
|
apps: results,
|
|
4793
4327
|
dockerCompose: composePath,
|
|
@@ -4827,7 +4361,7 @@ function getAppNameFromPackageJson() {
|
|
|
4827
4361
|
} catch {}
|
|
4828
4362
|
return void 0;
|
|
4829
4363
|
}
|
|
4830
|
-
const logger$
|
|
4364
|
+
const logger$5 = console;
|
|
4831
4365
|
/**
|
|
4832
4366
|
* Get the full image reference
|
|
4833
4367
|
*/
|
|
@@ -4842,18 +4376,18 @@ function getImageRef(registry, imageName, tag) {
|
|
|
4842
4376
|
* @param buildArgs - Build arguments to pass to docker build
|
|
4843
4377
|
*/
|
|
4844
4378
|
async function buildImage(imageRef, appName, buildArgs) {
|
|
4845
|
-
logger$
|
|
4379
|
+
logger$5.log(`\n🔨 Building Docker image: ${imageRef}`);
|
|
4846
4380
|
const cwd = process.cwd();
|
|
4847
4381
|
const lockfilePath = findLockfilePath(cwd);
|
|
4848
4382
|
const lockfileDir = lockfilePath ? dirname(lockfilePath) : cwd;
|
|
4849
4383
|
const inMonorepo = lockfileDir !== cwd;
|
|
4850
|
-
if (appName || inMonorepo) logger$
|
|
4851
|
-
else logger$
|
|
4384
|
+
if (appName || inMonorepo) logger$5.log(" Generating Dockerfile for monorepo (turbo prune)...");
|
|
4385
|
+
else logger$5.log(" Generating Dockerfile...");
|
|
4852
4386
|
await dockerCommand({});
|
|
4853
4387
|
const dockerfileSuffix = appName ? `.${appName}` : "";
|
|
4854
4388
|
const dockerfilePath = `.gkm/docker/Dockerfile${dockerfileSuffix}`;
|
|
4855
4389
|
const buildCwd = lockfilePath && (inMonorepo || appName) ? lockfileDir : cwd;
|
|
4856
|
-
if (buildCwd !== cwd) logger$
|
|
4390
|
+
if (buildCwd !== cwd) logger$5.log(` Building from workspace root: ${buildCwd}`);
|
|
4857
4391
|
const buildArgsString = buildArgs && buildArgs.length > 0 ? buildArgs.map((arg) => `--build-arg "${arg}"`).join(" ") : "";
|
|
4858
4392
|
try {
|
|
4859
4393
|
const cmd = [
|
|
@@ -4872,7 +4406,7 @@ async function buildImage(imageRef, appName, buildArgs) {
|
|
|
4872
4406
|
DOCKER_BUILDKIT: "1"
|
|
4873
4407
|
}
|
|
4874
4408
|
});
|
|
4875
|
-
logger$
|
|
4409
|
+
logger$5.log(`✅ Image built: ${imageRef}`);
|
|
4876
4410
|
} catch (error) {
|
|
4877
4411
|
throw new Error(`Failed to build Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
4878
4412
|
}
|
|
@@ -4881,13 +4415,13 @@ async function buildImage(imageRef, appName, buildArgs) {
|
|
|
4881
4415
|
* Push Docker image to registry
|
|
4882
4416
|
*/
|
|
4883
4417
|
async function pushImage(imageRef) {
|
|
4884
|
-
logger$
|
|
4418
|
+
logger$5.log(`\n☁️ Pushing image: ${imageRef}`);
|
|
4885
4419
|
try {
|
|
4886
4420
|
execSync(`docker push ${imageRef}`, {
|
|
4887
4421
|
cwd: process.cwd(),
|
|
4888
4422
|
stdio: "inherit"
|
|
4889
4423
|
});
|
|
4890
|
-
logger$
|
|
4424
|
+
logger$5.log(`✅ Image pushed: ${imageRef}`);
|
|
4891
4425
|
} catch (error) {
|
|
4892
4426
|
throw new Error(`Failed to push Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
4893
4427
|
}
|
|
@@ -4896,21 +4430,21 @@ async function pushImage(imageRef) {
|
|
|
4896
4430
|
* Deploy using Docker (build and optionally push image)
|
|
4897
4431
|
*/
|
|
4898
4432
|
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
|
|
4433
|
+
const { stage, tag, skipPush, masterKey, config, buildArgs } = options;
|
|
4434
|
+
const imageName = config.imageName;
|
|
4435
|
+
const imageRef = getImageRef(config.registry, imageName, tag);
|
|
4436
|
+
await buildImage(imageRef, config.appName, buildArgs);
|
|
4437
|
+
if (!skipPush) if (!config.registry) logger$5.warn("\n⚠️ No registry configured. Use --skip-push or configure docker.registry in gkm.config.ts");
|
|
4904
4438
|
else await pushImage(imageRef);
|
|
4905
|
-
logger$
|
|
4906
|
-
logger$
|
|
4907
|
-
logger$
|
|
4908
|
-
logger$
|
|
4439
|
+
logger$5.log("\n✅ Docker deployment ready!");
|
|
4440
|
+
logger$5.log(`\n📋 Deployment details:`);
|
|
4441
|
+
logger$5.log(` Image: ${imageRef}`);
|
|
4442
|
+
logger$5.log(` Stage: ${stage}`);
|
|
4909
4443
|
if (masterKey) {
|
|
4910
|
-
logger$
|
|
4911
|
-
logger$
|
|
4912
|
-
logger$
|
|
4913
|
-
logger$
|
|
4444
|
+
logger$5.log(`\n🔐 Deploy with this environment variable:`);
|
|
4445
|
+
logger$5.log(` GKM_MASTER_KEY=${masterKey}`);
|
|
4446
|
+
logger$5.log("\n Example docker run:");
|
|
4447
|
+
logger$5.log(` docker run -e GKM_MASTER_KEY=${masterKey} ${imageRef}`);
|
|
4914
4448
|
}
|
|
4915
4449
|
return {
|
|
4916
4450
|
imageRef,
|
|
@@ -4923,12 +4457,12 @@ async function deployDocker(options) {
|
|
|
4923
4457
|
* - projectName: from root package.json, or 'app' (for Dokploy project)
|
|
4924
4458
|
* - appName: from cwd package.json, or projectName (for Dokploy app within project)
|
|
4925
4459
|
*/
|
|
4926
|
-
function resolveDockerConfig(config
|
|
4460
|
+
function resolveDockerConfig(config) {
|
|
4927
4461
|
const projectName = getAppNameFromPackageJson() ?? "app";
|
|
4928
4462
|
const appName = getAppNameFromCwd$1() ?? projectName;
|
|
4929
|
-
const imageName = config
|
|
4463
|
+
const imageName = config.docker?.imageName ?? appName;
|
|
4930
4464
|
return {
|
|
4931
|
-
registry: config
|
|
4465
|
+
registry: config.docker?.registry,
|
|
4932
4466
|
imageName,
|
|
4933
4467
|
projectName,
|
|
4934
4468
|
appName
|
|
@@ -4937,7 +4471,7 @@ function resolveDockerConfig(config$1) {
|
|
|
4937
4471
|
|
|
4938
4472
|
//#endregion
|
|
4939
4473
|
//#region src/deploy/dokploy.ts
|
|
4940
|
-
const logger$
|
|
4474
|
+
const logger$4 = console;
|
|
4941
4475
|
/**
|
|
4942
4476
|
* Get the Dokploy API token from stored credentials or environment
|
|
4943
4477
|
*/
|
|
@@ -4960,59 +4494,59 @@ async function createApi$1(endpoint) {
|
|
|
4960
4494
|
* Deploy to Dokploy
|
|
4961
4495
|
*/
|
|
4962
4496
|
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$
|
|
4497
|
+
const { stage, imageRef, masterKey, config } = options;
|
|
4498
|
+
logger$4.log(`\n🎯 Deploying to Dokploy...`);
|
|
4499
|
+
logger$4.log(` Endpoint: ${config.endpoint}`);
|
|
4500
|
+
logger$4.log(` Application: ${config.applicationId}`);
|
|
4501
|
+
const api = await createApi$1(config.endpoint);
|
|
4502
|
+
logger$4.log(` Configuring Docker image: ${imageRef}`);
|
|
4969
4503
|
const registryOptions = {};
|
|
4970
|
-
if (config
|
|
4971
|
-
registryOptions.registryId = config
|
|
4972
|
-
logger$
|
|
4504
|
+
if (config.registryId) {
|
|
4505
|
+
registryOptions.registryId = config.registryId;
|
|
4506
|
+
logger$4.log(` Using Dokploy registry: ${config.registryId}`);
|
|
4973
4507
|
} else {
|
|
4974
4508
|
const storedRegistryId = await getDokployRegistryId();
|
|
4975
4509
|
if (storedRegistryId) {
|
|
4976
4510
|
registryOptions.registryId = storedRegistryId;
|
|
4977
|
-
logger$
|
|
4978
|
-
} else if (config
|
|
4979
|
-
registryOptions.username = config
|
|
4980
|
-
registryOptions.password = config
|
|
4981
|
-
registryOptions.registryUrl = config
|
|
4982
|
-
logger$
|
|
4511
|
+
logger$4.log(` Using stored Dokploy registry: ${storedRegistryId}`);
|
|
4512
|
+
} else if (config.registryCredentials) {
|
|
4513
|
+
registryOptions.username = config.registryCredentials.username;
|
|
4514
|
+
registryOptions.password = config.registryCredentials.password;
|
|
4515
|
+
registryOptions.registryUrl = config.registryCredentials.registryUrl;
|
|
4516
|
+
logger$4.log(` Using registry credentials for: ${config.registryCredentials.registryUrl}`);
|
|
4983
4517
|
} else {
|
|
4984
4518
|
const username = process.env.DOCKER_REGISTRY_USERNAME;
|
|
4985
4519
|
const password = process.env.DOCKER_REGISTRY_PASSWORD;
|
|
4986
|
-
const registryUrl = process.env.DOCKER_REGISTRY_URL || config
|
|
4520
|
+
const registryUrl = process.env.DOCKER_REGISTRY_URL || config.registry;
|
|
4987
4521
|
if (username && password && registryUrl) {
|
|
4988
4522
|
registryOptions.username = username;
|
|
4989
4523
|
registryOptions.password = password;
|
|
4990
4524
|
registryOptions.registryUrl = registryUrl;
|
|
4991
|
-
logger$
|
|
4525
|
+
logger$4.log(` Using registry credentials from environment`);
|
|
4992
4526
|
}
|
|
4993
4527
|
}
|
|
4994
4528
|
}
|
|
4995
|
-
await api.saveDockerProvider(config
|
|
4996
|
-
logger$
|
|
4529
|
+
await api.saveDockerProvider(config.applicationId, imageRef, registryOptions);
|
|
4530
|
+
logger$4.log(" ✓ Docker provider configured");
|
|
4997
4531
|
const envVars = {};
|
|
4998
4532
|
if (masterKey) envVars.GKM_MASTER_KEY = masterKey;
|
|
4999
4533
|
if (Object.keys(envVars).length > 0) {
|
|
5000
|
-
logger$
|
|
4534
|
+
logger$4.log(" Updating environment variables...");
|
|
5001
4535
|
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$
|
|
4536
|
+
await api.saveApplicationEnv(config.applicationId, envString);
|
|
4537
|
+
logger$4.log(" ✓ Environment variables updated");
|
|
4538
|
+
}
|
|
4539
|
+
logger$4.log(" Triggering deployment...");
|
|
4540
|
+
await api.deployApplication(config.applicationId);
|
|
4541
|
+
logger$4.log(" ✓ Deployment triggered");
|
|
4542
|
+
logger$4.log("\n✅ Dokploy deployment initiated!");
|
|
4543
|
+
logger$4.log(`\n📋 Deployment details:`);
|
|
4544
|
+
logger$4.log(` Image: ${imageRef}`);
|
|
4545
|
+
logger$4.log(` Stage: ${stage}`);
|
|
4546
|
+
logger$4.log(` Application ID: ${config.applicationId}`);
|
|
4547
|
+
if (masterKey) logger$4.log(`\n🔐 GKM_MASTER_KEY has been set in Dokploy environment`);
|
|
4548
|
+
const deploymentUrl = `${config.endpoint}/project/${config.projectId}`;
|
|
4549
|
+
logger$4.log(`\n🔗 View deployment: ${deploymentUrl}`);
|
|
5016
4550
|
return {
|
|
5017
4551
|
imageRef,
|
|
5018
4552
|
masterKey,
|
|
@@ -5129,7 +4663,7 @@ function validateEnvVars(requiredVars, context) {
|
|
|
5129
4663
|
|
|
5130
4664
|
//#endregion
|
|
5131
4665
|
//#region src/deploy/init.ts
|
|
5132
|
-
const logger$
|
|
4666
|
+
const logger$3 = console;
|
|
5133
4667
|
/**
|
|
5134
4668
|
* Get the Dokploy API token from stored credentials or environment
|
|
5135
4669
|
*/
|
|
@@ -5160,29 +4694,29 @@ async function createApi(endpoint) {
|
|
|
5160
4694
|
/**
|
|
5161
4695
|
* Update gkm.config.ts with Dokploy configuration
|
|
5162
4696
|
*/
|
|
5163
|
-
async function updateConfig(config
|
|
4697
|
+
async function updateConfig(config, cwd = process.cwd()) {
|
|
5164
4698
|
const configPath = join(cwd, "gkm.config.ts");
|
|
5165
4699
|
if (!existsSync(configPath)) {
|
|
5166
|
-
logger$
|
|
5167
|
-
logger$
|
|
5168
|
-
logger$
|
|
5169
|
-
logger$
|
|
5170
|
-
logger$
|
|
5171
|
-
logger$
|
|
5172
|
-
logger$
|
|
5173
|
-
logger$
|
|
4700
|
+
logger$3.warn("\n gkm.config.ts not found. Add this configuration manually:\n");
|
|
4701
|
+
logger$3.log(` providers: {`);
|
|
4702
|
+
logger$3.log(` dokploy: {`);
|
|
4703
|
+
logger$3.log(` endpoint: '${config.endpoint}',`);
|
|
4704
|
+
logger$3.log(` projectId: '${config.projectId}',`);
|
|
4705
|
+
logger$3.log(` applicationId: '${config.applicationId}',`);
|
|
4706
|
+
logger$3.log(` },`);
|
|
4707
|
+
logger$3.log(` },`);
|
|
5174
4708
|
return;
|
|
5175
4709
|
}
|
|
5176
4710
|
const content = await readFile(configPath, "utf-8");
|
|
5177
4711
|
if (content.includes("dokploy:") && content.includes("applicationId:")) {
|
|
5178
|
-
logger$
|
|
5179
|
-
logger$
|
|
4712
|
+
logger$3.log("\n Dokploy config already exists in gkm.config.ts");
|
|
4713
|
+
logger$3.log(" Updating with new values...");
|
|
5180
4714
|
}
|
|
5181
|
-
const registryLine = config
|
|
4715
|
+
const registryLine = config.registryId ? `\n\t\t\tregistryId: '${config.registryId}',` : "";
|
|
5182
4716
|
const dokployConfigStr = `dokploy: {
|
|
5183
|
-
endpoint: '${config
|
|
5184
|
-
projectId: '${config
|
|
5185
|
-
applicationId: '${config
|
|
4717
|
+
endpoint: '${config.endpoint}',
|
|
4718
|
+
projectId: '${config.projectId}',
|
|
4719
|
+
applicationId: '${config.applicationId}',${registryLine}
|
|
5186
4720
|
}`;
|
|
5187
4721
|
let newContent;
|
|
5188
4722
|
if (content.includes("providers:")) if (content.includes("dokploy:")) newContent = content.replace(/dokploy:\s*\{[^}]*\}/s, dokployConfigStr);
|
|
@@ -5193,7 +4727,7 @@ async function updateConfig(config$1, cwd = process.cwd()) {
|
|
|
5193
4727
|
},
|
|
5194
4728
|
});`);
|
|
5195
4729
|
await writeFile(configPath, newContent);
|
|
5196
|
-
logger$
|
|
4730
|
+
logger$3.log("\n ✓ Updated gkm.config.ts with Dokploy configuration");
|
|
5197
4731
|
}
|
|
5198
4732
|
/**
|
|
5199
4733
|
* Initialize Dokploy deployment configuration
|
|
@@ -5202,24 +4736,24 @@ async function deployInitCommand(options) {
|
|
|
5202
4736
|
const { projectName, appName, projectId: existingProjectId, registryId } = options;
|
|
5203
4737
|
const endpoint = await getEndpoint(options.endpoint);
|
|
5204
4738
|
const api = await createApi(endpoint);
|
|
5205
|
-
logger$
|
|
5206
|
-
logger$
|
|
4739
|
+
logger$3.log(`\n🚀 Initializing Dokploy deployment...`);
|
|
4740
|
+
logger$3.log(` Endpoint: ${endpoint}`);
|
|
5207
4741
|
let projectId;
|
|
5208
4742
|
if (existingProjectId) {
|
|
5209
4743
|
projectId = existingProjectId;
|
|
5210
|
-
logger$
|
|
4744
|
+
logger$3.log(`\n📁 Using existing project: ${projectId}`);
|
|
5211
4745
|
} else {
|
|
5212
|
-
logger$
|
|
4746
|
+
logger$3.log(`\n📁 Looking for project: ${projectName}`);
|
|
5213
4747
|
const projects = await api.listProjects();
|
|
5214
4748
|
const existingProject = projects.find((p) => p.name.toLowerCase() === projectName.toLowerCase());
|
|
5215
4749
|
if (existingProject) {
|
|
5216
4750
|
projectId = existingProject.projectId;
|
|
5217
|
-
logger$
|
|
4751
|
+
logger$3.log(` Found existing project: ${projectId}`);
|
|
5218
4752
|
} else {
|
|
5219
|
-
logger$
|
|
4753
|
+
logger$3.log(` Creating new project...`);
|
|
5220
4754
|
const result = await api.createProject(projectName);
|
|
5221
4755
|
projectId = result.project.projectId;
|
|
5222
|
-
logger$
|
|
4756
|
+
logger$3.log(` ✓ Created project: ${projectId}`);
|
|
5223
4757
|
}
|
|
5224
4758
|
}
|
|
5225
4759
|
const project = await api.getProject(projectId);
|
|
@@ -5227,40 +4761,40 @@ async function deployInitCommand(options) {
|
|
|
5227
4761
|
const firstEnv = project.environments?.[0];
|
|
5228
4762
|
if (firstEnv) environmentId = firstEnv.environmentId;
|
|
5229
4763
|
else {
|
|
5230
|
-
logger$
|
|
4764
|
+
logger$3.log(` Creating production environment...`);
|
|
5231
4765
|
const env = await api.createEnvironment(projectId, "production");
|
|
5232
4766
|
environmentId = env.environmentId;
|
|
5233
4767
|
}
|
|
5234
|
-
logger$
|
|
4768
|
+
logger$3.log(`\n📦 Creating application: ${appName}`);
|
|
5235
4769
|
const application = await api.createApplication(appName, projectId, environmentId);
|
|
5236
|
-
logger$
|
|
4770
|
+
logger$3.log(` ✓ Created application: ${application.applicationId}`);
|
|
5237
4771
|
if (registryId) {
|
|
5238
|
-
logger$
|
|
4772
|
+
logger$3.log(`\n🔧 Configuring registry: ${registryId}`);
|
|
5239
4773
|
await api.updateApplication(application.applicationId, { registryId });
|
|
5240
|
-
logger$
|
|
4774
|
+
logger$3.log(` ✓ Registry configured`);
|
|
5241
4775
|
} else try {
|
|
5242
4776
|
const registries = await api.listRegistries();
|
|
5243
4777
|
if (registries.length > 0) {
|
|
5244
|
-
logger$
|
|
5245
|
-
for (const reg of registries) logger$
|
|
5246
|
-
logger$
|
|
4778
|
+
logger$3.log(`\n📋 Available registries:`);
|
|
4779
|
+
for (const reg of registries) logger$3.log(` - ${reg.registryName}: ${reg.registryUrl} (${reg.registryId})`);
|
|
4780
|
+
logger$3.log(`\n To use a registry, run with --registry-id <id>`);
|
|
5247
4781
|
}
|
|
5248
4782
|
} catch {}
|
|
5249
|
-
const config
|
|
4783
|
+
const config = {
|
|
5250
4784
|
endpoint,
|
|
5251
4785
|
projectId,
|
|
5252
4786
|
applicationId: application.applicationId
|
|
5253
4787
|
};
|
|
5254
|
-
await updateConfig(config
|
|
5255
|
-
logger$
|
|
5256
|
-
logger$
|
|
5257
|
-
logger$
|
|
5258
|
-
logger$
|
|
5259
|
-
logger$
|
|
5260
|
-
logger$
|
|
5261
|
-
logger$
|
|
5262
|
-
logger$
|
|
5263
|
-
return config
|
|
4788
|
+
await updateConfig(config);
|
|
4789
|
+
logger$3.log(`\n✅ Dokploy deployment initialized!`);
|
|
4790
|
+
logger$3.log(`\n📋 Configuration:`);
|
|
4791
|
+
logger$3.log(` Project ID: ${projectId}`);
|
|
4792
|
+
logger$3.log(` Application ID: ${application.applicationId}`);
|
|
4793
|
+
logger$3.log(`\n🔗 View in Dokploy: ${endpoint}/project/${projectId}`);
|
|
4794
|
+
logger$3.log(`\n📝 Next steps:`);
|
|
4795
|
+
logger$3.log(` 1. Initialize secrets: gkm secrets:init --stage production`);
|
|
4796
|
+
logger$3.log(` 2. Deploy: gkm deploy --provider dokploy --stage production`);
|
|
4797
|
+
return config;
|
|
5264
4798
|
}
|
|
5265
4799
|
/**
|
|
5266
4800
|
* List available Dokploy resources
|
|
@@ -5270,32 +4804,32 @@ async function deployListCommand(options) {
|
|
|
5270
4804
|
const api = await createApi(endpoint);
|
|
5271
4805
|
const { resource } = options;
|
|
5272
4806
|
if (resource === "projects") {
|
|
5273
|
-
logger$
|
|
4807
|
+
logger$3.log(`\n📁 Projects in ${endpoint}:`);
|
|
5274
4808
|
const projects = await api.listProjects();
|
|
5275
4809
|
if (projects.length === 0) {
|
|
5276
|
-
logger$
|
|
4810
|
+
logger$3.log(" No projects found");
|
|
5277
4811
|
return;
|
|
5278
4812
|
}
|
|
5279
4813
|
for (const project of projects) {
|
|
5280
|
-
logger$
|
|
5281
|
-
if (project.description) logger$
|
|
4814
|
+
logger$3.log(`\n ${project.name} (${project.projectId})`);
|
|
4815
|
+
if (project.description) logger$3.log(` ${project.description}`);
|
|
5282
4816
|
}
|
|
5283
4817
|
} else if (resource === "registries") {
|
|
5284
|
-
logger$
|
|
4818
|
+
logger$3.log(`\n🐳 Registries in ${endpoint}:`);
|
|
5285
4819
|
const registries = await api.listRegistries();
|
|
5286
4820
|
if (registries.length === 0) {
|
|
5287
|
-
logger$
|
|
5288
|
-
logger$
|
|
4821
|
+
logger$3.log(" No registries configured");
|
|
4822
|
+
logger$3.log(" Run \"gkm registry:setup\" to configure a registry");
|
|
5289
4823
|
return;
|
|
5290
4824
|
}
|
|
5291
4825
|
const storedRegistryId = await getDokployRegistryId();
|
|
5292
4826
|
for (const registry of registries) {
|
|
5293
4827
|
const isDefault = registry.registryId === storedRegistryId;
|
|
5294
4828
|
const marker = isDefault ? " (default)" : "";
|
|
5295
|
-
logger$
|
|
5296
|
-
logger$
|
|
5297
|
-
logger$
|
|
5298
|
-
if (registry.imagePrefix) logger$
|
|
4829
|
+
logger$3.log(`\n ${registry.registryName}${marker} (${registry.registryId})`);
|
|
4830
|
+
logger$3.log(` URL: ${registry.registryUrl}`);
|
|
4831
|
+
logger$3.log(` Username: ${registry.username}`);
|
|
4832
|
+
if (registry.imagePrefix) logger$3.log(` Prefix: ${registry.imagePrefix}`);
|
|
5299
4833
|
}
|
|
5300
4834
|
}
|
|
5301
4835
|
}
|
|
@@ -5316,23 +4850,23 @@ function isStateProvider(value) {
|
|
|
5316
4850
|
* - Custom: Use provided StateProvider implementation
|
|
5317
4851
|
*/
|
|
5318
4852
|
async function createStateProvider(options) {
|
|
5319
|
-
const { config
|
|
5320
|
-
if (!config
|
|
5321
|
-
const { LocalStateProvider } = await import("./LocalStateProvider-
|
|
4853
|
+
const { config, workspaceRoot, workspaceName } = options;
|
|
4854
|
+
if (!config) {
|
|
4855
|
+
const { LocalStateProvider } = await import("./LocalStateProvider-CP49rjzt.mjs");
|
|
5322
4856
|
return new LocalStateProvider(workspaceRoot);
|
|
5323
4857
|
}
|
|
5324
|
-
if (isStateProvider(config
|
|
5325
|
-
const provider = config
|
|
4858
|
+
if (isStateProvider(config.provider)) return config.provider;
|
|
4859
|
+
const provider = config.provider;
|
|
5326
4860
|
if (provider === "local") {
|
|
5327
|
-
const { LocalStateProvider } = await import("./LocalStateProvider-
|
|
4861
|
+
const { LocalStateProvider } = await import("./LocalStateProvider-CP49rjzt.mjs");
|
|
5328
4862
|
return new LocalStateProvider(workspaceRoot);
|
|
5329
4863
|
}
|
|
5330
4864
|
if (provider === "ssm") {
|
|
5331
4865
|
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
|
|
4866
|
+
const { LocalStateProvider } = await import("./LocalStateProvider-CP49rjzt.mjs");
|
|
4867
|
+
const { SSMStateProvider } = await import("./SSMStateProvider-BlrTiV5m.mjs");
|
|
4868
|
+
const { CachedStateProvider: CachedStateProvider$1 } = await import("./CachedStateProvider-GcGziXh1.mjs");
|
|
4869
|
+
const ssmConfig = config;
|
|
5336
4870
|
const local = new LocalStateProvider(workspaceRoot);
|
|
5337
4871
|
const ssm = SSMStateProvider.create({
|
|
5338
4872
|
workspaceName,
|
|
@@ -5341,7 +4875,7 @@ async function createStateProvider(options) {
|
|
|
5341
4875
|
});
|
|
5342
4876
|
return new CachedStateProvider$1(ssm, local);
|
|
5343
4877
|
}
|
|
5344
|
-
throw new Error(`Unknown state provider: ${JSON.stringify(config
|
|
4878
|
+
throw new Error(`Unknown state provider: ${JSON.stringify(config)}`);
|
|
5345
4879
|
}
|
|
5346
4880
|
|
|
5347
4881
|
//#endregion
|
|
@@ -5791,7 +5325,7 @@ async function sniffAllApps(apps, workspacePath, options = {}) {
|
|
|
5791
5325
|
|
|
5792
5326
|
//#endregion
|
|
5793
5327
|
//#region src/deploy/index.ts
|
|
5794
|
-
const logger$
|
|
5328
|
+
const logger$2 = console;
|
|
5795
5329
|
/**
|
|
5796
5330
|
* Prompt for input
|
|
5797
5331
|
*/
|
|
@@ -5863,7 +5397,7 @@ async function waitForPostgres(host, port, user, password, database, maxRetries
|
|
|
5863
5397
|
return;
|
|
5864
5398
|
} catch {
|
|
5865
5399
|
if (i < maxRetries - 1) {
|
|
5866
|
-
logger$
|
|
5400
|
+
logger$2.log(` Waiting for Postgres... (${i + 1}/${maxRetries})`);
|
|
5867
5401
|
await new Promise((r) => setTimeout(r, retryIntervalMs));
|
|
5868
5402
|
}
|
|
5869
5403
|
}
|
|
@@ -5898,12 +5432,12 @@ async function waitForPostgres(host, port, user, password, database, maxRetries
|
|
|
5898
5432
|
* ```
|
|
5899
5433
|
*/
|
|
5900
5434
|
async function initializePostgresUsers(api, postgres, serverHostname, users) {
|
|
5901
|
-
logger$
|
|
5435
|
+
logger$2.log("\n🔧 Initializing database users...");
|
|
5902
5436
|
const externalPort = 5432;
|
|
5903
|
-
logger$
|
|
5437
|
+
logger$2.log(` Enabling external port ${externalPort}...`);
|
|
5904
5438
|
await api.savePostgresExternalPort(postgres.postgresId, externalPort);
|
|
5905
5439
|
await api.deployPostgres(postgres.postgresId);
|
|
5906
|
-
logger$
|
|
5440
|
+
logger$2.log(` Waiting for Postgres to be accessible at ${serverHostname}:${externalPort}...`);
|
|
5907
5441
|
await waitForPostgres(serverHostname, externalPort, postgres.databaseUser, postgres.databasePassword, postgres.databaseName);
|
|
5908
5442
|
const client = new Client({
|
|
5909
5443
|
host: serverHostname,
|
|
@@ -5916,7 +5450,7 @@ async function initializePostgresUsers(api, postgres, serverHostname, users) {
|
|
|
5916
5450
|
await client.connect();
|
|
5917
5451
|
for (const user of users) {
|
|
5918
5452
|
const schemaName = user.usePublicSchema ? "public" : user.name;
|
|
5919
|
-
logger$
|
|
5453
|
+
logger$2.log(` Creating user "${user.name}" with schema "${schemaName}"...`);
|
|
5920
5454
|
if (user.usePublicSchema) {
|
|
5921
5455
|
await client.query(`
|
|
5922
5456
|
DO $$ BEGIN
|
|
@@ -5951,15 +5485,15 @@ async function initializePostgresUsers(api, postgres, serverHostname, users) {
|
|
|
5951
5485
|
ALTER DEFAULT PRIVILEGES IN SCHEMA "${schemaName}" GRANT ALL ON TABLES TO "${user.name}";
|
|
5952
5486
|
`);
|
|
5953
5487
|
}
|
|
5954
|
-
logger$
|
|
5488
|
+
logger$2.log(` ✓ User "${user.name}" configured`);
|
|
5955
5489
|
}
|
|
5956
5490
|
} finally {
|
|
5957
5491
|
await client.end();
|
|
5958
5492
|
}
|
|
5959
|
-
logger$
|
|
5493
|
+
logger$2.log(" Disabling external port...");
|
|
5960
5494
|
await api.savePostgresExternalPort(postgres.postgresId, null);
|
|
5961
5495
|
await api.deployPostgres(postgres.postgresId);
|
|
5962
|
-
logger$
|
|
5496
|
+
logger$2.log(" ✓ Database users initialized");
|
|
5963
5497
|
}
|
|
5964
5498
|
/**
|
|
5965
5499
|
* Get the server hostname from the Dokploy endpoint URL
|
|
@@ -5973,24 +5507,24 @@ function getServerHostname(endpoint) {
|
|
|
5973
5507
|
* @internal Exported for testing
|
|
5974
5508
|
*/
|
|
5975
5509
|
async function provisionServices(api, projectId, environmentId, projectName, services, existingServiceIds) {
|
|
5976
|
-
logger$
|
|
5510
|
+
logger$2.log(`\n🔍 provisionServices called: services=${JSON.stringify(services)}, envId=${environmentId}`);
|
|
5977
5511
|
if (!services || !environmentId) {
|
|
5978
|
-
logger$
|
|
5512
|
+
logger$2.log(" Skipping: no services or no environmentId");
|
|
5979
5513
|
return void 0;
|
|
5980
5514
|
}
|
|
5981
5515
|
const serviceUrls = {};
|
|
5982
5516
|
const serviceIds = {};
|
|
5983
5517
|
if (services.postgres) {
|
|
5984
|
-
logger$
|
|
5518
|
+
logger$2.log("\n🐘 Checking PostgreSQL...");
|
|
5985
5519
|
const postgresName = "db";
|
|
5986
5520
|
try {
|
|
5987
5521
|
let postgres = null;
|
|
5988
5522
|
let created = false;
|
|
5989
5523
|
if (existingServiceIds?.postgresId) {
|
|
5990
|
-
logger$
|
|
5524
|
+
logger$2.log(` Using cached ID: ${existingServiceIds.postgresId}`);
|
|
5991
5525
|
postgres = await api.getPostgres(existingServiceIds.postgresId);
|
|
5992
|
-
if (postgres) logger$
|
|
5993
|
-
else logger$
|
|
5526
|
+
if (postgres) logger$2.log(` ✓ PostgreSQL found: ${postgres.postgresId}`);
|
|
5527
|
+
else logger$2.log(` ⚠ Cached ID invalid, will create new`);
|
|
5994
5528
|
}
|
|
5995
5529
|
if (!postgres) {
|
|
5996
5530
|
const databasePassword = randomBytes(16).toString("hex");
|
|
@@ -6002,10 +5536,10 @@ async function provisionServices(api, projectId, environmentId, projectName, ser
|
|
|
6002
5536
|
postgres = result.postgres;
|
|
6003
5537
|
created = result.created;
|
|
6004
5538
|
if (created) {
|
|
6005
|
-
logger$
|
|
5539
|
+
logger$2.log(` ✓ Created PostgreSQL: ${postgres.postgresId}`);
|
|
6006
5540
|
await api.deployPostgres(postgres.postgresId);
|
|
6007
|
-
logger$
|
|
6008
|
-
} else logger$
|
|
5541
|
+
logger$2.log(" ✓ PostgreSQL deployed");
|
|
5542
|
+
} else logger$2.log(` ✓ PostgreSQL already exists: ${postgres.postgresId}`);
|
|
6009
5543
|
}
|
|
6010
5544
|
serviceIds.postgresId = postgres.postgresId;
|
|
6011
5545
|
serviceUrls.DATABASE_HOST = postgres.appName;
|
|
@@ -6014,23 +5548,23 @@ async function provisionServices(api, projectId, environmentId, projectName, ser
|
|
|
6014
5548
|
serviceUrls.DATABASE_USER = postgres.databaseUser;
|
|
6015
5549
|
serviceUrls.DATABASE_PASSWORD = postgres.databasePassword;
|
|
6016
5550
|
serviceUrls.DATABASE_URL = `postgresql://${postgres.databaseUser}:${postgres.databasePassword}@${postgres.appName}:5432/${postgres.databaseName}`;
|
|
6017
|
-
logger$
|
|
5551
|
+
logger$2.log(` ✓ Database credentials configured`);
|
|
6018
5552
|
} catch (error) {
|
|
6019
5553
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
6020
|
-
logger$
|
|
5554
|
+
logger$2.log(` ⚠ Failed to provision PostgreSQL: ${message}`);
|
|
6021
5555
|
}
|
|
6022
5556
|
}
|
|
6023
5557
|
if (services.redis) {
|
|
6024
|
-
logger$
|
|
5558
|
+
logger$2.log("\n🔴 Checking Redis...");
|
|
6025
5559
|
const redisName = "cache";
|
|
6026
5560
|
try {
|
|
6027
5561
|
let redis = null;
|
|
6028
5562
|
let created = false;
|
|
6029
5563
|
if (existingServiceIds?.redisId) {
|
|
6030
|
-
logger$
|
|
5564
|
+
logger$2.log(` Using cached ID: ${existingServiceIds.redisId}`);
|
|
6031
5565
|
redis = await api.getRedis(existingServiceIds.redisId);
|
|
6032
|
-
if (redis) logger$
|
|
6033
|
-
else logger$
|
|
5566
|
+
if (redis) logger$2.log(` ✓ Redis found: ${redis.redisId}`);
|
|
5567
|
+
else logger$2.log(` ⚠ Cached ID invalid, will create new`);
|
|
6034
5568
|
}
|
|
6035
5569
|
if (!redis) {
|
|
6036
5570
|
const { randomBytes: randomBytes$1 } = await import("node:crypto");
|
|
@@ -6039,10 +5573,10 @@ async function provisionServices(api, projectId, environmentId, projectName, ser
|
|
|
6039
5573
|
redis = result.redis;
|
|
6040
5574
|
created = result.created;
|
|
6041
5575
|
if (created) {
|
|
6042
|
-
logger$
|
|
5576
|
+
logger$2.log(` ✓ Created Redis: ${redis.redisId}`);
|
|
6043
5577
|
await api.deployRedis(redis.redisId);
|
|
6044
|
-
logger$
|
|
6045
|
-
} else logger$
|
|
5578
|
+
logger$2.log(" ✓ Redis deployed");
|
|
5579
|
+
} else logger$2.log(` ✓ Redis already exists: ${redis.redisId}`);
|
|
6046
5580
|
}
|
|
6047
5581
|
serviceIds.redisId = redis.redisId;
|
|
6048
5582
|
serviceUrls.REDIS_HOST = redis.appName;
|
|
@@ -6050,10 +5584,10 @@ async function provisionServices(api, projectId, environmentId, projectName, ser
|
|
|
6050
5584
|
if (redis.databasePassword) serviceUrls.REDIS_PASSWORD = redis.databasePassword;
|
|
6051
5585
|
const password = redis.databasePassword ? `:${redis.databasePassword}@` : "";
|
|
6052
5586
|
serviceUrls.REDIS_URL = `redis://${password}${redis.appName}:6379`;
|
|
6053
|
-
logger$
|
|
5587
|
+
logger$2.log(` ✓ Redis credentials configured`);
|
|
6054
5588
|
} catch (error) {
|
|
6055
5589
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
6056
|
-
logger$
|
|
5590
|
+
logger$2.log(` ⚠ Failed to provision Redis: ${message}`);
|
|
6057
5591
|
}
|
|
6058
5592
|
}
|
|
6059
5593
|
return Object.keys(serviceUrls).length > 0 ? {
|
|
@@ -6064,11 +5598,11 @@ async function provisionServices(api, projectId, environmentId, projectName, ser
|
|
|
6064
5598
|
/**
|
|
6065
5599
|
* Ensure Dokploy is fully configured, recovering/creating resources as needed
|
|
6066
5600
|
*/
|
|
6067
|
-
async function ensureDokploySetup(config
|
|
6068
|
-
logger$
|
|
5601
|
+
async function ensureDokploySetup(config, dockerConfig, stage, services) {
|
|
5602
|
+
logger$2.log("\n🔧 Checking Dokploy setup...");
|
|
6069
5603
|
let creds = await getDokployCredentials();
|
|
6070
5604
|
if (!creds) {
|
|
6071
|
-
logger$
|
|
5605
|
+
logger$2.log("\n📋 Dokploy credentials not found. Let's set them up.");
|
|
6072
5606
|
const endpoint = await prompt("Dokploy URL (e.g., https://dokploy.example.com): ");
|
|
6073
5607
|
const normalizedEndpoint = endpoint.replace(/\/$/, "");
|
|
6074
5608
|
try {
|
|
@@ -6076,9 +5610,9 @@ async function ensureDokploySetup(config$1, dockerConfig, stage, services) {
|
|
|
6076
5610
|
} catch {
|
|
6077
5611
|
throw new Error("Invalid URL format");
|
|
6078
5612
|
}
|
|
6079
|
-
logger$
|
|
5613
|
+
logger$2.log(`\nGenerate a token at: ${normalizedEndpoint}/settings/profile\n`);
|
|
6080
5614
|
const token = await prompt("API Token: ", true);
|
|
6081
|
-
logger$
|
|
5615
|
+
logger$2.log("\nValidating credentials...");
|
|
6082
5616
|
const isValid = await validateDokployToken(normalizedEndpoint, token);
|
|
6083
5617
|
if (!isValid) throw new Error("Invalid credentials. Please check your token.");
|
|
6084
5618
|
await storeDokployCredentials(token, normalizedEndpoint);
|
|
@@ -6086,28 +5620,28 @@ async function ensureDokploySetup(config$1, dockerConfig, stage, services) {
|
|
|
6086
5620
|
token,
|
|
6087
5621
|
endpoint: normalizedEndpoint
|
|
6088
5622
|
};
|
|
6089
|
-
logger$
|
|
5623
|
+
logger$2.log("✓ Credentials saved");
|
|
6090
5624
|
}
|
|
6091
5625
|
const api = new DokployApi({
|
|
6092
5626
|
baseUrl: creds.endpoint,
|
|
6093
5627
|
token: creds.token
|
|
6094
5628
|
});
|
|
6095
|
-
const existingConfig = config
|
|
5629
|
+
const existingConfig = config.providers?.dokploy;
|
|
6096
5630
|
if (existingConfig && typeof existingConfig !== "boolean" && existingConfig.applicationId && existingConfig.projectId) {
|
|
6097
|
-
logger$
|
|
5631
|
+
logger$2.log("✓ Dokploy config found in gkm.config.ts");
|
|
6098
5632
|
try {
|
|
6099
5633
|
const projectDetails = await api.getProject(existingConfig.projectId);
|
|
6100
|
-
logger$
|
|
5634
|
+
logger$2.log("✓ Project verified");
|
|
6101
5635
|
const storedRegistryId = existingConfig.registryId ?? await getDokployRegistryId();
|
|
6102
5636
|
const environments = projectDetails.environments ?? [];
|
|
6103
5637
|
let environment = environments.find((e) => e.name.toLowerCase() === stage.toLowerCase());
|
|
6104
5638
|
if (!environment) {
|
|
6105
|
-
logger$
|
|
5639
|
+
logger$2.log(` Creating "${stage}" environment...`);
|
|
6106
5640
|
environment = await api.createEnvironment(existingConfig.projectId, stage);
|
|
6107
|
-
logger$
|
|
5641
|
+
logger$2.log(` ✓ Created environment: ${environment.environmentId}`);
|
|
6108
5642
|
}
|
|
6109
5643
|
const environmentId$1 = environment.environmentId;
|
|
6110
|
-
logger$
|
|
5644
|
+
logger$2.log(` Services config: ${JSON.stringify(services)}, envId: ${environmentId$1}`);
|
|
6111
5645
|
const provisionResult$1 = await provisionServices(api, existingConfig.projectId, environmentId$1, dockerConfig.appName, services, void 0);
|
|
6112
5646
|
return {
|
|
6113
5647
|
config: {
|
|
@@ -6120,97 +5654,97 @@ async function ensureDokploySetup(config$1, dockerConfig, stage, services) {
|
|
|
6120
5654
|
serviceUrls: provisionResult$1?.serviceUrls
|
|
6121
5655
|
};
|
|
6122
5656
|
} catch {
|
|
6123
|
-
logger$
|
|
5657
|
+
logger$2.log("⚠ Project not found, will recover...");
|
|
6124
5658
|
}
|
|
6125
5659
|
}
|
|
6126
|
-
logger$
|
|
5660
|
+
logger$2.log("\n📁 Looking for project...");
|
|
6127
5661
|
const projectName = dockerConfig.projectName;
|
|
6128
5662
|
const projects = await api.listProjects();
|
|
6129
5663
|
let project = projects.find((p) => p.name.toLowerCase() === projectName.toLowerCase());
|
|
6130
5664
|
let environmentId;
|
|
6131
5665
|
if (project) {
|
|
6132
|
-
logger$
|
|
5666
|
+
logger$2.log(` Found existing project: ${project.name} (${project.projectId})`);
|
|
6133
5667
|
const projectDetails = await api.getProject(project.projectId);
|
|
6134
5668
|
const environments = projectDetails.environments ?? [];
|
|
6135
5669
|
const matchingEnv = environments.find((e) => e.name.toLowerCase() === stage.toLowerCase());
|
|
6136
5670
|
if (matchingEnv) {
|
|
6137
5671
|
environmentId = matchingEnv.environmentId;
|
|
6138
|
-
logger$
|
|
5672
|
+
logger$2.log(` Using environment: ${matchingEnv.name}`);
|
|
6139
5673
|
} else {
|
|
6140
|
-
logger$
|
|
5674
|
+
logger$2.log(` Creating "${stage}" environment...`);
|
|
6141
5675
|
const env = await api.createEnvironment(project.projectId, stage);
|
|
6142
5676
|
environmentId = env.environmentId;
|
|
6143
|
-
logger$
|
|
5677
|
+
logger$2.log(` ✓ Created environment: ${stage}`);
|
|
6144
5678
|
}
|
|
6145
5679
|
} else {
|
|
6146
|
-
logger$
|
|
5680
|
+
logger$2.log(` Creating project: ${projectName}`);
|
|
6147
5681
|
const result = await api.createProject(projectName);
|
|
6148
5682
|
project = result.project;
|
|
6149
5683
|
if (result.environment.name.toLowerCase() !== stage.toLowerCase()) {
|
|
6150
|
-
logger$
|
|
5684
|
+
logger$2.log(` Creating "${stage}" environment...`);
|
|
6151
5685
|
const env = await api.createEnvironment(project.projectId, stage);
|
|
6152
5686
|
environmentId = env.environmentId;
|
|
6153
5687
|
} else environmentId = result.environment.environmentId;
|
|
6154
|
-
logger$
|
|
6155
|
-
logger$
|
|
5688
|
+
logger$2.log(` ✓ Created project: ${project.projectId}`);
|
|
5689
|
+
logger$2.log(` ✓ Using environment: ${stage}`);
|
|
6156
5690
|
}
|
|
6157
|
-
logger$
|
|
5691
|
+
logger$2.log("\n📦 Looking for application...");
|
|
6158
5692
|
const appName = dockerConfig.appName;
|
|
6159
5693
|
let applicationId;
|
|
6160
5694
|
if (existingConfig && typeof existingConfig !== "boolean" && existingConfig.applicationId) {
|
|
6161
5695
|
applicationId = existingConfig.applicationId;
|
|
6162
|
-
logger$
|
|
5696
|
+
logger$2.log(` Using application from config: ${applicationId}`);
|
|
6163
5697
|
} else {
|
|
6164
|
-
logger$
|
|
5698
|
+
logger$2.log(` Creating application: ${appName}`);
|
|
6165
5699
|
const app = await api.createApplication(appName, project.projectId, environmentId);
|
|
6166
5700
|
applicationId = app.applicationId;
|
|
6167
|
-
logger$
|
|
5701
|
+
logger$2.log(` ✓ Created application: ${applicationId}`);
|
|
6168
5702
|
}
|
|
6169
|
-
logger$
|
|
5703
|
+
logger$2.log("\n🐳 Checking registry...");
|
|
6170
5704
|
let registryId = await getDokployRegistryId();
|
|
6171
5705
|
if (registryId) try {
|
|
6172
5706
|
const registry = await api.getRegistry(registryId);
|
|
6173
|
-
logger$
|
|
5707
|
+
logger$2.log(` Using registry: ${registry.registryName}`);
|
|
6174
5708
|
} catch {
|
|
6175
|
-
logger$
|
|
5709
|
+
logger$2.log(" ⚠ Stored registry not found, clearing...");
|
|
6176
5710
|
registryId = void 0;
|
|
6177
5711
|
await storeDokployRegistryId("");
|
|
6178
5712
|
}
|
|
6179
5713
|
if (!registryId) {
|
|
6180
5714
|
const registries = await api.listRegistries();
|
|
6181
5715
|
if (registries.length === 0) if (dockerConfig.registry) {
|
|
6182
|
-
logger$
|
|
6183
|
-
logger$
|
|
5716
|
+
logger$2.log(" No registries found in Dokploy. Let's create one.");
|
|
5717
|
+
logger$2.log(` Registry URL: ${dockerConfig.registry}`);
|
|
6184
5718
|
const username = await prompt("Registry username: ");
|
|
6185
5719
|
const password = await prompt("Registry password/token: ", true);
|
|
6186
5720
|
const registry = await api.createRegistry("Default Registry", dockerConfig.registry, username, password);
|
|
6187
5721
|
registryId = registry.registryId;
|
|
6188
5722
|
await storeDokployRegistryId(registryId);
|
|
6189
|
-
logger$
|
|
6190
|
-
} else logger$
|
|
5723
|
+
logger$2.log(` ✓ Registry created: ${registryId}`);
|
|
5724
|
+
} else logger$2.log(" ⚠ No registry configured. Set docker.registry in gkm.config.ts");
|
|
6191
5725
|
else {
|
|
6192
|
-
logger$
|
|
5726
|
+
logger$2.log(" Available registries:");
|
|
6193
5727
|
registries.forEach((reg, i) => {
|
|
6194
|
-
logger$
|
|
5728
|
+
logger$2.log(` ${i + 1}. ${reg.registryName} (${reg.registryUrl})`);
|
|
6195
5729
|
});
|
|
6196
|
-
if (dockerConfig.registry) logger$
|
|
5730
|
+
if (dockerConfig.registry) logger$2.log(` ${registries.length + 1}. Create new registry`);
|
|
6197
5731
|
const maxOption = dockerConfig.registry ? registries.length + 1 : registries.length;
|
|
6198
5732
|
const selection = await prompt(` Select registry (1-${maxOption}): `);
|
|
6199
5733
|
const index = parseInt(selection, 10) - 1;
|
|
6200
5734
|
if (index >= 0 && index < registries.length) {
|
|
6201
5735
|
registryId = registries[index].registryId;
|
|
6202
5736
|
await storeDokployRegistryId(registryId);
|
|
6203
|
-
logger$
|
|
5737
|
+
logger$2.log(` ✓ Selected: ${registries[index].registryName}`);
|
|
6204
5738
|
} else if (dockerConfig.registry && index === registries.length) {
|
|
6205
|
-
logger$
|
|
6206
|
-
logger$
|
|
5739
|
+
logger$2.log(`\n Creating new registry...`);
|
|
5740
|
+
logger$2.log(` Registry URL: ${dockerConfig.registry}`);
|
|
6207
5741
|
const username = await prompt(" Registry username: ");
|
|
6208
5742
|
const password = await prompt(" Registry password/token: ", true);
|
|
6209
5743
|
const registry = await api.createRegistry(dockerConfig.registry.replace(/^https?:\/\//, ""), dockerConfig.registry, username, password);
|
|
6210
5744
|
registryId = registry.registryId;
|
|
6211
5745
|
await storeDokployRegistryId(registryId);
|
|
6212
|
-
logger$
|
|
6213
|
-
} else logger$
|
|
5746
|
+
logger$2.log(` ✓ Registry created: ${registryId}`);
|
|
5747
|
+
} else logger$2.log(" ⚠ Invalid selection, skipping registry setup");
|
|
6214
5748
|
}
|
|
6215
5749
|
}
|
|
6216
5750
|
const dokployConfig = {
|
|
@@ -6220,10 +5754,10 @@ async function ensureDokploySetup(config$1, dockerConfig, stage, services) {
|
|
|
6220
5754
|
registryId: registryId ?? void 0
|
|
6221
5755
|
};
|
|
6222
5756
|
await updateConfig(dokployConfig);
|
|
6223
|
-
logger$
|
|
6224
|
-
logger$
|
|
6225
|
-
logger$
|
|
6226
|
-
if (registryId) logger$
|
|
5757
|
+
logger$2.log("\n✅ Dokploy setup complete!");
|
|
5758
|
+
logger$2.log(` Project: ${project.projectId}`);
|
|
5759
|
+
logger$2.log(` Application: ${applicationId}`);
|
|
5760
|
+
if (registryId) logger$2.log(` Registry: ${registryId}`);
|
|
6227
5761
|
const provisionResult = await provisionServices(api, project.projectId, environmentId, dockerConfig.appName, services, void 0);
|
|
6228
5762
|
return {
|
|
6229
5763
|
config: dokployConfig,
|
|
@@ -6254,45 +5788,45 @@ function generateTag(stage) {
|
|
|
6254
5788
|
async function workspaceDeployCommand(workspace, options) {
|
|
6255
5789
|
const { provider, stage, tag, apps: selectedApps } = options;
|
|
6256
5790
|
if (provider !== "dokploy") throw new Error(`Workspace deployment only supports Dokploy. Got: ${provider}`);
|
|
6257
|
-
logger$
|
|
6258
|
-
logger$
|
|
5791
|
+
logger$2.log(`\n🚀 Deploying workspace "${workspace.name}" to Dokploy...`);
|
|
5792
|
+
logger$2.log(` Stage: ${stage}`);
|
|
6259
5793
|
const imageTag = tag ?? generateTag(stage);
|
|
6260
|
-
logger$
|
|
5794
|
+
logger$2.log(` Tag: ${imageTag}`);
|
|
6261
5795
|
const buildOrder = getAppBuildOrder(workspace);
|
|
6262
5796
|
let appsToDeployNames = buildOrder;
|
|
6263
5797
|
if (selectedApps && selectedApps.length > 0) {
|
|
6264
5798
|
const invalidApps = selectedApps.filter((name$1) => !workspace.apps[name$1]);
|
|
6265
5799
|
if (invalidApps.length > 0) throw new Error(`Unknown apps: ${invalidApps.join(", ")}\nAvailable apps: ${Object.keys(workspace.apps).join(", ")}`);
|
|
6266
5800
|
appsToDeployNames = buildOrder.filter((name$1) => selectedApps.includes(name$1));
|
|
6267
|
-
logger$
|
|
6268
|
-
} else logger$
|
|
5801
|
+
logger$2.log(` Deploying apps: ${appsToDeployNames.join(", ")}`);
|
|
5802
|
+
} else logger$2.log(` Deploying all apps: ${appsToDeployNames.join(", ")}`);
|
|
6269
5803
|
const dokployApps = appsToDeployNames.filter((name$1) => {
|
|
6270
5804
|
const app = workspace.apps[name$1];
|
|
6271
5805
|
const target = app.resolvedDeployTarget;
|
|
6272
5806
|
if (!isDeployTargetSupported(target)) {
|
|
6273
|
-
logger$
|
|
5807
|
+
logger$2.log(` ⚠️ Skipping ${name$1}: ${getDeployTargetError(target, name$1)}`);
|
|
6274
5808
|
return false;
|
|
6275
5809
|
}
|
|
6276
5810
|
return true;
|
|
6277
5811
|
});
|
|
6278
5812
|
if (dokployApps.length === 0) throw new Error("No apps to deploy. All selected apps have unsupported deploy targets.");
|
|
6279
5813
|
appsToDeployNames = dokployApps;
|
|
6280
|
-
logger$
|
|
5814
|
+
logger$2.log("\n🔐 Loading secrets and analyzing environment requirements...");
|
|
6281
5815
|
const stageSecrets = await readStageSecrets(stage, workspace.root);
|
|
6282
5816
|
if (!stageSecrets) {
|
|
6283
|
-
logger$
|
|
6284
|
-
logger$
|
|
5817
|
+
logger$2.log(` ⚠️ No secrets found for stage "${stage}"`);
|
|
5818
|
+
logger$2.log(` Run "gkm secrets:init --stage ${stage}" to create secrets`);
|
|
6285
5819
|
}
|
|
6286
5820
|
const sniffedApps = await sniffAllApps(workspace.apps, workspace.root);
|
|
6287
5821
|
const encryptedSecrets = stageSecrets ? prepareSecretsForAllApps(stageSecrets, sniffedApps) : /* @__PURE__ */ new Map();
|
|
6288
5822
|
if (stageSecrets) {
|
|
6289
5823
|
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$
|
|
5824
|
+
if (report.appsWithSecrets.length > 0) logger$2.log(` ✓ Encrypted secrets for: ${report.appsWithSecrets.join(", ")}`);
|
|
5825
|
+
if (report.appsWithMissingSecrets.length > 0) for (const { appName, missing } of report.appsWithMissingSecrets) logger$2.log(` ⚠️ ${appName}: Missing secrets: ${missing.join(", ")}`);
|
|
6292
5826
|
}
|
|
6293
5827
|
let creds = await getDokployCredentials();
|
|
6294
5828
|
if (!creds) {
|
|
6295
|
-
logger$
|
|
5829
|
+
logger$2.log("\n📋 Dokploy credentials not found. Let's set them up.");
|
|
6296
5830
|
const endpoint = await prompt("Dokploy URL (e.g., https://dokploy.example.com): ");
|
|
6297
5831
|
const normalizedEndpoint = endpoint.replace(/\/$/, "");
|
|
6298
5832
|
try {
|
|
@@ -6300,9 +5834,9 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6300
5834
|
} catch {
|
|
6301
5835
|
throw new Error("Invalid URL format");
|
|
6302
5836
|
}
|
|
6303
|
-
logger$
|
|
5837
|
+
logger$2.log(`\nGenerate a token at: ${normalizedEndpoint}/settings/profile\n`);
|
|
6304
5838
|
const token = await prompt("API Token: ", true);
|
|
6305
|
-
logger$
|
|
5839
|
+
logger$2.log("\nValidating credentials...");
|
|
6306
5840
|
const isValid = await validateDokployToken(normalizedEndpoint, token);
|
|
6307
5841
|
if (!isValid) throw new Error("Invalid credentials. Please check your token.");
|
|
6308
5842
|
await storeDokployCredentials(token, normalizedEndpoint);
|
|
@@ -6310,43 +5844,43 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6310
5844
|
token,
|
|
6311
5845
|
endpoint: normalizedEndpoint
|
|
6312
5846
|
};
|
|
6313
|
-
logger$
|
|
5847
|
+
logger$2.log("✓ Credentials saved");
|
|
6314
5848
|
}
|
|
6315
5849
|
const api = new DokployApi({
|
|
6316
5850
|
baseUrl: creds.endpoint,
|
|
6317
5851
|
token: creds.token
|
|
6318
5852
|
});
|
|
6319
|
-
logger$
|
|
5853
|
+
logger$2.log("\n📁 Setting up Dokploy project...");
|
|
6320
5854
|
const projectName = workspace.name;
|
|
6321
5855
|
const projects = await api.listProjects();
|
|
6322
5856
|
let project = projects.find((p) => p.name.toLowerCase() === projectName.toLowerCase());
|
|
6323
5857
|
let environmentId;
|
|
6324
5858
|
if (project) {
|
|
6325
|
-
logger$
|
|
5859
|
+
logger$2.log(` Found existing project: ${project.name}`);
|
|
6326
5860
|
const projectDetails = await api.getProject(project.projectId);
|
|
6327
5861
|
const environments = projectDetails.environments ?? [];
|
|
6328
5862
|
const matchingEnv = environments.find((e) => e.name.toLowerCase() === stage.toLowerCase());
|
|
6329
5863
|
if (matchingEnv) {
|
|
6330
5864
|
environmentId = matchingEnv.environmentId;
|
|
6331
|
-
logger$
|
|
5865
|
+
logger$2.log(` Using environment: ${matchingEnv.name}`);
|
|
6332
5866
|
} else {
|
|
6333
|
-
logger$
|
|
5867
|
+
logger$2.log(` Creating "${stage}" environment...`);
|
|
6334
5868
|
const env = await api.createEnvironment(project.projectId, stage);
|
|
6335
5869
|
environmentId = env.environmentId;
|
|
6336
|
-
logger$
|
|
5870
|
+
logger$2.log(` ✓ Created environment: ${stage}`);
|
|
6337
5871
|
}
|
|
6338
5872
|
} else {
|
|
6339
|
-
logger$
|
|
5873
|
+
logger$2.log(` Creating project: ${projectName}`);
|
|
6340
5874
|
const result = await api.createProject(projectName);
|
|
6341
5875
|
project = result.project;
|
|
6342
5876
|
if (result.environment.name.toLowerCase() !== stage.toLowerCase()) {
|
|
6343
|
-
logger$
|
|
5877
|
+
logger$2.log(` Creating "${stage}" environment...`);
|
|
6344
5878
|
const env = await api.createEnvironment(project.projectId, stage);
|
|
6345
5879
|
environmentId = env.environmentId;
|
|
6346
5880
|
} else environmentId = result.environment.environmentId;
|
|
6347
|
-
logger$
|
|
5881
|
+
logger$2.log(` ✓ Created project: ${project.projectId}`);
|
|
6348
5882
|
}
|
|
6349
|
-
logger$
|
|
5883
|
+
logger$2.log("\n📋 Loading deploy state...");
|
|
6350
5884
|
const stateProvider = await createStateProvider({
|
|
6351
5885
|
config: workspace.state,
|
|
6352
5886
|
workspaceRoot: workspace.root,
|
|
@@ -6354,27 +5888,27 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6354
5888
|
});
|
|
6355
5889
|
let state = await stateProvider.read(stage);
|
|
6356
5890
|
if (state) {
|
|
6357
|
-
logger$
|
|
5891
|
+
logger$2.log(` Found existing state for stage "${stage}"`);
|
|
6358
5892
|
if (state.projectId !== project.projectId) {
|
|
6359
|
-
logger$
|
|
5893
|
+
logger$2.log(` ⚠ Project ID changed, updating state`);
|
|
6360
5894
|
state.projectId = project.projectId;
|
|
6361
5895
|
}
|
|
6362
5896
|
if (state.environmentId !== environmentId) {
|
|
6363
|
-
logger$
|
|
5897
|
+
logger$2.log(` ⚠ Environment ID changed, updating state`);
|
|
6364
5898
|
state.environmentId = environmentId;
|
|
6365
5899
|
}
|
|
6366
5900
|
} else {
|
|
6367
|
-
logger$
|
|
5901
|
+
logger$2.log(` Creating new state for stage "${stage}"`);
|
|
6368
5902
|
state = createEmptyState(stage, project.projectId, environmentId);
|
|
6369
5903
|
}
|
|
6370
|
-
logger$
|
|
5904
|
+
logger$2.log("\n🐳 Checking registry...");
|
|
6371
5905
|
let registryId = await getDokployRegistryId();
|
|
6372
5906
|
const registry = workspace.deploy.dokploy?.registry;
|
|
6373
5907
|
if (registryId) try {
|
|
6374
5908
|
const reg = await api.getRegistry(registryId);
|
|
6375
|
-
logger$
|
|
5909
|
+
logger$2.log(` Using registry: ${reg.registryName}`);
|
|
6376
5910
|
} catch {
|
|
6377
|
-
logger$
|
|
5911
|
+
logger$2.log(" ⚠ Stored registry not found, clearing...");
|
|
6378
5912
|
registryId = void 0;
|
|
6379
5913
|
await storeDokployRegistryId("");
|
|
6380
5914
|
}
|
|
@@ -6383,17 +5917,17 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6383
5917
|
if (registries.length > 0) {
|
|
6384
5918
|
registryId = registries[0].registryId;
|
|
6385
5919
|
await storeDokployRegistryId(registryId);
|
|
6386
|
-
logger$
|
|
5920
|
+
logger$2.log(` Using registry: ${registries[0].registryName}`);
|
|
6387
5921
|
} else if (registry) {
|
|
6388
|
-
logger$
|
|
6389
|
-
logger$
|
|
5922
|
+
logger$2.log(" No registries found in Dokploy. Let's create one.");
|
|
5923
|
+
logger$2.log(` Registry URL: ${registry}`);
|
|
6390
5924
|
const username = await prompt("Registry username: ");
|
|
6391
5925
|
const password = await prompt("Registry password/token: ", true);
|
|
6392
5926
|
const reg = await api.createRegistry("Default Registry", registry, username, password);
|
|
6393
5927
|
registryId = reg.registryId;
|
|
6394
5928
|
await storeDokployRegistryId(registryId);
|
|
6395
|
-
logger$
|
|
6396
|
-
} else logger$
|
|
5929
|
+
logger$2.log(` ✓ Registry created: ${registryId}`);
|
|
5930
|
+
} else logger$2.log(" ⚠ No registry configured. Set deploy.dokploy.registry in workspace config");
|
|
6397
5931
|
}
|
|
6398
5932
|
const services = workspace.services;
|
|
6399
5933
|
const dockerServices = {
|
|
@@ -6403,7 +5937,7 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6403
5937
|
let provisionedPostgres = null;
|
|
6404
5938
|
let provisionedRedis = null;
|
|
6405
5939
|
if (dockerServices.postgres || dockerServices.redis) {
|
|
6406
|
-
logger$
|
|
5940
|
+
logger$2.log("\n🔧 Provisioning infrastructure services...");
|
|
6407
5941
|
const existingServiceIds = {
|
|
6408
5942
|
postgresId: getPostgresId(state),
|
|
6409
5943
|
redisId: getRedisId(state)
|
|
@@ -6421,7 +5955,7 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6421
5955
|
}
|
|
6422
5956
|
}
|
|
6423
5957
|
const skippedMobileApps = appsToDeployNames.filter((name$1) => workspace.apps[name$1].type === "mobile");
|
|
6424
|
-
if (skippedMobileApps.length > 0) logger$
|
|
5958
|
+
if (skippedMobileApps.length > 0) logger$2.log(`\n📱 Skipping ${skippedMobileApps.length} mobile app(s) — deploy via framework toolchain: ${skippedMobileApps.join(", ")}`);
|
|
6425
5959
|
const backendApps = appsToDeployNames.filter((name$1) => workspace.apps[name$1].type === "backend");
|
|
6426
5960
|
const frontendApps = appsToDeployNames.filter((name$1) => workspace.apps[name$1].type === "web");
|
|
6427
5961
|
const perAppDbCredentials = /* @__PURE__ */ new Map();
|
|
@@ -6431,13 +5965,13 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6431
5965
|
return requirements?.requiredEnvVars.includes("DATABASE_URL");
|
|
6432
5966
|
});
|
|
6433
5967
|
if (appsNeedingDb.length > 0) {
|
|
6434
|
-
logger$
|
|
6435
|
-
logger$
|
|
5968
|
+
logger$2.log(`\n🔐 Setting up per-app database credentials...`);
|
|
5969
|
+
logger$2.log(` Apps needing DATABASE_URL: ${appsNeedingDb.join(", ")}`);
|
|
6436
5970
|
const existingCredentials = getAllAppCredentials(state);
|
|
6437
5971
|
const usersToCreate = [];
|
|
6438
5972
|
for (const appName of appsNeedingDb) {
|
|
6439
5973
|
let credentials = existingCredentials[appName];
|
|
6440
|
-
if (credentials) logger$
|
|
5974
|
+
if (credentials) logger$2.log(` ${appName}: Using existing credentials from state`);
|
|
6441
5975
|
else {
|
|
6442
5976
|
const password = randomBytes(16).toString("hex");
|
|
6443
5977
|
credentials = {
|
|
@@ -6445,7 +5979,7 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6445
5979
|
dbPassword: password
|
|
6446
5980
|
};
|
|
6447
5981
|
setAppCredentials(state, appName, credentials);
|
|
6448
|
-
logger$
|
|
5982
|
+
logger$2.log(` ${appName}: Generated new credentials`);
|
|
6449
5983
|
}
|
|
6450
5984
|
perAppDbCredentials.set(appName, credentials);
|
|
6451
5985
|
usersToCreate.push({
|
|
@@ -6459,8 +5993,8 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6459
5993
|
}
|
|
6460
5994
|
}
|
|
6461
5995
|
if (workspace.deploy?.backups && provisionedPostgres) {
|
|
6462
|
-
logger$
|
|
6463
|
-
const { provisionBackupDestination } = await import("./backup-provisioner-
|
|
5996
|
+
logger$2.log("\n💾 Provisioning backup destination...");
|
|
5997
|
+
const { provisionBackupDestination } = await import("./backup-provisioner-Xunq-p_8.mjs");
|
|
6464
5998
|
const backupState = await provisionBackupDestination({
|
|
6465
5999
|
api,
|
|
6466
6000
|
projectId: project.projectId,
|
|
@@ -6468,13 +6002,13 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6468
6002
|
stage,
|
|
6469
6003
|
config: workspace.deploy.backups,
|
|
6470
6004
|
existingState: getBackupState(state),
|
|
6471
|
-
logger: logger$
|
|
6005
|
+
logger: logger$2
|
|
6472
6006
|
});
|
|
6473
6007
|
setBackupState(state, backupState);
|
|
6474
6008
|
if (!backupState.postgresBackupId) {
|
|
6475
6009
|
const backupSchedule = workspace.deploy.backups.schedule ?? "0 2 * * *";
|
|
6476
6010
|
const backupRetention = workspace.deploy.backups.retention ?? 30;
|
|
6477
|
-
logger$
|
|
6011
|
+
logger$2.log(" Creating postgres backup schedule...");
|
|
6478
6012
|
const backup = await api.createPostgresBackup({
|
|
6479
6013
|
schedule: backupSchedule,
|
|
6480
6014
|
prefix: `${stage}/postgres`,
|
|
@@ -6485,8 +6019,8 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6485
6019
|
keepLatestCount: backupRetention
|
|
6486
6020
|
});
|
|
6487
6021
|
setPostgresBackupId(state, backup.backupId);
|
|
6488
|
-
logger$
|
|
6489
|
-
} else logger$
|
|
6022
|
+
logger$2.log(` ✓ Postgres backup schedule created (${backupSchedule})`);
|
|
6023
|
+
} else logger$2.log(" ✓ Using existing postgres backup schedule");
|
|
6490
6024
|
}
|
|
6491
6025
|
const publicUrls = {};
|
|
6492
6026
|
const results = [];
|
|
@@ -6501,25 +6035,25 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6501
6035
|
frontendUrls.push(`https://${hostname}`);
|
|
6502
6036
|
}
|
|
6503
6037
|
if (backendApps.length > 0) {
|
|
6504
|
-
logger$
|
|
6038
|
+
logger$2.log("\n📦 PHASE 1: Deploying backend applications...");
|
|
6505
6039
|
for (const appName of backendApps) {
|
|
6506
6040
|
const app = workspace.apps[appName];
|
|
6507
|
-
logger$
|
|
6041
|
+
logger$2.log(`\n ⚙️ Deploying ${appName}...`);
|
|
6508
6042
|
try {
|
|
6509
6043
|
const dokployAppName = appName;
|
|
6510
6044
|
let application = null;
|
|
6511
6045
|
const cachedAppId = getApplicationId(state, appName);
|
|
6512
6046
|
if (cachedAppId) {
|
|
6513
|
-
logger$
|
|
6047
|
+
logger$2.log(` Using cached ID: ${cachedAppId}`);
|
|
6514
6048
|
application = await api.getApplication(cachedAppId);
|
|
6515
|
-
if (application) logger$
|
|
6516
|
-
else logger$
|
|
6049
|
+
if (application) logger$2.log(` ✓ Application found: ${application.applicationId}`);
|
|
6050
|
+
else logger$2.log(` ⚠ Cached ID invalid, will create new`);
|
|
6517
6051
|
}
|
|
6518
6052
|
if (!application) {
|
|
6519
6053
|
const result = await api.findOrCreateApplication(dokployAppName, project.projectId, environmentId);
|
|
6520
6054
|
application = result.application;
|
|
6521
|
-
if (result.created) logger$
|
|
6522
|
-
else logger$
|
|
6055
|
+
if (result.created) logger$2.log(` Created application: ${application.applicationId}`);
|
|
6056
|
+
else logger$2.log(` Found existing application: ${application.applicationId}`);
|
|
6523
6057
|
}
|
|
6524
6058
|
setApplicationId(state, appName, application.applicationId);
|
|
6525
6059
|
const appSecrets = encryptedSecrets.get(appName);
|
|
@@ -6527,11 +6061,11 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6527
6061
|
if (appSecrets && appSecrets.secretCount > 0) {
|
|
6528
6062
|
buildArgs.push(`GKM_ENCRYPTED_CREDENTIALS=${appSecrets.payload.encrypted}`);
|
|
6529
6063
|
buildArgs.push(`GKM_CREDENTIALS_IV=${appSecrets.payload.iv}`);
|
|
6530
|
-
logger$
|
|
6064
|
+
logger$2.log(` Encrypted ${appSecrets.secretCount} secrets`);
|
|
6531
6065
|
}
|
|
6532
6066
|
const imageName = `${workspace.name}-${appName}`;
|
|
6533
6067
|
const imageRef = registry ? `${registry}/${imageName}:${imageTag}` : `${imageName}:${imageTag}`;
|
|
6534
|
-
logger$
|
|
6068
|
+
logger$2.log(` Building Docker image: ${imageRef}`);
|
|
6535
6069
|
await deployDocker({
|
|
6536
6070
|
stage,
|
|
6537
6071
|
tag: imageTag,
|
|
@@ -6581,10 +6115,10 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6581
6115
|
const { valid, missing, resolved } = validateEnvVars(requiredVars, envContext);
|
|
6582
6116
|
if (!valid) throw new Error(formatMissingVarsError(appName, missing, stage));
|
|
6583
6117
|
const envVars = Object.entries(resolved).map(([key, value]) => `${key}=${value}`);
|
|
6584
|
-
if (Object.keys(resolved).length > 0) logger$
|
|
6118
|
+
if (Object.keys(resolved).length > 0) logger$2.log(` Resolved ${Object.keys(resolved).length} env vars: ${Object.keys(resolved).join(", ")}`);
|
|
6585
6119
|
await api.saveDockerProvider(application.applicationId, imageRef, { registryId });
|
|
6586
6120
|
await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
|
|
6587
|
-
logger$
|
|
6121
|
+
logger$2.log(` Deploying to Dokploy...`);
|
|
6588
6122
|
await api.deployApplication(application.applicationId);
|
|
6589
6123
|
const existingDomains = await api.getDomainsByApplicationId(application.applicationId);
|
|
6590
6124
|
const existingDomain = existingDomains.find((d) => d.host === backendHost);
|
|
@@ -6592,7 +6126,7 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6592
6126
|
appHostnames.set(appName, backendHost);
|
|
6593
6127
|
appDomainIds.set(appName, existingDomain.domainId);
|
|
6594
6128
|
publicUrls[appName] = `https://${backendHost}`;
|
|
6595
|
-
logger$
|
|
6129
|
+
logger$2.log(` ✓ Domain: https://${backendHost} (existing)`);
|
|
6596
6130
|
} else try {
|
|
6597
6131
|
const domain = await api.createDomain({
|
|
6598
6132
|
host: backendHost,
|
|
@@ -6604,10 +6138,10 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6604
6138
|
appHostnames.set(appName, backendHost);
|
|
6605
6139
|
appDomainIds.set(appName, domain.domainId);
|
|
6606
6140
|
publicUrls[appName] = `https://${backendHost}`;
|
|
6607
|
-
logger$
|
|
6141
|
+
logger$2.log(` ✓ Domain: https://${backendHost} (created)`);
|
|
6608
6142
|
} catch (domainError) {
|
|
6609
6143
|
const message = domainError instanceof Error ? domainError.message : "Unknown error";
|
|
6610
|
-
logger$
|
|
6144
|
+
logger$2.log(` ⚠ Domain creation failed: ${message}`);
|
|
6611
6145
|
appHostnames.set(appName, backendHost);
|
|
6612
6146
|
publicUrls[appName] = `https://${backendHost}`;
|
|
6613
6147
|
}
|
|
@@ -6618,10 +6152,10 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6618
6152
|
applicationId: application.applicationId,
|
|
6619
6153
|
imageRef
|
|
6620
6154
|
});
|
|
6621
|
-
logger$
|
|
6155
|
+
logger$2.log(` ✓ ${appName} deployed successfully`);
|
|
6622
6156
|
} catch (error) {
|
|
6623
6157
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
6624
|
-
logger$
|
|
6158
|
+
logger$2.log(` ✗ Failed to deploy ${appName}: ${message}`);
|
|
6625
6159
|
results.push({
|
|
6626
6160
|
appName,
|
|
6627
6161
|
type: app.type,
|
|
@@ -6633,25 +6167,25 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6633
6167
|
}
|
|
6634
6168
|
}
|
|
6635
6169
|
if (frontendApps.length > 0) {
|
|
6636
|
-
logger$
|
|
6170
|
+
logger$2.log("\n🌐 PHASE 2: Deploying frontend applications...");
|
|
6637
6171
|
for (const appName of frontendApps) {
|
|
6638
6172
|
const app = workspace.apps[appName];
|
|
6639
|
-
logger$
|
|
6173
|
+
logger$2.log(`\n 🌐 Deploying ${appName}...`);
|
|
6640
6174
|
try {
|
|
6641
6175
|
const dokployAppName = appName;
|
|
6642
6176
|
let application = null;
|
|
6643
6177
|
const cachedAppId = getApplicationId(state, appName);
|
|
6644
6178
|
if (cachedAppId) {
|
|
6645
|
-
logger$
|
|
6179
|
+
logger$2.log(` Using cached ID: ${cachedAppId}`);
|
|
6646
6180
|
application = await api.getApplication(cachedAppId);
|
|
6647
|
-
if (application) logger$
|
|
6648
|
-
else logger$
|
|
6181
|
+
if (application) logger$2.log(` ✓ Application found: ${application.applicationId}`);
|
|
6182
|
+
else logger$2.log(` ⚠ Cached ID invalid, will create new`);
|
|
6649
6183
|
}
|
|
6650
6184
|
if (!application) {
|
|
6651
6185
|
const result = await api.findOrCreateApplication(dokployAppName, project.projectId, environmentId);
|
|
6652
6186
|
application = result.application;
|
|
6653
|
-
if (result.created) logger$
|
|
6654
|
-
else logger$
|
|
6187
|
+
if (result.created) logger$2.log(` Created application: ${application.applicationId}`);
|
|
6188
|
+
else logger$2.log(` Found existing application: ${application.applicationId}`);
|
|
6655
6189
|
}
|
|
6656
6190
|
setApplicationId(state, appName, application.applicationId);
|
|
6657
6191
|
const dependencyUrls = {};
|
|
@@ -6673,7 +6207,7 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6673
6207
|
const sniffedVars = sniffedApps.get(appName)?.requiredEnvVars ?? [];
|
|
6674
6208
|
const { valid, missing, resolved } = validateEnvVars(sniffedVars, envContext);
|
|
6675
6209
|
if (!valid) throw new Error(formatMissingVarsError(appName, missing, stage));
|
|
6676
|
-
if (Object.keys(resolved).length > 0) logger$
|
|
6210
|
+
if (Object.keys(resolved).length > 0) logger$2.log(` Resolved ${Object.keys(resolved).length} env vars: ${Object.keys(resolved).join(", ")}`);
|
|
6677
6211
|
const publicPrefix = getPublicEnvPrefix(app.framework);
|
|
6678
6212
|
const buildArgs = [];
|
|
6679
6213
|
const publicUrlArgNames = [];
|
|
@@ -6683,10 +6217,10 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6683
6217
|
publicUrlArgNames.push(key);
|
|
6684
6218
|
}
|
|
6685
6219
|
}
|
|
6686
|
-
if (buildArgs.length > 0) logger$
|
|
6220
|
+
if (buildArgs.length > 0) logger$2.log(` Build args: ${publicUrlArgNames.join(", ")}`);
|
|
6687
6221
|
const imageName = `${workspace.name}-${appName}`;
|
|
6688
6222
|
const imageRef = registry ? `${registry}/${imageName}:${imageTag}` : `${imageName}:${imageTag}`;
|
|
6689
|
-
logger$
|
|
6223
|
+
logger$2.log(` Building Docker image: ${imageRef}`);
|
|
6690
6224
|
await deployDocker({
|
|
6691
6225
|
stage,
|
|
6692
6226
|
tag: imageTag,
|
|
@@ -6707,7 +6241,7 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6707
6241
|
for (const [key, value] of Object.entries(resolved)) envVars.push(`${key}=${value}`);
|
|
6708
6242
|
await api.saveDockerProvider(application.applicationId, imageRef, { registryId });
|
|
6709
6243
|
await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
|
|
6710
|
-
logger$
|
|
6244
|
+
logger$2.log(` Deploying to Dokploy...`);
|
|
6711
6245
|
await api.deployApplication(application.applicationId);
|
|
6712
6246
|
const existingFrontendDomains = await api.getDomainsByApplicationId(application.applicationId);
|
|
6713
6247
|
const existingFrontendDomain = existingFrontendDomains.find((d) => d.host === frontendHost);
|
|
@@ -6715,7 +6249,7 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6715
6249
|
appHostnames.set(appName, frontendHost);
|
|
6716
6250
|
appDomainIds.set(appName, existingFrontendDomain.domainId);
|
|
6717
6251
|
publicUrls[appName] = `https://${frontendHost}`;
|
|
6718
|
-
logger$
|
|
6252
|
+
logger$2.log(` ✓ Domain: https://${frontendHost} (existing)`);
|
|
6719
6253
|
} else try {
|
|
6720
6254
|
const domain = await api.createDomain({
|
|
6721
6255
|
host: frontendHost,
|
|
@@ -6727,10 +6261,10 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6727
6261
|
appHostnames.set(appName, frontendHost);
|
|
6728
6262
|
appDomainIds.set(appName, domain.domainId);
|
|
6729
6263
|
publicUrls[appName] = `https://${frontendHost}`;
|
|
6730
|
-
logger$
|
|
6264
|
+
logger$2.log(` ✓ Domain: https://${frontendHost} (created)`);
|
|
6731
6265
|
} catch (domainError) {
|
|
6732
6266
|
const message = domainError instanceof Error ? domainError.message : "Unknown error";
|
|
6733
|
-
logger$
|
|
6267
|
+
logger$2.log(` ⚠ Domain creation failed: ${message}`);
|
|
6734
6268
|
appHostnames.set(appName, frontendHost);
|
|
6735
6269
|
publicUrls[appName] = `https://${frontendHost}`;
|
|
6736
6270
|
}
|
|
@@ -6741,10 +6275,10 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6741
6275
|
applicationId: application.applicationId,
|
|
6742
6276
|
imageRef
|
|
6743
6277
|
});
|
|
6744
|
-
logger$
|
|
6278
|
+
logger$2.log(` ✓ ${appName} deployed successfully`);
|
|
6745
6279
|
} catch (error) {
|
|
6746
6280
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
6747
|
-
logger$
|
|
6281
|
+
logger$2.log(` ✗ Failed to deploy ${appName}: ${message}`);
|
|
6748
6282
|
results.push({
|
|
6749
6283
|
appName,
|
|
6750
6284
|
type: app.type,
|
|
@@ -6754,9 +6288,9 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6754
6288
|
}
|
|
6755
6289
|
}
|
|
6756
6290
|
}
|
|
6757
|
-
logger$
|
|
6291
|
+
logger$2.log("\n📋 Saving deploy state...");
|
|
6758
6292
|
await stateProvider.write(stage, state);
|
|
6759
|
-
logger$
|
|
6293
|
+
logger$2.log(" ✓ State saved");
|
|
6760
6294
|
const dnsConfig = workspace.deploy.dns;
|
|
6761
6295
|
if (dnsConfig && appHostnames.size > 0) {
|
|
6762
6296
|
const dnsResult = await orchestrateDns(appHostnames, dnsConfig, creds.endpoint);
|
|
@@ -6765,27 +6299,27 @@ async function workspaceDeployCommand(workspace, options) {
|
|
|
6765
6299
|
await stateProvider.write(stage, state);
|
|
6766
6300
|
}
|
|
6767
6301
|
if (dnsResult?.success && appHostnames.size > 0) {
|
|
6768
|
-
logger$
|
|
6302
|
+
logger$2.log("\n🔒 Validating domains for SSL certificates...");
|
|
6769
6303
|
for (const [appName, hostname] of appHostnames) try {
|
|
6770
6304
|
const result = await api.validateDomain(hostname);
|
|
6771
|
-
if (result.isValid) logger$
|
|
6772
|
-
else logger$
|
|
6305
|
+
if (result.isValid) logger$2.log(` ✓ ${appName}: ${hostname} → ${result.resolvedIp}`);
|
|
6306
|
+
else logger$2.log(` ⚠ ${appName}: ${hostname} not valid`);
|
|
6773
6307
|
} catch (validationError) {
|
|
6774
6308
|
const message = validationError instanceof Error ? validationError.message : "Unknown error";
|
|
6775
|
-
logger$
|
|
6309
|
+
logger$2.log(` ⚠ ${appName}: validation failed - ${message}`);
|
|
6776
6310
|
}
|
|
6777
6311
|
}
|
|
6778
6312
|
}
|
|
6779
6313
|
const successCount = results.filter((r) => r.success).length;
|
|
6780
6314
|
const failedCount = results.filter((r) => !r.success).length;
|
|
6781
|
-
logger$
|
|
6782
|
-
logger$
|
|
6783
|
-
logger$
|
|
6784
|
-
logger$
|
|
6785
|
-
if (failedCount > 0) logger$
|
|
6315
|
+
logger$2.log(`\n${"─".repeat(50)}`);
|
|
6316
|
+
logger$2.log(`\n✅ Workspace deployment complete!`);
|
|
6317
|
+
logger$2.log(` Project: ${project.projectId}`);
|
|
6318
|
+
logger$2.log(` Successful: ${successCount}`);
|
|
6319
|
+
if (failedCount > 0) logger$2.log(` Failed: ${failedCount}`);
|
|
6786
6320
|
if (Object.keys(publicUrls).length > 0) {
|
|
6787
|
-
logger$
|
|
6788
|
-
for (const [name$1, url] of Object.entries(publicUrls)) logger$
|
|
6321
|
+
logger$2.log("\n 📡 Deployed URLs:");
|
|
6322
|
+
for (const [name$1, url] of Object.entries(publicUrls)) logger$2.log(` ${name$1}: ${url}`);
|
|
6789
6323
|
}
|
|
6790
6324
|
return {
|
|
6791
6325
|
apps: results,
|
|
@@ -6801,23 +6335,23 @@ async function deployCommand(options) {
|
|
|
6801
6335
|
const { provider, stage, tag, skipPush, skipBuild } = options;
|
|
6802
6336
|
const loadedConfig = await loadWorkspaceConfig();
|
|
6803
6337
|
if (loadedConfig.type === "workspace") {
|
|
6804
|
-
logger$
|
|
6338
|
+
logger$2.log("📦 Detected workspace configuration");
|
|
6805
6339
|
return workspaceDeployCommand(loadedConfig.workspace, options);
|
|
6806
6340
|
}
|
|
6807
|
-
logger$
|
|
6808
|
-
logger$
|
|
6809
|
-
const config
|
|
6341
|
+
logger$2.log(`\n🚀 Deploying to ${provider}...`);
|
|
6342
|
+
logger$2.log(` Stage: ${stage}`);
|
|
6343
|
+
const config = await loadConfig();
|
|
6810
6344
|
const imageTag = tag ?? generateTag(stage);
|
|
6811
|
-
logger$
|
|
6812
|
-
const dockerConfig = resolveDockerConfig(config
|
|
6345
|
+
logger$2.log(` Tag: ${imageTag}`);
|
|
6346
|
+
const dockerConfig = resolveDockerConfig(config);
|
|
6813
6347
|
const imageName = dockerConfig.imageName;
|
|
6814
6348
|
const registry = dockerConfig.registry;
|
|
6815
6349
|
const imageRef = registry ? `${registry}/${imageName}:${imageTag}` : `${imageName}:${imageTag}`;
|
|
6816
6350
|
let dokployConfig;
|
|
6817
6351
|
let finalRegistry = registry;
|
|
6818
6352
|
if (provider === "dokploy") {
|
|
6819
|
-
const composeServices = config
|
|
6820
|
-
logger$
|
|
6353
|
+
const composeServices = config.docker?.compose?.services;
|
|
6354
|
+
logger$2.log(`\n🔍 Docker compose config: ${JSON.stringify(config.docker?.compose)}`);
|
|
6821
6355
|
const dockerServices = composeServices ? Array.isArray(composeServices) ? {
|
|
6822
6356
|
postgres: composeServices.includes("postgres"),
|
|
6823
6357
|
redis: composeServices.includes("redis"),
|
|
@@ -6827,14 +6361,14 @@ async function deployCommand(options) {
|
|
|
6827
6361
|
redis: Boolean(composeServices.redis),
|
|
6828
6362
|
rabbitmq: Boolean(composeServices.rabbitmq)
|
|
6829
6363
|
} : void 0;
|
|
6830
|
-
const setupResult = await ensureDokploySetup(config
|
|
6364
|
+
const setupResult = await ensureDokploySetup(config, dockerConfig, stage, dockerServices);
|
|
6831
6365
|
dokployConfig = setupResult.config;
|
|
6832
6366
|
finalRegistry = dokployConfig.registry ?? dockerConfig.registry;
|
|
6833
6367
|
if (setupResult.serviceUrls) {
|
|
6834
6368
|
const { readStageSecrets: readStageSecrets$1, writeStageSecrets: writeStageSecrets$1, initStageSecrets } = await import("./storage-Cs4WBsc4.mjs");
|
|
6835
6369
|
let secrets = await readStageSecrets$1(stage);
|
|
6836
6370
|
if (!secrets) {
|
|
6837
|
-
logger$
|
|
6371
|
+
logger$2.log(` Creating secrets file for stage "${stage}"...`);
|
|
6838
6372
|
secrets = initStageSecrets(stage);
|
|
6839
6373
|
}
|
|
6840
6374
|
let updated = false;
|
|
@@ -6849,12 +6383,12 @@ async function deployCommand(options) {
|
|
|
6849
6383
|
const urlKey = key;
|
|
6850
6384
|
if (!secrets.urls[urlKey]) {
|
|
6851
6385
|
secrets.urls[urlKey] = value;
|
|
6852
|
-
logger$
|
|
6386
|
+
logger$2.log(` Saved ${key} to secrets.urls`);
|
|
6853
6387
|
updated = true;
|
|
6854
6388
|
}
|
|
6855
6389
|
} else if (!secrets.custom[key]) {
|
|
6856
6390
|
secrets.custom[key] = value;
|
|
6857
|
-
logger$
|
|
6391
|
+
logger$2.log(` Saved ${key} to secrets.custom`);
|
|
6858
6392
|
updated = true;
|
|
6859
6393
|
}
|
|
6860
6394
|
}
|
|
@@ -6863,14 +6397,14 @@ async function deployCommand(options) {
|
|
|
6863
6397
|
}
|
|
6864
6398
|
let masterKey;
|
|
6865
6399
|
if (!skipBuild) {
|
|
6866
|
-
logger$
|
|
6400
|
+
logger$2.log(`\n📦 Building for production...`);
|
|
6867
6401
|
const buildResult = await buildCommand({
|
|
6868
6402
|
provider: "server",
|
|
6869
6403
|
production: true,
|
|
6870
6404
|
stage
|
|
6871
6405
|
});
|
|
6872
6406
|
masterKey = buildResult.masterKey;
|
|
6873
|
-
} else logger$
|
|
6407
|
+
} else logger$2.log(`\n⏭️ Skipping build (--skip-build)`);
|
|
6874
6408
|
let result;
|
|
6875
6409
|
switch (provider) {
|
|
6876
6410
|
case "docker": {
|
|
@@ -6906,8 +6440,8 @@ async function deployCommand(options) {
|
|
|
6906
6440
|
break;
|
|
6907
6441
|
}
|
|
6908
6442
|
case "aws-lambda": {
|
|
6909
|
-
logger$
|
|
6910
|
-
logger$
|
|
6443
|
+
logger$2.log("\n⚠️ AWS Lambda deployment is not yet implemented.");
|
|
6444
|
+
logger$2.log(" Use SST or AWS CDK for Lambda deployments.");
|
|
6911
6445
|
result = {
|
|
6912
6446
|
imageRef,
|
|
6913
6447
|
masterKey
|
|
@@ -6916,7 +6450,7 @@ async function deployCommand(options) {
|
|
|
6916
6450
|
}
|
|
6917
6451
|
default: throw new Error(`Unknown deploy provider: ${provider}\nSupported providers: docker, dokploy, aws-lambda`);
|
|
6918
6452
|
}
|
|
6919
|
-
logger$
|
|
6453
|
+
logger$2.log("\n✅ Deployment complete!");
|
|
6920
6454
|
return result;
|
|
6921
6455
|
}
|
|
6922
6456
|
|
|
@@ -7113,22 +6647,22 @@ const GEEKMIDAS_VERSIONS = {
|
|
|
7113
6647
|
"@geekmidas/audit": "~2.0.1",
|
|
7114
6648
|
"@geekmidas/auth": "~2.0.1",
|
|
7115
6649
|
"@geekmidas/cache": "~1.1.1",
|
|
7116
|
-
"@geekmidas/client": "~
|
|
6650
|
+
"@geekmidas/client": "~6.0.0",
|
|
7117
6651
|
"@geekmidas/cloud": "~1.0.1",
|
|
7118
|
-
"@geekmidas/constructs": "~
|
|
6652
|
+
"@geekmidas/constructs": "~4.0.0",
|
|
7119
6653
|
"@geekmidas/db": "~1.0.2",
|
|
7120
6654
|
"@geekmidas/emailkit": "~1.0.1",
|
|
7121
6655
|
"@geekmidas/envkit": "~1.0.7",
|
|
7122
6656
|
"@geekmidas/errors": "~1.0.1",
|
|
7123
6657
|
"@geekmidas/events": "~1.1.4",
|
|
7124
6658
|
"@geekmidas/logger": "~1.0.2",
|
|
7125
|
-
"@geekmidas/rate-limit": "~
|
|
6659
|
+
"@geekmidas/rate-limit": "~3.0.0",
|
|
7126
6660
|
"@geekmidas/schema": "~1.0.2",
|
|
7127
|
-
"@geekmidas/services": "~1.
|
|
6661
|
+
"@geekmidas/services": "~1.1.1",
|
|
7128
6662
|
"@geekmidas/storage": "~2.0.3",
|
|
7129
6663
|
"@geekmidas/studio": "~1.0.1",
|
|
7130
6664
|
"@geekmidas/telescope": "~1.0.1",
|
|
7131
|
-
"@geekmidas/testkit": "~
|
|
6665
|
+
"@geekmidas/testkit": "~2.0.0",
|
|
7132
6666
|
"@geekmidas/cli": CLI_VERSION
|
|
7133
6667
|
};
|
|
7134
6668
|
|
|
@@ -8961,7 +8495,7 @@ function generateWorkspaceConfig(options) {
|
|
|
8961
8495
|
case "domain-based": return "./src/**/routes/*.ts";
|
|
8962
8496
|
}
|
|
8963
8497
|
};
|
|
8964
|
-
let config
|
|
8498
|
+
let config = `import { defineWorkspace } from '@geekmidas/cli/config';
|
|
8965
8499
|
|
|
8966
8500
|
export default defineWorkspace({
|
|
8967
8501
|
name: '${options.name}',
|
|
@@ -8974,12 +8508,12 @@ export default defineWorkspace({
|
|
|
8974
8508
|
envParser: './src/config/env#envParser',
|
|
8975
8509
|
logger: './src/config/logger#logger',
|
|
8976
8510
|
dependencies: ['auth'],`;
|
|
8977
|
-
if (telescope) config
|
|
8511
|
+
if (telescope) config += `
|
|
8978
8512
|
telescope: {
|
|
8979
8513
|
enabled: true,
|
|
8980
8514
|
path: '/__telescope',
|
|
8981
8515
|
},`;
|
|
8982
|
-
config
|
|
8516
|
+
config += `
|
|
8983
8517
|
openapi: {
|
|
8984
8518
|
enabled: true,
|
|
8985
8519
|
},
|
|
@@ -9003,33 +8537,33 @@ export default defineWorkspace({
|
|
|
9003
8537
|
},
|
|
9004
8538
|
},`;
|
|
9005
8539
|
if (services.db || services.cache || services.mail || services.storage || services.events) {
|
|
9006
|
-
config
|
|
8540
|
+
config += `
|
|
9007
8541
|
services: {`;
|
|
9008
|
-
if (services.db) config
|
|
8542
|
+
if (services.db) config += `
|
|
9009
8543
|
db: true,`;
|
|
9010
|
-
if (services.cache) config
|
|
8544
|
+
if (services.cache) config += `
|
|
9011
8545
|
cache: true,`;
|
|
9012
|
-
if (services.mail) config
|
|
8546
|
+
if (services.mail) config += `
|
|
9013
8547
|
mail: true,`;
|
|
9014
|
-
if (services.storage) config
|
|
8548
|
+
if (services.storage) config += `
|
|
9015
8549
|
storage: true,`;
|
|
9016
|
-
if (services.events) config
|
|
8550
|
+
if (services.events) config += `
|
|
9017
8551
|
events: '${services.events}',`;
|
|
9018
|
-
config
|
|
8552
|
+
config += `
|
|
9019
8553
|
},`;
|
|
9020
8554
|
}
|
|
9021
|
-
if (deployTarget === "dokploy") config
|
|
8555
|
+
if (deployTarget === "dokploy") config += `
|
|
9022
8556
|
deploy: {
|
|
9023
8557
|
default: 'dokploy',
|
|
9024
8558
|
},`;
|
|
9025
|
-
config
|
|
8559
|
+
config += `
|
|
9026
8560
|
secrets: {
|
|
9027
8561
|
enabled: true,
|
|
9028
8562
|
},`;
|
|
9029
|
-
config
|
|
8563
|
+
config += `
|
|
9030
8564
|
});
|
|
9031
8565
|
`;
|
|
9032
|
-
return config
|
|
8566
|
+
return config;
|
|
9033
8567
|
}
|
|
9034
8568
|
|
|
9035
8569
|
//#endregion
|
|
@@ -12868,14 +12402,14 @@ function printNextSteps(projectName, options, pkgManager) {
|
|
|
12868
12402
|
|
|
12869
12403
|
//#endregion
|
|
12870
12404
|
//#region src/secrets/index.ts
|
|
12871
|
-
const logger$
|
|
12405
|
+
const logger$1 = console;
|
|
12872
12406
|
/**
|
|
12873
12407
|
* Extract service names from compose config.
|
|
12874
12408
|
*/
|
|
12875
12409
|
function getServicesFromConfig(services) {
|
|
12876
12410
|
if (!services) return [];
|
|
12877
12411
|
if (Array.isArray(services)) return services;
|
|
12878
|
-
return Object.entries(services).filter(([, config
|
|
12412
|
+
return Object.entries(services).filter(([, config]) => config).map(([name$1]) => name$1);
|
|
12879
12413
|
}
|
|
12880
12414
|
/**
|
|
12881
12415
|
* Initialize secrets for a stage.
|
|
@@ -12884,12 +12418,12 @@ function getServicesFromConfig(services) {
|
|
|
12884
12418
|
async function secretsInitCommand(options) {
|
|
12885
12419
|
const { stage, force } = options;
|
|
12886
12420
|
if (!force && secretsExist(stage)) {
|
|
12887
|
-
logger$
|
|
12421
|
+
logger$1.error(`Secrets already exist for stage "${stage}". Use --force to overwrite.`);
|
|
12888
12422
|
process.exit(1);
|
|
12889
12423
|
}
|
|
12890
|
-
const config
|
|
12891
|
-
const services = getServicesFromConfig(config
|
|
12892
|
-
if (services.length === 0) logger$
|
|
12424
|
+
const config = await loadConfig();
|
|
12425
|
+
const services = getServicesFromConfig(config.docker?.compose?.services);
|
|
12426
|
+
if (services.length === 0) logger$1.warn("No services configured in docker.compose.services. Creating secrets with empty services.");
|
|
12893
12427
|
let projectName;
|
|
12894
12428
|
let workspaceSecrets;
|
|
12895
12429
|
try {
|
|
@@ -12898,23 +12432,23 @@ async function secretsInitCommand(options) {
|
|
|
12898
12432
|
const isMultiApp = Object.keys(loaded.workspace.apps).length > 1;
|
|
12899
12433
|
if (isMultiApp) {
|
|
12900
12434
|
workspaceSecrets = generateFullstackCustomSecrets(loaded.workspace);
|
|
12901
|
-
logger$
|
|
12435
|
+
logger$1.log(" Detected workspace mode — generating per-app secrets");
|
|
12902
12436
|
}
|
|
12903
12437
|
} catch {}
|
|
12904
12438
|
const secrets = createStageSecrets(stage, services, { projectName });
|
|
12905
12439
|
if (workspaceSecrets) secrets.custom = workspaceSecrets;
|
|
12906
12440
|
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$
|
|
12441
|
+
logger$1.log(`\n✓ Secrets initialized for stage "${stage}"`);
|
|
12442
|
+
logger$1.log(` Location: .gkm/secrets/${stage}.json`);
|
|
12443
|
+
logger$1.log("\n Generated credentials for:");
|
|
12444
|
+
for (const service of services) logger$1.log(` - ${service}`);
|
|
12445
|
+
if (secrets.urls.DATABASE_URL) logger$1.log(`\n DATABASE_URL: ${maskUrl(secrets.urls.DATABASE_URL)}`);
|
|
12446
|
+
if (secrets.urls.REDIS_URL) logger$1.log(` REDIS_URL: ${maskUrl(secrets.urls.REDIS_URL)}`);
|
|
12447
|
+
if (secrets.urls.RABBITMQ_URL) logger$1.log(` RABBITMQ_URL: ${maskUrl(secrets.urls.RABBITMQ_URL)}`);
|
|
12448
|
+
if (secrets.urls.STORAGE_ENDPOINT) logger$1.log(` STORAGE_ENDPOINT: ${secrets.urls.STORAGE_ENDPOINT}`);
|
|
12449
|
+
if (Object.keys(secrets.custom).length > 0) logger$1.log(`\n Custom secrets: ${Object.keys(secrets.custom).length}`);
|
|
12450
|
+
logger$1.log(`\n Use "gkm secrets:show --stage ${stage}" to view secrets`);
|
|
12451
|
+
logger$1.log(" Use \"gkm secrets:set <KEY> <VALUE> --stage " + stage + "\" to add custom secrets");
|
|
12918
12452
|
}
|
|
12919
12453
|
/**
|
|
12920
12454
|
* Read all data from stdin.
|
|
@@ -12933,21 +12467,21 @@ async function secretsSetCommand(key, value, options) {
|
|
|
12933
12467
|
let secretValue = value;
|
|
12934
12468
|
if (!secretValue) {
|
|
12935
12469
|
if (process.stdin.isTTY) {
|
|
12936
|
-
logger$
|
|
12937
|
-
logger$
|
|
12470
|
+
logger$1.error("No value provided. Use: gkm secrets:set KEY VALUE --stage <stage>");
|
|
12471
|
+
logger$1.error("Or pipe from stdin: echo \"value\" | gkm secrets:set KEY --stage <stage>");
|
|
12938
12472
|
process.exit(1);
|
|
12939
12473
|
}
|
|
12940
12474
|
secretValue = await readStdin();
|
|
12941
12475
|
if (!secretValue) {
|
|
12942
|
-
logger$
|
|
12476
|
+
logger$1.error("No value received from stdin");
|
|
12943
12477
|
process.exit(1);
|
|
12944
12478
|
}
|
|
12945
12479
|
}
|
|
12946
12480
|
try {
|
|
12947
12481
|
await setCustomSecret(stage, key, secretValue);
|
|
12948
|
-
logger$
|
|
12482
|
+
logger$1.log(`\n✓ Secret "${key}" set for stage "${stage}"`);
|
|
12949
12483
|
} catch (error) {
|
|
12950
|
-
logger$
|
|
12484
|
+
logger$1.error(error instanceof Error ? error.message : "Failed to set secret");
|
|
12951
12485
|
process.exit(1);
|
|
12952
12486
|
}
|
|
12953
12487
|
}
|
|
@@ -12958,34 +12492,34 @@ async function secretsShowCommand(options) {
|
|
|
12958
12492
|
const { stage, reveal } = options;
|
|
12959
12493
|
const secrets = await readStageSecrets(stage);
|
|
12960
12494
|
if (!secrets) {
|
|
12961
|
-
logger$
|
|
12495
|
+
logger$1.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
12962
12496
|
process.exit(1);
|
|
12963
12497
|
}
|
|
12964
|
-
logger$
|
|
12965
|
-
logger$
|
|
12966
|
-
logger$
|
|
12967
|
-
logger$
|
|
12498
|
+
logger$1.log(`\nSecrets for stage "${stage}":`);
|
|
12499
|
+
logger$1.log(` Created: ${secrets.createdAt}`);
|
|
12500
|
+
logger$1.log(` Updated: ${secrets.updatedAt}`);
|
|
12501
|
+
logger$1.log("\nService Credentials:");
|
|
12968
12502
|
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$
|
|
12503
|
+
logger$1.log(`\n ${service}:`);
|
|
12504
|
+
logger$1.log(` host: ${creds.host}`);
|
|
12505
|
+
logger$1.log(` port: ${creds.port}`);
|
|
12506
|
+
logger$1.log(` username: ${creds.username}`);
|
|
12507
|
+
logger$1.log(` password: ${reveal ? creds.password : maskPassword(creds.password)}`);
|
|
12508
|
+
if (creds.database) logger$1.log(` database: ${creds.database}`);
|
|
12509
|
+
if (creds.vhost) logger$1.log(` vhost: ${creds.vhost}`);
|
|
12510
|
+
if (creds.bucket) logger$1.log(` bucket: ${creds.bucket}`);
|
|
12511
|
+
}
|
|
12512
|
+
logger$1.log("\nConnection URLs:");
|
|
12513
|
+
if (secrets.urls.DATABASE_URL) logger$1.log(` DATABASE_URL: ${reveal ? secrets.urls.DATABASE_URL : maskUrl(secrets.urls.DATABASE_URL)}`);
|
|
12514
|
+
if (secrets.urls.REDIS_URL) logger$1.log(` REDIS_URL: ${reveal ? secrets.urls.REDIS_URL : maskUrl(secrets.urls.REDIS_URL)}`);
|
|
12515
|
+
if (secrets.urls.RABBITMQ_URL) logger$1.log(` RABBITMQ_URL: ${reveal ? secrets.urls.RABBITMQ_URL : maskUrl(secrets.urls.RABBITMQ_URL)}`);
|
|
12516
|
+
if (secrets.urls.STORAGE_ENDPOINT) logger$1.log(` STORAGE_ENDPOINT: ${secrets.urls.STORAGE_ENDPOINT}`);
|
|
12983
12517
|
const customKeys = Object.keys(secrets.custom);
|
|
12984
12518
|
if (customKeys.length > 0) {
|
|
12985
|
-
logger$
|
|
12986
|
-
for (const [key, value] of Object.entries(secrets.custom)) logger$
|
|
12519
|
+
logger$1.log("\nCustom Secrets:");
|
|
12520
|
+
for (const [key, value] of Object.entries(secrets.custom)) logger$1.log(` ${key}: ${reveal ? value : maskPassword(value)}`);
|
|
12987
12521
|
}
|
|
12988
|
-
if (!reveal) logger$
|
|
12522
|
+
if (!reveal) logger$1.log("\nUse --reveal to show actual values");
|
|
12989
12523
|
}
|
|
12990
12524
|
/**
|
|
12991
12525
|
* Rotate passwords for services.
|
|
@@ -12994,25 +12528,25 @@ async function secretsRotateCommand(options) {
|
|
|
12994
12528
|
const { stage, service } = options;
|
|
12995
12529
|
const secrets = await readStageSecrets(stage);
|
|
12996
12530
|
if (!secrets) {
|
|
12997
|
-
logger$
|
|
12531
|
+
logger$1.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
12998
12532
|
process.exit(1);
|
|
12999
12533
|
}
|
|
13000
12534
|
if (service) {
|
|
13001
12535
|
if (!secrets.services[service]) {
|
|
13002
|
-
logger$
|
|
12536
|
+
logger$1.error(`Service "${service}" not configured in stage "${stage}"`);
|
|
13003
12537
|
process.exit(1);
|
|
13004
12538
|
}
|
|
13005
12539
|
const updated = rotateServicePassword(secrets, service);
|
|
13006
12540
|
await writeStageSecrets(updated);
|
|
13007
|
-
logger$
|
|
12541
|
+
logger$1.log(`\n✓ Password rotated for ${service} in stage "${stage}"`);
|
|
13008
12542
|
} else {
|
|
13009
12543
|
let updated = secrets;
|
|
13010
12544
|
const services = Object.keys(secrets.services);
|
|
13011
12545
|
for (const svc of services) updated = rotateServicePassword(updated, svc);
|
|
13012
12546
|
await writeStageSecrets(updated);
|
|
13013
|
-
logger$
|
|
12547
|
+
logger$1.log(`\n✓ Passwords rotated for all services in stage "${stage}": ${services.join(", ")}`);
|
|
13014
12548
|
}
|
|
13015
|
-
logger$
|
|
12549
|
+
logger$1.log(`\nUse "gkm secrets:show --stage ${stage}" to view new values`);
|
|
13016
12550
|
}
|
|
13017
12551
|
/**
|
|
13018
12552
|
* Import secrets from a JSON file.
|
|
@@ -13020,7 +12554,7 @@ async function secretsRotateCommand(options) {
|
|
|
13020
12554
|
async function secretsImportCommand(file, options) {
|
|
13021
12555
|
const { stage, merge = true } = options;
|
|
13022
12556
|
if (!existsSync(file)) {
|
|
13023
|
-
logger$
|
|
12557
|
+
logger$1.error(`File not found: ${file}`);
|
|
13024
12558
|
process.exit(1);
|
|
13025
12559
|
}
|
|
13026
12560
|
let importedSecrets;
|
|
@@ -13030,12 +12564,12 @@ async function secretsImportCommand(file, options) {
|
|
|
13030
12564
|
if (typeof importedSecrets !== "object" || importedSecrets === null) throw new Error("JSON must be an object");
|
|
13031
12565
|
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
12566
|
} catch (error) {
|
|
13033
|
-
logger$
|
|
12567
|
+
logger$1.error(`Failed to parse JSON file: ${error instanceof Error ? error.message : "Invalid JSON"}`);
|
|
13034
12568
|
process.exit(1);
|
|
13035
12569
|
}
|
|
13036
12570
|
const secrets = await readStageSecrets(stage);
|
|
13037
12571
|
if (!secrets) {
|
|
13038
|
-
logger$
|
|
12572
|
+
logger$1.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
13039
12573
|
process.exit(1);
|
|
13040
12574
|
}
|
|
13041
12575
|
const updatedCustom = merge ? {
|
|
@@ -13050,10 +12584,10 @@ async function secretsImportCommand(file, options) {
|
|
|
13050
12584
|
await writeStageSecrets(updated);
|
|
13051
12585
|
const importedCount = Object.keys(importedSecrets).length;
|
|
13052
12586
|
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$
|
|
12587
|
+
logger$1.log(`\n✓ Imported ${importedCount} secrets for stage "${stage}"`);
|
|
12588
|
+
if (merge && totalCount > importedCount) logger$1.log(` Total custom secrets: ${totalCount}`);
|
|
12589
|
+
logger$1.log("\n Imported keys:");
|
|
12590
|
+
for (const key of Object.keys(importedSecrets)) logger$1.log(` - ${key}`);
|
|
13057
12591
|
}
|
|
13058
12592
|
/**
|
|
13059
12593
|
* Mask password in a URL for display.
|
|
@@ -13068,264 +12602,6 @@ function maskUrl(url) {
|
|
|
13068
12602
|
}
|
|
13069
12603
|
}
|
|
13070
12604
|
|
|
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
12605
|
//#endregion
|
|
13330
12606
|
//#region src/test/index.ts
|
|
13331
12607
|
/**
|
|
@@ -13336,6 +12612,12 @@ async function testCommand(options = {}) {
|
|
|
13336
12612
|
const stage = options.stage ?? "development";
|
|
13337
12613
|
const cwd = process.cwd();
|
|
13338
12614
|
console.log(`\n🧪 Running tests with ${stage} environment...\n`);
|
|
12615
|
+
const autoSetup = options.autoSetup || Boolean(process.env.GKM_AUTO_SETUP);
|
|
12616
|
+
if (autoSetup) {
|
|
12617
|
+
const { ensureStageSecrets } = await import("./setup-Dh-B7YTX.mjs");
|
|
12618
|
+
const generated = await ensureStageSecrets(stage, cwd);
|
|
12619
|
+
if (generated) console.log(` 🔐 Generated fresh ${stage} secrets (auto-setup)`);
|
|
12620
|
+
}
|
|
13339
12621
|
const defaultEnv = loadEnvFiles(".env");
|
|
13340
12622
|
if (defaultEnv.loaded.length > 0) console.log(` 📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
13341
12623
|
const result = await prepareEntryCredentials({
|
|
@@ -13648,7 +12930,7 @@ program.command("exec").description("Run a command with secrets injected into Cr
|
|
|
13648
12930
|
process.exit(1);
|
|
13649
12931
|
}
|
|
13650
12932
|
});
|
|
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) => {
|
|
12933
|
+
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
12934
|
try {
|
|
13653
12935
|
const globalOptions = program.opts();
|
|
13654
12936
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
@@ -13790,8 +13072,8 @@ program.command("secrets:push").description("Push secrets to remote provider (SS
|
|
|
13790
13072
|
const globalOptions = program.opts();
|
|
13791
13073
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
13792
13074
|
const { loadWorkspaceConfig: loadWorkspaceConfig$1 } = await import("./config.mjs");
|
|
13793
|
-
const { pushSecrets
|
|
13794
|
-
const { reconcileMissingSecrets } = await import("./reconcile-
|
|
13075
|
+
const { pushSecrets } = await import("./sync-B5UHXwRM.mjs");
|
|
13076
|
+
const { reconcileMissingSecrets } = await import("./reconcile-B2FoJE0a.mjs");
|
|
13795
13077
|
const { readStageSecrets: readStageSecrets$1, writeStageSecrets: writeStageSecrets$1 } = await import("./storage-Cs4WBsc4.mjs");
|
|
13796
13078
|
const { workspace } = await loadWorkspaceConfig$1();
|
|
13797
13079
|
const secrets = await readStageSecrets$1(options.stage, workspace.root);
|
|
@@ -13803,7 +13085,7 @@ program.command("secrets:push").description("Push secrets to remote provider (SS
|
|
|
13803
13085
|
for (const key of result.addedKeys) console.log(` + ${key}`);
|
|
13804
13086
|
}
|
|
13805
13087
|
}
|
|
13806
|
-
await pushSecrets
|
|
13088
|
+
await pushSecrets(options.stage, workspace);
|
|
13807
13089
|
console.log(`\n✓ Secrets pushed for stage "${options.stage}"`);
|
|
13808
13090
|
} catch (error) {
|
|
13809
13091
|
console.error(formatError(error));
|
|
@@ -13815,11 +13097,11 @@ program.command("secrets:pull").description("Pull secrets from remote provider (
|
|
|
13815
13097
|
const globalOptions = program.opts();
|
|
13816
13098
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
13817
13099
|
const { loadWorkspaceConfig: loadWorkspaceConfig$1 } = await import("./config.mjs");
|
|
13818
|
-
const { pullSecrets
|
|
13100
|
+
const { pullSecrets } = await import("./sync-B5UHXwRM.mjs");
|
|
13819
13101
|
const { writeStageSecrets: writeStageSecrets$1 } = await import("./storage-Cs4WBsc4.mjs");
|
|
13820
|
-
const { reconcileMissingSecrets } = await import("./reconcile-
|
|
13102
|
+
const { reconcileMissingSecrets } = await import("./reconcile-B2FoJE0a.mjs");
|
|
13821
13103
|
const { workspace } = await loadWorkspaceConfig$1();
|
|
13822
|
-
let secrets = await pullSecrets
|
|
13104
|
+
let secrets = await pullSecrets(options.stage, workspace);
|
|
13823
13105
|
if (!secrets) {
|
|
13824
13106
|
console.error(`No remote secrets found for stage "${options.stage}".`);
|
|
13825
13107
|
process.exit(1);
|
|
@@ -13842,7 +13124,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
|
|
|
13842
13124
|
const globalOptions = program.opts();
|
|
13843
13125
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
13844
13126
|
const { loadWorkspaceConfig: loadWorkspaceConfig$1 } = await import("./config.mjs");
|
|
13845
|
-
const { reconcileMissingSecrets } = await import("./reconcile-
|
|
13127
|
+
const { reconcileMissingSecrets } = await import("./reconcile-B2FoJE0a.mjs");
|
|
13846
13128
|
const { readStageSecrets: readStageSecrets$1, writeStageSecrets: writeStageSecrets$1 } = await import("./storage-Cs4WBsc4.mjs");
|
|
13847
13129
|
const { workspace } = await loadWorkspaceConfig$1();
|
|
13848
13130
|
const secrets = await readStageSecrets$1(options.stage, workspace.root);
|