@geekmidas/cli 10.0.0-alpha.27 → 10.0.0-alpha.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{HostingerProvider-BY26bWwB.mjs → HostingerProvider-Btxpto71.mjs} +2 -2
- package/dist/{HostingerProvider-BY26bWwB.mjs.map → HostingerProvider-Btxpto71.mjs.map} +1 -1
- package/dist/{bundler-mwH7WOb1.mjs → bundler-D5RcGmVM.mjs} +2 -2
- package/dist/{bundler-mwH7WOb1.mjs.map → bundler-D5RcGmVM.mjs.map} +1 -1
- package/dist/{config-7R_-WyHq.cjs → config-BTFLHfRb.cjs} +2 -2
- package/dist/{config-7R_-WyHq.cjs.map → config-BTFLHfRb.cjs.map} +1 -1
- package/dist/{config-DXrrYvob.mjs → config-Dqxa-_Ge.mjs} +2 -2
- package/dist/{config-DXrrYvob.mjs.map → config-Dqxa-_Ge.mjs.map} +1 -1
- package/dist/config.cjs +1 -1
- package/dist/config.mjs +1 -1
- package/dist/deploy/sniffer-loader.cjs +1 -1
- package/dist/{discover-B_HtYJCr.cjs → discover-BTh87DOO.cjs} +24 -2
- package/dist/{discover-B_HtYJCr.cjs.map → discover-BTh87DOO.cjs.map} +1 -1
- package/dist/{discover-0fhCbnk0.mjs → discover-CXGlQHm1.mjs} +24 -2
- package/dist/{discover-0fhCbnk0.mjs.map → discover-CXGlQHm1.mjs.map} +1 -1
- package/dist/{github-CQflOKKV.cjs → github-BUCXEl2v.cjs} +5 -5
- package/dist/{github-CQflOKKV.cjs.map → github-BUCXEl2v.cjs.map} +1 -1
- package/dist/{github-Ba5kXEJo.mjs → github-zkzu-ka2.mjs} +3 -3
- package/dist/{github-Ba5kXEJo.mjs.map → github-zkzu-ka2.mjs.map} +1 -1
- package/dist/index.cjs +740 -655
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +735 -652
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-3NLVvtz-.mjs → openapi-D-Lk5Hr5.mjs} +3 -3
- package/dist/{openapi-3NLVvtz-.mjs.map → openapi-D-Lk5Hr5.mjs.map} +1 -1
- package/dist/{openapi-rhJdy2GC.cjs → openapi-K4H5tHzA.cjs} +3 -3
- package/dist/{openapi-rhJdy2GC.cjs.map → openapi-K4H5tHzA.cjs.map} +1 -1
- package/dist/openapi.cjs +1 -1
- package/dist/openapi.mjs +1 -1
- package/dist/reconcile/public.cjs +1 -1
- package/dist/reconcile/public.d.cts +12 -0
- package/dist/reconcile/public.d.cts.map +1 -1
- package/dist/reconcile/public.d.mts +12 -0
- package/dist/reconcile/public.d.mts.map +1 -1
- package/dist/reconcile/public.mjs +1 -1
- package/dist/{workspace-DgeVgxmm.cjs → workspace-Cv48QwPK.cjs} +128 -9
- package/dist/workspace-Cv48QwPK.cjs.map +1 -0
- package/dist/{workspace-CUmR_k4J.mjs → workspace-cBobQVoA.mjs} +124 -11
- package/dist/workspace-cBobQVoA.mjs.map +1 -0
- package/package.json +14 -14
- package/dist/transfer-BV00TxZv.mjs +0 -122
- package/dist/transfer-BV00TxZv.mjs.map +0 -1
- package/dist/transfer-BYIn7z8m.cjs +0 -151
- package/dist/transfer-BYIn7z8m.cjs.map +0 -1
- package/dist/workspace-CUmR_k4J.mjs.map +0 -1
- package/dist/workspace-DgeVgxmm.cjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env -S npx tsx
|
|
2
2
|
import { n as __require, t as __exportAll } from "./rolldown-runtime-BhDjJH2R.mjs";
|
|
3
|
-
import { i as DEFAULT_CACHE, r as ConstructGenerator, s as isPartitionedRoutes, t as discover } from "./discover-
|
|
4
|
-
import { a as loadConfig, c as parseModuleConfig, i as loadAppConfig, r as getAppNameFromCwd, s as loadWorkspaceConfig, u as derivedApps } from "./config-
|
|
3
|
+
import { i as DEFAULT_CACHE, r as ConstructGenerator, s as isPartitionedRoutes, t as discover } from "./discover-CXGlQHm1.mjs";
|
|
4
|
+
import { a as loadConfig, c as parseModuleConfig, i as loadAppConfig, l as appKey, r as getAppNameFromCwd, s as loadWorkspaceConfig, u as derivedApps } from "./config-Dqxa-_Ge.mjs";
|
|
5
5
|
import { D as assertDeployedStage, E as TEST_STAGE, O as deployedProblems, T as InvalidStages, a as getAppBuildOrder, b as getDeployTargetError, k as stageProblems, m as stripPublicPrefix, o as getAppGkmConfig, p as getPublicEnvPrefix, t as allConstructGlobs, x as isDeployTargetSupported } from "./workspace-T4IVGSoB.mjs";
|
|
6
6
|
import { c as getKeyPath, i as setCustomSecret, n as readStageSecrets, o as toEmbeddableSecrets, r as secretsExist, s as writeStageSecrets, t as maskPassword } from "./storage-TP0x118M.mjs";
|
|
7
|
-
import { _ as
|
|
8
|
-
import { a as openapiCommand, c as EndpointGenerator, l as runtimeFor, n as generateOpenApi, o as resolveOpenApiConfig, s as OpenApiTsGenerator
|
|
7
|
+
import { _ as findAvailablePort, b as loadSecretsForApp, c as applyPostgres, d as cacheBackendFor, f as eventsBackendFor, g as createEntryWrapper, h as createCredentialsPreload, i as reconcileWorkspace, l as resourceName, m as storageBackendFor, n as constructGlobs, o as COMPOSE_PATH, p as providerOf, r as derivedContainers, s as LOCAL_CA_PATH, t as backendsOf, u as rootSite, v as isPortAvailable, x as prepareEntryCredentials, y as loadEnvFiles } from "./workspace-cBobQVoA.mjs";
|
|
8
|
+
import { a as openapiCommand, c as EndpointGenerator, l as runtimeFor, n as generateOpenApi, o as resolveOpenApiConfig, s as OpenApiTsGenerator } from "./openapi-D-Lk5Hr5.mjs";
|
|
9
9
|
import { i as withRoutes, r as withCompute } from "./emit-Bzq7JsMP.mjs";
|
|
10
|
-
import { a as createStageSecrets, c as generateServiceCredentials, i as writeDockerEnvFromSecrets, l as rotateServicePassword, o as generateConnectionUrls, r as generateFullstackCustomSecrets, s as generateSecurePassword } from "./reconcile-DhbCvh4K.mjs";
|
|
11
|
-
import { a as isRemoteStore, n as pullStageSecrets, r as pushStageSecrets, t as NoStoredSecrets } from "./transfer-BV00TxZv.mjs";
|
|
10
|
+
import { a as createStageSecrets, c as generateServiceCredentials, i as writeDockerEnvFromSecrets, l as rotateServicePassword, o as generateConnectionUrls, r as generateFullstackCustomSecrets, s as generateSecurePassword, t as reconcileMissingSecrets } from "./reconcile-DhbCvh4K.mjs";
|
|
12
11
|
import { createRequire } from "node:module";
|
|
13
12
|
import { Command } from "commander";
|
|
14
13
|
import { stdin, stdout } from "node:process";
|
|
@@ -28,17 +27,17 @@ import { Client } from "pg";
|
|
|
28
27
|
import { parse as parse$1, parseDocument } from "yaml";
|
|
29
28
|
import { promisify } from "node:util";
|
|
30
29
|
import { cacheTableStatements } from "@geekmidas/cache/postgres";
|
|
30
|
+
import prompts from "prompts";
|
|
31
31
|
import { Cron } from "@geekmidas/constructs/crons";
|
|
32
32
|
import { Function } from "@geekmidas/constructs/functions";
|
|
33
33
|
import { Queue } from "@geekmidas/constructs/queue";
|
|
34
34
|
import { Subscriber } from "@geekmidas/constructs/subscribers";
|
|
35
35
|
import { Topic } from "@geekmidas/constructs/topic";
|
|
36
36
|
import { lookup } from "node:dns/promises";
|
|
37
|
-
import prompts from "prompts";
|
|
38
37
|
import { EnvironmentParser } from "@geekmidas/envkit";
|
|
39
38
|
import { serviceContext } from "@geekmidas/services";
|
|
40
39
|
//#region package.json
|
|
41
|
-
var version = "10.0.0-alpha.
|
|
40
|
+
var version = "10.0.0-alpha.28";
|
|
42
41
|
//#endregion
|
|
43
42
|
//#region src/auth/credentials.ts
|
|
44
43
|
/**
|
|
@@ -405,8 +404,182 @@ function normalizeRoutes(routes) {
|
|
|
405
404
|
return Array.isArray(routes) ? routes : [routes];
|
|
406
405
|
}
|
|
407
406
|
//#endregion
|
|
408
|
-
//#region src/
|
|
407
|
+
//#region src/trust/index.ts
|
|
408
|
+
/**
|
|
409
|
+
* Installing the local edge's certificate authority into the OS trust store.
|
|
410
|
+
*
|
|
411
|
+
* `NODE_EXTRA_CA_CERTS` covers everything gkm starts — the dev server, the test
|
|
412
|
+
* suite, anything under `gkm exec` — because reconcile injects it. A browser
|
|
413
|
+
* reads none of that: it asks the operating system, so until the root is in the
|
|
414
|
+
* system store a local `https://` address is "not secure", which is true and
|
|
415
|
+
* unhelpful.
|
|
416
|
+
*
|
|
417
|
+
* A separate command rather than part of `gkm setup`, for one reason: this
|
|
418
|
+
* needs `sudo`, and a setup that silently escalates on every run is a worse
|
|
419
|
+
* trade than one that tells you what to type. Caddy's own `caddy trust` draws
|
|
420
|
+
* the line in the same place.
|
|
421
|
+
*/
|
|
409
422
|
const logger$12 = console;
|
|
423
|
+
const run = promisify(execFile);
|
|
424
|
+
/**
|
|
425
|
+
* How each platform is told about a new authority.
|
|
426
|
+
*
|
|
427
|
+
* Separate rather than shelled through one script because the failure modes
|
|
428
|
+
* differ and the message should name the real one: macOS refuses without an
|
|
429
|
+
* admin password, Debian needs the file in a specific directory before its
|
|
430
|
+
* updater will look at it.
|
|
431
|
+
*/
|
|
432
|
+
function installation(platform, certificate) {
|
|
433
|
+
if (platform === "darwin") return {
|
|
434
|
+
describe: "the System keychain",
|
|
435
|
+
steps: [["sudo", [
|
|
436
|
+
"security",
|
|
437
|
+
"add-trusted-cert",
|
|
438
|
+
"-d",
|
|
439
|
+
"-r",
|
|
440
|
+
"trustRoot",
|
|
441
|
+
"-k",
|
|
442
|
+
"/Library/Keychains/System.keychain",
|
|
443
|
+
certificate
|
|
444
|
+
]]]
|
|
445
|
+
};
|
|
446
|
+
if (platform === "linux") {
|
|
447
|
+
const debian = existsSync("/usr/local/share/ca-certificates");
|
|
448
|
+
return {
|
|
449
|
+
describe: debian ? "the system CA bundle" : "the system trust anchors",
|
|
450
|
+
steps: debian ? [["sudo", [
|
|
451
|
+
"cp",
|
|
452
|
+
certificate,
|
|
453
|
+
"/usr/local/share/ca-certificates/gkm-local-ca.crt"
|
|
454
|
+
]], ["sudo", ["update-ca-certificates"]]] : [["sudo", [
|
|
455
|
+
"cp",
|
|
456
|
+
certificate,
|
|
457
|
+
"/etc/pki/ca-trust/source/anchors/gkm-local-ca.crt"
|
|
458
|
+
]], ["sudo", ["update-ca-trust"]]]
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
throw new UnsupportedPlatform(platform, certificate);
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Trust the certificate authority this project's edge issues from.
|
|
465
|
+
*
|
|
466
|
+
* Idempotent in effect: installing a root that is already trusted replaces the
|
|
467
|
+
* same entry, so running it twice is not an error.
|
|
468
|
+
*/
|
|
469
|
+
async function trustCommand(options = {}) {
|
|
470
|
+
const { workspace } = await loadWorkspaceConfig();
|
|
471
|
+
const certificate = join(workspace.root, LOCAL_CA_PATH);
|
|
472
|
+
if (!existsSync(certificate)) throw new NoLocalAuthority(certificate);
|
|
473
|
+
const { describe, steps } = installation(process.platform, certificate);
|
|
474
|
+
logger$12.log(`\n🔐 Trusting the local authority in ${describe}`);
|
|
475
|
+
logger$12.log(` ${certificate}\n`);
|
|
476
|
+
if (options.dryRun) {
|
|
477
|
+
for (const [command, args] of steps) logger$12.log(` ${command} ${args.join(" ")}`);
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
logger$12.log(" This needs an admin password.\n");
|
|
481
|
+
for (const [command, args] of steps) await run(command, args, { stdio: "inherit" });
|
|
482
|
+
logger$12.log("\n✅ Local https addresses are trusted. Restart your browser.\n");
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Trust the authority if this machine lacks it and its owner agrees.
|
|
486
|
+
*
|
|
487
|
+
* A machine's answer, not the project's: whether a laptop installs a root
|
|
488
|
+
* certificate is its owner's call, so the decision is `--yes`, then what this
|
|
489
|
+
* machine previously answered, then a prompt. Never a silent `sudo` — that is
|
|
490
|
+
* the one thing a setup command should not do on somebody's behalf.
|
|
491
|
+
*
|
|
492
|
+
* Silent and free when the machine already trusts it, which is what makes it
|
|
493
|
+
* acceptable to call on every `gkm setup`.
|
|
494
|
+
*/
|
|
495
|
+
async function ensureTrusted(root, url, options = {}) {
|
|
496
|
+
if (await isTrusted(url)) return;
|
|
497
|
+
if (!options.assumeYes) {
|
|
498
|
+
if (!process.stdin.isTTY) {
|
|
499
|
+
logger$12.log(" Local https addresses are not trusted here. Run \"gkm trust\", or \"gkm setup --yes\".");
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
if (await declined(root)) return;
|
|
503
|
+
const { install } = await prompts({
|
|
504
|
+
type: "confirm",
|
|
505
|
+
name: "install",
|
|
506
|
+
message: "Trust this project's local certificate authority, so a browser accepts its https addresses? (needs sudo)",
|
|
507
|
+
initial: false
|
|
508
|
+
});
|
|
509
|
+
if (!install) {
|
|
510
|
+
await recordDeclined(root);
|
|
511
|
+
logger$12.log(" Skipped. Run \"gkm trust\" later.");
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
await trustCommand();
|
|
516
|
+
}
|
|
517
|
+
/** Where a declined answer is remembered, so setup asks once and not again. */
|
|
518
|
+
const TRUST_STATE = ".gkm/trust.json";
|
|
519
|
+
async function declined(root) {
|
|
520
|
+
try {
|
|
521
|
+
const raw = await readFile(join(root, TRUST_STATE), "utf-8");
|
|
522
|
+
return JSON.parse(raw).declined === true;
|
|
523
|
+
} catch {
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
async function recordDeclined(root) {
|
|
528
|
+
const path = join(root, TRUST_STATE);
|
|
529
|
+
await mkdir(dirname(path), { recursive: true });
|
|
530
|
+
await writeFile(path, `${JSON.stringify({ declined: true }, null, 2)}\n`);
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Whether this machine already trusts the local authority.
|
|
534
|
+
*
|
|
535
|
+
* Asked by connecting rather than by reading a keychain: what matters is
|
|
536
|
+
* whether a client with no special configuration verifies the edge, and that is
|
|
537
|
+
* one platform-neutral question instead of three platform-specific ones. It is
|
|
538
|
+
* also the exact thing a browser is about to do.
|
|
539
|
+
*
|
|
540
|
+
* A child process, because `NODE_EXTRA_CA_CERTS` is read once at startup and
|
|
541
|
+
* this process almost certainly has it set — asking in-process would answer
|
|
542
|
+
* "yes" for everyone.
|
|
543
|
+
*/
|
|
544
|
+
async function isTrusted(url) {
|
|
545
|
+
const { NODE_EXTRA_CA_CERTS: _ignored, ...env } = process.env;
|
|
546
|
+
try {
|
|
547
|
+
await run(process.execPath, [
|
|
548
|
+
"--use-system-ca",
|
|
549
|
+
"-e",
|
|
550
|
+
`fetch(${JSON.stringify(url)}).then(()=>0,()=>process.exit(1))`
|
|
551
|
+
], {
|
|
552
|
+
env,
|
|
553
|
+
timeout: 15e3
|
|
554
|
+
});
|
|
555
|
+
return true;
|
|
556
|
+
} catch {
|
|
557
|
+
return false;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
/** The edge has not run, so there is no authority to trust yet. */
|
|
561
|
+
var NoLocalAuthority = class extends Error {
|
|
562
|
+
path;
|
|
563
|
+
constructor(path) {
|
|
564
|
+
super(`No local certificate authority at ${path}. It is generated the first time the edge starts, so run "gkm setup" (or "gkm dev") once — and if this project declares no file server, nothing needs one.`);
|
|
565
|
+
this.path = path;
|
|
566
|
+
this.name = "NoLocalAuthority";
|
|
567
|
+
}
|
|
568
|
+
};
|
|
569
|
+
/** A platform with no trust store this knows how to write to. */
|
|
570
|
+
var UnsupportedPlatform = class extends Error {
|
|
571
|
+
platform;
|
|
572
|
+
certificate;
|
|
573
|
+
constructor(platform, certificate) {
|
|
574
|
+
super(`gkm does not know how to install a certificate authority on ${platform}. The root is at ${certificate} — add it to the trust store by hand, or point a client at it with NODE_EXTRA_CA_CERTS.`);
|
|
575
|
+
this.platform = platform;
|
|
576
|
+
this.certificate = certificate;
|
|
577
|
+
this.name = "UnsupportedPlatform";
|
|
578
|
+
}
|
|
579
|
+
};
|
|
580
|
+
//#endregion
|
|
581
|
+
//#region src/exec/index.ts
|
|
582
|
+
const logger$11 = console;
|
|
410
583
|
/** `gkm exec` was given nothing to run. */
|
|
411
584
|
var NoCommandSpecified = class extends Error {
|
|
412
585
|
constructor() {
|
|
@@ -429,17 +602,17 @@ async function execCommand(commandArgs, options = {}) {
|
|
|
429
602
|
const cwd = options.cwd ?? process.cwd();
|
|
430
603
|
if (!commandArgs[0]) throw new NoCommandSpecified();
|
|
431
604
|
const defaultEnv = loadEnvFiles(".env");
|
|
432
|
-
if (defaultEnv.loaded.length > 0) logger$
|
|
605
|
+
if (defaultEnv.loaded.length > 0) logger$11.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
433
606
|
const { credentials, secretsJsonPath, appName } = await prepareEntryCredentials({ cwd });
|
|
434
|
-
if (appName) logger$
|
|
607
|
+
if (appName) logger$11.log(`📦 App: ${appName}`);
|
|
435
608
|
const secretCount = Object.keys(credentials).filter((k) => k !== "PORT").length;
|
|
436
|
-
if (secretCount > 0) logger$
|
|
609
|
+
if (secretCount > 0) logger$11.log(`🔐 Loaded ${secretCount} secret(s)`);
|
|
437
610
|
const preloadDir = join(cwd, ".gkm");
|
|
438
611
|
await mkdir(preloadDir, { recursive: true });
|
|
439
612
|
const preloadPath = join(preloadDir, "credentials-preload.mjs");
|
|
440
613
|
await createCredentialsPreload(preloadPath, secretsJsonPath);
|
|
441
614
|
const [cmd, ...args] = commandArgs;
|
|
442
|
-
logger$
|
|
615
|
+
logger$11.log(`🚀 Running: ${[cmd, ...args].join(" ")}`);
|
|
443
616
|
const nodeOptions = [(process.env.NODE_OPTIONS ?? "").replace(/--import[= ]\S+/g, "").trim(), `--import=${preloadPath}`].filter(Boolean).join(" ");
|
|
444
617
|
const child = spawn(cmd, args, {
|
|
445
618
|
cwd,
|
|
@@ -453,7 +626,7 @@ async function execCommand(commandArgs, options = {}) {
|
|
|
453
626
|
const exitCode = await new Promise((resolve) => {
|
|
454
627
|
child.on("close", (code) => resolve(code ?? 0));
|
|
455
628
|
child.on("error", (error) => {
|
|
456
|
-
logger$
|
|
629
|
+
logger$11.error(`Failed to run command: ${error.message}`);
|
|
457
630
|
resolve(1);
|
|
458
631
|
});
|
|
459
632
|
});
|
|
@@ -461,7 +634,7 @@ async function execCommand(commandArgs, options = {}) {
|
|
|
461
634
|
}
|
|
462
635
|
//#endregion
|
|
463
636
|
//#region src/dev/index.ts
|
|
464
|
-
const logger$
|
|
637
|
+
const logger$10 = console;
|
|
465
638
|
/**
|
|
466
639
|
* Normalize telescope configuration
|
|
467
640
|
* @internal Exported for testing
|
|
@@ -555,7 +728,7 @@ function getProductionConfigFromGkm(config) {
|
|
|
555
728
|
async function devCommand(options) {
|
|
556
729
|
if (options.entry) return entryDevCommand(options);
|
|
557
730
|
const defaultEnv = loadEnvFiles(".env");
|
|
558
|
-
if (defaultEnv.loaded.length > 0) logger$
|
|
731
|
+
if (defaultEnv.loaded.length > 0) logger$10.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
559
732
|
const appName = getAppNameFromCwd();
|
|
560
733
|
let config;
|
|
561
734
|
let appRoot = process.cwd();
|
|
@@ -571,9 +744,8 @@ async function devCommand(options) {
|
|
|
571
744
|
workspaceAppName = appConfig.appName;
|
|
572
745
|
workspaceAppPort = appConfig.app.port;
|
|
573
746
|
workspace = appConfig.workspace;
|
|
574
|
-
logger$11.log(`📦 Running app: ${appConfig.appName} on port ${workspaceAppPort}`);
|
|
575
747
|
if (appConfig.app.entry) {
|
|
576
|
-
logger$
|
|
748
|
+
logger$10.log(`📄 Using entry point: ${appConfig.app.entry}`);
|
|
577
749
|
return entryDevCommand({
|
|
578
750
|
...options,
|
|
579
751
|
entry: appConfig.app.entry,
|
|
@@ -583,39 +755,27 @@ async function devCommand(options) {
|
|
|
583
755
|
}
|
|
584
756
|
} catch {
|
|
585
757
|
const loadedConfig = await loadWorkspaceConfig();
|
|
586
|
-
if (loadedConfig.type === "workspace")
|
|
587
|
-
logger$11.log("📦 Detected workspace configuration");
|
|
588
|
-
return workspaceDevCommand(loadedConfig.workspace, options);
|
|
589
|
-
}
|
|
758
|
+
if (loadedConfig.type === "workspace") return workspaceDevCommand(loadedConfig.workspace, options);
|
|
590
759
|
config = loadedConfig.raw;
|
|
591
760
|
workspace = loadedConfig.workspace;
|
|
592
761
|
}
|
|
593
762
|
else {
|
|
594
763
|
const loadedConfig = await loadWorkspaceConfig();
|
|
595
|
-
if (loadedConfig.type === "workspace")
|
|
596
|
-
logger$11.log("📦 Detected workspace configuration");
|
|
597
|
-
return workspaceDevCommand(loadedConfig.workspace, options);
|
|
598
|
-
}
|
|
764
|
+
if (loadedConfig.type === "workspace") return workspaceDevCommand(loadedConfig.workspace, options);
|
|
599
765
|
config = loadedConfig.raw;
|
|
600
766
|
workspace = loadedConfig.workspace;
|
|
601
767
|
}
|
|
602
768
|
if (config.env) {
|
|
603
769
|
const { loaded, missing } = loadEnvFiles(config.env, appRoot);
|
|
604
|
-
if (loaded.length > 0) logger$
|
|
605
|
-
if (missing.length > 0) logger$
|
|
770
|
+
if (loaded.length > 0) logger$10.log(`📦 Loaded env: ${loaded.join(", ")}`);
|
|
771
|
+
if (missing.length > 0) logger$10.warn(`⚠️ Missing env files: ${missing.join(", ")}`);
|
|
606
772
|
}
|
|
607
773
|
const resolved = resolveProviders(config, { provider: "server" });
|
|
608
|
-
logger$11.log("🚀 Starting development server...");
|
|
609
|
-
logger$11.log(`Loading constructs from: ${config.constructs}`);
|
|
610
774
|
const telescope = normalizeTelescopeConfig(config.telescope);
|
|
611
|
-
if (telescope) logger$11.log(`🔭 Telescope enabled at ${telescope.path}`);
|
|
612
775
|
const studio = normalizeStudioConfig(config.studio);
|
|
613
|
-
if (studio) logger$11.log(`🗄️ Studio enabled at ${studio.path}`);
|
|
614
776
|
const hooks = normalizeHooksConfig(config.hooks, appRoot);
|
|
615
|
-
if (hooks) logger$11.log(`🪝 Server hooks enabled from ${config.hooks?.server}`);
|
|
616
777
|
const enableOpenApi = resolveOpenApiConfig(config).enabled || resolved.enableOpenApi;
|
|
617
|
-
|
|
618
|
-
const build = (bustCache = false) => buildApp({
|
|
778
|
+
const build = (bustCache = false) => quietly(() => buildApp({
|
|
619
779
|
config,
|
|
620
780
|
workspaceRoot: workspace?.root ?? secretsRoot,
|
|
621
781
|
appRoot,
|
|
@@ -628,18 +788,21 @@ async function devCommand(options) {
|
|
|
628
788
|
skipBundle: true,
|
|
629
789
|
bustCache,
|
|
630
790
|
serveEmpty: true
|
|
631
|
-
});
|
|
791
|
+
}));
|
|
632
792
|
const initial = await build();
|
|
633
793
|
const runtime = config.runtime ?? "node";
|
|
634
794
|
let secretsJsonPath;
|
|
795
|
+
let publicUrl;
|
|
635
796
|
const appSecrets = await loadSecretsForApp(secretsRoot, config.stages.local, workspaceAppName);
|
|
636
797
|
if (workspace) {
|
|
637
798
|
const reconciled = await reconcileWorkspace(workspace, { stage: workspace.stages.local });
|
|
638
799
|
if (reconciled.changed && reconciled.plan.containers.length > 0) {
|
|
639
|
-
logger$
|
|
640
|
-
for (const [container, address] of Object.entries(reconciled.addresses)) logger$
|
|
800
|
+
logger$10.log(`🐳 Services: ${reconciled.plan.containers.join(", ")}`);
|
|
801
|
+
for (const [container, address] of Object.entries(reconciled.addresses)) logger$10.log(` ${container}: ${address}`);
|
|
641
802
|
}
|
|
642
803
|
Object.assign(appSecrets, reconciled.env);
|
|
804
|
+
const own = reconciled.plan.resources.find((r) => (r.kind === "rest-api" || r.kind === "site") && appKey(r.id) === workspaceAppName);
|
|
805
|
+
publicUrl = own ? reconciled.env[own.envKey] : void 0;
|
|
643
806
|
}
|
|
644
807
|
if (Object.keys(appSecrets).length > 0) {
|
|
645
808
|
const secretsDir = join(secretsRoot, ".gkm");
|
|
@@ -647,13 +810,13 @@ async function devCommand(options) {
|
|
|
647
810
|
const secretsFileName = workspaceAppName ? `dev-secrets-${workspaceAppName}.json` : "dev-secrets.json";
|
|
648
811
|
secretsJsonPath = join(secretsDir, secretsFileName);
|
|
649
812
|
await writeFile(secretsJsonPath, JSON.stringify(appSecrets, null, 2));
|
|
650
|
-
logger$11.log(`🔐 Loaded ${Object.keys(appSecrets).length} secret(s)`);
|
|
651
813
|
}
|
|
652
814
|
const devServer = new DevServer(resolved.providers[0], options.port ?? workspaceAppPort ?? 3e3, options.portExplicit ?? workspaceAppPort !== void 0, enableOpenApi, telescope, studio, runtime, appRoot, secretsJsonPath, initial.selfServing !== void 0);
|
|
815
|
+
devServer.label = workspaceAppName ?? "server";
|
|
816
|
+
devServer.publicUrl = publicUrl;
|
|
653
817
|
await devServer.start();
|
|
654
818
|
const hooksFile = (config.hooks?.server?.split("#"))?.[0];
|
|
655
819
|
const normalizedPatterns = [config.constructs, ...hooksFile ? [hooksFile.endsWith(".ts") ? hooksFile : `${hooksFile}.ts`] : []].flat().filter((p) => typeof p === "string").map((p) => p.startsWith("./") ? p.slice(2) : p);
|
|
656
|
-
logger$11.log(`👀 Watching for changes in: ${normalizedPatterns.join(", ")}`);
|
|
657
820
|
const resolvedFiles = await fg(normalizedPatterns, {
|
|
658
821
|
cwd: appRoot,
|
|
659
822
|
absolute: false,
|
|
@@ -662,34 +825,28 @@ async function devCommand(options) {
|
|
|
662
825
|
const dirsToWatch = [...new Set(resolvedFiles.map((f) => {
|
|
663
826
|
return f.split("/").slice(0, -1).join("/");
|
|
664
827
|
}))];
|
|
665
|
-
logger$11.log(`📁 Found ${resolvedFiles.length} files in ${dirsToWatch.length} directories`);
|
|
666
828
|
const watcher = chokidar.watch([...resolvedFiles, ...dirsToWatch], {
|
|
667
829
|
ignored: /(^|[/\\])\../,
|
|
668
830
|
persistent: true,
|
|
669
831
|
ignoreInitial: true,
|
|
670
832
|
cwd: appRoot
|
|
671
833
|
});
|
|
672
|
-
watcher.on("ready", () => {
|
|
673
|
-
logger$11.log("🔍 File watcher ready");
|
|
674
|
-
});
|
|
675
834
|
watcher.on("error", (error) => {
|
|
676
|
-
logger$
|
|
835
|
+
logger$10.error("❌ Watcher error:", error);
|
|
677
836
|
});
|
|
678
837
|
let rebuildTimeout = null;
|
|
679
838
|
watcher.on("change", async (path) => {
|
|
680
|
-
logger$
|
|
839
|
+
logger$10.log(`🔄 ${path} changed — rebuilding`);
|
|
681
840
|
if (rebuildTimeout) clearTimeout(rebuildTimeout);
|
|
682
841
|
rebuildTimeout = setTimeout(async () => {
|
|
683
842
|
try {
|
|
684
|
-
logger$11.log("🔄 Rebuilding...");
|
|
685
843
|
const rebuilt = await build(true);
|
|
686
|
-
logger$11.log("✅ Rebuild complete, restarting server...");
|
|
687
844
|
devServer.selfServing = rebuilt.selfServing !== void 0;
|
|
688
845
|
await devServer.restart();
|
|
689
846
|
} catch (error) {
|
|
690
847
|
const err = error;
|
|
691
|
-
logger$
|
|
692
|
-
if (err.stack) logger$
|
|
848
|
+
logger$10.error(`❌ Rebuild failed: ${err.message}`);
|
|
849
|
+
if (err.stack) logger$10.error(err.stack);
|
|
693
850
|
}
|
|
694
851
|
}, 300);
|
|
695
852
|
});
|
|
@@ -697,9 +854,9 @@ async function devCommand(options) {
|
|
|
697
854
|
const shutdown = () => {
|
|
698
855
|
if (isShuttingDown) return;
|
|
699
856
|
isShuttingDown = true;
|
|
700
|
-
logger$
|
|
857
|
+
logger$10.log("\n🛑 Shutting down...");
|
|
701
858
|
Promise.all([watcher.close(), devServer.stop()]).catch((err) => {
|
|
702
|
-
logger$
|
|
859
|
+
logger$10.error("Error during shutdown:", err);
|
|
703
860
|
}).finally(() => {
|
|
704
861
|
process.exit(0);
|
|
705
862
|
});
|
|
@@ -885,12 +1042,8 @@ async function loadDevSecrets(workspace) {
|
|
|
885
1042
|
const stage = workspace.stages.local;
|
|
886
1043
|
if (secretsExist(stage, workspace.root)) {
|
|
887
1044
|
const secrets = await readStageSecrets(stage, workspace.root);
|
|
888
|
-
if (secrets)
|
|
889
|
-
logger$11.log(`🔐 Loading secrets from stage: ${stage}`);
|
|
890
|
-
return toEmbeddableSecrets(secrets);
|
|
891
|
-
}
|
|
1045
|
+
if (secrets) return toEmbeddableSecrets(secrets);
|
|
892
1046
|
}
|
|
893
|
-
logger$11.warn(`⚠️ Secrets enabled but no "${stage}" secrets found. Run "gkm setup" to initialize the local stage`);
|
|
894
1047
|
return {};
|
|
895
1048
|
}
|
|
896
1049
|
/**
|
|
@@ -904,38 +1057,30 @@ async function loadDevSecrets(workspace) {
|
|
|
904
1057
|
*/
|
|
905
1058
|
async function workspaceDevCommand(workspace, options) {
|
|
906
1059
|
const appCount = Object.keys(workspace.apps).length;
|
|
907
|
-
const backendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "backend");
|
|
908
1060
|
const frontendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "web");
|
|
909
|
-
const mobileApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "mobile");
|
|
910
|
-
logger$11.log(`\n🚀 Starting workspace: ${workspace.name}`);
|
|
911
|
-
const counts = [`${backendApps.length} backend`, `${frontendApps.length} web`];
|
|
912
|
-
if (mobileApps.length > 0) counts.push(`${mobileApps.length} mobile`);
|
|
913
|
-
logger$11.log(` ${counts.join(", ")} app(s)`);
|
|
914
1061
|
const conflicts = checkPortConflicts(workspace);
|
|
915
1062
|
if (conflicts.length > 0) {
|
|
916
|
-
for (const conflict of conflicts) logger$
|
|
1063
|
+
for (const conflict of conflicts) logger$10.error(`❌ Port conflict: Apps "${conflict.app1}" and "${conflict.app2}" both use port ${conflict.port}`);
|
|
917
1064
|
throw new Error("Port conflicts detected. Please assign unique ports to each app.");
|
|
918
1065
|
}
|
|
919
1066
|
if (frontendApps.length > 0) {
|
|
920
|
-
logger$11.log("\n🔍 Validating frontend apps...");
|
|
921
1067
|
const validationResults = await validateFrontendApps(workspace);
|
|
922
1068
|
let hasErrors = false;
|
|
923
1069
|
for (const result of validationResults) {
|
|
924
1070
|
if (!result.valid) {
|
|
925
1071
|
hasErrors = true;
|
|
926
|
-
logger$
|
|
927
|
-
for (const error of result.errors) logger$
|
|
1072
|
+
logger$10.error(`\n❌ Frontend app "${result.appName}" validation failed:`);
|
|
1073
|
+
for (const error of result.errors) logger$10.error(` • ${error}`);
|
|
928
1074
|
}
|
|
929
|
-
for (const warning of result.warnings) logger$
|
|
1075
|
+
for (const warning of result.warnings) logger$10.warn(` ⚠️ ${result.appName}: ${warning}`);
|
|
930
1076
|
}
|
|
931
1077
|
if (hasErrors) throw new Error("Frontend app validation failed. Fix the issues above and try again.");
|
|
932
|
-
logger$11.log("✅ Frontend apps validated");
|
|
933
1078
|
}
|
|
934
1079
|
const rawSecrets = await loadDevSecrets(workspace);
|
|
935
1080
|
const reconciled = await reconcileWorkspace(workspace, { stage: workspace.stages.local });
|
|
936
1081
|
if (reconciled.changed && reconciled.plan.containers.length > 0) {
|
|
937
|
-
logger$
|
|
938
|
-
for (const [container, address] of Object.entries(reconciled.addresses)) logger$
|
|
1082
|
+
logger$10.log(`🐳 Services: ${reconciled.plan.containers.join(", ")}`);
|
|
1083
|
+
for (const [container, address] of Object.entries(reconciled.addresses)) logger$10.log(` ${container}: ${address}`);
|
|
939
1084
|
}
|
|
940
1085
|
const secretsEnv = {
|
|
941
1086
|
...rawSecrets,
|
|
@@ -947,11 +1092,8 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
947
1092
|
const address = reconciled.env[resource.envKey];
|
|
948
1093
|
if (address) appUrls[resource.id] = address;
|
|
949
1094
|
}
|
|
950
|
-
|
|
951
|
-
if (
|
|
952
|
-
logger$11.log("🔒 App URLs:");
|
|
953
|
-
for (const [id, address] of Object.entries(appUrls)) logger$11.log(` ${id}: ${address}`);
|
|
954
|
-
}
|
|
1095
|
+
const firstUrl = Object.values(appUrls).find((url) => url.startsWith("https://"));
|
|
1096
|
+
if (firstUrl) await ensureTrusted(workspace.root, firstUrl);
|
|
955
1097
|
let turboFilter = [];
|
|
956
1098
|
if (options.app) {
|
|
957
1099
|
if (!workspace.apps[options.app]) {
|
|
@@ -959,25 +1101,23 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
959
1101
|
throw new Error(`App "${options.app}" not found. Available apps: ${appNames}`);
|
|
960
1102
|
}
|
|
961
1103
|
turboFilter = ["--filter", appPackageName(workspace, options.app) ?? options.app];
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
turboFilter = ["--filter", options.filter];
|
|
965
|
-
logger$11.log(`\n🔍 Using filter: ${options.filter}`);
|
|
966
|
-
} else {
|
|
1104
|
+
} else if (options.filter) turboFilter = ["--filter", options.filter];
|
|
1105
|
+
else {
|
|
967
1106
|
const { filters, unpackaged } = turboFilters(workspace);
|
|
968
1107
|
turboFilter = filters.flatMap((name) => ["--filter", name]);
|
|
969
|
-
if (unpackaged.length > 0) logger$
|
|
970
|
-
logger$11.log(`\n🎯 Running all ${appCount} apps`);
|
|
1108
|
+
if (unpackaged.length > 0) logger$10.warn(`⚠️ No package.json with a name for: ${unpackaged.join(", ")} — turbo cannot run ${unpackaged.length === 1 ? "it" : "them"}.`);
|
|
971
1109
|
}
|
|
972
1110
|
const buildOrder = getAppBuildOrder(workspace);
|
|
973
|
-
|
|
1111
|
+
const width = Math.max(...buildOrder.map((name) => name.length));
|
|
1112
|
+
logger$10.log(`\n${workspace.name}: ${appCount} app(s)`);
|
|
974
1113
|
for (const appName of buildOrder) {
|
|
975
1114
|
const app = workspace.apps[appName];
|
|
976
1115
|
if (!app) continue;
|
|
977
|
-
const
|
|
978
|
-
const
|
|
979
|
-
logger$
|
|
1116
|
+
const local = `http://localhost:${app.port}`;
|
|
1117
|
+
const url = Object.entries(appUrls).find(([id]) => appKey(id) === appName)?.[1];
|
|
1118
|
+
logger$10.log(` ${appName.padEnd(width)} ${url ? `${url} -> ${local}` : local}`);
|
|
980
1119
|
}
|
|
1120
|
+
logger$10.log("");
|
|
981
1121
|
const configFiles = [
|
|
982
1122
|
"gkm.config.ts",
|
|
983
1123
|
"gkm.config.js",
|
|
@@ -1008,7 +1148,6 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1008
1148
|
});
|
|
1009
1149
|
}
|
|
1010
1150
|
if (held.length > 0) throw new WorkspacePortsInUse(held);
|
|
1011
|
-
logger$11.log("\n🏃 Starting turbo run dev...\n");
|
|
1012
1151
|
const turboProcess = spawn("pnpm", [
|
|
1013
1152
|
"turbo",
|
|
1014
1153
|
"run",
|
|
@@ -1024,7 +1163,7 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1024
1163
|
const shutdown = () => {
|
|
1025
1164
|
if (isShuttingDown) return;
|
|
1026
1165
|
isShuttingDown = true;
|
|
1027
|
-
logger$
|
|
1166
|
+
logger$10.log("\n🛑 Shutting down workspace...");
|
|
1028
1167
|
const pid = turboProcess.pid;
|
|
1029
1168
|
if (pid) try {
|
|
1030
1169
|
process.kill(-pid, "SIGTERM");
|
|
@@ -1048,7 +1187,7 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1048
1187
|
process.on("SIGTERM", shutdown);
|
|
1049
1188
|
return new Promise((resolve, reject) => {
|
|
1050
1189
|
turboProcess.on("error", (error) => {
|
|
1051
|
-
logger$
|
|
1190
|
+
logger$10.error("❌ Turbo error:", error);
|
|
1052
1191
|
reject(error);
|
|
1053
1192
|
});
|
|
1054
1193
|
turboProcess.on("exit", (code) => {
|
|
@@ -1067,11 +1206,11 @@ async function entryDevCommand(options) {
|
|
|
1067
1206
|
const entryPath = resolve(process.cwd(), entry);
|
|
1068
1207
|
if (!existsSync(entryPath)) throw new Error(`Entry file not found: ${entryPath}`);
|
|
1069
1208
|
const defaultEnv = loadEnvFiles(".env");
|
|
1070
|
-
if (defaultEnv.loaded.length > 0) logger$
|
|
1209
|
+
if (defaultEnv.loaded.length > 0) logger$10.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
1071
1210
|
const { credentials, resolvedPort, secretsJsonPath, appName } = await prepareEntryCredentials({ explicitPort: options.portExplicit ? options.port : void 0 });
|
|
1072
|
-
if (appName) logger$
|
|
1073
|
-
logger$
|
|
1074
|
-
if (Object.keys(credentials).length > 1) logger$
|
|
1211
|
+
if (appName) logger$10.log(`📦 App: ${appName} (port ${resolvedPort})`);
|
|
1212
|
+
logger$10.log(`🚀 Starting entry file: ${entry} on port ${resolvedPort}`);
|
|
1213
|
+
if (Object.keys(credentials).length > 1) logger$10.log(`🔐 Loaded ${Object.keys(credentials).length - 1} secret(s) + PORT`);
|
|
1075
1214
|
const wrapperDir = join(process.cwd(), ".gkm");
|
|
1076
1215
|
await mkdir(wrapperDir, { recursive: true });
|
|
1077
1216
|
const wrapperPath = join(wrapperDir, "entry-wrapper.ts");
|
|
@@ -1082,7 +1221,7 @@ async function entryDevCommand(options) {
|
|
|
1082
1221
|
const shutdown = () => {
|
|
1083
1222
|
if (isShuttingDown) return;
|
|
1084
1223
|
isShuttingDown = true;
|
|
1085
|
-
logger$
|
|
1224
|
+
logger$10.log("\n🛑 Shutting down...");
|
|
1086
1225
|
runner.stop();
|
|
1087
1226
|
process.exit(0);
|
|
1088
1227
|
};
|
|
@@ -1118,14 +1257,14 @@ var EntryRunner = class {
|
|
|
1118
1257
|
});
|
|
1119
1258
|
let restartTimeout = null;
|
|
1120
1259
|
this.watcher.on("change", (path) => {
|
|
1121
|
-
logger$
|
|
1260
|
+
logger$10.log(`📝 File changed: ${path}`);
|
|
1122
1261
|
if (restartTimeout) clearTimeout(restartTimeout);
|
|
1123
1262
|
restartTimeout = setTimeout(async () => {
|
|
1124
|
-
logger$
|
|
1263
|
+
logger$10.log("🔄 Restarting...");
|
|
1125
1264
|
await this.restart();
|
|
1126
1265
|
}, 300);
|
|
1127
1266
|
});
|
|
1128
|
-
logger$
|
|
1267
|
+
logger$10.log(`👀 Watching for changes in: ${watchDir}`);
|
|
1129
1268
|
}
|
|
1130
1269
|
}
|
|
1131
1270
|
async runProcess() {
|
|
@@ -1133,24 +1272,24 @@ var EntryRunner = class {
|
|
|
1133
1272
|
...process.env,
|
|
1134
1273
|
PORT: String(this.port)
|
|
1135
1274
|
};
|
|
1136
|
-
this.childProcess = spawn(
|
|
1275
|
+
this.childProcess = spawn(...tsxCommand(dirname(this.wrapperPath), [this.wrapperPath]), {
|
|
1137
1276
|
stdio: "inherit",
|
|
1138
1277
|
env,
|
|
1139
1278
|
detached: true
|
|
1140
1279
|
});
|
|
1141
1280
|
this.isRunning = true;
|
|
1142
1281
|
this.childProcess.on("error", (error) => {
|
|
1143
|
-
logger$
|
|
1282
|
+
logger$10.error("❌ Process error:", error);
|
|
1144
1283
|
});
|
|
1145
1284
|
this.childProcess.on("exit", (code) => {
|
|
1146
|
-
if (code !== null && code !== 0 && code !== 143) logger$
|
|
1285
|
+
if (code !== null && code !== 0 && code !== 143) logger$10.error(`❌ Process exited with code ${code}`);
|
|
1147
1286
|
this.isRunning = false;
|
|
1148
1287
|
});
|
|
1149
1288
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1150
1289
|
if (this.isRunning) {
|
|
1151
|
-
logger$
|
|
1152
|
-
logger$
|
|
1153
|
-
logger$
|
|
1290
|
+
logger$10.log("");
|
|
1291
|
+
logger$10.log(` \x1b[32m✓ Ready\x1b[0m at \x1b[36mhttp://localhost:${this.port}\x1b[0m`);
|
|
1292
|
+
logger$10.log("");
|
|
1154
1293
|
}
|
|
1155
1294
|
}
|
|
1156
1295
|
async restart() {
|
|
@@ -1236,7 +1375,6 @@ ${credentialsInjection}${parentWatch}const port = process.argv.includes('--port'
|
|
|
1236
1375
|
const { app } = await import('${appImportPath}');
|
|
1237
1376
|
|
|
1238
1377
|
${serveCode.replace(/^ {4}/gm, "")}
|
|
1239
|
-
console.log(\`Server started on port \${port}\`);
|
|
1240
1378
|
`;
|
|
1241
1379
|
return `#!/usr/bin/env node
|
|
1242
1380
|
/**
|
|
@@ -1265,6 +1403,31 @@ start({
|
|
|
1265
1403
|
});
|
|
1266
1404
|
`;
|
|
1267
1405
|
}
|
|
1406
|
+
/** Run `fn` with `console.log` muted — warnings and errors still print. */
|
|
1407
|
+
async function quietly(fn) {
|
|
1408
|
+
const log = console.log;
|
|
1409
|
+
console.log = () => {};
|
|
1410
|
+
try {
|
|
1411
|
+
return await fn();
|
|
1412
|
+
} finally {
|
|
1413
|
+
console.log = log;
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
/**
|
|
1417
|
+
* How to run a TypeScript entry with the app's own tsx.
|
|
1418
|
+
*
|
|
1419
|
+
* Straight through node rather than `npx tsx`: npx reads the developer's npm
|
|
1420
|
+
* config on every start and warned about each key it did not recognise — nine
|
|
1421
|
+
* lines per app, per restart, about nothing gkm does.
|
|
1422
|
+
*/
|
|
1423
|
+
function tsxCommand(appRoot, args) {
|
|
1424
|
+
try {
|
|
1425
|
+
const cli = createRequire(join(appRoot, "package.json")).resolve("tsx/cli");
|
|
1426
|
+
return [process.execPath, [cli, ...args]];
|
|
1427
|
+
} catch {
|
|
1428
|
+
return ["npx", ["tsx", ...args]];
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1268
1431
|
/** A port this app must serve on is held by something else. */
|
|
1269
1432
|
var DevPortInUse = class extends Error {
|
|
1270
1433
|
port;
|
|
@@ -1331,6 +1494,13 @@ var DevServer = class {
|
|
|
1331
1494
|
this.selfServing = selfServing;
|
|
1332
1495
|
this.actualPort = requestedPort;
|
|
1333
1496
|
}
|
|
1497
|
+
/** What the ready line calls this app. */
|
|
1498
|
+
label = "server";
|
|
1499
|
+
/**
|
|
1500
|
+
* Where the app is reached — its HTTPS address behind the edge, when it has
|
|
1501
|
+
* one. The local port is only what the edge forwards to.
|
|
1502
|
+
*/
|
|
1503
|
+
publicUrl;
|
|
1334
1504
|
async start() {
|
|
1335
1505
|
this.startTime = Date.now();
|
|
1336
1506
|
if (this.isRunning) await this.stop();
|
|
@@ -1339,17 +1509,15 @@ var DevServer = class {
|
|
|
1339
1509
|
this.actualPort = this.requestedPort;
|
|
1340
1510
|
} else {
|
|
1341
1511
|
this.actualPort = await findAvailablePort(this.requestedPort);
|
|
1342
|
-
if (this.actualPort !== this.requestedPort) logger$
|
|
1512
|
+
if (this.actualPort !== this.requestedPort) logger$10.log(`ℹ️ Port ${this.requestedPort} was in use, using port ${this.actualPort} instead`);
|
|
1343
1513
|
}
|
|
1344
1514
|
const serverEntryPath = join(this.appRoot, ".gkm", this.provider, "server.ts");
|
|
1345
1515
|
await this.createServerEntry();
|
|
1346
|
-
|
|
1347
|
-
this.serverProcess = spawn("npx", [
|
|
1348
|
-
"tsx",
|
|
1516
|
+
this.serverProcess = spawn(...tsxCommand(this.appRoot, [
|
|
1349
1517
|
serverEntryPath,
|
|
1350
1518
|
"--port",
|
|
1351
1519
|
this.actualPort.toString()
|
|
1352
|
-
], {
|
|
1520
|
+
]), {
|
|
1353
1521
|
stdio: "inherit",
|
|
1354
1522
|
env: {
|
|
1355
1523
|
...process.env,
|
|
@@ -1360,29 +1528,24 @@ var DevServer = class {
|
|
|
1360
1528
|
});
|
|
1361
1529
|
this.isRunning = true;
|
|
1362
1530
|
this.serverProcess.on("error", (error) => {
|
|
1363
|
-
logger$
|
|
1531
|
+
logger$10.error("❌ Server error:", error);
|
|
1364
1532
|
});
|
|
1365
1533
|
this.serverProcess.on("exit", (code, signal) => {
|
|
1366
|
-
if (code !== null && code !== 0 && signal !== "SIGTERM") logger$
|
|
1534
|
+
if (code !== null && code !== 0 && signal !== "SIGTERM") logger$10.error(`❌ Server exited with code ${code}`);
|
|
1367
1535
|
this.isRunning = false;
|
|
1368
1536
|
});
|
|
1369
1537
|
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
1370
1538
|
if (this.isRunning) {
|
|
1371
|
-
const
|
|
1372
|
-
const
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
logger$
|
|
1381
|
-
logger$11.log("");
|
|
1382
|
-
logger$11.log(` ┌${border}┐`);
|
|
1383
|
-
for (const line of lines) logger$11.log(` │ ${pad(line)} │`);
|
|
1384
|
-
logger$11.log(` └${border}┘`);
|
|
1385
|
-
logger$11.log("");
|
|
1539
|
+
const local = `http://localhost:${this.actualPort}`;
|
|
1540
|
+
const address = this.publicUrl ? `${this.publicUrl} -> ${local}` : local;
|
|
1541
|
+
const seconds = ((Date.now() - this.startTime) / 1e3).toFixed(1);
|
|
1542
|
+
logger$10.log(`\x1b[32m✓\x1b[0m ${this.label} ready in ${seconds}s ${address}`);
|
|
1543
|
+
const tools = this.selfServing ? [] : [
|
|
1544
|
+
...this.enableOpenApi ? ["docs /__docs"] : [],
|
|
1545
|
+
...this.telescope ? [`telescope ${this.telescope.path}`] : [],
|
|
1546
|
+
...this.studio ? [`studio ${this.studio.path}`] : []
|
|
1547
|
+
];
|
|
1548
|
+
if (tools.length > 0) logger$10.log(` ${tools.join(" · ")}`);
|
|
1386
1549
|
}
|
|
1387
1550
|
}
|
|
1388
1551
|
async stop() {
|
|
@@ -2197,7 +2360,7 @@ var TopicGenerator = class extends ConstructGenerator {
|
|
|
2197
2360
|
};
|
|
2198
2361
|
//#endregion
|
|
2199
2362
|
//#region src/build/manifests.ts
|
|
2200
|
-
const logger$
|
|
2363
|
+
const logger$9 = console;
|
|
2201
2364
|
function isPartitioned(field) {
|
|
2202
2365
|
return !Array.isArray(field);
|
|
2203
2366
|
}
|
|
@@ -2294,8 +2457,8 @@ export type RoutePath = Route['path'];
|
|
|
2294
2457
|
`;
|
|
2295
2458
|
const manifestPath = join(manifestDir, "aws.ts");
|
|
2296
2459
|
await writeFile(manifestPath, content);
|
|
2297
|
-
logger$
|
|
2298
|
-
logger$
|
|
2460
|
+
logger$9.log(`Generated AWS manifest with ${countItems(awsRoutes)} routes, ${countItems(functions)} functions, ${countItems(crons)} crons, ${countItems(subscribers)} subscribers, ${countItems(queues)} queues, ${countItems(topics)} topics`);
|
|
2461
|
+
logger$9.log(`Manifest: ${relative(process.cwd(), manifestPath)}`);
|
|
2299
2462
|
}
|
|
2300
2463
|
async function generateServerManifest(outputDir, appInfo, routes, subscribers, queues = [], topics = [], constructs = {}, backends = {}) {
|
|
2301
2464
|
const manifestDir = join(outputDir, "manifest");
|
|
@@ -2350,8 +2513,8 @@ export type RoutePath = Route['path'];
|
|
|
2350
2513
|
`;
|
|
2351
2514
|
const manifestPath = join(manifestDir, "server.ts");
|
|
2352
2515
|
await writeFile(manifestPath, content);
|
|
2353
|
-
logger$
|
|
2354
|
-
logger$
|
|
2516
|
+
logger$9.log(`Generated server manifest with ${countItems(serverRoutes)} routes, ${countItems(serverSubscribers)} subscribers, ${countItems(serverQueues)} queues, ${countItems(serverTopics)} topics`);
|
|
2517
|
+
logger$9.log(`Manifest: ${relative(process.cwd(), manifestPath)}`);
|
|
2355
2518
|
}
|
|
2356
2519
|
/**
|
|
2357
2520
|
* Filter out 'ALL' method routes from a manifest field (flat or partitioned).
|
|
@@ -2556,7 +2719,7 @@ export default app;
|
|
|
2556
2719
|
}
|
|
2557
2720
|
//#endregion
|
|
2558
2721
|
//#region src/build/index.ts
|
|
2559
|
-
const logger$
|
|
2722
|
+
const logger$8 = console;
|
|
2560
2723
|
/**
|
|
2561
2724
|
* What a build run at the workspace root reads.
|
|
2562
2725
|
*
|
|
@@ -2581,7 +2744,7 @@ async function buildCommand(options) {
|
|
|
2581
2744
|
const isAtWorkspaceRoot = cwd === workspaceRoot;
|
|
2582
2745
|
const someAppIsElsewhere = Object.values(loadedConfig.workspace.apps).some((app) => resolve(workspaceRoot, app.path) !== workspaceRoot);
|
|
2583
2746
|
if (isAtWorkspaceRoot && someAppIsElsewhere) {
|
|
2584
|
-
logger$
|
|
2747
|
+
logger$8.log("📦 Detected workspace configuration");
|
|
2585
2748
|
return workspaceBuildCommand(loadedConfig.workspace, options);
|
|
2586
2749
|
}
|
|
2587
2750
|
}
|
|
@@ -2590,15 +2753,15 @@ async function buildCommand(options) {
|
|
|
2590
2753
|
const cacheBackend = cacheBackendFor(providerOf(loadedConfig.workspace));
|
|
2591
2754
|
const productionConfigFromGkm = getProductionConfigFromGkm(config);
|
|
2592
2755
|
const production = normalizeProductionConfig(options.production ?? false, productionConfigFromGkm);
|
|
2593
|
-
if (production) logger$
|
|
2594
|
-
logger$
|
|
2595
|
-
logger$
|
|
2756
|
+
if (production) logger$8.log(`🏭 Building for PRODUCTION`);
|
|
2757
|
+
logger$8.log(`Building with providers: ${resolved.providers.join(", ")}`);
|
|
2758
|
+
logger$8.log(`Loading constructs from: ${formatRoutes(config.constructs)}`);
|
|
2596
2759
|
const telescope = production ? void 0 : normalizeTelescopeConfig(config.telescope);
|
|
2597
|
-
if (telescope) logger$
|
|
2760
|
+
if (telescope) logger$8.log(`🔭 Telescope enabled at ${telescope.path}`);
|
|
2598
2761
|
const studio = production ? void 0 : normalizeStudioConfig(config.studio);
|
|
2599
|
-
if (studio) logger$
|
|
2762
|
+
if (studio) logger$8.log(`🗄️ Studio enabled at ${studio.path}`);
|
|
2600
2763
|
const hooks = normalizeHooksConfig(config.hooks);
|
|
2601
|
-
if (hooks) logger$
|
|
2764
|
+
if (hooks) logger$8.log(`🪝 Server hooks enabled`);
|
|
2602
2765
|
return buildApp({
|
|
2603
2766
|
config,
|
|
2604
2767
|
workspaceRoot: loadedConfig.workspace.root,
|
|
@@ -2641,7 +2804,7 @@ async function buildApp(input) {
|
|
|
2641
2804
|
});
|
|
2642
2805
|
if (selfServing) {
|
|
2643
2806
|
await writeSurfaceEntry(join(appRoot, ".gkm", "server"), selfServing.source);
|
|
2644
|
-
logger$
|
|
2807
|
+
logger$8.log(`Generated a server for ${selfServing.id} from its own declaration`);
|
|
2645
2808
|
return { selfServing: selfServing.id };
|
|
2646
2809
|
}
|
|
2647
2810
|
const derived = ownersContext({
|
|
@@ -2681,14 +2844,14 @@ async function buildApp(input) {
|
|
|
2681
2844
|
topicGenerator.load(code, appRoot, bustCache)
|
|
2682
2845
|
]);
|
|
2683
2846
|
const allEndpoints = servedBy(loadedEndpoints, derived.surface);
|
|
2684
|
-
logger$
|
|
2685
|
-
logger$
|
|
2686
|
-
logger$
|
|
2687
|
-
logger$
|
|
2688
|
-
logger$
|
|
2689
|
-
logger$
|
|
2847
|
+
logger$8.log(`Found ${allEndpoints.length} endpoints`);
|
|
2848
|
+
logger$8.log(`Found ${allFunctions.length} functions`);
|
|
2849
|
+
logger$8.log(`Found ${allCrons.length} crons`);
|
|
2850
|
+
logger$8.log(`Found ${allSubscribers.length} subscribers`);
|
|
2851
|
+
logger$8.log(`Found ${allQueues.length} queues`);
|
|
2852
|
+
logger$8.log(`Found ${allTopics.length} topics`);
|
|
2690
2853
|
if (!input.serveEmpty && allEndpoints.length === 0 && allFunctions.length === 0 && allCrons.length === 0 && allSubscribers.length === 0 && allQueues.length === 0 && allTopics.length === 0) {
|
|
2691
|
-
logger$
|
|
2854
|
+
logger$8.log("No endpoints, functions, crons, subscribers, queues, or topics found to process");
|
|
2692
2855
|
return {};
|
|
2693
2856
|
}
|
|
2694
2857
|
const rootOutputDir = join(appRoot, ".gkm");
|
|
@@ -2705,7 +2868,7 @@ async function buildForProvider(provider, context, appRoot, discovered, endpoint
|
|
|
2705
2868
|
const rootOutputDir = join(appRoot, ".gkm");
|
|
2706
2869
|
const outputDir = join(rootOutputDir, provider);
|
|
2707
2870
|
await mkdir(outputDir, { recursive: true });
|
|
2708
|
-
logger$
|
|
2871
|
+
logger$8.log(`\nGenerating handlers for provider: ${provider}`);
|
|
2709
2872
|
const [routes, functionInfos, cronInfos, subscriberInfos, queueInfos, topicInfos] = await Promise.all([
|
|
2710
2873
|
endpointGenerator.build(context, endpoints, outputDir, {
|
|
2711
2874
|
provider,
|
|
@@ -2717,7 +2880,7 @@ async function buildForProvider(provider, context, appRoot, discovered, endpoint
|
|
|
2717
2880
|
queueGenerator.build(context, queues, outputDir, { provider }),
|
|
2718
2881
|
topicGenerator.build(context, topics, outputDir, { provider })
|
|
2719
2882
|
]);
|
|
2720
|
-
logger$
|
|
2883
|
+
logger$8.log(`Generated ${routes.length} routes, ${functionInfos.length} functions, ${cronInfos.length} crons, ${subscriberInfos.length} subscribers, ${queueInfos.length} queues, ${topicInfos.length} topics for ${provider}`);
|
|
2721
2884
|
const manifestRoutes = assembleManifestField(routes, endpoints);
|
|
2722
2885
|
const manifestFunctions = assembleManifestField(functionInfos, functions);
|
|
2723
2886
|
const manifestCrons = assembleManifestField(cronInfos, crons);
|
|
@@ -2737,8 +2900,8 @@ async function buildForProvider(provider, context, appRoot, discovered, endpoint
|
|
|
2737
2900
|
}, serverRouteField, manifestSubscribers, manifestQueues, manifestTopics);
|
|
2738
2901
|
let masterKey;
|
|
2739
2902
|
if (context.production?.bundle && !skipBundle) {
|
|
2740
|
-
logger$
|
|
2741
|
-
const { bundleServer } = await import("./bundler-
|
|
2903
|
+
logger$8.log(`\n📦 Bundling production server...`);
|
|
2904
|
+
const { bundleServer } = await import("./bundler-D5RcGmVM.mjs");
|
|
2742
2905
|
const allConstructs = [
|
|
2743
2906
|
...endpoints.map((e) => e.construct),
|
|
2744
2907
|
...functions.map((f) => f.construct),
|
|
@@ -2755,10 +2918,10 @@ async function buildForProvider(provider, context, appRoot, discovered, endpoint
|
|
|
2755
2918
|
stage,
|
|
2756
2919
|
constructs: allConstructs
|
|
2757
2920
|
})).masterKey;
|
|
2758
|
-
logger$
|
|
2921
|
+
logger$8.log(`✅ Bundle complete: .gkm/server/dist/server.mjs`);
|
|
2759
2922
|
if (masterKey) {
|
|
2760
|
-
logger$
|
|
2761
|
-
logger$
|
|
2923
|
+
logger$8.log(`\n🔐 Secrets encrypted for deployment`);
|
|
2924
|
+
logger$8.log(` Deploy with: GKM_MASTER_KEY=${masterKey}`);
|
|
2762
2925
|
}
|
|
2763
2926
|
}
|
|
2764
2927
|
return { masterKey };
|
|
@@ -2845,19 +3008,19 @@ async function workspaceBuildCommand(workspace, options) {
|
|
|
2845
3008
|
const apps = Object.entries(workspace.apps);
|
|
2846
3009
|
const backendApps = apps.filter(([, app]) => app.type === "backend");
|
|
2847
3010
|
const frontendApps = apps.filter(([, app]) => app.type === "web");
|
|
2848
|
-
logger$
|
|
2849
|
-
logger$
|
|
2850
|
-
logger$
|
|
2851
|
-
if (options.production) logger$
|
|
3011
|
+
logger$8.log(`\n🏗️ Building workspace: ${workspace.name}`);
|
|
3012
|
+
logger$8.log(` Backend apps: ${backendApps.map(([name]) => name).join(", ") || "none"}`);
|
|
3013
|
+
logger$8.log(` Frontend apps: ${frontendApps.map(([name]) => name).join(", ") || "none"}`);
|
|
3014
|
+
if (options.production) logger$8.log(` 🏭 Production mode enabled`);
|
|
2852
3015
|
const buildOrder = getAppBuildOrder(workspace);
|
|
2853
|
-
logger$
|
|
3016
|
+
logger$8.log(` Build order: ${buildOrder.join(" → ")}`);
|
|
2854
3017
|
const pm = detectPackageManager$2();
|
|
2855
|
-
logger$
|
|
3018
|
+
logger$8.log(`\n📦 Using ${pm} with Turbo for parallel builds...\n`);
|
|
2856
3019
|
try {
|
|
2857
3020
|
const { filters, unpackaged } = turboFilters(workspace);
|
|
2858
3021
|
if (unpackaged.length > 0) throw new Error(`No package.json for workspace app(s): ${unpackaged.join(", ")}. Each app needs one — turbo builds packages, and gkm reads the name back to know which app it is building.`);
|
|
2859
3022
|
const turboCommand = getTurboCommand(pm, filters);
|
|
2860
|
-
logger$
|
|
3023
|
+
logger$8.log(`Running: ${turboCommand}`);
|
|
2861
3024
|
await new Promise((resolve, reject) => {
|
|
2862
3025
|
const child = spawn(turboCommand, {
|
|
2863
3026
|
shell: true,
|
|
@@ -2885,15 +3048,15 @@ async function workspaceBuildCommand(workspace, options) {
|
|
|
2885
3048
|
outputPath
|
|
2886
3049
|
});
|
|
2887
3050
|
}
|
|
2888
|
-
logger$
|
|
2889
|
-
logger$
|
|
3051
|
+
logger$8.log(`\n✅ Workspace build complete!`);
|
|
3052
|
+
logger$8.log(`\n📋 Build Summary:`);
|
|
2890
3053
|
for (const result of results) {
|
|
2891
3054
|
const icon = result.type === "backend" ? "⚙️" : result.type === "mobile" ? "📱" : "🌐";
|
|
2892
|
-
logger$
|
|
3055
|
+
logger$8.log(` ${icon} ${result.appName}: ${result.outputPath || "built"}`);
|
|
2893
3056
|
}
|
|
2894
3057
|
} catch (error) {
|
|
2895
3058
|
const errorMessage = error instanceof Error ? error.message : "Build failed";
|
|
2896
|
-
logger$
|
|
3059
|
+
logger$8.log(`\n❌ Build failed: ${errorMessage}`);
|
|
2897
3060
|
for (const [appName, app] of apps) results.push({
|
|
2898
3061
|
appName,
|
|
2899
3062
|
type: app.type,
|
|
@@ -3749,7 +3912,7 @@ async function createDnsProvider(options) {
|
|
|
3749
3912
|
if (isDnsProvider(config.provider)) return config.provider;
|
|
3750
3913
|
const provider = config.provider;
|
|
3751
3914
|
if (provider === "hostinger") {
|
|
3752
|
-
const { HostingerProvider } = await import("./HostingerProvider-
|
|
3915
|
+
const { HostingerProvider } = await import("./HostingerProvider-Btxpto71.mjs");
|
|
3753
3916
|
return new HostingerProvider();
|
|
3754
3917
|
}
|
|
3755
3918
|
if (provider === "route53") {
|
|
@@ -3771,7 +3934,7 @@ async function createDnsProvider(options) {
|
|
|
3771
3934
|
*
|
|
3772
3935
|
* Handles automatic DNS record creation for deployed applications.
|
|
3773
3936
|
*/
|
|
3774
|
-
const logger$
|
|
3937
|
+
const logger$7 = console;
|
|
3775
3938
|
/**
|
|
3776
3939
|
* Check if DNS config is legacy format (single domain with `domain` property)
|
|
3777
3940
|
*/
|
|
@@ -3808,7 +3971,7 @@ function groupHostnamesByDomain(appHostnames, dnsConfig) {
|
|
|
3808
3971
|
for (const [appName, hostname] of appHostnames) {
|
|
3809
3972
|
const rootDomain = findRootDomain(hostname, dnsConfig);
|
|
3810
3973
|
if (!rootDomain) {
|
|
3811
|
-
logger$
|
|
3974
|
+
logger$7.log(` ⚠ No DNS config found for hostname: ${hostname}`);
|
|
3812
3975
|
continue;
|
|
3813
3976
|
}
|
|
3814
3977
|
if (!grouped.has(rootDomain)) grouped.set(rootDomain, /* @__PURE__ */ new Map());
|
|
@@ -3858,10 +4021,10 @@ function generateRequiredRecords(appHostnames, rootDomain, serverIp) {
|
|
|
3858
4021
|
* Print DNS records table
|
|
3859
4022
|
*/
|
|
3860
4023
|
function printDnsRecordsTable(records, rootDomain) {
|
|
3861
|
-
logger$
|
|
3862
|
-
logger$
|
|
3863
|
-
logger$
|
|
3864
|
-
logger$
|
|
4024
|
+
logger$7.log(`\n 📋 DNS Records for ${rootDomain}:`);
|
|
4025
|
+
logger$7.log(" ┌─────────────────────────────────────┬──────┬─────────────────┬────────┐");
|
|
4026
|
+
logger$7.log(" │ Subdomain │ Type │ Value │ Status │");
|
|
4027
|
+
logger$7.log(" ├─────────────────────────────────────┼──────┼─────────────────┼────────┤");
|
|
3865
4028
|
for (const record of records) {
|
|
3866
4029
|
const subdomain = record.subdomain.padEnd(35);
|
|
3867
4030
|
const type = record.type.padEnd(4);
|
|
@@ -3871,18 +4034,18 @@ function printDnsRecordsTable(records, rootDomain) {
|
|
|
3871
4034
|
else if (record.created) status = "✓ new";
|
|
3872
4035
|
else if (record.existed) status = "✓";
|
|
3873
4036
|
else status = "?";
|
|
3874
|
-
logger$
|
|
4037
|
+
logger$7.log(` │ ${subdomain} │ ${type} │ ${value} │ ${status.padEnd(6)} │`);
|
|
3875
4038
|
}
|
|
3876
|
-
logger$
|
|
4039
|
+
logger$7.log(" └─────────────────────────────────────┴──────┴─────────────────┴────────┘");
|
|
3877
4040
|
}
|
|
3878
4041
|
/**
|
|
3879
4042
|
* Print DNS records in a simple format for manual setup
|
|
3880
4043
|
*/
|
|
3881
4044
|
function printDnsRecordsSimple(records, rootDomain) {
|
|
3882
|
-
logger$
|
|
3883
|
-
logger$
|
|
3884
|
-
for (const record of records) logger$
|
|
3885
|
-
logger$
|
|
4045
|
+
logger$7.log("\n 📋 Required DNS Records:");
|
|
4046
|
+
logger$7.log(` Add these A records to your DNS provider (${rootDomain}):\n`);
|
|
4047
|
+
for (const record of records) logger$7.log(` ${record.subdomain} → ${record.value} (A record)`);
|
|
4048
|
+
logger$7.log("");
|
|
3886
4049
|
}
|
|
3887
4050
|
/**
|
|
3888
4051
|
* Create DNS records for a single domain using its configured provider
|
|
@@ -3894,7 +4057,7 @@ async function createDnsRecordsForDomain(records, rootDomain, providerConfig) {
|
|
|
3894
4057
|
provider = await createDnsProvider({ config: providerConfig });
|
|
3895
4058
|
} catch (error) {
|
|
3896
4059
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
3897
|
-
logger$
|
|
4060
|
+
logger$7.log(` ⚠ Failed to create DNS provider for ${rootDomain}: ${message}`);
|
|
3898
4061
|
return records.map((r) => ({
|
|
3899
4062
|
...r,
|
|
3900
4063
|
error: message
|
|
@@ -3948,7 +4111,7 @@ async function createDnsRecordsForDomain(records, rootDomain, providerConfig) {
|
|
|
3948
4111
|
}
|
|
3949
4112
|
} catch (error) {
|
|
3950
4113
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
3951
|
-
logger$
|
|
4114
|
+
logger$7.log(` ⚠ Failed to create DNS records for ${rootDomain}: ${message}`);
|
|
3952
4115
|
return records.map((r) => ({
|
|
3953
4116
|
hostname: r.hostname,
|
|
3954
4117
|
subdomain: r.subdomain,
|
|
@@ -3975,20 +4138,20 @@ async function createDnsRecordsForDomain(records, rootDomain, providerConfig) {
|
|
|
3975
4138
|
async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint, state) {
|
|
3976
4139
|
if (!dnsConfig) return null;
|
|
3977
4140
|
const normalizedConfig = normalizeDnsConfig(dnsConfig);
|
|
3978
|
-
logger$
|
|
4141
|
+
logger$7.log("\n🌐 Setting up DNS records...");
|
|
3979
4142
|
let serverIp;
|
|
3980
4143
|
try {
|
|
3981
4144
|
const endpointUrl = new URL(dokployEndpoint);
|
|
3982
4145
|
serverIp = await resolveHostnameToIp(endpointUrl.hostname);
|
|
3983
|
-
logger$
|
|
4146
|
+
logger$7.log(` Server IP: ${serverIp} (from ${endpointUrl.hostname})`);
|
|
3984
4147
|
} catch (error) {
|
|
3985
4148
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
3986
|
-
logger$
|
|
4149
|
+
logger$7.log(` ⚠ Failed to resolve server IP: ${message}`);
|
|
3987
4150
|
return null;
|
|
3988
4151
|
}
|
|
3989
4152
|
const groupedHostnames = groupHostnamesByDomain(appHostnames, normalizedConfig);
|
|
3990
4153
|
if (groupedHostnames.size === 0) {
|
|
3991
|
-
logger$
|
|
4154
|
+
logger$7.log(" No DNS records needed (no hostnames match configured domains)");
|
|
3992
4155
|
return {
|
|
3993
4156
|
records: [],
|
|
3994
4157
|
success: true,
|
|
@@ -4000,22 +4163,22 @@ async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint, state) {
|
|
|
4000
4163
|
for (const [rootDomain, domainHostnames] of groupedHostnames) {
|
|
4001
4164
|
const providerConfig = normalizedConfig[rootDomain];
|
|
4002
4165
|
if (!providerConfig) {
|
|
4003
|
-
logger$
|
|
4166
|
+
logger$7.log(` ⚠ No provider config for ${rootDomain}`);
|
|
4004
4167
|
continue;
|
|
4005
4168
|
}
|
|
4006
4169
|
const providerName = typeof providerConfig.provider === "string" ? providerConfig.provider : "custom";
|
|
4007
4170
|
const requiredRecords = generateRequiredRecords(domainHostnames, rootDomain, serverIp);
|
|
4008
4171
|
if (requiredRecords.length === 0) continue;
|
|
4009
|
-
logger$
|
|
4172
|
+
logger$7.log(` Creating DNS records for ${rootDomain} (${providerName})...`);
|
|
4010
4173
|
const domainRecords = await createDnsRecordsForDomain(requiredRecords, rootDomain, providerConfig);
|
|
4011
4174
|
allRecords.push(...domainRecords);
|
|
4012
4175
|
const created = domainRecords.filter((r) => r.created).length;
|
|
4013
4176
|
const existed = domainRecords.filter((r) => r.existed).length;
|
|
4014
4177
|
const failed = domainRecords.filter((r) => r.error).length;
|
|
4015
|
-
if (created > 0) logger$
|
|
4016
|
-
if (existed > 0) logger$
|
|
4178
|
+
if (created > 0) logger$7.log(` ✓ Created ${created} DNS record(s) for ${rootDomain}`);
|
|
4179
|
+
if (existed > 0) logger$7.log(` ✓ ${existed} record(s) already exist for ${rootDomain}`);
|
|
4017
4180
|
if (failed > 0) {
|
|
4018
|
-
logger$
|
|
4181
|
+
logger$7.log(` ⚠ ${failed} record(s) failed for ${rootDomain}`);
|
|
4019
4182
|
hasFailures = true;
|
|
4020
4183
|
}
|
|
4021
4184
|
if (state) {
|
|
@@ -4052,10 +4215,10 @@ async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint, state) {
|
|
|
4052
4215
|
*/
|
|
4053
4216
|
async function verifyDnsRecords(appHostnames, serverIp, state) {
|
|
4054
4217
|
const results = [];
|
|
4055
|
-
logger$
|
|
4218
|
+
logger$7.log("\n🔍 Verifying DNS records...");
|
|
4056
4219
|
for (const [appName, hostname] of appHostnames) {
|
|
4057
4220
|
if (isDnsVerified(state, hostname, serverIp)) {
|
|
4058
|
-
logger$
|
|
4221
|
+
logger$7.log(` ✓ ${hostname} (previously verified)`);
|
|
4059
4222
|
results.push({
|
|
4060
4223
|
hostname,
|
|
4061
4224
|
appName,
|
|
@@ -4069,7 +4232,7 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
|
|
|
4069
4232
|
const resolvedIp = await resolveHostnameToIp(hostname);
|
|
4070
4233
|
if (resolvedIp === serverIp) {
|
|
4071
4234
|
setDnsVerification(state, hostname, serverIp);
|
|
4072
|
-
logger$
|
|
4235
|
+
logger$7.log(` ✓ ${hostname} → ${resolvedIp}`);
|
|
4073
4236
|
results.push({
|
|
4074
4237
|
hostname,
|
|
4075
4238
|
appName,
|
|
@@ -4078,7 +4241,7 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
|
|
|
4078
4241
|
expectedIp: serverIp
|
|
4079
4242
|
});
|
|
4080
4243
|
} else {
|
|
4081
|
-
logger$
|
|
4244
|
+
logger$7.log(` ⚠ ${hostname} resolves to ${resolvedIp}, expected ${serverIp}`);
|
|
4082
4245
|
results.push({
|
|
4083
4246
|
hostname,
|
|
4084
4247
|
appName,
|
|
@@ -4089,7 +4252,7 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
|
|
|
4089
4252
|
}
|
|
4090
4253
|
} catch (error) {
|
|
4091
4254
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4092
|
-
logger$
|
|
4255
|
+
logger$7.log(` ⚠ ${hostname} DNS not propagated (${message})`);
|
|
4093
4256
|
results.push({
|
|
4094
4257
|
hostname,
|
|
4095
4258
|
appName,
|
|
@@ -4103,9 +4266,9 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
|
|
|
4103
4266
|
const skipped = results.filter((r) => r.skipped).length;
|
|
4104
4267
|
const pending = results.filter((r) => !r.verified).length;
|
|
4105
4268
|
if (pending > 0) {
|
|
4106
|
-
logger$
|
|
4107
|
-
logger$
|
|
4108
|
-
} else if (skipped > 0) logger$
|
|
4269
|
+
logger$7.log(`\n ${verified} verified, ${pending} pending propagation`);
|
|
4270
|
+
logger$7.log(" DNS changes may take 5-30 minutes to propagate");
|
|
4271
|
+
} else if (skipped > 0) logger$7.log(` ${verified} verified (${skipped} from cache)`);
|
|
4109
4272
|
return results;
|
|
4110
4273
|
}
|
|
4111
4274
|
//#endregion
|
|
@@ -4138,43 +4301,19 @@ function resolveHost(appName, app, stage, dokployConfig, isMainFrontend) {
|
|
|
4138
4301
|
return `${appName}.${baseDomain}`;
|
|
4139
4302
|
}
|
|
4140
4303
|
/**
|
|
4141
|
-
*
|
|
4142
|
-
*
|
|
4143
|
-
* Three rules, in order, and the last one is the point:
|
|
4144
|
-
*
|
|
4145
|
-
* 1. **One site** — it is the root. Unambiguous, so nothing has to be said.
|
|
4146
|
-
* 2. **Named `web`** — the convention wins, because people already rely on it.
|
|
4147
|
-
* 3. **Declared `root: true`** — the site says so itself.
|
|
4148
|
-
*
|
|
4149
|
-
* Anything else throws. It used to take the *first* `type: 'web'` it iterated,
|
|
4150
|
-
* and that object is built from the manifest, which is built in glob traversal
|
|
4151
|
-
* order — so two sites with neither named `web` meant renaming a file could
|
|
4152
|
-
* move the production root domain, silently. The same shape as
|
|
4153
|
-
* `CacheIsAmbiguous`: unambiguous with one and arbitrary with two, so two is an
|
|
4154
|
-
* error rather than a coin toss.
|
|
4304
|
+
* Whether this app is the site the base domain points at — see `rootSite`,
|
|
4305
|
+
* which the local edge asks too.
|
|
4155
4306
|
*
|
|
4156
4307
|
* @throws {AmbiguousRootSite} when several sites could be the root and none says it is
|
|
4157
4308
|
*/
|
|
4158
4309
|
function isMainFrontendApp(appName, app, allApps) {
|
|
4159
4310
|
if (app.type !== "web") return false;
|
|
4160
|
-
const sites = Object.entries(allApps).filter(([, a]) => a.type === "web")
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
}
|
|
4167
|
-
/** Several sites could hold the base domain, and none of them says it does. */
|
|
4168
|
-
var AmbiguousRootSite = class extends Error {
|
|
4169
|
-
sites;
|
|
4170
|
-
tooMany;
|
|
4171
|
-
constructor(sites, tooMany = false) {
|
|
4172
|
-
super(tooMany ? `More than one site declares \`root: true\` — ${sites.join(", ")}. The base domain points at one of them.` : `${sites.length} sites and nothing says which holds the base domain: ${sites.join(", ")}. Name one of them \`web\`, or declare \`root: true\` on the one the base domain points at. It used to be whichever the glob reached first, which is not a thing to decide a production hostname.`);
|
|
4173
|
-
this.sites = sites;
|
|
4174
|
-
this.tooMany = tooMany;
|
|
4175
|
-
this.name = "AmbiguousRootSite";
|
|
4176
|
-
}
|
|
4177
|
-
};
|
|
4311
|
+
const sites = Object.entries(allApps).filter(([, a]) => a.type === "web").map(([name, a]) => ({
|
|
4312
|
+
name,
|
|
4313
|
+
root: a.root
|
|
4314
|
+
}));
|
|
4315
|
+
return rootSite(sites) === appName;
|
|
4316
|
+
}
|
|
4178
4317
|
/**
|
|
4179
4318
|
* Get public URL arg names from app dependencies.
|
|
4180
4319
|
*
|
|
@@ -5037,7 +5176,7 @@ CMD ["nginx", "-g", "daemon off;"]
|
|
|
5037
5176
|
}
|
|
5038
5177
|
//#endregion
|
|
5039
5178
|
//#region src/docker/index.ts
|
|
5040
|
-
const logger$
|
|
5179
|
+
const logger$6 = console;
|
|
5041
5180
|
/**
|
|
5042
5181
|
* Docker command implementation
|
|
5043
5182
|
* Generates Dockerfile, docker-compose.yml, and related files
|
|
@@ -5048,7 +5187,7 @@ const logger$7 = console;
|
|
|
5048
5187
|
async function dockerCommand(options) {
|
|
5049
5188
|
const loadedConfig = await loadWorkspaceConfig();
|
|
5050
5189
|
if (loadedConfig.type === "workspace") {
|
|
5051
|
-
logger$
|
|
5190
|
+
logger$6.log("📦 Detected workspace configuration");
|
|
5052
5191
|
return workspaceDockerCommand(loadedConfig.workspace);
|
|
5053
5192
|
}
|
|
5054
5193
|
const config = await loadConfig();
|
|
@@ -5068,7 +5207,7 @@ async function dockerCommand(options) {
|
|
|
5068
5207
|
if (inMonorepo && !useSlim) {
|
|
5069
5208
|
if (hasTurbo) {
|
|
5070
5209
|
useTurbo = true;
|
|
5071
|
-
logger$
|
|
5210
|
+
logger$6.log(" Detected monorepo with turbo.json - using turbo prune");
|
|
5072
5211
|
} 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");
|
|
5073
5212
|
}
|
|
5074
5213
|
let turboPackage = options.turboPackage ?? dockerConfig.imageName;
|
|
@@ -5076,7 +5215,7 @@ async function dockerCommand(options) {
|
|
|
5076
5215
|
const pkg = __require(`${process.cwd()}/package.json`);
|
|
5077
5216
|
if (pkg.name) {
|
|
5078
5217
|
turboPackage = pkg.name;
|
|
5079
|
-
logger$
|
|
5218
|
+
logger$6.log(` Turbo package: ${turboPackage}`);
|
|
5080
5219
|
}
|
|
5081
5220
|
} catch {}
|
|
5082
5221
|
const templateOptions = {
|
|
@@ -5093,16 +5232,16 @@ async function dockerCommand(options) {
|
|
|
5093
5232
|
const dockerMode = useSlim ? "slim" : useTurbo ? "turbo" : "multi-stage";
|
|
5094
5233
|
const dockerfilePath = join(dockerDir, "Dockerfile");
|
|
5095
5234
|
await writeFile(dockerfilePath, dockerfile);
|
|
5096
|
-
logger$
|
|
5235
|
+
logger$6.log(`Generated: .gkm/docker/Dockerfile (${dockerMode}, ${packageManager})`);
|
|
5097
5236
|
const composePath = await writeConstructsCompose(loadedConfig.workspace);
|
|
5098
5237
|
const dockerignore = generateDockerignore();
|
|
5099
5238
|
const dockerignorePath = join(process.cwd(), ".dockerignore");
|
|
5100
5239
|
await writeFile(dockerignorePath, dockerignore);
|
|
5101
|
-
logger$
|
|
5240
|
+
logger$6.log("Generated: .dockerignore (project root)");
|
|
5102
5241
|
const entrypoint = generateDockerEntrypoint();
|
|
5103
5242
|
const entrypointPath = join(dockerDir, "docker-entrypoint.sh");
|
|
5104
5243
|
await writeFile(entrypointPath, entrypoint);
|
|
5105
|
-
logger$
|
|
5244
|
+
logger$6.log("Generated: .gkm/docker/docker-entrypoint.sh");
|
|
5106
5245
|
const result = {
|
|
5107
5246
|
dockerfile: dockerfilePath,
|
|
5108
5247
|
dockerCompose: composePath,
|
|
@@ -5121,13 +5260,13 @@ async function dockerCommand(options) {
|
|
|
5121
5260
|
function ensureLockfile(cwd) {
|
|
5122
5261
|
const lockfilePath = findLockfilePath(cwd);
|
|
5123
5262
|
if (!lockfilePath) {
|
|
5124
|
-
logger$
|
|
5263
|
+
logger$6.warn("\n⚠️ No lockfile found. Docker build may fail or use stale dependencies.");
|
|
5125
5264
|
return null;
|
|
5126
5265
|
}
|
|
5127
5266
|
const lockfileName = basename(lockfilePath);
|
|
5128
5267
|
const localLockfile = join(cwd, lockfileName);
|
|
5129
5268
|
if (lockfilePath === localLockfile) return null;
|
|
5130
|
-
logger$
|
|
5269
|
+
logger$6.log(` Copying ${lockfileName} from monorepo root...`);
|
|
5131
5270
|
copyFileSync(lockfilePath, localLockfile);
|
|
5132
5271
|
return () => {
|
|
5133
5272
|
try {
|
|
@@ -5143,7 +5282,7 @@ async function buildDockerImage(imageName, options) {
|
|
|
5143
5282
|
const tag = options.tag ?? "latest";
|
|
5144
5283
|
const registry = options.registry;
|
|
5145
5284
|
const fullImageName = registry ? `${registry}/${imageName}:${tag}` : `${imageName}:${tag}`;
|
|
5146
|
-
logger$
|
|
5285
|
+
logger$6.log(`\n🐳 Building Docker image: ${fullImageName}`);
|
|
5147
5286
|
const cwd = process.cwd();
|
|
5148
5287
|
const cleanup = ensureLockfile(cwd);
|
|
5149
5288
|
try {
|
|
@@ -5155,7 +5294,7 @@ async function buildDockerImage(imageName, options) {
|
|
|
5155
5294
|
DOCKER_BUILDKIT: "1"
|
|
5156
5295
|
}
|
|
5157
5296
|
});
|
|
5158
|
-
logger$
|
|
5297
|
+
logger$6.log(`✅ Docker image built: ${fullImageName}`);
|
|
5159
5298
|
} catch (error) {
|
|
5160
5299
|
throw new Error(`Failed to build Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
5161
5300
|
} finally {
|
|
@@ -5170,13 +5309,13 @@ async function pushDockerImage(imageName, options) {
|
|
|
5170
5309
|
const registry = options.registry;
|
|
5171
5310
|
if (!registry) throw new Error("Registry is required to push Docker image. Use --registry or configure docker.registry in gkm.config.ts");
|
|
5172
5311
|
const fullImageName = `${registry}/${imageName}:${tag}`;
|
|
5173
|
-
logger$
|
|
5312
|
+
logger$6.log(`\n🚀 Pushing Docker image: ${fullImageName}`);
|
|
5174
5313
|
try {
|
|
5175
5314
|
execSync(`docker push ${fullImageName}`, {
|
|
5176
5315
|
cwd: process.cwd(),
|
|
5177
5316
|
stdio: "inherit"
|
|
5178
5317
|
});
|
|
5179
|
-
logger$
|
|
5318
|
+
logger$6.log(`✅ Docker image pushed: ${fullImageName}`);
|
|
5180
5319
|
} catch (error) {
|
|
5181
5320
|
throw new Error(`Failed to push Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
5182
5321
|
}
|
|
@@ -5201,21 +5340,21 @@ function getAppPackageName(appPath) {
|
|
|
5201
5340
|
async function workspaceDockerCommand(workspace) {
|
|
5202
5341
|
const results = [];
|
|
5203
5342
|
const apps = Object.entries(workspace.apps);
|
|
5204
|
-
logger$
|
|
5343
|
+
logger$6.log(`\n🐳 Generating Dockerfiles for workspace: ${workspace.name}`);
|
|
5205
5344
|
const dockerDir = join(workspace.root, ".gkm", "docker");
|
|
5206
5345
|
await mkdir(dockerDir, { recursive: true });
|
|
5207
5346
|
const packageManager = detectPackageManager$1(workspace.root);
|
|
5208
|
-
logger$
|
|
5347
|
+
logger$6.log(` Package manager: ${packageManager}`);
|
|
5209
5348
|
for (const [appName, app] of apps) {
|
|
5210
5349
|
if (app.type === "mobile") {
|
|
5211
|
-
logger$
|
|
5350
|
+
logger$6.log(`\n 📱 Skipping Docker for ${appName} (mobile app — deploy via framework toolchain)`);
|
|
5212
5351
|
continue;
|
|
5213
5352
|
}
|
|
5214
5353
|
const appPath = app.path;
|
|
5215
5354
|
const turboPackage = getAppPackageName(join(workspace.root, appPath)) ?? appName;
|
|
5216
5355
|
const imageName = appName;
|
|
5217
5356
|
const buildType = !!app.entry ? "entry" : app.type;
|
|
5218
|
-
logger$
|
|
5357
|
+
logger$6.log(`\n 📄 Generating Dockerfile for ${appName} (${buildType})`);
|
|
5219
5358
|
let dockerfile;
|
|
5220
5359
|
if (app.type === "web") {
|
|
5221
5360
|
const publicUrlArgs = getPublicUrlArgNames(app);
|
|
@@ -5259,7 +5398,7 @@ async function workspaceDockerCommand(workspace) {
|
|
|
5259
5398
|
});
|
|
5260
5399
|
const dockerfilePath = join(dockerDir, `Dockerfile.${appName}`);
|
|
5261
5400
|
await writeFile(dockerfilePath, dockerfile);
|
|
5262
|
-
logger$
|
|
5401
|
+
logger$6.log(` Generated: .gkm/docker/Dockerfile.${appName}`);
|
|
5263
5402
|
results.push({
|
|
5264
5403
|
appName,
|
|
5265
5404
|
type: app.type,
|
|
@@ -5270,13 +5409,13 @@ async function workspaceDockerCommand(workspace) {
|
|
|
5270
5409
|
const dockerignore = generateDockerignore();
|
|
5271
5410
|
const dockerignorePath = join(workspace.root, ".dockerignore");
|
|
5272
5411
|
await writeFile(dockerignorePath, dockerignore);
|
|
5273
|
-
logger$
|
|
5412
|
+
logger$6.log(`\n Generated: .dockerignore (workspace root)`);
|
|
5274
5413
|
const composePath = await writeConstructsCompose(workspace);
|
|
5275
|
-
logger$
|
|
5276
|
-
logger$
|
|
5414
|
+
logger$6.log(`\n✅ Generated ${results.length} Dockerfile(s) + ${COMPOSE_PATH}`);
|
|
5415
|
+
logger$6.log("\n📋 Build commands:");
|
|
5277
5416
|
for (const result of results) {
|
|
5278
5417
|
const icon = result.type === "backend" ? "⚙️" : result.type === "mobile" ? "📱" : "🌐";
|
|
5279
|
-
logger$
|
|
5418
|
+
logger$6.log(` ${icon} docker build -f .gkm/docker/Dockerfile.${result.appName} -t ${result.imageName} .`);
|
|
5280
5419
|
}
|
|
5281
5420
|
printRunInstructions(workspace);
|
|
5282
5421
|
return {
|
|
@@ -5297,18 +5436,18 @@ async function writeConstructsCompose(workspace) {
|
|
|
5297
5436
|
stage: workspace.stages.local,
|
|
5298
5437
|
start: false
|
|
5299
5438
|
});
|
|
5300
|
-
logger$
|
|
5439
|
+
logger$6.log(` Generated: ${COMPOSE_PATH} (project root)`);
|
|
5301
5440
|
return join(workspace.root, COMPOSE_PATH);
|
|
5302
5441
|
}
|
|
5303
5442
|
function printRunInstructions(workspace) {
|
|
5304
5443
|
const project = existsSync(join(workspace.root, "docker-compose.yml")) ? " -f docker-compose.yml" : "";
|
|
5305
|
-
logger$
|
|
5306
|
-
logger$
|
|
5307
|
-
logger$
|
|
5444
|
+
logger$6.log("\n📋 Run everything (the apps sit behind the \"apps\" profile):");
|
|
5445
|
+
logger$6.log(" gkm setup # starts the containers and creates what they hold");
|
|
5446
|
+
logger$6.log(` docker compose -f ${COMPOSE_PATH}${project} --profile apps up --build`);
|
|
5308
5447
|
}
|
|
5309
5448
|
//#endregion
|
|
5310
5449
|
//#region src/deploy/docker.ts
|
|
5311
|
-
const logger$
|
|
5450
|
+
const logger$5 = console;
|
|
5312
5451
|
/**
|
|
5313
5452
|
* Get the full image reference
|
|
5314
5453
|
*/
|
|
@@ -5323,9 +5462,9 @@ function getImageRef(registry, imageName, tag) {
|
|
|
5323
5462
|
* @param buildArgs - Build arguments to pass to docker build
|
|
5324
5463
|
*/
|
|
5325
5464
|
async function buildImage(imageRef, _appName, buildArgs) {
|
|
5326
|
-
logger$
|
|
5465
|
+
logger$5.log(`\n🔨 Building Docker image: ${imageRef}`);
|
|
5327
5466
|
const cwd = process.cwd();
|
|
5328
|
-
logger$
|
|
5467
|
+
logger$5.log(" Generating Dockerfile for the pre-built bundle...");
|
|
5329
5468
|
await dockerCommand({ slim: true });
|
|
5330
5469
|
const dockerfilePath = join(cwd, ".gkm", "docker", "Dockerfile");
|
|
5331
5470
|
const buildCwd = cwd;
|
|
@@ -5347,7 +5486,7 @@ async function buildImage(imageRef, _appName, buildArgs) {
|
|
|
5347
5486
|
DOCKER_BUILDKIT: "1"
|
|
5348
5487
|
}
|
|
5349
5488
|
});
|
|
5350
|
-
logger$
|
|
5489
|
+
logger$5.log(`✅ Image built: ${imageRef}`);
|
|
5351
5490
|
} catch (error) {
|
|
5352
5491
|
throw new Error(`Failed to build Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
5353
5492
|
}
|
|
@@ -5356,13 +5495,13 @@ async function buildImage(imageRef, _appName, buildArgs) {
|
|
|
5356
5495
|
* Push Docker image to registry
|
|
5357
5496
|
*/
|
|
5358
5497
|
async function pushImage(imageRef) {
|
|
5359
|
-
logger$
|
|
5498
|
+
logger$5.log(`\n☁️ Pushing image: ${imageRef}`);
|
|
5360
5499
|
try {
|
|
5361
5500
|
execSync(`docker push ${imageRef}`, {
|
|
5362
5501
|
cwd: process.cwd(),
|
|
5363
5502
|
stdio: "inherit"
|
|
5364
5503
|
});
|
|
5365
|
-
logger$
|
|
5504
|
+
logger$5.log(`✅ Image pushed: ${imageRef}`);
|
|
5366
5505
|
} catch (error) {
|
|
5367
5506
|
throw new Error(`Failed to push Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
5368
5507
|
}
|
|
@@ -5376,18 +5515,18 @@ async function deployDocker(options) {
|
|
|
5376
5515
|
const imageRef = getImageRef(config.registry, imageName, tag);
|
|
5377
5516
|
await buildImage(imageRef, config.appName, buildArgs);
|
|
5378
5517
|
if (!skipPush) {
|
|
5379
|
-
if (!config.registry) logger$
|
|
5518
|
+
if (!config.registry) logger$5.warn("\n⚠️ No registry configured. Use --skip-push or configure docker.registry in gkm.config.ts");
|
|
5380
5519
|
else await pushImage(imageRef);
|
|
5381
5520
|
}
|
|
5382
|
-
logger$
|
|
5383
|
-
logger$
|
|
5384
|
-
logger$
|
|
5385
|
-
logger$
|
|
5521
|
+
logger$5.log("\n✅ Docker deployment ready!");
|
|
5522
|
+
logger$5.log(`\n📋 Deployment details:`);
|
|
5523
|
+
logger$5.log(` Image: ${imageRef}`);
|
|
5524
|
+
logger$5.log(` Stage: ${stage}`);
|
|
5386
5525
|
if (masterKey) {
|
|
5387
|
-
logger$
|
|
5388
|
-
logger$
|
|
5389
|
-
logger$
|
|
5390
|
-
logger$
|
|
5526
|
+
logger$5.log(`\n🔐 Deploy with this environment variable:`);
|
|
5527
|
+
logger$5.log(` GKM_MASTER_KEY=${masterKey}`);
|
|
5528
|
+
logger$5.log("\n Example docker run:");
|
|
5529
|
+
logger$5.log(` docker run -e GKM_MASTER_KEY=${masterKey} ${imageRef}`);
|
|
5391
5530
|
}
|
|
5392
5531
|
return {
|
|
5393
5532
|
imageRef,
|
|
@@ -6766,7 +6905,7 @@ async function sniffAllApps(apps, workspacePath, options = {}) {
|
|
|
6766
6905
|
*
|
|
6767
6906
|
* @module deploy
|
|
6768
6907
|
*/
|
|
6769
|
-
const logger$
|
|
6908
|
+
const logger$4 = console;
|
|
6770
6909
|
/**
|
|
6771
6910
|
* Prompt for input
|
|
6772
6911
|
*/
|
|
@@ -6840,7 +6979,7 @@ async function waitForPostgres(host, port, user, password, database, maxRetries
|
|
|
6840
6979
|
return;
|
|
6841
6980
|
} catch {
|
|
6842
6981
|
if (i < maxRetries - 1) {
|
|
6843
|
-
logger$
|
|
6982
|
+
logger$4.log(` Waiting for Postgres... (${i + 1}/${maxRetries})`);
|
|
6844
6983
|
await new Promise((r) => setTimeout(r, retryIntervalMs));
|
|
6845
6984
|
}
|
|
6846
6985
|
}
|
|
@@ -6883,10 +7022,10 @@ async function applyDeclaredStatements(api, postgres, serverHostname, statements
|
|
|
6883
7022
|
break;
|
|
6884
7023
|
} catch (error) {
|
|
6885
7024
|
if (!(error instanceof Error ? error.message : String(error)).includes("already in use")) throw error;
|
|
6886
|
-
logger$
|
|
7025
|
+
logger$4.log(` Port ${candidate} is taken; trying another...`);
|
|
6887
7026
|
}
|
|
6888
7027
|
if (externalPort === void 0) throw new Error(`Could not publish a port for ${postgres.appName}. In use on this server: ${[...taken].sort((a, b) => a - b).join(", ") || "none reported"}. The role DDL needs to reach the cluster from here.`);
|
|
6889
|
-
logger$
|
|
7028
|
+
logger$4.log(` Publishing ${postgres.appName} on ${externalPort}...`);
|
|
6890
7029
|
}
|
|
6891
7030
|
await api.deployPostgres(postgres.postgresId);
|
|
6892
7031
|
await waitForPostgres(serverHostname, externalPort, postgres.databaseUser, postgres.databasePassword, postgres.databaseName);
|
|
@@ -6910,7 +7049,7 @@ async function applyDeclaredStatements(api, postgres, serverHostname, statements
|
|
|
6910
7049
|
const unpublish = async () => {
|
|
6911
7050
|
if (!opened) return;
|
|
6912
7051
|
await api.savePostgresExternalPort(postgres.postgresId, null).then(() => api.deployPostgres(postgres.postgresId)).catch(() => {
|
|
6913
|
-
logger$
|
|
7052
|
+
logger$4.log(` ⚠ Could not close external port ${externalPort} on ${postgres.appName} — close it in Dokploy.`);
|
|
6914
7053
|
});
|
|
6915
7054
|
};
|
|
6916
7055
|
let lastError;
|
|
@@ -6921,7 +7060,7 @@ async function applyDeclaredStatements(api, postgres, serverHostname, statements
|
|
|
6921
7060
|
lastError = error;
|
|
6922
7061
|
if (attempt === 3) break;
|
|
6923
7062
|
const message = error instanceof Error ? error.message : String(error);
|
|
6924
|
-
logger$
|
|
7063
|
+
logger$4.log(` ⏳ Cluster still settling (${message}); retrying (${attempt}/2)...`);
|
|
6925
7064
|
await new Promise((resolve) => setTimeout(resolve, 1e4));
|
|
6926
7065
|
await waitForPostgres(serverHostname, externalPort, postgres.databaseUser, postgres.databasePassword, postgres.databaseName).catch(() => {});
|
|
6927
7066
|
}
|
|
@@ -6996,91 +7135,91 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
6996
7135
|
apps: units
|
|
6997
7136
|
} : configured;
|
|
6998
7137
|
if (provider !== "dokploy") throw new Error(`Workspace deployment only supports Dokploy. Got: ${provider}`);
|
|
6999
|
-
logger$
|
|
7000
|
-
logger$
|
|
7138
|
+
logger$4.log(`\n🚀 Deploying workspace "${workspace.name}" to Dokploy...`);
|
|
7139
|
+
logger$4.log(` Stage: ${stage}`);
|
|
7001
7140
|
const imageTag = tag ?? generateTag(stage);
|
|
7002
|
-
logger$
|
|
7141
|
+
logger$4.log(` Tag: ${imageTag}`);
|
|
7003
7142
|
const buildOrder = getAppBuildOrder(workspace);
|
|
7004
7143
|
let appsToDeployNames = buildOrder;
|
|
7005
7144
|
if (selectedApps && selectedApps.length > 0) {
|
|
7006
7145
|
const invalidApps = selectedApps.filter((name) => !workspace.apps[name]);
|
|
7007
7146
|
if (invalidApps.length > 0) throw new Error(`Unknown apps: ${invalidApps.join(", ")}\nAvailable apps: ${Object.keys(workspace.apps).join(", ")}`);
|
|
7008
7147
|
appsToDeployNames = buildOrder.filter((name) => selectedApps.includes(name));
|
|
7009
|
-
logger$
|
|
7010
|
-
} else logger$
|
|
7148
|
+
logger$4.log(` Deploying apps: ${appsToDeployNames.join(", ")}`);
|
|
7149
|
+
} else logger$4.log(` Deploying all apps: ${appsToDeployNames.join(", ")}`);
|
|
7011
7150
|
const dokployApps = appsToDeployNames.filter((name) => {
|
|
7012
7151
|
const target = workspace.apps[name].resolvedDeployTarget;
|
|
7013
7152
|
if (!isDeployTargetSupported(target)) {
|
|
7014
|
-
logger$
|
|
7153
|
+
logger$4.log(` ⚠️ Skipping ${name}: ${getDeployTargetError(target, name)}`);
|
|
7015
7154
|
return false;
|
|
7016
7155
|
}
|
|
7017
7156
|
return true;
|
|
7018
7157
|
});
|
|
7019
7158
|
if (dokployApps.length === 0) throw new Error("No apps to deploy. All selected apps have unsupported deploy targets.");
|
|
7020
7159
|
appsToDeployNames = dokployApps;
|
|
7021
|
-
logger$
|
|
7160
|
+
logger$4.log("\n🔐 Loading secrets and analyzing environment requirements...");
|
|
7022
7161
|
const stageSecrets = await readStageSecrets(stage, workspace.root);
|
|
7023
7162
|
if (!stageSecrets) {
|
|
7024
|
-
logger$
|
|
7025
|
-
logger$
|
|
7163
|
+
logger$4.log(` ⚠️ No secrets found for stage "${stage}"`);
|
|
7164
|
+
logger$4.log(` Run "gkm secrets:init --stage ${stage}" to create secrets`);
|
|
7026
7165
|
}
|
|
7027
7166
|
const sniffedApps = await sniffAllApps(workspace.apps, workspace.root);
|
|
7028
7167
|
const encryptedSecrets = stageSecrets ? prepareSecretsForAllApps(stageSecrets, sniffedApps) : /* @__PURE__ */ new Map();
|
|
7029
7168
|
if (stageSecrets) {
|
|
7030
7169
|
const report = generateSecretsReport(encryptedSecrets, sniffedApps);
|
|
7031
|
-
if (report.appsWithSecrets.length > 0) logger$
|
|
7032
|
-
if (report.appsWithMissingSecrets.length > 0) for (const { appName, missing } of report.appsWithMissingSecrets) logger$
|
|
7170
|
+
if (report.appsWithSecrets.length > 0) logger$4.log(` ✓ Encrypted secrets for: ${report.appsWithSecrets.join(", ")}`);
|
|
7171
|
+
if (report.appsWithMissingSecrets.length > 0) for (const { appName, missing } of report.appsWithMissingSecrets) logger$4.log(` ⚠️ ${appName}: Missing secrets: ${missing.join(", ")}`);
|
|
7033
7172
|
}
|
|
7034
7173
|
let creds = await getDokployCredentials();
|
|
7035
7174
|
if (!creds) {
|
|
7036
|
-
logger$
|
|
7175
|
+
logger$4.log("\n📋 Dokploy credentials not found. Let's set them up.");
|
|
7037
7176
|
const normalizedEndpoint = (await prompt("Dokploy URL (e.g., https://dokploy.example.com): ")).replace(/\/$/, "");
|
|
7038
7177
|
try {
|
|
7039
7178
|
new URL(normalizedEndpoint);
|
|
7040
7179
|
} catch {
|
|
7041
7180
|
throw new Error("Invalid URL format");
|
|
7042
7181
|
}
|
|
7043
|
-
logger$
|
|
7182
|
+
logger$4.log(`\nGenerate a token at: ${normalizedEndpoint}/settings/profile\n`);
|
|
7044
7183
|
const token = await prompt("API Token: ", true);
|
|
7045
|
-
logger$
|
|
7184
|
+
logger$4.log("\nValidating credentials...");
|
|
7046
7185
|
if (!await validateDokployToken(normalizedEndpoint, token)) throw new Error("Invalid credentials. Please check your token.");
|
|
7047
7186
|
await storeDokployCredentials(token, normalizedEndpoint);
|
|
7048
7187
|
creds = {
|
|
7049
7188
|
token,
|
|
7050
7189
|
endpoint: normalizedEndpoint
|
|
7051
7190
|
};
|
|
7052
|
-
logger$
|
|
7191
|
+
logger$4.log("✓ Credentials saved");
|
|
7053
7192
|
}
|
|
7054
7193
|
const api = new DokployApi({
|
|
7055
7194
|
baseUrl: creds.endpoint,
|
|
7056
7195
|
token: creds.token
|
|
7057
7196
|
});
|
|
7058
|
-
logger$
|
|
7197
|
+
logger$4.log("\n📁 Setting up Dokploy project...");
|
|
7059
7198
|
const projectName = workspace.name;
|
|
7060
7199
|
let project = (await api.listProjects()).find((p) => p.name.toLowerCase() === projectName.toLowerCase());
|
|
7061
7200
|
let environmentId;
|
|
7062
7201
|
if (project) {
|
|
7063
|
-
logger$
|
|
7202
|
+
logger$4.log(` Found existing project: ${project.name}`);
|
|
7064
7203
|
const matchingEnv = ((await api.getProject(project.projectId)).environments ?? []).find((e) => e.name.toLowerCase() === stage.toLowerCase());
|
|
7065
7204
|
if (matchingEnv) {
|
|
7066
7205
|
environmentId = matchingEnv.environmentId;
|
|
7067
|
-
logger$
|
|
7206
|
+
logger$4.log(` Using environment: ${matchingEnv.name}`);
|
|
7068
7207
|
} else {
|
|
7069
|
-
logger$
|
|
7208
|
+
logger$4.log(` Creating "${stage}" environment...`);
|
|
7070
7209
|
environmentId = (await api.createEnvironment(project.projectId, stage)).environmentId;
|
|
7071
|
-
logger$
|
|
7210
|
+
logger$4.log(` ✓ Created environment: ${stage}`);
|
|
7072
7211
|
}
|
|
7073
7212
|
} else {
|
|
7074
|
-
logger$
|
|
7213
|
+
logger$4.log(` Creating project: ${projectName}`);
|
|
7075
7214
|
const result = await api.createProject(projectName);
|
|
7076
7215
|
project = result.project;
|
|
7077
7216
|
if (result.environment.name.toLowerCase() !== stage.toLowerCase()) {
|
|
7078
|
-
logger$
|
|
7217
|
+
logger$4.log(` Creating "${stage}" environment...`);
|
|
7079
7218
|
environmentId = (await api.createEnvironment(project.projectId, stage)).environmentId;
|
|
7080
7219
|
} else environmentId = result.environment.environmentId;
|
|
7081
|
-
logger$
|
|
7220
|
+
logger$4.log(` ✓ Created project: ${project.projectId}`);
|
|
7082
7221
|
}
|
|
7083
|
-
logger$
|
|
7222
|
+
logger$4.log("\n📋 Loading deploy state...");
|
|
7084
7223
|
const stateProvider = await createStateProvider({
|
|
7085
7224
|
config: workspace.state,
|
|
7086
7225
|
workspaceRoot: workspace.root,
|
|
@@ -7088,27 +7227,27 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7088
7227
|
});
|
|
7089
7228
|
let state = await stateProvider.read(stage);
|
|
7090
7229
|
if (state) {
|
|
7091
|
-
logger$
|
|
7230
|
+
logger$4.log(` Found existing state for stage "${stage}"`);
|
|
7092
7231
|
if (state.projectId !== project.projectId) {
|
|
7093
|
-
logger$
|
|
7232
|
+
logger$4.log(` ⚠ Project ID changed, updating state`);
|
|
7094
7233
|
state.projectId = project.projectId;
|
|
7095
7234
|
}
|
|
7096
7235
|
if (state.environmentId !== environmentId) {
|
|
7097
|
-
logger$
|
|
7236
|
+
logger$4.log(` ⚠ Environment ID changed, updating state`);
|
|
7098
7237
|
state.environmentId = environmentId;
|
|
7099
7238
|
}
|
|
7100
7239
|
} else {
|
|
7101
|
-
logger$
|
|
7240
|
+
logger$4.log(` Creating new state for stage "${stage}"`);
|
|
7102
7241
|
state = createEmptyState(stage, project.projectId, environmentId);
|
|
7103
7242
|
}
|
|
7104
|
-
logger$
|
|
7243
|
+
logger$4.log("\n🐳 Checking registry...");
|
|
7105
7244
|
let registryId = await getDokployRegistryId();
|
|
7106
7245
|
const registry = workspace.deploy.dokploy?.registry;
|
|
7107
7246
|
if (registryId) try {
|
|
7108
7247
|
const reg = await api.getRegistry(registryId);
|
|
7109
|
-
logger$
|
|
7248
|
+
logger$4.log(` Using registry: ${reg.registryName}`);
|
|
7110
7249
|
} catch {
|
|
7111
|
-
logger$
|
|
7250
|
+
logger$4.log(" ⚠ Stored registry not found, clearing...");
|
|
7112
7251
|
registryId = void 0;
|
|
7113
7252
|
await storeDokployRegistryId("");
|
|
7114
7253
|
}
|
|
@@ -7117,26 +7256,26 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7117
7256
|
if (registries.length > 0) {
|
|
7118
7257
|
registryId = registries[0].registryId;
|
|
7119
7258
|
await storeDokployRegistryId(registryId);
|
|
7120
|
-
logger$
|
|
7259
|
+
logger$4.log(` Using registry: ${registries[0].registryName}`);
|
|
7121
7260
|
} else if (registry) {
|
|
7122
|
-
logger$
|
|
7123
|
-
logger$
|
|
7261
|
+
logger$4.log(" No registries found in Dokploy. Let's create one.");
|
|
7262
|
+
logger$4.log(` Registry URL: ${registry}`);
|
|
7124
7263
|
const username = await prompt("Registry username: ");
|
|
7125
7264
|
const password = await prompt("Registry password/token: ", true);
|
|
7126
7265
|
registryId = (await api.createRegistry("Default Registry", registry, username, password)).registryId;
|
|
7127
7266
|
await storeDokployRegistryId(registryId);
|
|
7128
|
-
logger$
|
|
7129
|
-
} else logger$
|
|
7267
|
+
logger$4.log(` ✓ Registry created: ${registryId}`);
|
|
7268
|
+
} else logger$4.log(" ⚠ No registry configured. Set deploy.dokploy.registry in workspace config");
|
|
7130
7269
|
}
|
|
7131
7270
|
const skippedMobileApps = appsToDeployNames.filter((name) => workspace.apps[name].type === "mobile");
|
|
7132
|
-
if (skippedMobileApps.length > 0) logger$
|
|
7271
|
+
if (skippedMobileApps.length > 0) logger$4.log(`\n📱 Skipping ${skippedMobileApps.length} mobile app(s) — deploy via framework toolchain: ${skippedMobileApps.join(", ")}`);
|
|
7133
7272
|
const backendApps = appsToDeployNames.filter((name) => workspace.apps[name].type === "backend");
|
|
7134
7273
|
const frontendApps = appsToDeployNames.filter((name) => workspace.apps[name].type === "web");
|
|
7135
7274
|
const dokployConfig = workspace.deploy.dokploy;
|
|
7136
7275
|
let declaredEnv = {};
|
|
7137
7276
|
let declaredClusters = {};
|
|
7138
7277
|
{
|
|
7139
|
-
logger$
|
|
7278
|
+
logger$4.log("\n📦 Provisioning declared constructs...");
|
|
7140
7279
|
const appUrls = {};
|
|
7141
7280
|
for (const appName of backendApps) {
|
|
7142
7281
|
const app = workspace.apps[appName];
|
|
@@ -7154,20 +7293,20 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7154
7293
|
});
|
|
7155
7294
|
declaredEnv = declared.env;
|
|
7156
7295
|
declaredClusters = declared.clusters;
|
|
7157
|
-
if (Object.keys(declaredEnv).length > 0) logger$
|
|
7296
|
+
if (Object.keys(declaredEnv).length > 0) logger$4.log(` 🔌 Resolved ${Object.keys(declaredEnv).length} declared URL(s)`);
|
|
7158
7297
|
if (declared.statements.length > 0) {
|
|
7159
7298
|
const serverHostname = getServerHostname(creds.endpoint);
|
|
7160
7299
|
for (const [databaseName, cluster] of Object.entries(declared.clusters)) {
|
|
7161
7300
|
const statements = declared.statements.filter((statement) => statement.database === databaseName);
|
|
7162
7301
|
if (statements.length === 0) continue;
|
|
7163
7302
|
const created = await applyDeclaredStatements(api, cluster, serverHostname, statements);
|
|
7164
|
-
logger$
|
|
7303
|
+
logger$4.log(` 🗄️ ${databaseName}: applied ${statements.length} statement(s), ${created} new`);
|
|
7165
7304
|
}
|
|
7166
7305
|
}
|
|
7167
7306
|
}
|
|
7168
7307
|
const backupCluster = Object.values(declaredClusters)[0];
|
|
7169
7308
|
if (workspace.deploy?.backups && backupCluster) {
|
|
7170
|
-
logger$
|
|
7309
|
+
logger$4.log("\n💾 Provisioning backup destination...");
|
|
7171
7310
|
const { provisionBackupDestination } = await import("./backup-provisioner-DIVCbfee.mjs");
|
|
7172
7311
|
const backupState = await provisionBackupDestination({
|
|
7173
7312
|
api,
|
|
@@ -7176,13 +7315,13 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7176
7315
|
stage,
|
|
7177
7316
|
config: workspace.deploy.backups,
|
|
7178
7317
|
existingState: getBackupState(state),
|
|
7179
|
-
logger: logger$
|
|
7318
|
+
logger: logger$4
|
|
7180
7319
|
});
|
|
7181
7320
|
setBackupState(state, backupState);
|
|
7182
7321
|
if (!backupState.postgresBackupId) {
|
|
7183
7322
|
const backupSchedule = workspace.deploy.backups.schedule ?? "0 2 * * *";
|
|
7184
7323
|
const backupRetention = workspace.deploy.backups.retention ?? 30;
|
|
7185
|
-
logger$
|
|
7324
|
+
logger$4.log(" Creating postgres backup schedule...");
|
|
7186
7325
|
const backup = await api.createPostgresBackup({
|
|
7187
7326
|
schedule: backupSchedule,
|
|
7188
7327
|
prefix: `${stage}/postgres`,
|
|
@@ -7193,8 +7332,8 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7193
7332
|
keepLatestCount: backupRetention
|
|
7194
7333
|
});
|
|
7195
7334
|
setPostgresBackupId(state, backup.backupId);
|
|
7196
|
-
logger$
|
|
7197
|
-
} else logger$
|
|
7335
|
+
logger$4.log(` ✓ Postgres backup schedule created (${backupSchedule})`);
|
|
7336
|
+
} else logger$4.log(" ✓ Using existing postgres backup schedule");
|
|
7198
7337
|
}
|
|
7199
7338
|
const publicUrls = {};
|
|
7200
7339
|
const results = [];
|
|
@@ -7207,25 +7346,25 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7207
7346
|
frontendUrls.push(`https://${hostname}`);
|
|
7208
7347
|
}
|
|
7209
7348
|
if (backendApps.length > 0) {
|
|
7210
|
-
logger$
|
|
7349
|
+
logger$4.log("\n📦 PHASE 1: Deploying backend applications...");
|
|
7211
7350
|
for (const appName of backendApps) {
|
|
7212
7351
|
const app = workspace.apps[appName];
|
|
7213
|
-
logger$
|
|
7352
|
+
logger$4.log(`\n ⚙️ Deploying ${appName}...`);
|
|
7214
7353
|
try {
|
|
7215
7354
|
const dokployAppName = applicationName(stage, workspace.name, appName);
|
|
7216
7355
|
let application = null;
|
|
7217
7356
|
const cachedAppId = getApplicationId(state, appName);
|
|
7218
7357
|
if (cachedAppId) {
|
|
7219
|
-
logger$
|
|
7358
|
+
logger$4.log(` Using cached ID: ${cachedAppId}`);
|
|
7220
7359
|
application = await api.getApplication(cachedAppId);
|
|
7221
|
-
if (application) logger$
|
|
7222
|
-
else logger$
|
|
7360
|
+
if (application) logger$4.log(` ✓ Application found: ${application.applicationId}`);
|
|
7361
|
+
else logger$4.log(` ⚠ Cached ID invalid, will create new`);
|
|
7223
7362
|
}
|
|
7224
7363
|
if (!application) {
|
|
7225
7364
|
const result = await api.findOrCreateApplication(dokployAppName, project.projectId, environmentId);
|
|
7226
7365
|
application = result.application;
|
|
7227
|
-
if (result.created) logger$
|
|
7228
|
-
else logger$
|
|
7366
|
+
if (result.created) logger$4.log(` Created application: ${application.applicationId}`);
|
|
7367
|
+
else logger$4.log(` Found existing application: ${application.applicationId}`);
|
|
7229
7368
|
}
|
|
7230
7369
|
setApplicationId(state, appName, application.applicationId);
|
|
7231
7370
|
const appSecrets = encryptedSecrets.get(appName);
|
|
@@ -7233,11 +7372,11 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7233
7372
|
if (appSecrets && appSecrets.secretCount > 0) {
|
|
7234
7373
|
buildArgs.push(`GKM_ENCRYPTED_CREDENTIALS=${appSecrets.payload.encrypted}`);
|
|
7235
7374
|
buildArgs.push(`GKM_CREDENTIALS_IV=${appSecrets.payload.iv}`);
|
|
7236
|
-
logger$
|
|
7375
|
+
logger$4.log(` Encrypted ${appSecrets.secretCount} secrets`);
|
|
7237
7376
|
}
|
|
7238
7377
|
const imageName = `${workspace.name}-${appName}`;
|
|
7239
7378
|
const imageRef = registry ? `${registry}/${imageName}:${imageTag}` : `${imageName}:${imageTag}`;
|
|
7240
|
-
logger$
|
|
7379
|
+
logger$4.log(` Building Docker image: ${imageRef}`);
|
|
7241
7380
|
await deployDocker({
|
|
7242
7381
|
stage,
|
|
7243
7382
|
tag: imageTag,
|
|
@@ -7278,17 +7417,17 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7278
7417
|
...declaredEnv
|
|
7279
7418
|
};
|
|
7280
7419
|
const envVars = Object.entries(withDeclared).map(([key, value]) => `${key}=${value}`);
|
|
7281
|
-
if (Object.keys(withDeclared).length > 0) logger$
|
|
7420
|
+
if (Object.keys(withDeclared).length > 0) logger$4.log(` Resolved ${Object.keys(withDeclared).length} env vars: ${Object.keys(withDeclared).sort().join(", ")}`);
|
|
7282
7421
|
await api.saveDockerProvider(application.applicationId, imageRef, { registryId });
|
|
7283
7422
|
await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
|
|
7284
|
-
logger$
|
|
7423
|
+
logger$4.log(` Deploying to Dokploy...`);
|
|
7285
7424
|
await api.deployApplication(application.applicationId);
|
|
7286
7425
|
const existingDomain = (await api.getDomainsByApplicationId(application.applicationId)).find((d) => d.host === backendHost);
|
|
7287
7426
|
if (existingDomain) {
|
|
7288
7427
|
appHostnames.set(appName, backendHost);
|
|
7289
7428
|
appDomainIds.set(appName, existingDomain.domainId);
|
|
7290
7429
|
publicUrls[appName] = `https://${backendHost}`;
|
|
7291
|
-
logger$
|
|
7430
|
+
logger$4.log(` ✓ Domain: https://${backendHost} (existing)`);
|
|
7292
7431
|
} else try {
|
|
7293
7432
|
const domain = await api.createDomain({
|
|
7294
7433
|
host: backendHost,
|
|
@@ -7300,10 +7439,10 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7300
7439
|
appHostnames.set(appName, backendHost);
|
|
7301
7440
|
appDomainIds.set(appName, domain.domainId);
|
|
7302
7441
|
publicUrls[appName] = `https://${backendHost}`;
|
|
7303
|
-
logger$
|
|
7442
|
+
logger$4.log(` ✓ Domain: https://${backendHost} (created)`);
|
|
7304
7443
|
} catch (domainError) {
|
|
7305
7444
|
const message = domainError instanceof Error ? domainError.message : "Unknown error";
|
|
7306
|
-
logger$
|
|
7445
|
+
logger$4.log(` ⚠ Domain creation failed: ${message}`);
|
|
7307
7446
|
appHostnames.set(appName, backendHost);
|
|
7308
7447
|
publicUrls[appName] = `https://${backendHost}`;
|
|
7309
7448
|
}
|
|
@@ -7314,10 +7453,10 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7314
7453
|
applicationId: application.applicationId,
|
|
7315
7454
|
imageRef
|
|
7316
7455
|
});
|
|
7317
|
-
logger$
|
|
7456
|
+
logger$4.log(` ✓ ${appName} deployed successfully`);
|
|
7318
7457
|
} catch (error) {
|
|
7319
7458
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
7320
|
-
logger$
|
|
7459
|
+
logger$4.log(` ✗ Failed to deploy ${appName}: ${message}`);
|
|
7321
7460
|
results.push({
|
|
7322
7461
|
appName,
|
|
7323
7462
|
type: app.type,
|
|
@@ -7329,25 +7468,25 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7329
7468
|
}
|
|
7330
7469
|
}
|
|
7331
7470
|
if (frontendApps.length > 0) {
|
|
7332
|
-
logger$
|
|
7471
|
+
logger$4.log("\n🌐 PHASE 2: Deploying frontend applications...");
|
|
7333
7472
|
for (const appName of frontendApps) {
|
|
7334
7473
|
const app = workspace.apps[appName];
|
|
7335
|
-
logger$
|
|
7474
|
+
logger$4.log(`\n 🌐 Deploying ${appName}...`);
|
|
7336
7475
|
try {
|
|
7337
7476
|
const dokployAppName = applicationName(stage, workspace.name, appName);
|
|
7338
7477
|
let application = null;
|
|
7339
7478
|
const cachedAppId = getApplicationId(state, appName);
|
|
7340
7479
|
if (cachedAppId) {
|
|
7341
|
-
logger$
|
|
7480
|
+
logger$4.log(` Using cached ID: ${cachedAppId}`);
|
|
7342
7481
|
application = await api.getApplication(cachedAppId);
|
|
7343
|
-
if (application) logger$
|
|
7344
|
-
else logger$
|
|
7482
|
+
if (application) logger$4.log(` ✓ Application found: ${application.applicationId}`);
|
|
7483
|
+
else logger$4.log(` ⚠ Cached ID invalid, will create new`);
|
|
7345
7484
|
}
|
|
7346
7485
|
if (!application) {
|
|
7347
7486
|
const result = await api.findOrCreateApplication(dokployAppName, project.projectId, environmentId);
|
|
7348
7487
|
application = result.application;
|
|
7349
|
-
if (result.created) logger$
|
|
7350
|
-
else logger$
|
|
7488
|
+
if (result.created) logger$4.log(` Created application: ${application.applicationId}`);
|
|
7489
|
+
else logger$4.log(` Found existing application: ${application.applicationId}`);
|
|
7351
7490
|
}
|
|
7352
7491
|
setApplicationId(state, appName, application.applicationId);
|
|
7353
7492
|
const dependencyUrls = {};
|
|
@@ -7367,7 +7506,7 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7367
7506
|
};
|
|
7368
7507
|
const { valid, missing, resolved } = validateEnvVars(requiredOf(sniffedApps.get(appName)), envContext);
|
|
7369
7508
|
if (!valid) throw new Error(formatMissingVarsError(appName, missing, stage));
|
|
7370
|
-
if (Object.keys(resolved).length > 0) logger$
|
|
7509
|
+
if (Object.keys(resolved).length > 0) logger$4.log(` Resolved ${Object.keys(resolved).length} env vars: ${Object.keys(resolved).join(", ")}`);
|
|
7371
7510
|
const publicPrefix = getPublicEnvPrefix(app.framework);
|
|
7372
7511
|
const buildArgs = [];
|
|
7373
7512
|
const publicUrlArgNames = [];
|
|
@@ -7377,10 +7516,10 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7377
7516
|
publicUrlArgNames.push(key);
|
|
7378
7517
|
}
|
|
7379
7518
|
}
|
|
7380
|
-
if (buildArgs.length > 0) logger$
|
|
7519
|
+
if (buildArgs.length > 0) logger$4.log(` Build args: ${publicUrlArgNames.join(", ")}`);
|
|
7381
7520
|
const imageName = `${workspace.name}-${appName}`;
|
|
7382
7521
|
const imageRef = registry ? `${registry}/${imageName}:${imageTag}` : `${imageName}:${imageTag}`;
|
|
7383
|
-
logger$
|
|
7522
|
+
logger$4.log(` Building Docker image: ${imageRef}`);
|
|
7384
7523
|
await deployDocker({
|
|
7385
7524
|
stage,
|
|
7386
7525
|
tag: imageTag,
|
|
@@ -7401,14 +7540,14 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7401
7540
|
for (const [key, value] of Object.entries(resolved)) envVars.push(`${key}=${value}`);
|
|
7402
7541
|
await api.saveDockerProvider(application.applicationId, imageRef, { registryId });
|
|
7403
7542
|
await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
|
|
7404
|
-
logger$
|
|
7543
|
+
logger$4.log(` Deploying to Dokploy...`);
|
|
7405
7544
|
await api.deployApplication(application.applicationId);
|
|
7406
7545
|
const existingFrontendDomain = (await api.getDomainsByApplicationId(application.applicationId)).find((d) => d.host === frontendHost);
|
|
7407
7546
|
if (existingFrontendDomain) {
|
|
7408
7547
|
appHostnames.set(appName, frontendHost);
|
|
7409
7548
|
appDomainIds.set(appName, existingFrontendDomain.domainId);
|
|
7410
7549
|
publicUrls[appName] = `https://${frontendHost}`;
|
|
7411
|
-
logger$
|
|
7550
|
+
logger$4.log(` ✓ Domain: https://${frontendHost} (existing)`);
|
|
7412
7551
|
} else try {
|
|
7413
7552
|
const domain = await api.createDomain({
|
|
7414
7553
|
host: frontendHost,
|
|
@@ -7420,10 +7559,10 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7420
7559
|
appHostnames.set(appName, frontendHost);
|
|
7421
7560
|
appDomainIds.set(appName, domain.domainId);
|
|
7422
7561
|
publicUrls[appName] = `https://${frontendHost}`;
|
|
7423
|
-
logger$
|
|
7562
|
+
logger$4.log(` ✓ Domain: https://${frontendHost} (created)`);
|
|
7424
7563
|
} catch (domainError) {
|
|
7425
7564
|
const message = domainError instanceof Error ? domainError.message : "Unknown error";
|
|
7426
|
-
logger$
|
|
7565
|
+
logger$4.log(` ⚠ Domain creation failed: ${message}`);
|
|
7427
7566
|
appHostnames.set(appName, frontendHost);
|
|
7428
7567
|
publicUrls[appName] = `https://${frontendHost}`;
|
|
7429
7568
|
}
|
|
@@ -7434,10 +7573,10 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7434
7573
|
applicationId: application.applicationId,
|
|
7435
7574
|
imageRef
|
|
7436
7575
|
});
|
|
7437
|
-
logger$
|
|
7576
|
+
logger$4.log(` ✓ ${appName} deployed successfully`);
|
|
7438
7577
|
} catch (error) {
|
|
7439
7578
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
7440
|
-
logger$
|
|
7579
|
+
logger$4.log(` ✗ Failed to deploy ${appName}: ${message}`);
|
|
7441
7580
|
results.push({
|
|
7442
7581
|
appName,
|
|
7443
7582
|
type: app.type,
|
|
@@ -7447,9 +7586,9 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7447
7586
|
}
|
|
7448
7587
|
}
|
|
7449
7588
|
}
|
|
7450
|
-
logger$
|
|
7589
|
+
logger$4.log("\n📋 Saving deploy state...");
|
|
7451
7590
|
await stateProvider.write(stage, state);
|
|
7452
|
-
logger$
|
|
7591
|
+
logger$4.log(" ✓ State saved");
|
|
7453
7592
|
const dnsConfig = workspace.deploy.dns;
|
|
7454
7593
|
if (dnsConfig && appHostnames.size > 0) {
|
|
7455
7594
|
const dnsResult = await orchestrateDns(appHostnames, dnsConfig, creds.endpoint);
|
|
@@ -7458,27 +7597,27 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7458
7597
|
await stateProvider.write(stage, state);
|
|
7459
7598
|
}
|
|
7460
7599
|
if (dnsResult?.success && appHostnames.size > 0) {
|
|
7461
|
-
logger$
|
|
7600
|
+
logger$4.log("\n🔒 Validating domains for SSL certificates...");
|
|
7462
7601
|
for (const [appName, hostname] of appHostnames) try {
|
|
7463
7602
|
const result = await api.validateDomain(hostname);
|
|
7464
|
-
if (result.isValid) logger$
|
|
7465
|
-
else logger$
|
|
7603
|
+
if (result.isValid) logger$4.log(` ✓ ${appName}: ${hostname} → ${result.resolvedIp}`);
|
|
7604
|
+
else logger$4.log(` ⚠ ${appName}: ${hostname} not valid`);
|
|
7466
7605
|
} catch (validationError) {
|
|
7467
7606
|
const message = validationError instanceof Error ? validationError.message : "Unknown error";
|
|
7468
|
-
logger$
|
|
7607
|
+
logger$4.log(` ⚠ ${appName}: validation failed - ${message}`);
|
|
7469
7608
|
}
|
|
7470
7609
|
}
|
|
7471
7610
|
}
|
|
7472
7611
|
const successCount = results.filter((r) => r.success).length;
|
|
7473
7612
|
const failedCount = results.filter((r) => !r.success).length;
|
|
7474
|
-
logger$
|
|
7475
|
-
logger$
|
|
7476
|
-
logger$
|
|
7477
|
-
logger$
|
|
7478
|
-
if (failedCount > 0) logger$
|
|
7613
|
+
logger$4.log(`\n${"─".repeat(50)}`);
|
|
7614
|
+
logger$4.log(`\n✅ Workspace deployment complete!`);
|
|
7615
|
+
logger$4.log(` Project: ${project.projectId}`);
|
|
7616
|
+
logger$4.log(` Successful: ${successCount}`);
|
|
7617
|
+
if (failedCount > 0) logger$4.log(` Failed: ${failedCount}`);
|
|
7479
7618
|
if (Object.keys(publicUrls).length > 0) {
|
|
7480
|
-
logger$
|
|
7481
|
-
for (const [name, url] of Object.entries(publicUrls)) logger$
|
|
7619
|
+
logger$4.log("\n 📡 Deployed URLs:");
|
|
7620
|
+
for (const [name, url] of Object.entries(publicUrls)) logger$4.log(` ${name}: ${url}`);
|
|
7482
7621
|
}
|
|
7483
7622
|
return {
|
|
7484
7623
|
apps: results,
|
|
@@ -7497,7 +7636,7 @@ async function deployCommand(options) {
|
|
|
7497
7636
|
}
|
|
7498
7637
|
//#endregion
|
|
7499
7638
|
//#region src/deploy/init.ts
|
|
7500
|
-
const logger$
|
|
7639
|
+
const logger$3 = console;
|
|
7501
7640
|
/**
|
|
7502
7641
|
* Get the Dokploy API token from stored credentials or environment
|
|
7503
7642
|
*/
|
|
@@ -7543,28 +7682,28 @@ function unsafeToRewrite(content) {
|
|
|
7543
7682
|
async function updateConfig(config, cwd = process.cwd()) {
|
|
7544
7683
|
const configPath = join(cwd, "gkm.config.ts");
|
|
7545
7684
|
if (!existsSync(configPath)) {
|
|
7546
|
-
logger$
|
|
7547
|
-
logger$
|
|
7548
|
-
logger$
|
|
7549
|
-
logger$
|
|
7550
|
-
logger$
|
|
7551
|
-
logger$
|
|
7552
|
-
logger$
|
|
7553
|
-
logger$
|
|
7685
|
+
logger$3.warn("\n gkm.config.ts not found. Add this configuration manually:\n");
|
|
7686
|
+
logger$3.log(` providers: {`);
|
|
7687
|
+
logger$3.log(` dokploy: {`);
|
|
7688
|
+
logger$3.log(` endpoint: '${config.endpoint}',`);
|
|
7689
|
+
logger$3.log(` projectId: '${config.projectId}',`);
|
|
7690
|
+
logger$3.log(` applicationId: '${config.applicationId}',`);
|
|
7691
|
+
logger$3.log(` },`);
|
|
7692
|
+
logger$3.log(` },`);
|
|
7554
7693
|
return;
|
|
7555
7694
|
}
|
|
7556
7695
|
const content = await readFile(configPath, "utf-8");
|
|
7557
7696
|
if (unsafeToRewrite(content)) {
|
|
7558
|
-
logger$
|
|
7559
|
-
logger$
|
|
7560
|
-
logger$
|
|
7561
|
-
logger$
|
|
7562
|
-
if (config.registryId) logger$
|
|
7697
|
+
logger$3.warn("\n gkm.config.ts holds a dokploy block this cannot safely rewrite —\n it contains an expression or a nested object. Leaving it alone.");
|
|
7698
|
+
logger$3.log(`\n Discovered, if you want them written down:`);
|
|
7699
|
+
logger$3.log(` projectId: '${config.projectId}'`);
|
|
7700
|
+
logger$3.log(` applicationId: '${config.applicationId}'`);
|
|
7701
|
+
if (config.registryId) logger$3.log(` registryId: '${config.registryId}'`);
|
|
7563
7702
|
return;
|
|
7564
7703
|
}
|
|
7565
7704
|
if (content.includes("dokploy:") && content.includes("applicationId:")) {
|
|
7566
|
-
logger$
|
|
7567
|
-
logger$
|
|
7705
|
+
logger$3.log("\n Dokploy config already exists in gkm.config.ts");
|
|
7706
|
+
logger$3.log(" Updating with new values...");
|
|
7568
7707
|
}
|
|
7569
7708
|
const registryLine = config.registryId ? `\n\t\t\tregistryId: '${config.registryId}',` : "";
|
|
7570
7709
|
const dokployConfigStr = `dokploy: {
|
|
@@ -7582,7 +7721,7 @@ async function updateConfig(config, cwd = process.cwd()) {
|
|
|
7582
7721
|
},
|
|
7583
7722
|
});`);
|
|
7584
7723
|
await writeFile(configPath, newContent);
|
|
7585
|
-
logger$
|
|
7724
|
+
logger$3.log("\n ✓ Updated gkm.config.ts with Dokploy configuration");
|
|
7586
7725
|
}
|
|
7587
7726
|
/**
|
|
7588
7727
|
* Initialize Dokploy deployment configuration
|
|
@@ -7591,22 +7730,22 @@ async function deployInitCommand(options) {
|
|
|
7591
7730
|
const { projectName, appName, projectId: existingProjectId, registryId } = options;
|
|
7592
7731
|
const endpoint = await getEndpoint(options.endpoint);
|
|
7593
7732
|
const api = await createApi(endpoint);
|
|
7594
|
-
logger$
|
|
7595
|
-
logger$
|
|
7733
|
+
logger$3.log(`\n🚀 Initializing Dokploy deployment...`);
|
|
7734
|
+
logger$3.log(` Endpoint: ${endpoint}`);
|
|
7596
7735
|
let projectId;
|
|
7597
7736
|
if (existingProjectId) {
|
|
7598
7737
|
projectId = existingProjectId;
|
|
7599
|
-
logger$
|
|
7738
|
+
logger$3.log(`\n📁 Using existing project: ${projectId}`);
|
|
7600
7739
|
} else {
|
|
7601
|
-
logger$
|
|
7740
|
+
logger$3.log(`\n📁 Looking for project: ${projectName}`);
|
|
7602
7741
|
const existingProject = (await api.listProjects()).find((p) => p.name.toLowerCase() === projectName.toLowerCase());
|
|
7603
7742
|
if (existingProject) {
|
|
7604
7743
|
projectId = existingProject.projectId;
|
|
7605
|
-
logger$
|
|
7744
|
+
logger$3.log(` Found existing project: ${projectId}`);
|
|
7606
7745
|
} else {
|
|
7607
|
-
logger$
|
|
7746
|
+
logger$3.log(` Creating new project...`);
|
|
7608
7747
|
projectId = (await api.createProject(projectName)).project.projectId;
|
|
7609
|
-
logger$
|
|
7748
|
+
logger$3.log(` ✓ Created project: ${projectId}`);
|
|
7610
7749
|
}
|
|
7611
7750
|
}
|
|
7612
7751
|
const project = await api.getProject(projectId);
|
|
@@ -7614,22 +7753,22 @@ async function deployInitCommand(options) {
|
|
|
7614
7753
|
const firstEnv = project.environments?.[0];
|
|
7615
7754
|
if (firstEnv) environmentId = firstEnv.environmentId;
|
|
7616
7755
|
else {
|
|
7617
|
-
logger$
|
|
7756
|
+
logger$3.log(` Creating production environment...`);
|
|
7618
7757
|
environmentId = (await api.createEnvironment(projectId, "production")).environmentId;
|
|
7619
7758
|
}
|
|
7620
|
-
logger$
|
|
7759
|
+
logger$3.log(`\n📦 Creating application: ${appName}`);
|
|
7621
7760
|
const application = await api.createApplication(appName, projectId, environmentId);
|
|
7622
|
-
logger$
|
|
7761
|
+
logger$3.log(` ✓ Created application: ${application.applicationId}`);
|
|
7623
7762
|
if (registryId) {
|
|
7624
|
-
logger$
|
|
7763
|
+
logger$3.log(`\n🔧 Configuring registry: ${registryId}`);
|
|
7625
7764
|
await api.updateApplication(application.applicationId, { registryId });
|
|
7626
|
-
logger$
|
|
7765
|
+
logger$3.log(` ✓ Registry configured`);
|
|
7627
7766
|
} else try {
|
|
7628
7767
|
const registries = await api.listRegistries();
|
|
7629
7768
|
if (registries.length > 0) {
|
|
7630
|
-
logger$
|
|
7631
|
-
for (const reg of registries) logger$
|
|
7632
|
-
logger$
|
|
7769
|
+
logger$3.log(`\n📋 Available registries:`);
|
|
7770
|
+
for (const reg of registries) logger$3.log(` - ${reg.registryName}: ${reg.registryUrl} (${reg.registryId})`);
|
|
7771
|
+
logger$3.log(`\n To use a registry, run with --registry-id <id>`);
|
|
7633
7772
|
}
|
|
7634
7773
|
} catch {}
|
|
7635
7774
|
const config = {
|
|
@@ -7638,14 +7777,14 @@ async function deployInitCommand(options) {
|
|
|
7638
7777
|
applicationId: application.applicationId
|
|
7639
7778
|
};
|
|
7640
7779
|
await updateConfig(config);
|
|
7641
|
-
logger$
|
|
7642
|
-
logger$
|
|
7643
|
-
logger$
|
|
7644
|
-
logger$
|
|
7645
|
-
logger$
|
|
7646
|
-
logger$
|
|
7647
|
-
logger$
|
|
7648
|
-
logger$
|
|
7780
|
+
logger$3.log(`\n✅ Dokploy deployment initialized!`);
|
|
7781
|
+
logger$3.log(`\n📋 Configuration:`);
|
|
7782
|
+
logger$3.log(` Project ID: ${projectId}`);
|
|
7783
|
+
logger$3.log(` Application ID: ${application.applicationId}`);
|
|
7784
|
+
logger$3.log(`\n🔗 View in Dokploy: ${endpoint}/project/${projectId}`);
|
|
7785
|
+
logger$3.log(`\n📝 Next steps:`);
|
|
7786
|
+
logger$3.log(` 1. Initialize secrets: gkm secrets:init --stage production`);
|
|
7787
|
+
logger$3.log(` 2. Deploy: gkm deploy --provider dokploy --stage production`);
|
|
7649
7788
|
return config;
|
|
7650
7789
|
}
|
|
7651
7790
|
/**
|
|
@@ -7656,31 +7795,31 @@ async function deployListCommand(options) {
|
|
|
7656
7795
|
const api = await createApi(endpoint);
|
|
7657
7796
|
const { resource } = options;
|
|
7658
7797
|
if (resource === "projects") {
|
|
7659
|
-
logger$
|
|
7798
|
+
logger$3.log(`\n📁 Projects in ${endpoint}:`);
|
|
7660
7799
|
const projects = await api.listProjects();
|
|
7661
7800
|
if (projects.length === 0) {
|
|
7662
|
-
logger$
|
|
7801
|
+
logger$3.log(" No projects found");
|
|
7663
7802
|
return;
|
|
7664
7803
|
}
|
|
7665
7804
|
for (const project of projects) {
|
|
7666
|
-
logger$
|
|
7667
|
-
if (project.description) logger$
|
|
7805
|
+
logger$3.log(`\n ${project.name} (${project.projectId})`);
|
|
7806
|
+
if (project.description) logger$3.log(` ${project.description}`);
|
|
7668
7807
|
}
|
|
7669
7808
|
} else if (resource === "registries") {
|
|
7670
|
-
logger$
|
|
7809
|
+
logger$3.log(`\n🐳 Registries in ${endpoint}:`);
|
|
7671
7810
|
const registries = await api.listRegistries();
|
|
7672
7811
|
if (registries.length === 0) {
|
|
7673
|
-
logger$
|
|
7674
|
-
logger$
|
|
7812
|
+
logger$3.log(" No registries configured");
|
|
7813
|
+
logger$3.log(" Run \"gkm registry:setup\" to configure a registry");
|
|
7675
7814
|
return;
|
|
7676
7815
|
}
|
|
7677
7816
|
const storedRegistryId = await getDokployRegistryId();
|
|
7678
7817
|
for (const registry of registries) {
|
|
7679
7818
|
const marker = registry.registryId === storedRegistryId ? " (default)" : "";
|
|
7680
|
-
logger$
|
|
7681
|
-
logger$
|
|
7682
|
-
logger$
|
|
7683
|
-
if (registry.imagePrefix) logger$
|
|
7819
|
+
logger$3.log(`\n ${registry.registryName}${marker} (${registry.registryId})`);
|
|
7820
|
+
logger$3.log(` URL: ${registry.registryUrl}`);
|
|
7821
|
+
logger$3.log(` Username: ${registry.username}`);
|
|
7822
|
+
if (registry.imagePrefix) logger$3.log(` Prefix: ${registry.imagePrefix}`);
|
|
7684
7823
|
}
|
|
7685
7824
|
}
|
|
7686
7825
|
}
|
|
@@ -8541,26 +8680,26 @@ function loadPackageJson() {
|
|
|
8541
8680
|
* Run: pnpm --filter @geekmidas/cli sync-versions
|
|
8542
8681
|
*/
|
|
8543
8682
|
const GEEKMIDAS_VERSIONS = {
|
|
8544
|
-
"@geekmidas/audit": "~10.0.0-alpha.
|
|
8545
|
-
"@geekmidas/auth": "~10.0.0-alpha.
|
|
8546
|
-
"@geekmidas/cache": "~10.0.0-alpha.
|
|
8547
|
-
"@geekmidas/client": "~10.0.0-alpha.
|
|
8548
|
-
"@geekmidas/cloud": "~10.0.0-alpha.
|
|
8549
|
-
"@geekmidas/constructs": "~10.0.0-alpha.
|
|
8550
|
-
"@geekmidas/db": "~10.0.0-alpha.
|
|
8551
|
-
"@geekmidas/emailkit": "~10.0.0-alpha.
|
|
8552
|
-
"@geekmidas/envkit": "~10.0.0-alpha.
|
|
8553
|
-
"@geekmidas/errors": "~10.0.0-alpha.
|
|
8554
|
-
"@geekmidas/events": "~10.0.0-alpha.
|
|
8555
|
-
"@geekmidas/logger": "~10.0.0-alpha.
|
|
8556
|
-
"@geekmidas/manifest": "~10.0.0-alpha.
|
|
8557
|
-
"@geekmidas/rate-limit": "~10.0.0-alpha.
|
|
8558
|
-
"@geekmidas/schema": "~10.0.0-alpha.
|
|
8559
|
-
"@geekmidas/services": "~10.0.0-alpha.
|
|
8560
|
-
"@geekmidas/storage": "~10.0.0-alpha.
|
|
8561
|
-
"@geekmidas/studio": "~10.0.0-alpha.
|
|
8562
|
-
"@geekmidas/telescope": "~10.0.0-alpha.
|
|
8563
|
-
"@geekmidas/testkit": "~10.0.0-alpha.
|
|
8683
|
+
"@geekmidas/audit": "~10.0.0-alpha.28",
|
|
8684
|
+
"@geekmidas/auth": "~10.0.0-alpha.28",
|
|
8685
|
+
"@geekmidas/cache": "~10.0.0-alpha.28",
|
|
8686
|
+
"@geekmidas/client": "~10.0.0-alpha.28",
|
|
8687
|
+
"@geekmidas/cloud": "~10.0.0-alpha.28",
|
|
8688
|
+
"@geekmidas/constructs": "~10.0.0-alpha.28",
|
|
8689
|
+
"@geekmidas/db": "~10.0.0-alpha.28",
|
|
8690
|
+
"@geekmidas/emailkit": "~10.0.0-alpha.28",
|
|
8691
|
+
"@geekmidas/envkit": "~10.0.0-alpha.28",
|
|
8692
|
+
"@geekmidas/errors": "~10.0.0-alpha.28",
|
|
8693
|
+
"@geekmidas/events": "~10.0.0-alpha.28",
|
|
8694
|
+
"@geekmidas/logger": "~10.0.0-alpha.28",
|
|
8695
|
+
"@geekmidas/manifest": "~10.0.0-alpha.28",
|
|
8696
|
+
"@geekmidas/rate-limit": "~10.0.0-alpha.28",
|
|
8697
|
+
"@geekmidas/schema": "~10.0.0-alpha.28",
|
|
8698
|
+
"@geekmidas/services": "~10.0.0-alpha.28",
|
|
8699
|
+
"@geekmidas/storage": "~10.0.0-alpha.28",
|
|
8700
|
+
"@geekmidas/studio": "~10.0.0-alpha.28",
|
|
8701
|
+
"@geekmidas/telescope": "~10.0.0-alpha.28",
|
|
8702
|
+
"@geekmidas/testkit": "~10.0.0-alpha.28",
|
|
8564
8703
|
"@geekmidas/cli": `~${loadPackageJson().version}`
|
|
8565
8704
|
};
|
|
8566
8705
|
//#endregion
|
|
@@ -14639,7 +14778,7 @@ var NotAnAwsRegion = class extends Error {
|
|
|
14639
14778
|
};
|
|
14640
14779
|
//#endregion
|
|
14641
14780
|
//#region src/secrets/index.ts
|
|
14642
|
-
const logger$
|
|
14781
|
+
const logger$2 = console;
|
|
14643
14782
|
/**
|
|
14644
14783
|
* Initialize secrets for a stage.
|
|
14645
14784
|
* Generates secure random passwords for configured services.
|
|
@@ -14647,7 +14786,7 @@ const logger$3 = console;
|
|
|
14647
14786
|
async function secretsInitCommand(options) {
|
|
14648
14787
|
const { stage, force } = options;
|
|
14649
14788
|
if (!force && secretsExist(stage)) {
|
|
14650
|
-
logger$
|
|
14789
|
+
logger$2.error(`Secrets already exist for stage "${stage}". Use --force to overwrite.`);
|
|
14651
14790
|
process.exit(1);
|
|
14652
14791
|
}
|
|
14653
14792
|
let projectName;
|
|
@@ -14657,21 +14796,21 @@ async function secretsInitCommand(options) {
|
|
|
14657
14796
|
projectName = loaded.workspace.name;
|
|
14658
14797
|
if (Object.keys(loaded.workspace.apps).length > 1) {
|
|
14659
14798
|
workspaceSecrets = generateFullstackCustomSecrets(loaded.workspace);
|
|
14660
|
-
logger$
|
|
14799
|
+
logger$2.log(" Detected workspace mode — generating per-app secrets");
|
|
14661
14800
|
}
|
|
14662
14801
|
} catch {}
|
|
14663
14802
|
const secrets = createStageSecrets(stage, [], { projectName });
|
|
14664
14803
|
if (workspaceSecrets) secrets.custom = workspaceSecrets;
|
|
14665
14804
|
await writeStageSecrets(secrets);
|
|
14666
|
-
logger$
|
|
14667
|
-
logger$
|
|
14668
|
-
if (secrets.urls.DATABASE_URL) logger$
|
|
14669
|
-
if (secrets.urls.REDIS_URL) logger$
|
|
14670
|
-
if (secrets.urls.RABBITMQ_URL) logger$
|
|
14671
|
-
if (secrets.urls.STORAGE_ENDPOINT) logger$
|
|
14672
|
-
if (Object.keys(secrets.custom).length > 0) logger$
|
|
14673
|
-
logger$
|
|
14674
|
-
logger$
|
|
14805
|
+
logger$2.log(`\n✓ Secrets initialized for stage "${stage}"`);
|
|
14806
|
+
logger$2.log(` Location: .gkm/secrets/${stage}.json`);
|
|
14807
|
+
if (secrets.urls.DATABASE_URL) logger$2.log(`\n DATABASE_URL: ${maskUrl(secrets.urls.DATABASE_URL)}`);
|
|
14808
|
+
if (secrets.urls.REDIS_URL) logger$2.log(` REDIS_URL: ${maskUrl(secrets.urls.REDIS_URL)}`);
|
|
14809
|
+
if (secrets.urls.RABBITMQ_URL) logger$2.log(` RABBITMQ_URL: ${maskUrl(secrets.urls.RABBITMQ_URL)}`);
|
|
14810
|
+
if (secrets.urls.STORAGE_ENDPOINT) logger$2.log(` STORAGE_ENDPOINT: ${secrets.urls.STORAGE_ENDPOINT}`);
|
|
14811
|
+
if (Object.keys(secrets.custom).length > 0) logger$2.log(`\n Custom secrets: ${Object.keys(secrets.custom).length}`);
|
|
14812
|
+
logger$2.log(`\n Use "gkm secrets:show --stage ${stage}" to view secrets`);
|
|
14813
|
+
logger$2.log(" Use \"gkm secrets:set <KEY> <VALUE> --stage " + stage + "\" to add custom secrets");
|
|
14675
14814
|
}
|
|
14676
14815
|
/**
|
|
14677
14816
|
* Read all data from stdin.
|
|
@@ -14690,21 +14829,21 @@ async function secretsSetCommand(key, value, options) {
|
|
|
14690
14829
|
let secretValue = value;
|
|
14691
14830
|
if (!secretValue) {
|
|
14692
14831
|
if (process.stdin.isTTY) {
|
|
14693
|
-
logger$
|
|
14694
|
-
logger$
|
|
14832
|
+
logger$2.error("No value provided. Use: gkm secrets:set KEY VALUE --stage <stage>");
|
|
14833
|
+
logger$2.error("Or pipe from stdin: echo \"value\" | gkm secrets:set KEY --stage <stage>");
|
|
14695
14834
|
process.exit(1);
|
|
14696
14835
|
}
|
|
14697
14836
|
secretValue = await readStdin();
|
|
14698
14837
|
if (!secretValue) {
|
|
14699
|
-
logger$
|
|
14838
|
+
logger$2.error("No value received from stdin");
|
|
14700
14839
|
process.exit(1);
|
|
14701
14840
|
}
|
|
14702
14841
|
}
|
|
14703
14842
|
try {
|
|
14704
14843
|
await setCustomSecret(stage, key, secretValue);
|
|
14705
|
-
logger$
|
|
14844
|
+
logger$2.log(`\n✓ Secret "${key}" set for stage "${stage}"`);
|
|
14706
14845
|
} catch (error) {
|
|
14707
|
-
logger$
|
|
14846
|
+
logger$2.error(error instanceof Error ? error.message : "Failed to set secret");
|
|
14708
14847
|
process.exit(1);
|
|
14709
14848
|
}
|
|
14710
14849
|
}
|
|
@@ -14715,33 +14854,33 @@ async function secretsShowCommand(options) {
|
|
|
14715
14854
|
const { stage, reveal } = options;
|
|
14716
14855
|
const secrets = await readStageSecrets(stage);
|
|
14717
14856
|
if (!secrets) {
|
|
14718
|
-
logger$
|
|
14857
|
+
logger$2.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
14719
14858
|
process.exit(1);
|
|
14720
14859
|
}
|
|
14721
|
-
logger$
|
|
14722
|
-
logger$
|
|
14723
|
-
logger$
|
|
14724
|
-
logger$
|
|
14860
|
+
logger$2.log(`\nSecrets for stage "${stage}":`);
|
|
14861
|
+
logger$2.log(` Created: ${secrets.createdAt}`);
|
|
14862
|
+
logger$2.log(` Updated: ${secrets.updatedAt}`);
|
|
14863
|
+
logger$2.log("\nService Credentials:");
|
|
14725
14864
|
for (const [service, creds] of Object.entries(secrets.services)) if (creds) {
|
|
14726
|
-
logger$
|
|
14727
|
-
logger$
|
|
14728
|
-
logger$
|
|
14729
|
-
logger$
|
|
14730
|
-
logger$
|
|
14731
|
-
if (creds.database) logger$
|
|
14732
|
-
if (creds.vhost) logger$
|
|
14733
|
-
if (creds.bucket) logger$
|
|
14734
|
-
}
|
|
14735
|
-
logger$
|
|
14736
|
-
if (secrets.urls.DATABASE_URL) logger$
|
|
14737
|
-
if (secrets.urls.REDIS_URL) logger$
|
|
14738
|
-
if (secrets.urls.RABBITMQ_URL) logger$
|
|
14739
|
-
if (secrets.urls.STORAGE_ENDPOINT) logger$
|
|
14865
|
+
logger$2.log(`\n ${service}:`);
|
|
14866
|
+
logger$2.log(` host: ${creds.host}`);
|
|
14867
|
+
logger$2.log(` port: ${creds.port}`);
|
|
14868
|
+
logger$2.log(` username: ${creds.username}`);
|
|
14869
|
+
logger$2.log(` password: ${reveal ? creds.password : maskPassword(creds.password)}`);
|
|
14870
|
+
if (creds.database) logger$2.log(` database: ${creds.database}`);
|
|
14871
|
+
if (creds.vhost) logger$2.log(` vhost: ${creds.vhost}`);
|
|
14872
|
+
if (creds.bucket) logger$2.log(` bucket: ${creds.bucket}`);
|
|
14873
|
+
}
|
|
14874
|
+
logger$2.log("\nConnection URLs:");
|
|
14875
|
+
if (secrets.urls.DATABASE_URL) logger$2.log(` DATABASE_URL: ${reveal ? secrets.urls.DATABASE_URL : maskUrl(secrets.urls.DATABASE_URL)}`);
|
|
14876
|
+
if (secrets.urls.REDIS_URL) logger$2.log(` REDIS_URL: ${reveal ? secrets.urls.REDIS_URL : maskUrl(secrets.urls.REDIS_URL)}`);
|
|
14877
|
+
if (secrets.urls.RABBITMQ_URL) logger$2.log(` RABBITMQ_URL: ${reveal ? secrets.urls.RABBITMQ_URL : maskUrl(secrets.urls.RABBITMQ_URL)}`);
|
|
14878
|
+
if (secrets.urls.STORAGE_ENDPOINT) logger$2.log(` STORAGE_ENDPOINT: ${secrets.urls.STORAGE_ENDPOINT}`);
|
|
14740
14879
|
if (Object.keys(secrets.custom).length > 0) {
|
|
14741
|
-
logger$
|
|
14742
|
-
for (const [key, value] of Object.entries(secrets.custom)) logger$
|
|
14880
|
+
logger$2.log("\nCustom Secrets:");
|
|
14881
|
+
for (const [key, value] of Object.entries(secrets.custom)) logger$2.log(` ${key}: ${reveal ? value : maskPassword(value)}`);
|
|
14743
14882
|
}
|
|
14744
|
-
if (!reveal) logger$
|
|
14883
|
+
if (!reveal) logger$2.log("\nUse --reveal to show actual values");
|
|
14745
14884
|
}
|
|
14746
14885
|
/**
|
|
14747
14886
|
* Rotate passwords for services.
|
|
@@ -14750,25 +14889,25 @@ async function secretsRotateCommand(options) {
|
|
|
14750
14889
|
const { stage, service } = options;
|
|
14751
14890
|
const secrets = await readStageSecrets(stage);
|
|
14752
14891
|
if (!secrets) {
|
|
14753
|
-
logger$
|
|
14892
|
+
logger$2.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
14754
14893
|
process.exit(1);
|
|
14755
14894
|
}
|
|
14756
14895
|
if (service) {
|
|
14757
14896
|
if (!secrets.services[service]) {
|
|
14758
|
-
logger$
|
|
14897
|
+
logger$2.error(`Service "${service}" not configured in stage "${stage}"`);
|
|
14759
14898
|
process.exit(1);
|
|
14760
14899
|
}
|
|
14761
14900
|
const updated = rotateServicePassword(secrets, service);
|
|
14762
14901
|
await writeStageSecrets(updated);
|
|
14763
|
-
logger$
|
|
14902
|
+
logger$2.log(`\n✓ Password rotated for ${service} in stage "${stage}"`);
|
|
14764
14903
|
} else {
|
|
14765
14904
|
let updated = secrets;
|
|
14766
14905
|
const services = Object.keys(secrets.services);
|
|
14767
14906
|
for (const svc of services) updated = rotateServicePassword(updated, svc);
|
|
14768
14907
|
await writeStageSecrets(updated);
|
|
14769
|
-
logger$
|
|
14908
|
+
logger$2.log(`\n✓ Passwords rotated for all services in stage "${stage}": ${services.join(", ")}`);
|
|
14770
14909
|
}
|
|
14771
|
-
logger$
|
|
14910
|
+
logger$2.log(`\nUse "gkm secrets:show --stage ${stage}" to view new values`);
|
|
14772
14911
|
}
|
|
14773
14912
|
/**
|
|
14774
14913
|
* Import secrets from a JSON file.
|
|
@@ -14776,7 +14915,7 @@ async function secretsRotateCommand(options) {
|
|
|
14776
14915
|
async function secretsImportCommand(file, options) {
|
|
14777
14916
|
const { stage, merge = true } = options;
|
|
14778
14917
|
if (!existsSync(file)) {
|
|
14779
|
-
logger$
|
|
14918
|
+
logger$2.error(`File not found: ${file}`);
|
|
14780
14919
|
process.exit(1);
|
|
14781
14920
|
}
|
|
14782
14921
|
let importedSecrets;
|
|
@@ -14786,12 +14925,12 @@ async function secretsImportCommand(file, options) {
|
|
|
14786
14925
|
if (typeof importedSecrets !== "object" || importedSecrets === null) throw new Error("JSON must be an object");
|
|
14787
14926
|
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}`);
|
|
14788
14927
|
} catch (error) {
|
|
14789
|
-
logger$
|
|
14928
|
+
logger$2.error(`Failed to parse JSON file: ${error instanceof Error ? error.message : "Invalid JSON"}`);
|
|
14790
14929
|
process.exit(1);
|
|
14791
14930
|
}
|
|
14792
14931
|
const secrets = await readStageSecrets(stage);
|
|
14793
14932
|
if (!secrets) {
|
|
14794
|
-
logger$
|
|
14933
|
+
logger$2.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
14795
14934
|
process.exit(1);
|
|
14796
14935
|
}
|
|
14797
14936
|
const updatedCustom = merge ? {
|
|
@@ -14806,10 +14945,10 @@ async function secretsImportCommand(file, options) {
|
|
|
14806
14945
|
await writeStageSecrets(updated);
|
|
14807
14946
|
const importedCount = Object.keys(importedSecrets).length;
|
|
14808
14947
|
const totalCount = Object.keys(updatedCustom).length;
|
|
14809
|
-
logger$
|
|
14810
|
-
if (merge && totalCount > importedCount) logger$
|
|
14811
|
-
logger$
|
|
14812
|
-
for (const key of Object.keys(importedSecrets)) logger$
|
|
14948
|
+
logger$2.log(`\n✓ Imported ${importedCount} secrets for stage "${stage}"`);
|
|
14949
|
+
if (merge && totalCount > importedCount) logger$2.log(` Total custom secrets: ${totalCount}`);
|
|
14950
|
+
logger$2.log("\n Imported keys:");
|
|
14951
|
+
for (const key of Object.keys(importedSecrets)) logger$2.log(` - ${key}`);
|
|
14813
14952
|
}
|
|
14814
14953
|
/**
|
|
14815
14954
|
* Mask password in a URL for display.
|
|
@@ -14824,175 +14963,120 @@ function maskUrl(url) {
|
|
|
14824
14963
|
}
|
|
14825
14964
|
}
|
|
14826
14965
|
//#endregion
|
|
14827
|
-
//#region src/
|
|
14828
|
-
/**
|
|
14829
|
-
|
|
14830
|
-
|
|
14831
|
-
|
|
14832
|
-
|
|
14833
|
-
* reads none of that: it asks the operating system, so until the root is in the
|
|
14834
|
-
* system store a local `https://` address is "not secure", which is true and
|
|
14835
|
-
* unhelpful.
|
|
14836
|
-
*
|
|
14837
|
-
* A separate command rather than part of `gkm setup`, for one reason: this
|
|
14838
|
-
* needs `sudo`, and a setup that silently escalates on every run is a worse
|
|
14839
|
-
* trade than one that tells you what to type. Caddy's own `caddy trust` draws
|
|
14840
|
-
* the line in the same place.
|
|
14841
|
-
*/
|
|
14842
|
-
const logger$2 = console;
|
|
14843
|
-
const run = promisify(execFile);
|
|
14844
|
-
/**
|
|
14845
|
-
* How each platform is told about a new authority.
|
|
14846
|
-
*
|
|
14847
|
-
* Separate rather than shelled through one script because the failure modes
|
|
14848
|
-
* differ and the message should name the real one: macOS refuses without an
|
|
14849
|
-
* admin password, Debian needs the file in a specific directory before its
|
|
14850
|
-
* updater will look at it.
|
|
14851
|
-
*/
|
|
14852
|
-
function installation(platform, certificate) {
|
|
14853
|
-
if (platform === "darwin") return {
|
|
14854
|
-
describe: "the System keychain",
|
|
14855
|
-
steps: [["sudo", [
|
|
14856
|
-
"security",
|
|
14857
|
-
"add-trusted-cert",
|
|
14858
|
-
"-d",
|
|
14859
|
-
"-r",
|
|
14860
|
-
"trustRoot",
|
|
14861
|
-
"-k",
|
|
14862
|
-
"/Library/Keychains/System.keychain",
|
|
14863
|
-
certificate
|
|
14864
|
-
]]]
|
|
14865
|
-
};
|
|
14866
|
-
if (platform === "linux") {
|
|
14867
|
-
const debian = existsSync("/usr/local/share/ca-certificates");
|
|
14868
|
-
return {
|
|
14869
|
-
describe: debian ? "the system CA bundle" : "the system trust anchors",
|
|
14870
|
-
steps: debian ? [["sudo", [
|
|
14871
|
-
"cp",
|
|
14872
|
-
certificate,
|
|
14873
|
-
"/usr/local/share/ca-certificates/gkm-local-ca.crt"
|
|
14874
|
-
]], ["sudo", ["update-ca-certificates"]]] : [["sudo", [
|
|
14875
|
-
"cp",
|
|
14876
|
-
certificate,
|
|
14877
|
-
"/etc/pki/ca-trust/source/anchors/gkm-local-ca.crt"
|
|
14878
|
-
]], ["sudo", ["update-ca-trust"]]]
|
|
14879
|
-
};
|
|
14966
|
+
//#region src/secrets/store.ts
|
|
14967
|
+
/** The encrypted file under `.gkm/secrets/`, and its key under `~/.gkm/`. */
|
|
14968
|
+
var FileSecretsStore = class {
|
|
14969
|
+
root;
|
|
14970
|
+
constructor(root) {
|
|
14971
|
+
this.root = root;
|
|
14880
14972
|
}
|
|
14881
|
-
|
|
14882
|
-
|
|
14883
|
-
/**
|
|
14884
|
-
* Trust the certificate authority this project's edge issues from.
|
|
14885
|
-
*
|
|
14886
|
-
* Idempotent in effect: installing a root that is already trusted replaces the
|
|
14887
|
-
* same entry, so running it twice is not an error.
|
|
14888
|
-
*/
|
|
14889
|
-
async function trustCommand(options = {}) {
|
|
14890
|
-
const { workspace } = await loadWorkspaceConfig();
|
|
14891
|
-
const certificate = join(workspace.root, LOCAL_CA_PATH);
|
|
14892
|
-
if (!existsSync(certificate)) throw new NoLocalAuthority(certificate);
|
|
14893
|
-
const { describe, steps } = installation(process.platform, certificate);
|
|
14894
|
-
logger$2.log(`\n🔐 Trusting the local authority in ${describe}`);
|
|
14895
|
-
logger$2.log(` ${certificate}\n`);
|
|
14896
|
-
if (options.dryRun) {
|
|
14897
|
-
for (const [command, args] of steps) logger$2.log(` ${command} ${args.join(" ")}`);
|
|
14898
|
-
return;
|
|
14973
|
+
pull(stage) {
|
|
14974
|
+
return readStageSecrets(stage, this.root);
|
|
14899
14975
|
}
|
|
14900
|
-
|
|
14901
|
-
|
|
14902
|
-
|
|
14903
|
-
}
|
|
14976
|
+
push(_stage, secrets) {
|
|
14977
|
+
return writeStageSecrets(secrets, this.root);
|
|
14978
|
+
}
|
|
14979
|
+
};
|
|
14904
14980
|
/**
|
|
14905
|
-
*
|
|
14906
|
-
*
|
|
14907
|
-
* A machine's answer, not the project's: whether a laptop installs a root
|
|
14908
|
-
* certificate is its owner's call, so the decision is `--yes`, then what this
|
|
14909
|
-
* machine previously answered, then a prompt. Never a silent `sudo` — that is
|
|
14910
|
-
* the one thing a setup command should not do on somebody's behalf.
|
|
14981
|
+
* The store a stage's secrets live in.
|
|
14911
14982
|
*
|
|
14912
|
-
*
|
|
14913
|
-
*
|
|
14914
|
-
*/
|
|
14915
|
-
async function
|
|
14916
|
-
|
|
14917
|
-
if (
|
|
14918
|
-
|
|
14919
|
-
|
|
14920
|
-
|
|
14921
|
-
|
|
14922
|
-
|
|
14923
|
-
|
|
14924
|
-
|
|
14925
|
-
name: "install",
|
|
14926
|
-
message: "Trust this project's local certificate authority, so a browser accepts its https addresses? (needs sudo)",
|
|
14927
|
-
initial: false
|
|
14928
|
-
});
|
|
14929
|
-
if (!install) {
|
|
14930
|
-
await recordDeclined(root);
|
|
14931
|
-
logger$2.log(" Skipped. Run \"gkm trust\" later.");
|
|
14932
|
-
return;
|
|
14933
|
-
}
|
|
14934
|
-
}
|
|
14935
|
-
await trustCommand();
|
|
14936
|
-
}
|
|
14937
|
-
/** Where a declined answer is remembered, so setup asks once and not again. */
|
|
14938
|
-
const TRUST_STATE = ".gkm/trust.json";
|
|
14939
|
-
async function declined(root) {
|
|
14940
|
-
try {
|
|
14941
|
-
const raw = await readFile(join(root, TRUST_STATE), "utf-8");
|
|
14942
|
-
return JSON.parse(raw).declined === true;
|
|
14943
|
-
} catch {
|
|
14944
|
-
return false;
|
|
14945
|
-
}
|
|
14983
|
+
* The local stage is always the file: its secrets belong to the machine
|
|
14984
|
+
* running `gkm dev`. A deployed stage uses `secrets.store`.
|
|
14985
|
+
*/
|
|
14986
|
+
async function secretsStoreFor(workspace, stage, options = {}) {
|
|
14987
|
+
const configured = workspace.secrets.store ?? "file";
|
|
14988
|
+
if (stage === workspace.stages.local || configured === "file") return new FileSecretsStore(workspace.root);
|
|
14989
|
+
if (configured.provider !== "ssm") return configured.provider;
|
|
14990
|
+
const { SsmSecretsStore } = await import("./ssm-BKN1Cg4z.mjs");
|
|
14991
|
+
return new SsmSecretsStore({
|
|
14992
|
+
project: workspace.name,
|
|
14993
|
+
region: configured.region,
|
|
14994
|
+
...options.profile ? { profile: options.profile } : {}
|
|
14995
|
+
});
|
|
14946
14996
|
}
|
|
14947
|
-
|
|
14948
|
-
|
|
14949
|
-
|
|
14950
|
-
|
|
14997
|
+
/** Whether a stage's secrets live somewhere other than this machine. */
|
|
14998
|
+
function isRemoteStore(workspace, stage) {
|
|
14999
|
+
const configured = workspace.secrets.store ?? "file";
|
|
15000
|
+
return stage !== workspace.stages.local && configured !== "file";
|
|
14951
15001
|
}
|
|
15002
|
+
//#endregion
|
|
15003
|
+
//#region src/secrets/transfer.ts
|
|
14952
15004
|
/**
|
|
14953
|
-
*
|
|
14954
|
-
*
|
|
14955
|
-
* Asked by connecting rather than by reading a keychain: what matters is
|
|
14956
|
-
* whether a client with no special configuration verifies the edge, and that is
|
|
14957
|
-
* one platform-neutral question instead of three platform-specific ones. It is
|
|
14958
|
-
* also the exact thing a browser is about to do.
|
|
15005
|
+
* Moving a deployed stage's secrets between this machine and its store.
|
|
14959
15006
|
*
|
|
14960
|
-
*
|
|
14961
|
-
*
|
|
14962
|
-
*
|
|
14963
|
-
|
|
14964
|
-
|
|
14965
|
-
|
|
14966
|
-
|
|
14967
|
-
|
|
14968
|
-
|
|
14969
|
-
|
|
14970
|
-
|
|
14971
|
-
|
|
14972
|
-
|
|
14973
|
-
|
|
15007
|
+
* `push` sends the encrypted local copy to the stage's store; `pull` brings it
|
|
15008
|
+
* back and writes the local copy a deploy reads — generating a key for it if
|
|
15009
|
+
* the machine has none, which is what a fresh CI runner needs. Both reconcile
|
|
15010
|
+
* the keys the workspace now derives, so a store never lags a new construct.
|
|
15011
|
+
*/
|
|
15012
|
+
var transfer_exports = /* @__PURE__ */ __exportAll({
|
|
15013
|
+
NoLocalSecrets: () => NoLocalSecrets,
|
|
15014
|
+
NoRemoteSecretsStore: () => NoRemoteSecretsStore,
|
|
15015
|
+
NoStoredSecrets: () => NoStoredSecrets,
|
|
15016
|
+
pullStageSecrets: () => pullStageSecrets,
|
|
15017
|
+
pushStageSecrets: () => pushStageSecrets
|
|
15018
|
+
});
|
|
15019
|
+
/** A push or pull for a stage with nowhere to push or pull from. */
|
|
15020
|
+
var NoRemoteSecretsStore = class extends Error {
|
|
15021
|
+
stage;
|
|
15022
|
+
constructor(stage) {
|
|
15023
|
+
super(`Stage "${stage}" keeps its secrets in the local file, so there is nothing to push or pull. Set secrets.store in gkm.config.ts (e.g. { provider: 'ssm', region: 'eu-west-1' }) for deployed stages.`);
|
|
15024
|
+
this.stage = stage;
|
|
15025
|
+
this.name = "NoRemoteSecretsStore";
|
|
14974
15026
|
}
|
|
14975
|
-
}
|
|
14976
|
-
/**
|
|
14977
|
-
var
|
|
14978
|
-
|
|
14979
|
-
constructor(
|
|
14980
|
-
super(`No
|
|
14981
|
-
this.
|
|
14982
|
-
this.name = "
|
|
15027
|
+
};
|
|
15028
|
+
/** A push for a stage whose secrets this machine does not have. */
|
|
15029
|
+
var NoLocalSecrets = class extends Error {
|
|
15030
|
+
stage;
|
|
15031
|
+
constructor(stage) {
|
|
15032
|
+
super(`No secrets for stage "${stage}" on this machine. Run gkm secrets:init --stage ${stage} first.`);
|
|
15033
|
+
this.stage = stage;
|
|
15034
|
+
this.name = "NoLocalSecrets";
|
|
14983
15035
|
}
|
|
14984
15036
|
};
|
|
14985
|
-
/** A
|
|
14986
|
-
var
|
|
14987
|
-
|
|
14988
|
-
|
|
14989
|
-
|
|
14990
|
-
|
|
14991
|
-
this.
|
|
14992
|
-
this.certificate = certificate;
|
|
14993
|
-
this.name = "UnsupportedPlatform";
|
|
15037
|
+
/** A pull for a stage the store holds nothing for. */
|
|
15038
|
+
var NoStoredSecrets = class extends Error {
|
|
15039
|
+
stage;
|
|
15040
|
+
constructor(stage) {
|
|
15041
|
+
super(`The store holds no secrets for stage "${stage}". Push them first: gkm secrets:push --stage ${stage}.`);
|
|
15042
|
+
this.stage = stage;
|
|
15043
|
+
this.name = "NoStoredSecrets";
|
|
14994
15044
|
}
|
|
14995
15045
|
};
|
|
15046
|
+
async function reconcile(secrets, workspace, stage) {
|
|
15047
|
+
const { derivedContainers } = await import("./workspace-cBobQVoA.mjs").then((n) => n.a);
|
|
15048
|
+
const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
|
|
15049
|
+
return result ? {
|
|
15050
|
+
secrets: result.secrets,
|
|
15051
|
+
addedKeys: result.addedKeys
|
|
15052
|
+
} : {
|
|
15053
|
+
secrets,
|
|
15054
|
+
addedKeys: []
|
|
15055
|
+
};
|
|
15056
|
+
}
|
|
15057
|
+
function remoteOnly(workspace, stage) {
|
|
15058
|
+
assertDeployedStage(workspace.stages, stage);
|
|
15059
|
+
if (!isRemoteStore(workspace, stage)) throw new NoRemoteSecretsStore(stage);
|
|
15060
|
+
}
|
|
15061
|
+
/** Send this machine's secrets for a deployed stage to its store. */
|
|
15062
|
+
async function pushStageSecrets(workspace, stage, options = {}) {
|
|
15063
|
+
remoteOnly(workspace, stage);
|
|
15064
|
+
const local = await readStageSecrets(stage, workspace.root);
|
|
15065
|
+
if (!local) throw new NoLocalSecrets(stage);
|
|
15066
|
+
const result = await reconcile(local, workspace, stage);
|
|
15067
|
+
if (result.addedKeys.length) await writeStageSecrets(result.secrets, workspace.root);
|
|
15068
|
+
await (await secretsStoreFor(workspace, stage, options)).push(stage, result.secrets);
|
|
15069
|
+
return result;
|
|
15070
|
+
}
|
|
15071
|
+
/** Bring a deployed stage's secrets from its store into the local copy. */
|
|
15072
|
+
async function pullStageSecrets(workspace, stage, options = {}) {
|
|
15073
|
+
remoteOnly(workspace, stage);
|
|
15074
|
+
const stored = await (await secretsStoreFor(workspace, stage, options)).pull(stage);
|
|
15075
|
+
if (!stored) throw new NoStoredSecrets(stage);
|
|
15076
|
+
const result = await reconcile(stored, workspace, stage);
|
|
15077
|
+
await writeStageSecrets(result.secrets, workspace.root);
|
|
15078
|
+
return result;
|
|
15079
|
+
}
|
|
14996
15080
|
//#endregion
|
|
14997
15081
|
//#region src/setup/index.ts
|
|
14998
15082
|
var setup_exports = /* @__PURE__ */ __exportAll({
|
|
@@ -15061,8 +15145,7 @@ async function reconcileLocal(workspace, stage, options) {
|
|
|
15061
15145
|
}
|
|
15062
15146
|
logger$1.log(`🐳 Services: ${result.plan.containers.join(", ")}`);
|
|
15063
15147
|
for (const [container, address] of Object.entries(result.addresses)) logger$1.log(` ${container}: ${address}`);
|
|
15064
|
-
const
|
|
15065
|
-
const url = served ? result.env[served.envKey] : void 0;
|
|
15148
|
+
const url = result.plan.resources.filter((r) => r.kind === "rest-api" || r.kind === "site" || r.kind === "file-server").map((r) => result.env[r.envKey]).find((address) => address?.startsWith("https://"));
|
|
15066
15149
|
if (url) await ensureTrusted(workspace.root, url, { ...options.yes ? { assumeYes: true } : {} });
|
|
15067
15150
|
}
|
|
15068
15151
|
/**
|
|
@@ -16109,7 +16192,7 @@ program.command("secrets:push").description("Send a deployed stage's secrets to
|
|
|
16109
16192
|
const globalOptions = program.opts();
|
|
16110
16193
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16111
16194
|
const { loadWorkspaceConfig } = await import("./config.mjs");
|
|
16112
|
-
const { pushStageSecrets } = await
|
|
16195
|
+
const { pushStageSecrets } = await Promise.resolve().then(() => transfer_exports);
|
|
16113
16196
|
const { workspace } = await loadWorkspaceConfig();
|
|
16114
16197
|
const { addedKeys } = await pushStageSecrets(workspace, options.stage, { profile: options.profile });
|
|
16115
16198
|
for (const key of addedKeys) console.log(` + ${key} (reconciled)`);
|
|
@@ -16124,7 +16207,7 @@ program.command("secrets:pull").description("Bring a deployed stage's secrets fr
|
|
|
16124
16207
|
const globalOptions = program.opts();
|
|
16125
16208
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16126
16209
|
const { loadWorkspaceConfig } = await import("./config.mjs");
|
|
16127
|
-
const { pullStageSecrets } = await
|
|
16210
|
+
const { pullStageSecrets } = await Promise.resolve().then(() => transfer_exports);
|
|
16128
16211
|
const { workspace } = await loadWorkspaceConfig();
|
|
16129
16212
|
const { addedKeys } = await pullStageSecrets(workspace, options.stage, { profile: options.profile });
|
|
16130
16213
|
for (const key of addedKeys) console.log(` + ${key} (reconciled)`);
|
|
@@ -16148,7 +16231,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
|
|
|
16148
16231
|
console.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
16149
16232
|
process.exit(1);
|
|
16150
16233
|
}
|
|
16151
|
-
const { derivedContainers } = await import("./workspace-
|
|
16234
|
+
const { derivedContainers } = await import("./workspace-cBobQVoA.mjs").then((n) => n.a);
|
|
16152
16235
|
const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
|
|
16153
16236
|
if (!result) {
|
|
16154
16237
|
console.log(`\n✓ Secrets for stage "${stage}" are up-to-date`);
|
|
@@ -16207,7 +16290,7 @@ program.command("deploy:github").description("Let GitHub Actions deploy a stage:
|
|
|
16207
16290
|
try {
|
|
16208
16291
|
const globalOptions = program.opts();
|
|
16209
16292
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16210
|
-
const { deployGithubCommand } = await import("./github-
|
|
16293
|
+
const { deployGithubCommand } = await import("./github-zkzu-ka2.mjs");
|
|
16211
16294
|
await deployGithubCommand(options);
|
|
16212
16295
|
} catch (error) {
|
|
16213
16296
|
console.error(formatError(error));
|
|
@@ -16332,6 +16415,6 @@ program.command("upgrade").description("Upgrade @geekmidas/cli (with --all, ever
|
|
|
16332
16415
|
});
|
|
16333
16416
|
program.parse();
|
|
16334
16417
|
//#endregion
|
|
16335
|
-
export { getHostingerToken as n, encryption_exports as t };
|
|
16418
|
+
export { getHostingerToken as i, isRemoteStore as n, encryption_exports as r, pushStageSecrets as t };
|
|
16336
16419
|
|
|
16337
16420
|
//# sourceMappingURL=index.mjs.map
|