@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.cjs
CHANGED
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env -S npx tsx
|
|
2
2
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
3
|
const require_rolldown_runtime = require("./rolldown-runtime-C0BPl7ul.cjs");
|
|
4
|
-
const require_discover = require("./discover-
|
|
5
|
-
const require_config = require("./config-
|
|
4
|
+
const require_discover = require("./discover-BTh87DOO.cjs");
|
|
5
|
+
const require_config = require("./config-BTFLHfRb.cjs");
|
|
6
6
|
const require_workspace = require("./workspace-Bav3TVrP.cjs");
|
|
7
7
|
const require_storage = require("./storage-DgypfSD6.cjs");
|
|
8
|
-
const require_workspace$1 = require("./workspace-
|
|
9
|
-
const require_openapi = require("./openapi-
|
|
8
|
+
const require_workspace$1 = require("./workspace-Cv48QwPK.cjs");
|
|
9
|
+
const require_openapi = require("./openapi-K4H5tHzA.cjs");
|
|
10
10
|
const require_emit = require("./emit-BdoPFjZf.cjs");
|
|
11
11
|
const require_reconcile = require("./reconcile-szmJw_Sb.cjs");
|
|
12
|
-
const require_transfer = require("./transfer-BYIn7z8m.cjs");
|
|
13
12
|
let commander = require("commander");
|
|
14
13
|
let node_process = require("node:process");
|
|
15
14
|
let node_readline_promises = require("node:readline/promises");
|
|
@@ -22,6 +21,7 @@ let node_child_process = require("node:child_process");
|
|
|
22
21
|
let _geekmidas_manifest = require("@geekmidas/manifest");
|
|
23
22
|
let fast_glob = require("fast-glob");
|
|
24
23
|
fast_glob = require_rolldown_runtime.__toESM(fast_glob, 1);
|
|
24
|
+
let node_module = require("node:module");
|
|
25
25
|
let chokidar = require("chokidar");
|
|
26
26
|
chokidar = require_rolldown_runtime.__toESM(chokidar, 1);
|
|
27
27
|
let node_crypto = require("node:crypto");
|
|
@@ -31,19 +31,18 @@ let pg = require("pg");
|
|
|
31
31
|
let yaml = require("yaml");
|
|
32
32
|
let node_util = require("node:util");
|
|
33
33
|
let _geekmidas_cache_postgres = require("@geekmidas/cache/postgres");
|
|
34
|
+
let prompts = require("prompts");
|
|
35
|
+
prompts = require_rolldown_runtime.__toESM(prompts, 1);
|
|
34
36
|
let _geekmidas_constructs_crons = require("@geekmidas/constructs/crons");
|
|
35
37
|
let _geekmidas_constructs_functions = require("@geekmidas/constructs/functions");
|
|
36
38
|
let _geekmidas_constructs_queue = require("@geekmidas/constructs/queue");
|
|
37
39
|
let _geekmidas_constructs_subscribers = require("@geekmidas/constructs/subscribers");
|
|
38
40
|
let _geekmidas_constructs_topic = require("@geekmidas/constructs/topic");
|
|
39
41
|
let node_dns_promises = require("node:dns/promises");
|
|
40
|
-
let node_module = require("node:module");
|
|
41
|
-
let prompts = require("prompts");
|
|
42
|
-
prompts = require_rolldown_runtime.__toESM(prompts, 1);
|
|
43
42
|
let _geekmidas_envkit = require("@geekmidas/envkit");
|
|
44
43
|
let _geekmidas_services = require("@geekmidas/services");
|
|
45
44
|
//#region package.json
|
|
46
|
-
var version = "10.0.0-alpha.
|
|
45
|
+
var version = "10.0.0-alpha.28";
|
|
47
46
|
//#endregion
|
|
48
47
|
//#region src/auth/credentials.ts
|
|
49
48
|
/**
|
|
@@ -410,8 +409,182 @@ function normalizeRoutes(routes) {
|
|
|
410
409
|
return Array.isArray(routes) ? routes : [routes];
|
|
411
410
|
}
|
|
412
411
|
//#endregion
|
|
413
|
-
//#region src/
|
|
412
|
+
//#region src/trust/index.ts
|
|
413
|
+
/**
|
|
414
|
+
* Installing the local edge's certificate authority into the OS trust store.
|
|
415
|
+
*
|
|
416
|
+
* `NODE_EXTRA_CA_CERTS` covers everything gkm starts — the dev server, the test
|
|
417
|
+
* suite, anything under `gkm exec` — because reconcile injects it. A browser
|
|
418
|
+
* reads none of that: it asks the operating system, so until the root is in the
|
|
419
|
+
* system store a local `https://` address is "not secure", which is true and
|
|
420
|
+
* unhelpful.
|
|
421
|
+
*
|
|
422
|
+
* A separate command rather than part of `gkm setup`, for one reason: this
|
|
423
|
+
* needs `sudo`, and a setup that silently escalates on every run is a worse
|
|
424
|
+
* trade than one that tells you what to type. Caddy's own `caddy trust` draws
|
|
425
|
+
* the line in the same place.
|
|
426
|
+
*/
|
|
414
427
|
const logger$12 = console;
|
|
428
|
+
const run = (0, node_util.promisify)(node_child_process.execFile);
|
|
429
|
+
/**
|
|
430
|
+
* How each platform is told about a new authority.
|
|
431
|
+
*
|
|
432
|
+
* Separate rather than shelled through one script because the failure modes
|
|
433
|
+
* differ and the message should name the real one: macOS refuses without an
|
|
434
|
+
* admin password, Debian needs the file in a specific directory before its
|
|
435
|
+
* updater will look at it.
|
|
436
|
+
*/
|
|
437
|
+
function installation(platform, certificate) {
|
|
438
|
+
if (platform === "darwin") return {
|
|
439
|
+
describe: "the System keychain",
|
|
440
|
+
steps: [["sudo", [
|
|
441
|
+
"security",
|
|
442
|
+
"add-trusted-cert",
|
|
443
|
+
"-d",
|
|
444
|
+
"-r",
|
|
445
|
+
"trustRoot",
|
|
446
|
+
"-k",
|
|
447
|
+
"/Library/Keychains/System.keychain",
|
|
448
|
+
certificate
|
|
449
|
+
]]]
|
|
450
|
+
};
|
|
451
|
+
if (platform === "linux") {
|
|
452
|
+
const debian = (0, node_fs.existsSync)("/usr/local/share/ca-certificates");
|
|
453
|
+
return {
|
|
454
|
+
describe: debian ? "the system CA bundle" : "the system trust anchors",
|
|
455
|
+
steps: debian ? [["sudo", [
|
|
456
|
+
"cp",
|
|
457
|
+
certificate,
|
|
458
|
+
"/usr/local/share/ca-certificates/gkm-local-ca.crt"
|
|
459
|
+
]], ["sudo", ["update-ca-certificates"]]] : [["sudo", [
|
|
460
|
+
"cp",
|
|
461
|
+
certificate,
|
|
462
|
+
"/etc/pki/ca-trust/source/anchors/gkm-local-ca.crt"
|
|
463
|
+
]], ["sudo", ["update-ca-trust"]]]
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
throw new UnsupportedPlatform(platform, certificate);
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Trust the certificate authority this project's edge issues from.
|
|
470
|
+
*
|
|
471
|
+
* Idempotent in effect: installing a root that is already trusted replaces the
|
|
472
|
+
* same entry, so running it twice is not an error.
|
|
473
|
+
*/
|
|
474
|
+
async function trustCommand(options = {}) {
|
|
475
|
+
const { workspace } = await require_config.loadWorkspaceConfig();
|
|
476
|
+
const certificate = (0, node_path.join)(workspace.root, require_workspace$1.LOCAL_CA_PATH);
|
|
477
|
+
if (!(0, node_fs.existsSync)(certificate)) throw new NoLocalAuthority(certificate);
|
|
478
|
+
const { describe, steps } = installation(process.platform, certificate);
|
|
479
|
+
logger$12.log(`\n🔐 Trusting the local authority in ${describe}`);
|
|
480
|
+
logger$12.log(` ${certificate}\n`);
|
|
481
|
+
if (options.dryRun) {
|
|
482
|
+
for (const [command, args] of steps) logger$12.log(` ${command} ${args.join(" ")}`);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
logger$12.log(" This needs an admin password.\n");
|
|
486
|
+
for (const [command, args] of steps) await run(command, args, { stdio: "inherit" });
|
|
487
|
+
logger$12.log("\n✅ Local https addresses are trusted. Restart your browser.\n");
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Trust the authority if this machine lacks it and its owner agrees.
|
|
491
|
+
*
|
|
492
|
+
* A machine's answer, not the project's: whether a laptop installs a root
|
|
493
|
+
* certificate is its owner's call, so the decision is `--yes`, then what this
|
|
494
|
+
* machine previously answered, then a prompt. Never a silent `sudo` — that is
|
|
495
|
+
* the one thing a setup command should not do on somebody's behalf.
|
|
496
|
+
*
|
|
497
|
+
* Silent and free when the machine already trusts it, which is what makes it
|
|
498
|
+
* acceptable to call on every `gkm setup`.
|
|
499
|
+
*/
|
|
500
|
+
async function ensureTrusted(root, url, options = {}) {
|
|
501
|
+
if (await isTrusted(url)) return;
|
|
502
|
+
if (!options.assumeYes) {
|
|
503
|
+
if (!process.stdin.isTTY) {
|
|
504
|
+
logger$12.log(" Local https addresses are not trusted here. Run \"gkm trust\", or \"gkm setup --yes\".");
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
if (await declined(root)) return;
|
|
508
|
+
const { install } = await (0, prompts.default)({
|
|
509
|
+
type: "confirm",
|
|
510
|
+
name: "install",
|
|
511
|
+
message: "Trust this project's local certificate authority, so a browser accepts its https addresses? (needs sudo)",
|
|
512
|
+
initial: false
|
|
513
|
+
});
|
|
514
|
+
if (!install) {
|
|
515
|
+
await recordDeclined(root);
|
|
516
|
+
logger$12.log(" Skipped. Run \"gkm trust\" later.");
|
|
517
|
+
return;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
await trustCommand();
|
|
521
|
+
}
|
|
522
|
+
/** Where a declined answer is remembered, so setup asks once and not again. */
|
|
523
|
+
const TRUST_STATE = ".gkm/trust.json";
|
|
524
|
+
async function declined(root) {
|
|
525
|
+
try {
|
|
526
|
+
const raw = await (0, node_fs_promises.readFile)((0, node_path.join)(root, TRUST_STATE), "utf-8");
|
|
527
|
+
return JSON.parse(raw).declined === true;
|
|
528
|
+
} catch {
|
|
529
|
+
return false;
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
async function recordDeclined(root) {
|
|
533
|
+
const path = (0, node_path.join)(root, TRUST_STATE);
|
|
534
|
+
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(path), { recursive: true });
|
|
535
|
+
await (0, node_fs_promises.writeFile)(path, `${JSON.stringify({ declined: true }, null, 2)}\n`);
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Whether this machine already trusts the local authority.
|
|
539
|
+
*
|
|
540
|
+
* Asked by connecting rather than by reading a keychain: what matters is
|
|
541
|
+
* whether a client with no special configuration verifies the edge, and that is
|
|
542
|
+
* one platform-neutral question instead of three platform-specific ones. It is
|
|
543
|
+
* also the exact thing a browser is about to do.
|
|
544
|
+
*
|
|
545
|
+
* A child process, because `NODE_EXTRA_CA_CERTS` is read once at startup and
|
|
546
|
+
* this process almost certainly has it set — asking in-process would answer
|
|
547
|
+
* "yes" for everyone.
|
|
548
|
+
*/
|
|
549
|
+
async function isTrusted(url) {
|
|
550
|
+
const { NODE_EXTRA_CA_CERTS: _ignored, ...env } = process.env;
|
|
551
|
+
try {
|
|
552
|
+
await run(process.execPath, [
|
|
553
|
+
"--use-system-ca",
|
|
554
|
+
"-e",
|
|
555
|
+
`fetch(${JSON.stringify(url)}).then(()=>0,()=>process.exit(1))`
|
|
556
|
+
], {
|
|
557
|
+
env,
|
|
558
|
+
timeout: 15e3
|
|
559
|
+
});
|
|
560
|
+
return true;
|
|
561
|
+
} catch {
|
|
562
|
+
return false;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
/** The edge has not run, so there is no authority to trust yet. */
|
|
566
|
+
var NoLocalAuthority = class extends Error {
|
|
567
|
+
path;
|
|
568
|
+
constructor(path) {
|
|
569
|
+
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.`);
|
|
570
|
+
this.path = path;
|
|
571
|
+
this.name = "NoLocalAuthority";
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
/** A platform with no trust store this knows how to write to. */
|
|
575
|
+
var UnsupportedPlatform = class extends Error {
|
|
576
|
+
platform;
|
|
577
|
+
certificate;
|
|
578
|
+
constructor(platform, certificate) {
|
|
579
|
+
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.`);
|
|
580
|
+
this.platform = platform;
|
|
581
|
+
this.certificate = certificate;
|
|
582
|
+
this.name = "UnsupportedPlatform";
|
|
583
|
+
}
|
|
584
|
+
};
|
|
585
|
+
//#endregion
|
|
586
|
+
//#region src/exec/index.ts
|
|
587
|
+
const logger$11 = console;
|
|
415
588
|
/** `gkm exec` was given nothing to run. */
|
|
416
589
|
var NoCommandSpecified = class extends Error {
|
|
417
590
|
constructor() {
|
|
@@ -434,17 +607,17 @@ async function execCommand(commandArgs, options = {}) {
|
|
|
434
607
|
const cwd = options.cwd ?? process.cwd();
|
|
435
608
|
if (!commandArgs[0]) throw new NoCommandSpecified();
|
|
436
609
|
const defaultEnv = require_workspace$1.loadEnvFiles(".env");
|
|
437
|
-
if (defaultEnv.loaded.length > 0) logger$
|
|
610
|
+
if (defaultEnv.loaded.length > 0) logger$11.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
438
611
|
const { credentials, secretsJsonPath, appName } = await require_workspace$1.prepareEntryCredentials({ cwd });
|
|
439
|
-
if (appName) logger$
|
|
612
|
+
if (appName) logger$11.log(`📦 App: ${appName}`);
|
|
440
613
|
const secretCount = Object.keys(credentials).filter((k) => k !== "PORT").length;
|
|
441
|
-
if (secretCount > 0) logger$
|
|
614
|
+
if (secretCount > 0) logger$11.log(`🔐 Loaded ${secretCount} secret(s)`);
|
|
442
615
|
const preloadDir = (0, node_path.join)(cwd, ".gkm");
|
|
443
616
|
await (0, node_fs_promises.mkdir)(preloadDir, { recursive: true });
|
|
444
617
|
const preloadPath = (0, node_path.join)(preloadDir, "credentials-preload.mjs");
|
|
445
618
|
await require_workspace$1.createCredentialsPreload(preloadPath, secretsJsonPath);
|
|
446
619
|
const [cmd, ...args] = commandArgs;
|
|
447
|
-
logger$
|
|
620
|
+
logger$11.log(`🚀 Running: ${[cmd, ...args].join(" ")}`);
|
|
448
621
|
const nodeOptions = [(process.env.NODE_OPTIONS ?? "").replace(/--import[= ]\S+/g, "").trim(), `--import=${preloadPath}`].filter(Boolean).join(" ");
|
|
449
622
|
const child = (0, node_child_process.spawn)(cmd, args, {
|
|
450
623
|
cwd,
|
|
@@ -458,7 +631,7 @@ async function execCommand(commandArgs, options = {}) {
|
|
|
458
631
|
const exitCode = await new Promise((resolve) => {
|
|
459
632
|
child.on("close", (code) => resolve(code ?? 0));
|
|
460
633
|
child.on("error", (error) => {
|
|
461
|
-
logger$
|
|
634
|
+
logger$11.error(`Failed to run command: ${error.message}`);
|
|
462
635
|
resolve(1);
|
|
463
636
|
});
|
|
464
637
|
});
|
|
@@ -466,7 +639,7 @@ async function execCommand(commandArgs, options = {}) {
|
|
|
466
639
|
}
|
|
467
640
|
//#endregion
|
|
468
641
|
//#region src/dev/index.ts
|
|
469
|
-
const logger$
|
|
642
|
+
const logger$10 = console;
|
|
470
643
|
/**
|
|
471
644
|
* Normalize telescope configuration
|
|
472
645
|
* @internal Exported for testing
|
|
@@ -560,7 +733,7 @@ function getProductionConfigFromGkm(config) {
|
|
|
560
733
|
async function devCommand(options) {
|
|
561
734
|
if (options.entry) return entryDevCommand(options);
|
|
562
735
|
const defaultEnv = require_workspace$1.loadEnvFiles(".env");
|
|
563
|
-
if (defaultEnv.loaded.length > 0) logger$
|
|
736
|
+
if (defaultEnv.loaded.length > 0) logger$10.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
564
737
|
const appName = require_config.getAppNameFromCwd();
|
|
565
738
|
let config;
|
|
566
739
|
let appRoot = process.cwd();
|
|
@@ -576,9 +749,8 @@ async function devCommand(options) {
|
|
|
576
749
|
workspaceAppName = appConfig.appName;
|
|
577
750
|
workspaceAppPort = appConfig.app.port;
|
|
578
751
|
workspace = appConfig.workspace;
|
|
579
|
-
logger$11.log(`📦 Running app: ${appConfig.appName} on port ${workspaceAppPort}`);
|
|
580
752
|
if (appConfig.app.entry) {
|
|
581
|
-
logger$
|
|
753
|
+
logger$10.log(`📄 Using entry point: ${appConfig.app.entry}`);
|
|
582
754
|
return entryDevCommand({
|
|
583
755
|
...options,
|
|
584
756
|
entry: appConfig.app.entry,
|
|
@@ -588,39 +760,27 @@ async function devCommand(options) {
|
|
|
588
760
|
}
|
|
589
761
|
} catch {
|
|
590
762
|
const loadedConfig = await require_config.loadWorkspaceConfig();
|
|
591
|
-
if (loadedConfig.type === "workspace")
|
|
592
|
-
logger$11.log("📦 Detected workspace configuration");
|
|
593
|
-
return workspaceDevCommand(loadedConfig.workspace, options);
|
|
594
|
-
}
|
|
763
|
+
if (loadedConfig.type === "workspace") return workspaceDevCommand(loadedConfig.workspace, options);
|
|
595
764
|
config = loadedConfig.raw;
|
|
596
765
|
workspace = loadedConfig.workspace;
|
|
597
766
|
}
|
|
598
767
|
else {
|
|
599
768
|
const loadedConfig = await require_config.loadWorkspaceConfig();
|
|
600
|
-
if (loadedConfig.type === "workspace")
|
|
601
|
-
logger$11.log("📦 Detected workspace configuration");
|
|
602
|
-
return workspaceDevCommand(loadedConfig.workspace, options);
|
|
603
|
-
}
|
|
769
|
+
if (loadedConfig.type === "workspace") return workspaceDevCommand(loadedConfig.workspace, options);
|
|
604
770
|
config = loadedConfig.raw;
|
|
605
771
|
workspace = loadedConfig.workspace;
|
|
606
772
|
}
|
|
607
773
|
if (config.env) {
|
|
608
774
|
const { loaded, missing } = require_workspace$1.loadEnvFiles(config.env, appRoot);
|
|
609
|
-
if (loaded.length > 0) logger$
|
|
610
|
-
if (missing.length > 0) logger$
|
|
775
|
+
if (loaded.length > 0) logger$10.log(`📦 Loaded env: ${loaded.join(", ")}`);
|
|
776
|
+
if (missing.length > 0) logger$10.warn(`⚠️ Missing env files: ${missing.join(", ")}`);
|
|
611
777
|
}
|
|
612
778
|
const resolved = resolveProviders(config, { provider: "server" });
|
|
613
|
-
logger$11.log("🚀 Starting development server...");
|
|
614
|
-
logger$11.log(`Loading constructs from: ${config.constructs}`);
|
|
615
779
|
const telescope = normalizeTelescopeConfig(config.telescope);
|
|
616
|
-
if (telescope) logger$11.log(`🔭 Telescope enabled at ${telescope.path}`);
|
|
617
780
|
const studio = normalizeStudioConfig(config.studio);
|
|
618
|
-
if (studio) logger$11.log(`🗄️ Studio enabled at ${studio.path}`);
|
|
619
781
|
const hooks = normalizeHooksConfig(config.hooks, appRoot);
|
|
620
|
-
if (hooks) logger$11.log(`🪝 Server hooks enabled from ${config.hooks?.server}`);
|
|
621
782
|
const enableOpenApi = require_openapi.resolveOpenApiConfig(config).enabled || resolved.enableOpenApi;
|
|
622
|
-
|
|
623
|
-
const build = (bustCache = false) => buildApp({
|
|
783
|
+
const build = (bustCache = false) => quietly(() => buildApp({
|
|
624
784
|
config,
|
|
625
785
|
workspaceRoot: workspace?.root ?? secretsRoot,
|
|
626
786
|
appRoot,
|
|
@@ -633,18 +793,21 @@ async function devCommand(options) {
|
|
|
633
793
|
skipBundle: true,
|
|
634
794
|
bustCache,
|
|
635
795
|
serveEmpty: true
|
|
636
|
-
});
|
|
796
|
+
}));
|
|
637
797
|
const initial = await build();
|
|
638
798
|
const runtime = config.runtime ?? "node";
|
|
639
799
|
let secretsJsonPath;
|
|
800
|
+
let publicUrl;
|
|
640
801
|
const appSecrets = await require_workspace$1.loadSecretsForApp(secretsRoot, config.stages.local, workspaceAppName);
|
|
641
802
|
if (workspace) {
|
|
642
803
|
const reconciled = await require_workspace$1.reconcileWorkspace(workspace, { stage: workspace.stages.local });
|
|
643
804
|
if (reconciled.changed && reconciled.plan.containers.length > 0) {
|
|
644
|
-
logger$
|
|
645
|
-
for (const [container, address] of Object.entries(reconciled.addresses)) logger$
|
|
805
|
+
logger$10.log(`🐳 Services: ${reconciled.plan.containers.join(", ")}`);
|
|
806
|
+
for (const [container, address] of Object.entries(reconciled.addresses)) logger$10.log(` ${container}: ${address}`);
|
|
646
807
|
}
|
|
647
808
|
Object.assign(appSecrets, reconciled.env);
|
|
809
|
+
const own = reconciled.plan.resources.find((r) => (r.kind === "rest-api" || r.kind === "site") && require_config.appKey(r.id) === workspaceAppName);
|
|
810
|
+
publicUrl = own ? reconciled.env[own.envKey] : void 0;
|
|
648
811
|
}
|
|
649
812
|
if (Object.keys(appSecrets).length > 0) {
|
|
650
813
|
const secretsDir = (0, node_path.join)(secretsRoot, ".gkm");
|
|
@@ -652,13 +815,13 @@ async function devCommand(options) {
|
|
|
652
815
|
const secretsFileName = workspaceAppName ? `dev-secrets-${workspaceAppName}.json` : "dev-secrets.json";
|
|
653
816
|
secretsJsonPath = (0, node_path.join)(secretsDir, secretsFileName);
|
|
654
817
|
await (0, node_fs_promises.writeFile)(secretsJsonPath, JSON.stringify(appSecrets, null, 2));
|
|
655
|
-
logger$11.log(`🔐 Loaded ${Object.keys(appSecrets).length} secret(s)`);
|
|
656
818
|
}
|
|
657
819
|
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);
|
|
820
|
+
devServer.label = workspaceAppName ?? "server";
|
|
821
|
+
devServer.publicUrl = publicUrl;
|
|
658
822
|
await devServer.start();
|
|
659
823
|
const hooksFile = (config.hooks?.server?.split("#"))?.[0];
|
|
660
824
|
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);
|
|
661
|
-
logger$11.log(`👀 Watching for changes in: ${normalizedPatterns.join(", ")}`);
|
|
662
825
|
const resolvedFiles = await (0, fast_glob.default)(normalizedPatterns, {
|
|
663
826
|
cwd: appRoot,
|
|
664
827
|
absolute: false,
|
|
@@ -667,34 +830,28 @@ async function devCommand(options) {
|
|
|
667
830
|
const dirsToWatch = [...new Set(resolvedFiles.map((f) => {
|
|
668
831
|
return f.split("/").slice(0, -1).join("/");
|
|
669
832
|
}))];
|
|
670
|
-
logger$11.log(`📁 Found ${resolvedFiles.length} files in ${dirsToWatch.length} directories`);
|
|
671
833
|
const watcher = chokidar.default.watch([...resolvedFiles, ...dirsToWatch], {
|
|
672
834
|
ignored: /(^|[/\\])\../,
|
|
673
835
|
persistent: true,
|
|
674
836
|
ignoreInitial: true,
|
|
675
837
|
cwd: appRoot
|
|
676
838
|
});
|
|
677
|
-
watcher.on("ready", () => {
|
|
678
|
-
logger$11.log("🔍 File watcher ready");
|
|
679
|
-
});
|
|
680
839
|
watcher.on("error", (error) => {
|
|
681
|
-
logger$
|
|
840
|
+
logger$10.error("❌ Watcher error:", error);
|
|
682
841
|
});
|
|
683
842
|
let rebuildTimeout = null;
|
|
684
843
|
watcher.on("change", async (path) => {
|
|
685
|
-
logger$
|
|
844
|
+
logger$10.log(`🔄 ${path} changed — rebuilding`);
|
|
686
845
|
if (rebuildTimeout) clearTimeout(rebuildTimeout);
|
|
687
846
|
rebuildTimeout = setTimeout(async () => {
|
|
688
847
|
try {
|
|
689
|
-
logger$11.log("🔄 Rebuilding...");
|
|
690
848
|
const rebuilt = await build(true);
|
|
691
|
-
logger$11.log("✅ Rebuild complete, restarting server...");
|
|
692
849
|
devServer.selfServing = rebuilt.selfServing !== void 0;
|
|
693
850
|
await devServer.restart();
|
|
694
851
|
} catch (error) {
|
|
695
852
|
const err = error;
|
|
696
|
-
logger$
|
|
697
|
-
if (err.stack) logger$
|
|
853
|
+
logger$10.error(`❌ Rebuild failed: ${err.message}`);
|
|
854
|
+
if (err.stack) logger$10.error(err.stack);
|
|
698
855
|
}
|
|
699
856
|
}, 300);
|
|
700
857
|
});
|
|
@@ -702,9 +859,9 @@ async function devCommand(options) {
|
|
|
702
859
|
const shutdown = () => {
|
|
703
860
|
if (isShuttingDown) return;
|
|
704
861
|
isShuttingDown = true;
|
|
705
|
-
logger$
|
|
862
|
+
logger$10.log("\n🛑 Shutting down...");
|
|
706
863
|
Promise.all([watcher.close(), devServer.stop()]).catch((err) => {
|
|
707
|
-
logger$
|
|
864
|
+
logger$10.error("Error during shutdown:", err);
|
|
708
865
|
}).finally(() => {
|
|
709
866
|
process.exit(0);
|
|
710
867
|
});
|
|
@@ -890,12 +1047,8 @@ async function loadDevSecrets(workspace) {
|
|
|
890
1047
|
const stage = workspace.stages.local;
|
|
891
1048
|
if (require_storage.secretsExist(stage, workspace.root)) {
|
|
892
1049
|
const secrets = await require_storage.readStageSecrets(stage, workspace.root);
|
|
893
|
-
if (secrets)
|
|
894
|
-
logger$11.log(`🔐 Loading secrets from stage: ${stage}`);
|
|
895
|
-
return require_storage.toEmbeddableSecrets(secrets);
|
|
896
|
-
}
|
|
1050
|
+
if (secrets) return require_storage.toEmbeddableSecrets(secrets);
|
|
897
1051
|
}
|
|
898
|
-
logger$11.warn(`⚠️ Secrets enabled but no "${stage}" secrets found. Run "gkm setup" to initialize the local stage`);
|
|
899
1052
|
return {};
|
|
900
1053
|
}
|
|
901
1054
|
/**
|
|
@@ -909,38 +1062,30 @@ async function loadDevSecrets(workspace) {
|
|
|
909
1062
|
*/
|
|
910
1063
|
async function workspaceDevCommand(workspace, options) {
|
|
911
1064
|
const appCount = Object.keys(workspace.apps).length;
|
|
912
|
-
const backendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "backend");
|
|
913
1065
|
const frontendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "web");
|
|
914
|
-
const mobileApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "mobile");
|
|
915
|
-
logger$11.log(`\n🚀 Starting workspace: ${workspace.name}`);
|
|
916
|
-
const counts = [`${backendApps.length} backend`, `${frontendApps.length} web`];
|
|
917
|
-
if (mobileApps.length > 0) counts.push(`${mobileApps.length} mobile`);
|
|
918
|
-
logger$11.log(` ${counts.join(", ")} app(s)`);
|
|
919
1066
|
const conflicts = checkPortConflicts(workspace);
|
|
920
1067
|
if (conflicts.length > 0) {
|
|
921
|
-
for (const conflict of conflicts) logger$
|
|
1068
|
+
for (const conflict of conflicts) logger$10.error(`❌ Port conflict: Apps "${conflict.app1}" and "${conflict.app2}" both use port ${conflict.port}`);
|
|
922
1069
|
throw new Error("Port conflicts detected. Please assign unique ports to each app.");
|
|
923
1070
|
}
|
|
924
1071
|
if (frontendApps.length > 0) {
|
|
925
|
-
logger$11.log("\n🔍 Validating frontend apps...");
|
|
926
1072
|
const validationResults = await validateFrontendApps(workspace);
|
|
927
1073
|
let hasErrors = false;
|
|
928
1074
|
for (const result of validationResults) {
|
|
929
1075
|
if (!result.valid) {
|
|
930
1076
|
hasErrors = true;
|
|
931
|
-
logger$
|
|
932
|
-
for (const error of result.errors) logger$
|
|
1077
|
+
logger$10.error(`\n❌ Frontend app "${result.appName}" validation failed:`);
|
|
1078
|
+
for (const error of result.errors) logger$10.error(` • ${error}`);
|
|
933
1079
|
}
|
|
934
|
-
for (const warning of result.warnings) logger$
|
|
1080
|
+
for (const warning of result.warnings) logger$10.warn(` ⚠️ ${result.appName}: ${warning}`);
|
|
935
1081
|
}
|
|
936
1082
|
if (hasErrors) throw new Error("Frontend app validation failed. Fix the issues above and try again.");
|
|
937
|
-
logger$11.log("✅ Frontend apps validated");
|
|
938
1083
|
}
|
|
939
1084
|
const rawSecrets = await loadDevSecrets(workspace);
|
|
940
1085
|
const reconciled = await require_workspace$1.reconcileWorkspace(workspace, { stage: workspace.stages.local });
|
|
941
1086
|
if (reconciled.changed && reconciled.plan.containers.length > 0) {
|
|
942
|
-
logger$
|
|
943
|
-
for (const [container, address] of Object.entries(reconciled.addresses)) logger$
|
|
1087
|
+
logger$10.log(`🐳 Services: ${reconciled.plan.containers.join(", ")}`);
|
|
1088
|
+
for (const [container, address] of Object.entries(reconciled.addresses)) logger$10.log(` ${container}: ${address}`);
|
|
944
1089
|
}
|
|
945
1090
|
const secretsEnv = {
|
|
946
1091
|
...rawSecrets,
|
|
@@ -952,11 +1097,8 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
952
1097
|
const address = reconciled.env[resource.envKey];
|
|
953
1098
|
if (address) appUrls[resource.id] = address;
|
|
954
1099
|
}
|
|
955
|
-
|
|
956
|
-
if (
|
|
957
|
-
logger$11.log("🔒 App URLs:");
|
|
958
|
-
for (const [id, address] of Object.entries(appUrls)) logger$11.log(` ${id}: ${address}`);
|
|
959
|
-
}
|
|
1100
|
+
const firstUrl = Object.values(appUrls).find((url) => url.startsWith("https://"));
|
|
1101
|
+
if (firstUrl) await ensureTrusted(workspace.root, firstUrl);
|
|
960
1102
|
let turboFilter = [];
|
|
961
1103
|
if (options.app) {
|
|
962
1104
|
if (!workspace.apps[options.app]) {
|
|
@@ -964,25 +1106,23 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
964
1106
|
throw new Error(`App "${options.app}" not found. Available apps: ${appNames}`);
|
|
965
1107
|
}
|
|
966
1108
|
turboFilter = ["--filter", appPackageName(workspace, options.app) ?? options.app];
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
turboFilter = ["--filter", options.filter];
|
|
970
|
-
logger$11.log(`\n🔍 Using filter: ${options.filter}`);
|
|
971
|
-
} else {
|
|
1109
|
+
} else if (options.filter) turboFilter = ["--filter", options.filter];
|
|
1110
|
+
else {
|
|
972
1111
|
const { filters, unpackaged } = turboFilters(workspace);
|
|
973
1112
|
turboFilter = filters.flatMap((name) => ["--filter", name]);
|
|
974
|
-
if (unpackaged.length > 0) logger$
|
|
975
|
-
logger$11.log(`\n🎯 Running all ${appCount} apps`);
|
|
1113
|
+
if (unpackaged.length > 0) logger$10.warn(`⚠️ No package.json with a name for: ${unpackaged.join(", ")} — turbo cannot run ${unpackaged.length === 1 ? "it" : "them"}.`);
|
|
976
1114
|
}
|
|
977
1115
|
const buildOrder = require_workspace.getAppBuildOrder(workspace);
|
|
978
|
-
|
|
1116
|
+
const width = Math.max(...buildOrder.map((name) => name.length));
|
|
1117
|
+
logger$10.log(`\n${workspace.name}: ${appCount} app(s)`);
|
|
979
1118
|
for (const appName of buildOrder) {
|
|
980
1119
|
const app = workspace.apps[appName];
|
|
981
1120
|
if (!app) continue;
|
|
982
|
-
const
|
|
983
|
-
const
|
|
984
|
-
logger$
|
|
1121
|
+
const local = `http://localhost:${app.port}`;
|
|
1122
|
+
const url = Object.entries(appUrls).find(([id]) => require_config.appKey(id) === appName)?.[1];
|
|
1123
|
+
logger$10.log(` ${appName.padEnd(width)} ${url ? `${url} -> ${local}` : local}`);
|
|
985
1124
|
}
|
|
1125
|
+
logger$10.log("");
|
|
986
1126
|
const configFiles = [
|
|
987
1127
|
"gkm.config.ts",
|
|
988
1128
|
"gkm.config.js",
|
|
@@ -1013,7 +1153,6 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1013
1153
|
});
|
|
1014
1154
|
}
|
|
1015
1155
|
if (held.length > 0) throw new WorkspacePortsInUse(held);
|
|
1016
|
-
logger$11.log("\n🏃 Starting turbo run dev...\n");
|
|
1017
1156
|
const turboProcess = (0, node_child_process.spawn)("pnpm", [
|
|
1018
1157
|
"turbo",
|
|
1019
1158
|
"run",
|
|
@@ -1029,7 +1168,7 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1029
1168
|
const shutdown = () => {
|
|
1030
1169
|
if (isShuttingDown) return;
|
|
1031
1170
|
isShuttingDown = true;
|
|
1032
|
-
logger$
|
|
1171
|
+
logger$10.log("\n🛑 Shutting down workspace...");
|
|
1033
1172
|
const pid = turboProcess.pid;
|
|
1034
1173
|
if (pid) try {
|
|
1035
1174
|
process.kill(-pid, "SIGTERM");
|
|
@@ -1053,7 +1192,7 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1053
1192
|
process.on("SIGTERM", shutdown);
|
|
1054
1193
|
return new Promise((resolve, reject) => {
|
|
1055
1194
|
turboProcess.on("error", (error) => {
|
|
1056
|
-
logger$
|
|
1195
|
+
logger$10.error("❌ Turbo error:", error);
|
|
1057
1196
|
reject(error);
|
|
1058
1197
|
});
|
|
1059
1198
|
turboProcess.on("exit", (code) => {
|
|
@@ -1072,11 +1211,11 @@ async function entryDevCommand(options) {
|
|
|
1072
1211
|
const entryPath = (0, node_path.resolve)(process.cwd(), entry);
|
|
1073
1212
|
if (!(0, node_fs.existsSync)(entryPath)) throw new Error(`Entry file not found: ${entryPath}`);
|
|
1074
1213
|
const defaultEnv = require_workspace$1.loadEnvFiles(".env");
|
|
1075
|
-
if (defaultEnv.loaded.length > 0) logger$
|
|
1214
|
+
if (defaultEnv.loaded.length > 0) logger$10.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
1076
1215
|
const { credentials, resolvedPort, secretsJsonPath, appName } = await require_workspace$1.prepareEntryCredentials({ explicitPort: options.portExplicit ? options.port : void 0 });
|
|
1077
|
-
if (appName) logger$
|
|
1078
|
-
logger$
|
|
1079
|
-
if (Object.keys(credentials).length > 1) logger$
|
|
1216
|
+
if (appName) logger$10.log(`📦 App: ${appName} (port ${resolvedPort})`);
|
|
1217
|
+
logger$10.log(`🚀 Starting entry file: ${entry} on port ${resolvedPort}`);
|
|
1218
|
+
if (Object.keys(credentials).length > 1) logger$10.log(`🔐 Loaded ${Object.keys(credentials).length - 1} secret(s) + PORT`);
|
|
1080
1219
|
const wrapperDir = (0, node_path.join)(process.cwd(), ".gkm");
|
|
1081
1220
|
await (0, node_fs_promises.mkdir)(wrapperDir, { recursive: true });
|
|
1082
1221
|
const wrapperPath = (0, node_path.join)(wrapperDir, "entry-wrapper.ts");
|
|
@@ -1087,7 +1226,7 @@ async function entryDevCommand(options) {
|
|
|
1087
1226
|
const shutdown = () => {
|
|
1088
1227
|
if (isShuttingDown) return;
|
|
1089
1228
|
isShuttingDown = true;
|
|
1090
|
-
logger$
|
|
1229
|
+
logger$10.log("\n🛑 Shutting down...");
|
|
1091
1230
|
runner.stop();
|
|
1092
1231
|
process.exit(0);
|
|
1093
1232
|
};
|
|
@@ -1123,14 +1262,14 @@ var EntryRunner = class {
|
|
|
1123
1262
|
});
|
|
1124
1263
|
let restartTimeout = null;
|
|
1125
1264
|
this.watcher.on("change", (path) => {
|
|
1126
|
-
logger$
|
|
1265
|
+
logger$10.log(`📝 File changed: ${path}`);
|
|
1127
1266
|
if (restartTimeout) clearTimeout(restartTimeout);
|
|
1128
1267
|
restartTimeout = setTimeout(async () => {
|
|
1129
|
-
logger$
|
|
1268
|
+
logger$10.log("🔄 Restarting...");
|
|
1130
1269
|
await this.restart();
|
|
1131
1270
|
}, 300);
|
|
1132
1271
|
});
|
|
1133
|
-
logger$
|
|
1272
|
+
logger$10.log(`👀 Watching for changes in: ${watchDir}`);
|
|
1134
1273
|
}
|
|
1135
1274
|
}
|
|
1136
1275
|
async runProcess() {
|
|
@@ -1138,24 +1277,24 @@ var EntryRunner = class {
|
|
|
1138
1277
|
...process.env,
|
|
1139
1278
|
PORT: String(this.port)
|
|
1140
1279
|
};
|
|
1141
|
-
this.childProcess = (0, node_child_process.spawn)(
|
|
1280
|
+
this.childProcess = (0, node_child_process.spawn)(...tsxCommand((0, node_path.dirname)(this.wrapperPath), [this.wrapperPath]), {
|
|
1142
1281
|
stdio: "inherit",
|
|
1143
1282
|
env,
|
|
1144
1283
|
detached: true
|
|
1145
1284
|
});
|
|
1146
1285
|
this.isRunning = true;
|
|
1147
1286
|
this.childProcess.on("error", (error) => {
|
|
1148
|
-
logger$
|
|
1287
|
+
logger$10.error("❌ Process error:", error);
|
|
1149
1288
|
});
|
|
1150
1289
|
this.childProcess.on("exit", (code) => {
|
|
1151
|
-
if (code !== null && code !== 0 && code !== 143) logger$
|
|
1290
|
+
if (code !== null && code !== 0 && code !== 143) logger$10.error(`❌ Process exited with code ${code}`);
|
|
1152
1291
|
this.isRunning = false;
|
|
1153
1292
|
});
|
|
1154
1293
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1155
1294
|
if (this.isRunning) {
|
|
1156
|
-
logger$
|
|
1157
|
-
logger$
|
|
1158
|
-
logger$
|
|
1295
|
+
logger$10.log("");
|
|
1296
|
+
logger$10.log(` \x1b[32m✓ Ready\x1b[0m at \x1b[36mhttp://localhost:${this.port}\x1b[0m`);
|
|
1297
|
+
logger$10.log("");
|
|
1159
1298
|
}
|
|
1160
1299
|
}
|
|
1161
1300
|
async restart() {
|
|
@@ -1241,7 +1380,6 @@ ${credentialsInjection}${parentWatch}const port = process.argv.includes('--port'
|
|
|
1241
1380
|
const { app } = await import('${appImportPath}');
|
|
1242
1381
|
|
|
1243
1382
|
${serveCode.replace(/^ {4}/gm, "")}
|
|
1244
|
-
console.log(\`Server started on port \${port}\`);
|
|
1245
1383
|
`;
|
|
1246
1384
|
return `#!/usr/bin/env node
|
|
1247
1385
|
/**
|
|
@@ -1270,6 +1408,31 @@ start({
|
|
|
1270
1408
|
});
|
|
1271
1409
|
`;
|
|
1272
1410
|
}
|
|
1411
|
+
/** Run `fn` with `console.log` muted — warnings and errors still print. */
|
|
1412
|
+
async function quietly(fn) {
|
|
1413
|
+
const log = console.log;
|
|
1414
|
+
console.log = () => {};
|
|
1415
|
+
try {
|
|
1416
|
+
return await fn();
|
|
1417
|
+
} finally {
|
|
1418
|
+
console.log = log;
|
|
1419
|
+
}
|
|
1420
|
+
}
|
|
1421
|
+
/**
|
|
1422
|
+
* How to run a TypeScript entry with the app's own tsx.
|
|
1423
|
+
*
|
|
1424
|
+
* Straight through node rather than `npx tsx`: npx reads the developer's npm
|
|
1425
|
+
* config on every start and warned about each key it did not recognise — nine
|
|
1426
|
+
* lines per app, per restart, about nothing gkm does.
|
|
1427
|
+
*/
|
|
1428
|
+
function tsxCommand(appRoot, args) {
|
|
1429
|
+
try {
|
|
1430
|
+
const cli = (0, node_module.createRequire)((0, node_path.join)(appRoot, "package.json")).resolve("tsx/cli");
|
|
1431
|
+
return [process.execPath, [cli, ...args]];
|
|
1432
|
+
} catch {
|
|
1433
|
+
return ["npx", ["tsx", ...args]];
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1273
1436
|
/** A port this app must serve on is held by something else. */
|
|
1274
1437
|
var DevPortInUse = class extends Error {
|
|
1275
1438
|
port;
|
|
@@ -1336,6 +1499,13 @@ var DevServer = class {
|
|
|
1336
1499
|
this.selfServing = selfServing;
|
|
1337
1500
|
this.actualPort = requestedPort;
|
|
1338
1501
|
}
|
|
1502
|
+
/** What the ready line calls this app. */
|
|
1503
|
+
label = "server";
|
|
1504
|
+
/**
|
|
1505
|
+
* Where the app is reached — its HTTPS address behind the edge, when it has
|
|
1506
|
+
* one. The local port is only what the edge forwards to.
|
|
1507
|
+
*/
|
|
1508
|
+
publicUrl;
|
|
1339
1509
|
async start() {
|
|
1340
1510
|
this.startTime = Date.now();
|
|
1341
1511
|
if (this.isRunning) await this.stop();
|
|
@@ -1344,17 +1514,15 @@ var DevServer = class {
|
|
|
1344
1514
|
this.actualPort = this.requestedPort;
|
|
1345
1515
|
} else {
|
|
1346
1516
|
this.actualPort = await require_workspace$1.findAvailablePort(this.requestedPort);
|
|
1347
|
-
if (this.actualPort !== this.requestedPort) logger$
|
|
1517
|
+
if (this.actualPort !== this.requestedPort) logger$10.log(`ℹ️ Port ${this.requestedPort} was in use, using port ${this.actualPort} instead`);
|
|
1348
1518
|
}
|
|
1349
1519
|
const serverEntryPath = (0, node_path.join)(this.appRoot, ".gkm", this.provider, "server.ts");
|
|
1350
1520
|
await this.createServerEntry();
|
|
1351
|
-
|
|
1352
|
-
this.serverProcess = (0, node_child_process.spawn)("npx", [
|
|
1353
|
-
"tsx",
|
|
1521
|
+
this.serverProcess = (0, node_child_process.spawn)(...tsxCommand(this.appRoot, [
|
|
1354
1522
|
serverEntryPath,
|
|
1355
1523
|
"--port",
|
|
1356
1524
|
this.actualPort.toString()
|
|
1357
|
-
], {
|
|
1525
|
+
]), {
|
|
1358
1526
|
stdio: "inherit",
|
|
1359
1527
|
env: {
|
|
1360
1528
|
...process.env,
|
|
@@ -1365,29 +1533,24 @@ var DevServer = class {
|
|
|
1365
1533
|
});
|
|
1366
1534
|
this.isRunning = true;
|
|
1367
1535
|
this.serverProcess.on("error", (error) => {
|
|
1368
|
-
logger$
|
|
1536
|
+
logger$10.error("❌ Server error:", error);
|
|
1369
1537
|
});
|
|
1370
1538
|
this.serverProcess.on("exit", (code, signal) => {
|
|
1371
|
-
if (code !== null && code !== 0 && signal !== "SIGTERM") logger$
|
|
1539
|
+
if (code !== null && code !== 0 && signal !== "SIGTERM") logger$10.error(`❌ Server exited with code ${code}`);
|
|
1372
1540
|
this.isRunning = false;
|
|
1373
1541
|
});
|
|
1374
1542
|
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
1375
1543
|
if (this.isRunning) {
|
|
1376
|
-
const
|
|
1377
|
-
const
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
logger$
|
|
1386
|
-
logger$11.log("");
|
|
1387
|
-
logger$11.log(` ┌${border}┐`);
|
|
1388
|
-
for (const line of lines) logger$11.log(` │ ${pad(line)} │`);
|
|
1389
|
-
logger$11.log(` └${border}┘`);
|
|
1390
|
-
logger$11.log("");
|
|
1544
|
+
const local = `http://localhost:${this.actualPort}`;
|
|
1545
|
+
const address = this.publicUrl ? `${this.publicUrl} -> ${local}` : local;
|
|
1546
|
+
const seconds = ((Date.now() - this.startTime) / 1e3).toFixed(1);
|
|
1547
|
+
logger$10.log(`\x1b[32m✓\x1b[0m ${this.label} ready in ${seconds}s ${address}`);
|
|
1548
|
+
const tools = this.selfServing ? [] : [
|
|
1549
|
+
...this.enableOpenApi ? ["docs /__docs"] : [],
|
|
1550
|
+
...this.telescope ? [`telescope ${this.telescope.path}`] : [],
|
|
1551
|
+
...this.studio ? [`studio ${this.studio.path}`] : []
|
|
1552
|
+
];
|
|
1553
|
+
if (tools.length > 0) logger$10.log(` ${tools.join(" · ")}`);
|
|
1391
1554
|
}
|
|
1392
1555
|
}
|
|
1393
1556
|
async stop() {
|
|
@@ -2202,7 +2365,7 @@ var TopicGenerator = class extends require_discover.ConstructGenerator {
|
|
|
2202
2365
|
};
|
|
2203
2366
|
//#endregion
|
|
2204
2367
|
//#region src/build/manifests.ts
|
|
2205
|
-
const logger$
|
|
2368
|
+
const logger$9 = console;
|
|
2206
2369
|
function isPartitioned(field) {
|
|
2207
2370
|
return !Array.isArray(field);
|
|
2208
2371
|
}
|
|
@@ -2299,8 +2462,8 @@ export type RoutePath = Route['path'];
|
|
|
2299
2462
|
`;
|
|
2300
2463
|
const manifestPath = (0, node_path.join)(manifestDir, "aws.ts");
|
|
2301
2464
|
await (0, node_fs_promises.writeFile)(manifestPath, content);
|
|
2302
|
-
logger$
|
|
2303
|
-
logger$
|
|
2465
|
+
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`);
|
|
2466
|
+
logger$9.log(`Manifest: ${(0, node_path.relative)(process.cwd(), manifestPath)}`);
|
|
2304
2467
|
}
|
|
2305
2468
|
async function generateServerManifest(outputDir, appInfo, routes, subscribers, queues = [], topics = [], constructs = {}, backends = {}) {
|
|
2306
2469
|
const manifestDir = (0, node_path.join)(outputDir, "manifest");
|
|
@@ -2355,8 +2518,8 @@ export type RoutePath = Route['path'];
|
|
|
2355
2518
|
`;
|
|
2356
2519
|
const manifestPath = (0, node_path.join)(manifestDir, "server.ts");
|
|
2357
2520
|
await (0, node_fs_promises.writeFile)(manifestPath, content);
|
|
2358
|
-
logger$
|
|
2359
|
-
logger$
|
|
2521
|
+
logger$9.log(`Generated server manifest with ${countItems(serverRoutes)} routes, ${countItems(serverSubscribers)} subscribers, ${countItems(serverQueues)} queues, ${countItems(serverTopics)} topics`);
|
|
2522
|
+
logger$9.log(`Manifest: ${(0, node_path.relative)(process.cwd(), manifestPath)}`);
|
|
2360
2523
|
}
|
|
2361
2524
|
/**
|
|
2362
2525
|
* Filter out 'ALL' method routes from a manifest field (flat or partitioned).
|
|
@@ -2561,7 +2724,7 @@ export default app;
|
|
|
2561
2724
|
}
|
|
2562
2725
|
//#endregion
|
|
2563
2726
|
//#region src/build/index.ts
|
|
2564
|
-
const logger$
|
|
2727
|
+
const logger$8 = console;
|
|
2565
2728
|
/**
|
|
2566
2729
|
* What a build run at the workspace root reads.
|
|
2567
2730
|
*
|
|
@@ -2586,7 +2749,7 @@ async function buildCommand(options) {
|
|
|
2586
2749
|
const isAtWorkspaceRoot = cwd === workspaceRoot;
|
|
2587
2750
|
const someAppIsElsewhere = Object.values(loadedConfig.workspace.apps).some((app) => (0, node_path.resolve)(workspaceRoot, app.path) !== workspaceRoot);
|
|
2588
2751
|
if (isAtWorkspaceRoot && someAppIsElsewhere) {
|
|
2589
|
-
logger$
|
|
2752
|
+
logger$8.log("📦 Detected workspace configuration");
|
|
2590
2753
|
return workspaceBuildCommand(loadedConfig.workspace, options);
|
|
2591
2754
|
}
|
|
2592
2755
|
}
|
|
@@ -2595,15 +2758,15 @@ async function buildCommand(options) {
|
|
|
2595
2758
|
const cacheBackend = require_workspace$1.cacheBackendFor(require_workspace$1.providerOf(loadedConfig.workspace));
|
|
2596
2759
|
const productionConfigFromGkm = getProductionConfigFromGkm(config);
|
|
2597
2760
|
const production = normalizeProductionConfig(options.production ?? false, productionConfigFromGkm);
|
|
2598
|
-
if (production) logger$
|
|
2599
|
-
logger$
|
|
2600
|
-
logger$
|
|
2761
|
+
if (production) logger$8.log(`🏭 Building for PRODUCTION`);
|
|
2762
|
+
logger$8.log(`Building with providers: ${resolved.providers.join(", ")}`);
|
|
2763
|
+
logger$8.log(`Loading constructs from: ${formatRoutes(config.constructs)}`);
|
|
2601
2764
|
const telescope = production ? void 0 : normalizeTelescopeConfig(config.telescope);
|
|
2602
|
-
if (telescope) logger$
|
|
2765
|
+
if (telescope) logger$8.log(`🔭 Telescope enabled at ${telescope.path}`);
|
|
2603
2766
|
const studio = production ? void 0 : normalizeStudioConfig(config.studio);
|
|
2604
|
-
if (studio) logger$
|
|
2767
|
+
if (studio) logger$8.log(`🗄️ Studio enabled at ${studio.path}`);
|
|
2605
2768
|
const hooks = normalizeHooksConfig(config.hooks);
|
|
2606
|
-
if (hooks) logger$
|
|
2769
|
+
if (hooks) logger$8.log(`🪝 Server hooks enabled`);
|
|
2607
2770
|
return buildApp({
|
|
2608
2771
|
config,
|
|
2609
2772
|
workspaceRoot: loadedConfig.workspace.root,
|
|
@@ -2646,7 +2809,7 @@ async function buildApp(input) {
|
|
|
2646
2809
|
});
|
|
2647
2810
|
if (selfServing) {
|
|
2648
2811
|
await writeSurfaceEntry((0, node_path.join)(appRoot, ".gkm", "server"), selfServing.source);
|
|
2649
|
-
logger$
|
|
2812
|
+
logger$8.log(`Generated a server for ${selfServing.id} from its own declaration`);
|
|
2650
2813
|
return { selfServing: selfServing.id };
|
|
2651
2814
|
}
|
|
2652
2815
|
const derived = ownersContext({
|
|
@@ -2686,14 +2849,14 @@ async function buildApp(input) {
|
|
|
2686
2849
|
topicGenerator.load(code, appRoot, bustCache)
|
|
2687
2850
|
]);
|
|
2688
2851
|
const allEndpoints = servedBy(loadedEndpoints, derived.surface);
|
|
2689
|
-
logger$
|
|
2690
|
-
logger$
|
|
2691
|
-
logger$
|
|
2692
|
-
logger$
|
|
2693
|
-
logger$
|
|
2694
|
-
logger$
|
|
2852
|
+
logger$8.log(`Found ${allEndpoints.length} endpoints`);
|
|
2853
|
+
logger$8.log(`Found ${allFunctions.length} functions`);
|
|
2854
|
+
logger$8.log(`Found ${allCrons.length} crons`);
|
|
2855
|
+
logger$8.log(`Found ${allSubscribers.length} subscribers`);
|
|
2856
|
+
logger$8.log(`Found ${allQueues.length} queues`);
|
|
2857
|
+
logger$8.log(`Found ${allTopics.length} topics`);
|
|
2695
2858
|
if (!input.serveEmpty && allEndpoints.length === 0 && allFunctions.length === 0 && allCrons.length === 0 && allSubscribers.length === 0 && allQueues.length === 0 && allTopics.length === 0) {
|
|
2696
|
-
logger$
|
|
2859
|
+
logger$8.log("No endpoints, functions, crons, subscribers, queues, or topics found to process");
|
|
2697
2860
|
return {};
|
|
2698
2861
|
}
|
|
2699
2862
|
const rootOutputDir = (0, node_path.join)(appRoot, ".gkm");
|
|
@@ -2710,7 +2873,7 @@ async function buildForProvider(provider, context, appRoot, discovered, endpoint
|
|
|
2710
2873
|
const rootOutputDir = (0, node_path.join)(appRoot, ".gkm");
|
|
2711
2874
|
const outputDir = (0, node_path.join)(rootOutputDir, provider);
|
|
2712
2875
|
await (0, node_fs_promises.mkdir)(outputDir, { recursive: true });
|
|
2713
|
-
logger$
|
|
2876
|
+
logger$8.log(`\nGenerating handlers for provider: ${provider}`);
|
|
2714
2877
|
const [routes, functionInfos, cronInfos, subscriberInfos, queueInfos, topicInfos] = await Promise.all([
|
|
2715
2878
|
endpointGenerator.build(context, endpoints, outputDir, {
|
|
2716
2879
|
provider,
|
|
@@ -2722,7 +2885,7 @@ async function buildForProvider(provider, context, appRoot, discovered, endpoint
|
|
|
2722
2885
|
queueGenerator.build(context, queues, outputDir, { provider }),
|
|
2723
2886
|
topicGenerator.build(context, topics, outputDir, { provider })
|
|
2724
2887
|
]);
|
|
2725
|
-
logger$
|
|
2888
|
+
logger$8.log(`Generated ${routes.length} routes, ${functionInfos.length} functions, ${cronInfos.length} crons, ${subscriberInfos.length} subscribers, ${queueInfos.length} queues, ${topicInfos.length} topics for ${provider}`);
|
|
2726
2889
|
const manifestRoutes = assembleManifestField(routes, endpoints);
|
|
2727
2890
|
const manifestFunctions = assembleManifestField(functionInfos, functions);
|
|
2728
2891
|
const manifestCrons = assembleManifestField(cronInfos, crons);
|
|
@@ -2742,7 +2905,7 @@ async function buildForProvider(provider, context, appRoot, discovered, endpoint
|
|
|
2742
2905
|
}, serverRouteField, manifestSubscribers, manifestQueues, manifestTopics);
|
|
2743
2906
|
let masterKey;
|
|
2744
2907
|
if (context.production?.bundle && !skipBundle) {
|
|
2745
|
-
logger$
|
|
2908
|
+
logger$8.log(`\n📦 Bundling production server...`);
|
|
2746
2909
|
const { bundleServer } = await Promise.resolve().then(() => require("./bundler-mjqGAvu1.cjs"));
|
|
2747
2910
|
const allConstructs = [
|
|
2748
2911
|
...endpoints.map((e) => e.construct),
|
|
@@ -2760,10 +2923,10 @@ async function buildForProvider(provider, context, appRoot, discovered, endpoint
|
|
|
2760
2923
|
stage,
|
|
2761
2924
|
constructs: allConstructs
|
|
2762
2925
|
})).masterKey;
|
|
2763
|
-
logger$
|
|
2926
|
+
logger$8.log(`✅ Bundle complete: .gkm/server/dist/server.mjs`);
|
|
2764
2927
|
if (masterKey) {
|
|
2765
|
-
logger$
|
|
2766
|
-
logger$
|
|
2928
|
+
logger$8.log(`\n🔐 Secrets encrypted for deployment`);
|
|
2929
|
+
logger$8.log(` Deploy with: GKM_MASTER_KEY=${masterKey}`);
|
|
2767
2930
|
}
|
|
2768
2931
|
}
|
|
2769
2932
|
return { masterKey };
|
|
@@ -2850,19 +3013,19 @@ async function workspaceBuildCommand(workspace, options) {
|
|
|
2850
3013
|
const apps = Object.entries(workspace.apps);
|
|
2851
3014
|
const backendApps = apps.filter(([, app]) => app.type === "backend");
|
|
2852
3015
|
const frontendApps = apps.filter(([, app]) => app.type === "web");
|
|
2853
|
-
logger$
|
|
2854
|
-
logger$
|
|
2855
|
-
logger$
|
|
2856
|
-
if (options.production) logger$
|
|
3016
|
+
logger$8.log(`\n🏗️ Building workspace: ${workspace.name}`);
|
|
3017
|
+
logger$8.log(` Backend apps: ${backendApps.map(([name]) => name).join(", ") || "none"}`);
|
|
3018
|
+
logger$8.log(` Frontend apps: ${frontendApps.map(([name]) => name).join(", ") || "none"}`);
|
|
3019
|
+
if (options.production) logger$8.log(` 🏭 Production mode enabled`);
|
|
2857
3020
|
const buildOrder = require_workspace.getAppBuildOrder(workspace);
|
|
2858
|
-
logger$
|
|
3021
|
+
logger$8.log(` Build order: ${buildOrder.join(" → ")}`);
|
|
2859
3022
|
const pm = detectPackageManager$2();
|
|
2860
|
-
logger$
|
|
3023
|
+
logger$8.log(`\n📦 Using ${pm} with Turbo for parallel builds...\n`);
|
|
2861
3024
|
try {
|
|
2862
3025
|
const { filters, unpackaged } = turboFilters(workspace);
|
|
2863
3026
|
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.`);
|
|
2864
3027
|
const turboCommand = getTurboCommand(pm, filters);
|
|
2865
|
-
logger$
|
|
3028
|
+
logger$8.log(`Running: ${turboCommand}`);
|
|
2866
3029
|
await new Promise((resolve, reject) => {
|
|
2867
3030
|
const child = (0, node_child_process.spawn)(turboCommand, {
|
|
2868
3031
|
shell: true,
|
|
@@ -2890,15 +3053,15 @@ async function workspaceBuildCommand(workspace, options) {
|
|
|
2890
3053
|
outputPath
|
|
2891
3054
|
});
|
|
2892
3055
|
}
|
|
2893
|
-
logger$
|
|
2894
|
-
logger$
|
|
3056
|
+
logger$8.log(`\n✅ Workspace build complete!`);
|
|
3057
|
+
logger$8.log(`\n📋 Build Summary:`);
|
|
2895
3058
|
for (const result of results) {
|
|
2896
3059
|
const icon = result.type === "backend" ? "⚙️" : result.type === "mobile" ? "📱" : "🌐";
|
|
2897
|
-
logger$
|
|
3060
|
+
logger$8.log(` ${icon} ${result.appName}: ${result.outputPath || "built"}`);
|
|
2898
3061
|
}
|
|
2899
3062
|
} catch (error) {
|
|
2900
3063
|
const errorMessage = error instanceof Error ? error.message : "Build failed";
|
|
2901
|
-
logger$
|
|
3064
|
+
logger$8.log(`\n❌ Build failed: ${errorMessage}`);
|
|
2902
3065
|
for (const [appName, app] of apps) results.push({
|
|
2903
3066
|
appName,
|
|
2904
3067
|
type: app.type,
|
|
@@ -3782,7 +3945,7 @@ async function createDnsProvider(options) {
|
|
|
3782
3945
|
*
|
|
3783
3946
|
* Handles automatic DNS record creation for deployed applications.
|
|
3784
3947
|
*/
|
|
3785
|
-
const logger$
|
|
3948
|
+
const logger$7 = console;
|
|
3786
3949
|
/**
|
|
3787
3950
|
* Check if DNS config is legacy format (single domain with `domain` property)
|
|
3788
3951
|
*/
|
|
@@ -3819,7 +3982,7 @@ function groupHostnamesByDomain(appHostnames, dnsConfig) {
|
|
|
3819
3982
|
for (const [appName, hostname] of appHostnames) {
|
|
3820
3983
|
const rootDomain = findRootDomain(hostname, dnsConfig);
|
|
3821
3984
|
if (!rootDomain) {
|
|
3822
|
-
logger$
|
|
3985
|
+
logger$7.log(` ⚠ No DNS config found for hostname: ${hostname}`);
|
|
3823
3986
|
continue;
|
|
3824
3987
|
}
|
|
3825
3988
|
if (!grouped.has(rootDomain)) grouped.set(rootDomain, /* @__PURE__ */ new Map());
|
|
@@ -3869,10 +4032,10 @@ function generateRequiredRecords(appHostnames, rootDomain, serverIp) {
|
|
|
3869
4032
|
* Print DNS records table
|
|
3870
4033
|
*/
|
|
3871
4034
|
function printDnsRecordsTable(records, rootDomain) {
|
|
3872
|
-
logger$
|
|
3873
|
-
logger$
|
|
3874
|
-
logger$
|
|
3875
|
-
logger$
|
|
4035
|
+
logger$7.log(`\n 📋 DNS Records for ${rootDomain}:`);
|
|
4036
|
+
logger$7.log(" ┌─────────────────────────────────────┬──────┬─────────────────┬────────┐");
|
|
4037
|
+
logger$7.log(" │ Subdomain │ Type │ Value │ Status │");
|
|
4038
|
+
logger$7.log(" ├─────────────────────────────────────┼──────┼─────────────────┼────────┤");
|
|
3876
4039
|
for (const record of records) {
|
|
3877
4040
|
const subdomain = record.subdomain.padEnd(35);
|
|
3878
4041
|
const type = record.type.padEnd(4);
|
|
@@ -3882,18 +4045,18 @@ function printDnsRecordsTable(records, rootDomain) {
|
|
|
3882
4045
|
else if (record.created) status = "✓ new";
|
|
3883
4046
|
else if (record.existed) status = "✓";
|
|
3884
4047
|
else status = "?";
|
|
3885
|
-
logger$
|
|
4048
|
+
logger$7.log(` │ ${subdomain} │ ${type} │ ${value} │ ${status.padEnd(6)} │`);
|
|
3886
4049
|
}
|
|
3887
|
-
logger$
|
|
4050
|
+
logger$7.log(" └─────────────────────────────────────┴──────┴─────────────────┴────────┘");
|
|
3888
4051
|
}
|
|
3889
4052
|
/**
|
|
3890
4053
|
* Print DNS records in a simple format for manual setup
|
|
3891
4054
|
*/
|
|
3892
4055
|
function printDnsRecordsSimple(records, rootDomain) {
|
|
3893
|
-
logger$
|
|
3894
|
-
logger$
|
|
3895
|
-
for (const record of records) logger$
|
|
3896
|
-
logger$
|
|
4056
|
+
logger$7.log("\n 📋 Required DNS Records:");
|
|
4057
|
+
logger$7.log(` Add these A records to your DNS provider (${rootDomain}):\n`);
|
|
4058
|
+
for (const record of records) logger$7.log(` ${record.subdomain} → ${record.value} (A record)`);
|
|
4059
|
+
logger$7.log("");
|
|
3897
4060
|
}
|
|
3898
4061
|
/**
|
|
3899
4062
|
* Create DNS records for a single domain using its configured provider
|
|
@@ -3905,7 +4068,7 @@ async function createDnsRecordsForDomain(records, rootDomain, providerConfig) {
|
|
|
3905
4068
|
provider = await createDnsProvider({ config: providerConfig });
|
|
3906
4069
|
} catch (error) {
|
|
3907
4070
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
3908
|
-
logger$
|
|
4071
|
+
logger$7.log(` ⚠ Failed to create DNS provider for ${rootDomain}: ${message}`);
|
|
3909
4072
|
return records.map((r) => ({
|
|
3910
4073
|
...r,
|
|
3911
4074
|
error: message
|
|
@@ -3959,7 +4122,7 @@ async function createDnsRecordsForDomain(records, rootDomain, providerConfig) {
|
|
|
3959
4122
|
}
|
|
3960
4123
|
} catch (error) {
|
|
3961
4124
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
3962
|
-
logger$
|
|
4125
|
+
logger$7.log(` ⚠ Failed to create DNS records for ${rootDomain}: ${message}`);
|
|
3963
4126
|
return records.map((r) => ({
|
|
3964
4127
|
hostname: r.hostname,
|
|
3965
4128
|
subdomain: r.subdomain,
|
|
@@ -3986,20 +4149,20 @@ async function createDnsRecordsForDomain(records, rootDomain, providerConfig) {
|
|
|
3986
4149
|
async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint, state) {
|
|
3987
4150
|
if (!dnsConfig) return null;
|
|
3988
4151
|
const normalizedConfig = normalizeDnsConfig(dnsConfig);
|
|
3989
|
-
logger$
|
|
4152
|
+
logger$7.log("\n🌐 Setting up DNS records...");
|
|
3990
4153
|
let serverIp;
|
|
3991
4154
|
try {
|
|
3992
4155
|
const endpointUrl = new URL(dokployEndpoint);
|
|
3993
4156
|
serverIp = await resolveHostnameToIp(endpointUrl.hostname);
|
|
3994
|
-
logger$
|
|
4157
|
+
logger$7.log(` Server IP: ${serverIp} (from ${endpointUrl.hostname})`);
|
|
3995
4158
|
} catch (error) {
|
|
3996
4159
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
3997
|
-
logger$
|
|
4160
|
+
logger$7.log(` ⚠ Failed to resolve server IP: ${message}`);
|
|
3998
4161
|
return null;
|
|
3999
4162
|
}
|
|
4000
4163
|
const groupedHostnames = groupHostnamesByDomain(appHostnames, normalizedConfig);
|
|
4001
4164
|
if (groupedHostnames.size === 0) {
|
|
4002
|
-
logger$
|
|
4165
|
+
logger$7.log(" No DNS records needed (no hostnames match configured domains)");
|
|
4003
4166
|
return {
|
|
4004
4167
|
records: [],
|
|
4005
4168
|
success: true,
|
|
@@ -4011,22 +4174,22 @@ async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint, state) {
|
|
|
4011
4174
|
for (const [rootDomain, domainHostnames] of groupedHostnames) {
|
|
4012
4175
|
const providerConfig = normalizedConfig[rootDomain];
|
|
4013
4176
|
if (!providerConfig) {
|
|
4014
|
-
logger$
|
|
4177
|
+
logger$7.log(` ⚠ No provider config for ${rootDomain}`);
|
|
4015
4178
|
continue;
|
|
4016
4179
|
}
|
|
4017
4180
|
const providerName = typeof providerConfig.provider === "string" ? providerConfig.provider : "custom";
|
|
4018
4181
|
const requiredRecords = generateRequiredRecords(domainHostnames, rootDomain, serverIp);
|
|
4019
4182
|
if (requiredRecords.length === 0) continue;
|
|
4020
|
-
logger$
|
|
4183
|
+
logger$7.log(` Creating DNS records for ${rootDomain} (${providerName})...`);
|
|
4021
4184
|
const domainRecords = await createDnsRecordsForDomain(requiredRecords, rootDomain, providerConfig);
|
|
4022
4185
|
allRecords.push(...domainRecords);
|
|
4023
4186
|
const created = domainRecords.filter((r) => r.created).length;
|
|
4024
4187
|
const existed = domainRecords.filter((r) => r.existed).length;
|
|
4025
4188
|
const failed = domainRecords.filter((r) => r.error).length;
|
|
4026
|
-
if (created > 0) logger$
|
|
4027
|
-
if (existed > 0) logger$
|
|
4189
|
+
if (created > 0) logger$7.log(` ✓ Created ${created} DNS record(s) for ${rootDomain}`);
|
|
4190
|
+
if (existed > 0) logger$7.log(` ✓ ${existed} record(s) already exist for ${rootDomain}`);
|
|
4028
4191
|
if (failed > 0) {
|
|
4029
|
-
logger$
|
|
4192
|
+
logger$7.log(` ⚠ ${failed} record(s) failed for ${rootDomain}`);
|
|
4030
4193
|
hasFailures = true;
|
|
4031
4194
|
}
|
|
4032
4195
|
if (state) {
|
|
@@ -4063,10 +4226,10 @@ async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint, state) {
|
|
|
4063
4226
|
*/
|
|
4064
4227
|
async function verifyDnsRecords(appHostnames, serverIp, state) {
|
|
4065
4228
|
const results = [];
|
|
4066
|
-
logger$
|
|
4229
|
+
logger$7.log("\n🔍 Verifying DNS records...");
|
|
4067
4230
|
for (const [appName, hostname] of appHostnames) {
|
|
4068
4231
|
if (isDnsVerified(state, hostname, serverIp)) {
|
|
4069
|
-
logger$
|
|
4232
|
+
logger$7.log(` ✓ ${hostname} (previously verified)`);
|
|
4070
4233
|
results.push({
|
|
4071
4234
|
hostname,
|
|
4072
4235
|
appName,
|
|
@@ -4080,7 +4243,7 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
|
|
|
4080
4243
|
const resolvedIp = await resolveHostnameToIp(hostname);
|
|
4081
4244
|
if (resolvedIp === serverIp) {
|
|
4082
4245
|
setDnsVerification(state, hostname, serverIp);
|
|
4083
|
-
logger$
|
|
4246
|
+
logger$7.log(` ✓ ${hostname} → ${resolvedIp}`);
|
|
4084
4247
|
results.push({
|
|
4085
4248
|
hostname,
|
|
4086
4249
|
appName,
|
|
@@ -4089,7 +4252,7 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
|
|
|
4089
4252
|
expectedIp: serverIp
|
|
4090
4253
|
});
|
|
4091
4254
|
} else {
|
|
4092
|
-
logger$
|
|
4255
|
+
logger$7.log(` ⚠ ${hostname} resolves to ${resolvedIp}, expected ${serverIp}`);
|
|
4093
4256
|
results.push({
|
|
4094
4257
|
hostname,
|
|
4095
4258
|
appName,
|
|
@@ -4100,7 +4263,7 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
|
|
|
4100
4263
|
}
|
|
4101
4264
|
} catch (error) {
|
|
4102
4265
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4103
|
-
logger$
|
|
4266
|
+
logger$7.log(` ⚠ ${hostname} DNS not propagated (${message})`);
|
|
4104
4267
|
results.push({
|
|
4105
4268
|
hostname,
|
|
4106
4269
|
appName,
|
|
@@ -4114,9 +4277,9 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
|
|
|
4114
4277
|
const skipped = results.filter((r) => r.skipped).length;
|
|
4115
4278
|
const pending = results.filter((r) => !r.verified).length;
|
|
4116
4279
|
if (pending > 0) {
|
|
4117
|
-
logger$
|
|
4118
|
-
logger$
|
|
4119
|
-
} else if (skipped > 0) logger$
|
|
4280
|
+
logger$7.log(`\n ${verified} verified, ${pending} pending propagation`);
|
|
4281
|
+
logger$7.log(" DNS changes may take 5-30 minutes to propagate");
|
|
4282
|
+
} else if (skipped > 0) logger$7.log(` ${verified} verified (${skipped} from cache)`);
|
|
4120
4283
|
return results;
|
|
4121
4284
|
}
|
|
4122
4285
|
//#endregion
|
|
@@ -4149,43 +4312,19 @@ function resolveHost(appName, app, stage, dokployConfig, isMainFrontend) {
|
|
|
4149
4312
|
return `${appName}.${baseDomain}`;
|
|
4150
4313
|
}
|
|
4151
4314
|
/**
|
|
4152
|
-
*
|
|
4153
|
-
*
|
|
4154
|
-
* Three rules, in order, and the last one is the point:
|
|
4155
|
-
*
|
|
4156
|
-
* 1. **One site** — it is the root. Unambiguous, so nothing has to be said.
|
|
4157
|
-
* 2. **Named `web`** — the convention wins, because people already rely on it.
|
|
4158
|
-
* 3. **Declared `root: true`** — the site says so itself.
|
|
4159
|
-
*
|
|
4160
|
-
* Anything else throws. It used to take the *first* `type: 'web'` it iterated,
|
|
4161
|
-
* and that object is built from the manifest, which is built in glob traversal
|
|
4162
|
-
* order — so two sites with neither named `web` meant renaming a file could
|
|
4163
|
-
* move the production root domain, silently. The same shape as
|
|
4164
|
-
* `CacheIsAmbiguous`: unambiguous with one and arbitrary with two, so two is an
|
|
4165
|
-
* error rather than a coin toss.
|
|
4315
|
+
* Whether this app is the site the base domain points at — see `rootSite`,
|
|
4316
|
+
* which the local edge asks too.
|
|
4166
4317
|
*
|
|
4167
4318
|
* @throws {AmbiguousRootSite} when several sites could be the root and none says it is
|
|
4168
4319
|
*/
|
|
4169
4320
|
function isMainFrontendApp(appName, app, allApps) {
|
|
4170
4321
|
if (app.type !== "web") return false;
|
|
4171
|
-
const sites = Object.entries(allApps).filter(([, a]) => a.type === "web")
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
}
|
|
4178
|
-
/** Several sites could hold the base domain, and none of them says it does. */
|
|
4179
|
-
var AmbiguousRootSite = class extends Error {
|
|
4180
|
-
sites;
|
|
4181
|
-
tooMany;
|
|
4182
|
-
constructor(sites, tooMany = false) {
|
|
4183
|
-
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.`);
|
|
4184
|
-
this.sites = sites;
|
|
4185
|
-
this.tooMany = tooMany;
|
|
4186
|
-
this.name = "AmbiguousRootSite";
|
|
4187
|
-
}
|
|
4188
|
-
};
|
|
4322
|
+
const sites = Object.entries(allApps).filter(([, a]) => a.type === "web").map(([name, a]) => ({
|
|
4323
|
+
name,
|
|
4324
|
+
root: a.root
|
|
4325
|
+
}));
|
|
4326
|
+
return require_workspace$1.rootSite(sites) === appName;
|
|
4327
|
+
}
|
|
4189
4328
|
/**
|
|
4190
4329
|
* Get public URL arg names from app dependencies.
|
|
4191
4330
|
*
|
|
@@ -5048,7 +5187,7 @@ CMD ["nginx", "-g", "daemon off;"]
|
|
|
5048
5187
|
}
|
|
5049
5188
|
//#endregion
|
|
5050
5189
|
//#region src/docker/index.ts
|
|
5051
|
-
const logger$
|
|
5190
|
+
const logger$6 = console;
|
|
5052
5191
|
/**
|
|
5053
5192
|
* Docker command implementation
|
|
5054
5193
|
* Generates Dockerfile, docker-compose.yml, and related files
|
|
@@ -5059,7 +5198,7 @@ const logger$7 = console;
|
|
|
5059
5198
|
async function dockerCommand(options) {
|
|
5060
5199
|
const loadedConfig = await require_config.loadWorkspaceConfig();
|
|
5061
5200
|
if (loadedConfig.type === "workspace") {
|
|
5062
|
-
logger$
|
|
5201
|
+
logger$6.log("📦 Detected workspace configuration");
|
|
5063
5202
|
return workspaceDockerCommand(loadedConfig.workspace);
|
|
5064
5203
|
}
|
|
5065
5204
|
const config = await require_config.loadConfig();
|
|
@@ -5079,7 +5218,7 @@ async function dockerCommand(options) {
|
|
|
5079
5218
|
if (inMonorepo && !useSlim) {
|
|
5080
5219
|
if (hasTurbo) {
|
|
5081
5220
|
useTurbo = true;
|
|
5082
|
-
logger$
|
|
5221
|
+
logger$6.log(" Detected monorepo with turbo.json - using turbo prune");
|
|
5083
5222
|
} 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");
|
|
5084
5223
|
}
|
|
5085
5224
|
let turboPackage = options.turboPackage ?? dockerConfig.imageName;
|
|
@@ -5087,7 +5226,7 @@ async function dockerCommand(options) {
|
|
|
5087
5226
|
const pkg = require(`${process.cwd()}/package.json`);
|
|
5088
5227
|
if (pkg.name) {
|
|
5089
5228
|
turboPackage = pkg.name;
|
|
5090
|
-
logger$
|
|
5229
|
+
logger$6.log(` Turbo package: ${turboPackage}`);
|
|
5091
5230
|
}
|
|
5092
5231
|
} catch {}
|
|
5093
5232
|
const templateOptions = {
|
|
@@ -5104,16 +5243,16 @@ async function dockerCommand(options) {
|
|
|
5104
5243
|
const dockerMode = useSlim ? "slim" : useTurbo ? "turbo" : "multi-stage";
|
|
5105
5244
|
const dockerfilePath = (0, node_path.join)(dockerDir, "Dockerfile");
|
|
5106
5245
|
await (0, node_fs_promises.writeFile)(dockerfilePath, dockerfile);
|
|
5107
|
-
logger$
|
|
5246
|
+
logger$6.log(`Generated: .gkm/docker/Dockerfile (${dockerMode}, ${packageManager})`);
|
|
5108
5247
|
const composePath = await writeConstructsCompose(loadedConfig.workspace);
|
|
5109
5248
|
const dockerignore = generateDockerignore();
|
|
5110
5249
|
const dockerignorePath = (0, node_path.join)(process.cwd(), ".dockerignore");
|
|
5111
5250
|
await (0, node_fs_promises.writeFile)(dockerignorePath, dockerignore);
|
|
5112
|
-
logger$
|
|
5251
|
+
logger$6.log("Generated: .dockerignore (project root)");
|
|
5113
5252
|
const entrypoint = generateDockerEntrypoint();
|
|
5114
5253
|
const entrypointPath = (0, node_path.join)(dockerDir, "docker-entrypoint.sh");
|
|
5115
5254
|
await (0, node_fs_promises.writeFile)(entrypointPath, entrypoint);
|
|
5116
|
-
logger$
|
|
5255
|
+
logger$6.log("Generated: .gkm/docker/docker-entrypoint.sh");
|
|
5117
5256
|
const result = {
|
|
5118
5257
|
dockerfile: dockerfilePath,
|
|
5119
5258
|
dockerCompose: composePath,
|
|
@@ -5132,13 +5271,13 @@ async function dockerCommand(options) {
|
|
|
5132
5271
|
function ensureLockfile(cwd) {
|
|
5133
5272
|
const lockfilePath = findLockfilePath(cwd);
|
|
5134
5273
|
if (!lockfilePath) {
|
|
5135
|
-
logger$
|
|
5274
|
+
logger$6.warn("\n⚠️ No lockfile found. Docker build may fail or use stale dependencies.");
|
|
5136
5275
|
return null;
|
|
5137
5276
|
}
|
|
5138
5277
|
const lockfileName = (0, node_path.basename)(lockfilePath);
|
|
5139
5278
|
const localLockfile = (0, node_path.join)(cwd, lockfileName);
|
|
5140
5279
|
if (lockfilePath === localLockfile) return null;
|
|
5141
|
-
logger$
|
|
5280
|
+
logger$6.log(` Copying ${lockfileName} from monorepo root...`);
|
|
5142
5281
|
(0, node_fs.copyFileSync)(lockfilePath, localLockfile);
|
|
5143
5282
|
return () => {
|
|
5144
5283
|
try {
|
|
@@ -5154,7 +5293,7 @@ async function buildDockerImage(imageName, options) {
|
|
|
5154
5293
|
const tag = options.tag ?? "latest";
|
|
5155
5294
|
const registry = options.registry;
|
|
5156
5295
|
const fullImageName = registry ? `${registry}/${imageName}:${tag}` : `${imageName}:${tag}`;
|
|
5157
|
-
logger$
|
|
5296
|
+
logger$6.log(`\n🐳 Building Docker image: ${fullImageName}`);
|
|
5158
5297
|
const cwd = process.cwd();
|
|
5159
5298
|
const cleanup = ensureLockfile(cwd);
|
|
5160
5299
|
try {
|
|
@@ -5166,7 +5305,7 @@ async function buildDockerImage(imageName, options) {
|
|
|
5166
5305
|
DOCKER_BUILDKIT: "1"
|
|
5167
5306
|
}
|
|
5168
5307
|
});
|
|
5169
|
-
logger$
|
|
5308
|
+
logger$6.log(`✅ Docker image built: ${fullImageName}`);
|
|
5170
5309
|
} catch (error) {
|
|
5171
5310
|
throw new Error(`Failed to build Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
5172
5311
|
} finally {
|
|
@@ -5181,13 +5320,13 @@ async function pushDockerImage(imageName, options) {
|
|
|
5181
5320
|
const registry = options.registry;
|
|
5182
5321
|
if (!registry) throw new Error("Registry is required to push Docker image. Use --registry or configure docker.registry in gkm.config.ts");
|
|
5183
5322
|
const fullImageName = `${registry}/${imageName}:${tag}`;
|
|
5184
|
-
logger$
|
|
5323
|
+
logger$6.log(`\n🚀 Pushing Docker image: ${fullImageName}`);
|
|
5185
5324
|
try {
|
|
5186
5325
|
(0, node_child_process.execSync)(`docker push ${fullImageName}`, {
|
|
5187
5326
|
cwd: process.cwd(),
|
|
5188
5327
|
stdio: "inherit"
|
|
5189
5328
|
});
|
|
5190
|
-
logger$
|
|
5329
|
+
logger$6.log(`✅ Docker image pushed: ${fullImageName}`);
|
|
5191
5330
|
} catch (error) {
|
|
5192
5331
|
throw new Error(`Failed to push Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
5193
5332
|
}
|
|
@@ -5212,21 +5351,21 @@ function getAppPackageName(appPath) {
|
|
|
5212
5351
|
async function workspaceDockerCommand(workspace) {
|
|
5213
5352
|
const results = [];
|
|
5214
5353
|
const apps = Object.entries(workspace.apps);
|
|
5215
|
-
logger$
|
|
5354
|
+
logger$6.log(`\n🐳 Generating Dockerfiles for workspace: ${workspace.name}`);
|
|
5216
5355
|
const dockerDir = (0, node_path.join)(workspace.root, ".gkm", "docker");
|
|
5217
5356
|
await (0, node_fs_promises.mkdir)(dockerDir, { recursive: true });
|
|
5218
5357
|
const packageManager = detectPackageManager$1(workspace.root);
|
|
5219
|
-
logger$
|
|
5358
|
+
logger$6.log(` Package manager: ${packageManager}`);
|
|
5220
5359
|
for (const [appName, app] of apps) {
|
|
5221
5360
|
if (app.type === "mobile") {
|
|
5222
|
-
logger$
|
|
5361
|
+
logger$6.log(`\n 📱 Skipping Docker for ${appName} (mobile app — deploy via framework toolchain)`);
|
|
5223
5362
|
continue;
|
|
5224
5363
|
}
|
|
5225
5364
|
const appPath = app.path;
|
|
5226
5365
|
const turboPackage = getAppPackageName((0, node_path.join)(workspace.root, appPath)) ?? appName;
|
|
5227
5366
|
const imageName = appName;
|
|
5228
5367
|
const buildType = !!app.entry ? "entry" : app.type;
|
|
5229
|
-
logger$
|
|
5368
|
+
logger$6.log(`\n 📄 Generating Dockerfile for ${appName} (${buildType})`);
|
|
5230
5369
|
let dockerfile;
|
|
5231
5370
|
if (app.type === "web") {
|
|
5232
5371
|
const publicUrlArgs = getPublicUrlArgNames(app);
|
|
@@ -5270,7 +5409,7 @@ async function workspaceDockerCommand(workspace) {
|
|
|
5270
5409
|
});
|
|
5271
5410
|
const dockerfilePath = (0, node_path.join)(dockerDir, `Dockerfile.${appName}`);
|
|
5272
5411
|
await (0, node_fs_promises.writeFile)(dockerfilePath, dockerfile);
|
|
5273
|
-
logger$
|
|
5412
|
+
logger$6.log(` Generated: .gkm/docker/Dockerfile.${appName}`);
|
|
5274
5413
|
results.push({
|
|
5275
5414
|
appName,
|
|
5276
5415
|
type: app.type,
|
|
@@ -5281,13 +5420,13 @@ async function workspaceDockerCommand(workspace) {
|
|
|
5281
5420
|
const dockerignore = generateDockerignore();
|
|
5282
5421
|
const dockerignorePath = (0, node_path.join)(workspace.root, ".dockerignore");
|
|
5283
5422
|
await (0, node_fs_promises.writeFile)(dockerignorePath, dockerignore);
|
|
5284
|
-
logger$
|
|
5423
|
+
logger$6.log(`\n Generated: .dockerignore (workspace root)`);
|
|
5285
5424
|
const composePath = await writeConstructsCompose(workspace);
|
|
5286
|
-
logger$
|
|
5287
|
-
logger$
|
|
5425
|
+
logger$6.log(`\n✅ Generated ${results.length} Dockerfile(s) + ${require_workspace$1.COMPOSE_PATH}`);
|
|
5426
|
+
logger$6.log("\n📋 Build commands:");
|
|
5288
5427
|
for (const result of results) {
|
|
5289
5428
|
const icon = result.type === "backend" ? "⚙️" : result.type === "mobile" ? "📱" : "🌐";
|
|
5290
|
-
logger$
|
|
5429
|
+
logger$6.log(` ${icon} docker build -f .gkm/docker/Dockerfile.${result.appName} -t ${result.imageName} .`);
|
|
5291
5430
|
}
|
|
5292
5431
|
printRunInstructions(workspace);
|
|
5293
5432
|
return {
|
|
@@ -5308,18 +5447,18 @@ async function writeConstructsCompose(workspace) {
|
|
|
5308
5447
|
stage: workspace.stages.local,
|
|
5309
5448
|
start: false
|
|
5310
5449
|
});
|
|
5311
|
-
logger$
|
|
5450
|
+
logger$6.log(` Generated: ${require_workspace$1.COMPOSE_PATH} (project root)`);
|
|
5312
5451
|
return (0, node_path.join)(workspace.root, require_workspace$1.COMPOSE_PATH);
|
|
5313
5452
|
}
|
|
5314
5453
|
function printRunInstructions(workspace) {
|
|
5315
5454
|
const project = (0, node_fs.existsSync)((0, node_path.join)(workspace.root, "docker-compose.yml")) ? " -f docker-compose.yml" : "";
|
|
5316
|
-
logger$
|
|
5317
|
-
logger$
|
|
5318
|
-
logger$
|
|
5455
|
+
logger$6.log("\n📋 Run everything (the apps sit behind the \"apps\" profile):");
|
|
5456
|
+
logger$6.log(" gkm setup # starts the containers and creates what they hold");
|
|
5457
|
+
logger$6.log(` docker compose -f ${require_workspace$1.COMPOSE_PATH}${project} --profile apps up --build`);
|
|
5319
5458
|
}
|
|
5320
5459
|
//#endregion
|
|
5321
5460
|
//#region src/deploy/docker.ts
|
|
5322
|
-
const logger$
|
|
5461
|
+
const logger$5 = console;
|
|
5323
5462
|
/**
|
|
5324
5463
|
* Get the full image reference
|
|
5325
5464
|
*/
|
|
@@ -5334,9 +5473,9 @@ function getImageRef(registry, imageName, tag) {
|
|
|
5334
5473
|
* @param buildArgs - Build arguments to pass to docker build
|
|
5335
5474
|
*/
|
|
5336
5475
|
async function buildImage(imageRef, _appName, buildArgs) {
|
|
5337
|
-
logger$
|
|
5476
|
+
logger$5.log(`\n🔨 Building Docker image: ${imageRef}`);
|
|
5338
5477
|
const cwd = process.cwd();
|
|
5339
|
-
logger$
|
|
5478
|
+
logger$5.log(" Generating Dockerfile for the pre-built bundle...");
|
|
5340
5479
|
await dockerCommand({ slim: true });
|
|
5341
5480
|
const dockerfilePath = (0, node_path.join)(cwd, ".gkm", "docker", "Dockerfile");
|
|
5342
5481
|
const buildCwd = cwd;
|
|
@@ -5358,7 +5497,7 @@ async function buildImage(imageRef, _appName, buildArgs) {
|
|
|
5358
5497
|
DOCKER_BUILDKIT: "1"
|
|
5359
5498
|
}
|
|
5360
5499
|
});
|
|
5361
|
-
logger$
|
|
5500
|
+
logger$5.log(`✅ Image built: ${imageRef}`);
|
|
5362
5501
|
} catch (error) {
|
|
5363
5502
|
throw new Error(`Failed to build Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
5364
5503
|
}
|
|
@@ -5367,13 +5506,13 @@ async function buildImage(imageRef, _appName, buildArgs) {
|
|
|
5367
5506
|
* Push Docker image to registry
|
|
5368
5507
|
*/
|
|
5369
5508
|
async function pushImage(imageRef) {
|
|
5370
|
-
logger$
|
|
5509
|
+
logger$5.log(`\n☁️ Pushing image: ${imageRef}`);
|
|
5371
5510
|
try {
|
|
5372
5511
|
(0, node_child_process.execSync)(`docker push ${imageRef}`, {
|
|
5373
5512
|
cwd: process.cwd(),
|
|
5374
5513
|
stdio: "inherit"
|
|
5375
5514
|
});
|
|
5376
|
-
logger$
|
|
5515
|
+
logger$5.log(`✅ Image pushed: ${imageRef}`);
|
|
5377
5516
|
} catch (error) {
|
|
5378
5517
|
throw new Error(`Failed to push Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
5379
5518
|
}
|
|
@@ -5387,18 +5526,18 @@ async function deployDocker(options) {
|
|
|
5387
5526
|
const imageRef = getImageRef(config.registry, imageName, tag);
|
|
5388
5527
|
await buildImage(imageRef, config.appName, buildArgs);
|
|
5389
5528
|
if (!skipPush) {
|
|
5390
|
-
if (!config.registry) logger$
|
|
5529
|
+
if (!config.registry) logger$5.warn("\n⚠️ No registry configured. Use --skip-push or configure docker.registry in gkm.config.ts");
|
|
5391
5530
|
else await pushImage(imageRef);
|
|
5392
5531
|
}
|
|
5393
|
-
logger$
|
|
5394
|
-
logger$
|
|
5395
|
-
logger$
|
|
5396
|
-
logger$
|
|
5532
|
+
logger$5.log("\n✅ Docker deployment ready!");
|
|
5533
|
+
logger$5.log(`\n📋 Deployment details:`);
|
|
5534
|
+
logger$5.log(` Image: ${imageRef}`);
|
|
5535
|
+
logger$5.log(` Stage: ${stage}`);
|
|
5397
5536
|
if (masterKey) {
|
|
5398
|
-
logger$
|
|
5399
|
-
logger$
|
|
5400
|
-
logger$
|
|
5401
|
-
logger$
|
|
5537
|
+
logger$5.log(`\n🔐 Deploy with this environment variable:`);
|
|
5538
|
+
logger$5.log(` GKM_MASTER_KEY=${masterKey}`);
|
|
5539
|
+
logger$5.log("\n Example docker run:");
|
|
5540
|
+
logger$5.log(` docker run -e GKM_MASTER_KEY=${masterKey} ${imageRef}`);
|
|
5402
5541
|
}
|
|
5403
5542
|
return {
|
|
5404
5543
|
imageRef,
|
|
@@ -6777,7 +6916,7 @@ async function sniffAllApps(apps, workspacePath, options = {}) {
|
|
|
6777
6916
|
*
|
|
6778
6917
|
* @module deploy
|
|
6779
6918
|
*/
|
|
6780
|
-
const logger$
|
|
6919
|
+
const logger$4 = console;
|
|
6781
6920
|
/**
|
|
6782
6921
|
* Prompt for input
|
|
6783
6922
|
*/
|
|
@@ -6851,7 +6990,7 @@ async function waitForPostgres(host, port, user, password, database, maxRetries
|
|
|
6851
6990
|
return;
|
|
6852
6991
|
} catch {
|
|
6853
6992
|
if (i < maxRetries - 1) {
|
|
6854
|
-
logger$
|
|
6993
|
+
logger$4.log(` Waiting for Postgres... (${i + 1}/${maxRetries})`);
|
|
6855
6994
|
await new Promise((r) => setTimeout(r, retryIntervalMs));
|
|
6856
6995
|
}
|
|
6857
6996
|
}
|
|
@@ -6894,10 +7033,10 @@ async function applyDeclaredStatements(api, postgres, serverHostname, statements
|
|
|
6894
7033
|
break;
|
|
6895
7034
|
} catch (error) {
|
|
6896
7035
|
if (!(error instanceof Error ? error.message : String(error)).includes("already in use")) throw error;
|
|
6897
|
-
logger$
|
|
7036
|
+
logger$4.log(` Port ${candidate} is taken; trying another...`);
|
|
6898
7037
|
}
|
|
6899
7038
|
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.`);
|
|
6900
|
-
logger$
|
|
7039
|
+
logger$4.log(` Publishing ${postgres.appName} on ${externalPort}...`);
|
|
6901
7040
|
}
|
|
6902
7041
|
await api.deployPostgres(postgres.postgresId);
|
|
6903
7042
|
await waitForPostgres(serverHostname, externalPort, postgres.databaseUser, postgres.databasePassword, postgres.databaseName);
|
|
@@ -6921,7 +7060,7 @@ async function applyDeclaredStatements(api, postgres, serverHostname, statements
|
|
|
6921
7060
|
const unpublish = async () => {
|
|
6922
7061
|
if (!opened) return;
|
|
6923
7062
|
await api.savePostgresExternalPort(postgres.postgresId, null).then(() => api.deployPostgres(postgres.postgresId)).catch(() => {
|
|
6924
|
-
logger$
|
|
7063
|
+
logger$4.log(` ⚠ Could not close external port ${externalPort} on ${postgres.appName} — close it in Dokploy.`);
|
|
6925
7064
|
});
|
|
6926
7065
|
};
|
|
6927
7066
|
let lastError;
|
|
@@ -6932,7 +7071,7 @@ async function applyDeclaredStatements(api, postgres, serverHostname, statements
|
|
|
6932
7071
|
lastError = error;
|
|
6933
7072
|
if (attempt === 3) break;
|
|
6934
7073
|
const message = error instanceof Error ? error.message : String(error);
|
|
6935
|
-
logger$
|
|
7074
|
+
logger$4.log(` ⏳ Cluster still settling (${message}); retrying (${attempt}/2)...`);
|
|
6936
7075
|
await new Promise((resolve) => setTimeout(resolve, 1e4));
|
|
6937
7076
|
await waitForPostgres(serverHostname, externalPort, postgres.databaseUser, postgres.databasePassword, postgres.databaseName).catch(() => {});
|
|
6938
7077
|
}
|
|
@@ -7007,91 +7146,91 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7007
7146
|
apps: units
|
|
7008
7147
|
} : configured;
|
|
7009
7148
|
if (provider !== "dokploy") throw new Error(`Workspace deployment only supports Dokploy. Got: ${provider}`);
|
|
7010
|
-
logger$
|
|
7011
|
-
logger$
|
|
7149
|
+
logger$4.log(`\n🚀 Deploying workspace "${workspace.name}" to Dokploy...`);
|
|
7150
|
+
logger$4.log(` Stage: ${stage}`);
|
|
7012
7151
|
const imageTag = tag ?? generateTag(stage);
|
|
7013
|
-
logger$
|
|
7152
|
+
logger$4.log(` Tag: ${imageTag}`);
|
|
7014
7153
|
const buildOrder = require_workspace.getAppBuildOrder(workspace);
|
|
7015
7154
|
let appsToDeployNames = buildOrder;
|
|
7016
7155
|
if (selectedApps && selectedApps.length > 0) {
|
|
7017
7156
|
const invalidApps = selectedApps.filter((name) => !workspace.apps[name]);
|
|
7018
7157
|
if (invalidApps.length > 0) throw new Error(`Unknown apps: ${invalidApps.join(", ")}\nAvailable apps: ${Object.keys(workspace.apps).join(", ")}`);
|
|
7019
7158
|
appsToDeployNames = buildOrder.filter((name) => selectedApps.includes(name));
|
|
7020
|
-
logger$
|
|
7021
|
-
} else logger$
|
|
7159
|
+
logger$4.log(` Deploying apps: ${appsToDeployNames.join(", ")}`);
|
|
7160
|
+
} else logger$4.log(` Deploying all apps: ${appsToDeployNames.join(", ")}`);
|
|
7022
7161
|
const dokployApps = appsToDeployNames.filter((name) => {
|
|
7023
7162
|
const target = workspace.apps[name].resolvedDeployTarget;
|
|
7024
7163
|
if (!require_workspace.isDeployTargetSupported(target)) {
|
|
7025
|
-
logger$
|
|
7164
|
+
logger$4.log(` ⚠️ Skipping ${name}: ${require_workspace.getDeployTargetError(target, name)}`);
|
|
7026
7165
|
return false;
|
|
7027
7166
|
}
|
|
7028
7167
|
return true;
|
|
7029
7168
|
});
|
|
7030
7169
|
if (dokployApps.length === 0) throw new Error("No apps to deploy. All selected apps have unsupported deploy targets.");
|
|
7031
7170
|
appsToDeployNames = dokployApps;
|
|
7032
|
-
logger$
|
|
7171
|
+
logger$4.log("\n🔐 Loading secrets and analyzing environment requirements...");
|
|
7033
7172
|
const stageSecrets = await require_storage.readStageSecrets(stage, workspace.root);
|
|
7034
7173
|
if (!stageSecrets) {
|
|
7035
|
-
logger$
|
|
7036
|
-
logger$
|
|
7174
|
+
logger$4.log(` ⚠️ No secrets found for stage "${stage}"`);
|
|
7175
|
+
logger$4.log(` Run "gkm secrets:init --stage ${stage}" to create secrets`);
|
|
7037
7176
|
}
|
|
7038
7177
|
const sniffedApps = await sniffAllApps(workspace.apps, workspace.root);
|
|
7039
7178
|
const encryptedSecrets = stageSecrets ? prepareSecretsForAllApps(stageSecrets, sniffedApps) : /* @__PURE__ */ new Map();
|
|
7040
7179
|
if (stageSecrets) {
|
|
7041
7180
|
const report = generateSecretsReport(encryptedSecrets, sniffedApps);
|
|
7042
|
-
if (report.appsWithSecrets.length > 0) logger$
|
|
7043
|
-
if (report.appsWithMissingSecrets.length > 0) for (const { appName, missing } of report.appsWithMissingSecrets) logger$
|
|
7181
|
+
if (report.appsWithSecrets.length > 0) logger$4.log(` ✓ Encrypted secrets for: ${report.appsWithSecrets.join(", ")}`);
|
|
7182
|
+
if (report.appsWithMissingSecrets.length > 0) for (const { appName, missing } of report.appsWithMissingSecrets) logger$4.log(` ⚠️ ${appName}: Missing secrets: ${missing.join(", ")}`);
|
|
7044
7183
|
}
|
|
7045
7184
|
let creds = await getDokployCredentials();
|
|
7046
7185
|
if (!creds) {
|
|
7047
|
-
logger$
|
|
7186
|
+
logger$4.log("\n📋 Dokploy credentials not found. Let's set them up.");
|
|
7048
7187
|
const normalizedEndpoint = (await prompt("Dokploy URL (e.g., https://dokploy.example.com): ")).replace(/\/$/, "");
|
|
7049
7188
|
try {
|
|
7050
7189
|
new URL(normalizedEndpoint);
|
|
7051
7190
|
} catch {
|
|
7052
7191
|
throw new Error("Invalid URL format");
|
|
7053
7192
|
}
|
|
7054
|
-
logger$
|
|
7193
|
+
logger$4.log(`\nGenerate a token at: ${normalizedEndpoint}/settings/profile\n`);
|
|
7055
7194
|
const token = await prompt("API Token: ", true);
|
|
7056
|
-
logger$
|
|
7195
|
+
logger$4.log("\nValidating credentials...");
|
|
7057
7196
|
if (!await validateDokployToken(normalizedEndpoint, token)) throw new Error("Invalid credentials. Please check your token.");
|
|
7058
7197
|
await storeDokployCredentials(token, normalizedEndpoint);
|
|
7059
7198
|
creds = {
|
|
7060
7199
|
token,
|
|
7061
7200
|
endpoint: normalizedEndpoint
|
|
7062
7201
|
};
|
|
7063
|
-
logger$
|
|
7202
|
+
logger$4.log("✓ Credentials saved");
|
|
7064
7203
|
}
|
|
7065
7204
|
const api = new DokployApi({
|
|
7066
7205
|
baseUrl: creds.endpoint,
|
|
7067
7206
|
token: creds.token
|
|
7068
7207
|
});
|
|
7069
|
-
logger$
|
|
7208
|
+
logger$4.log("\n📁 Setting up Dokploy project...");
|
|
7070
7209
|
const projectName = workspace.name;
|
|
7071
7210
|
let project = (await api.listProjects()).find((p) => p.name.toLowerCase() === projectName.toLowerCase());
|
|
7072
7211
|
let environmentId;
|
|
7073
7212
|
if (project) {
|
|
7074
|
-
logger$
|
|
7213
|
+
logger$4.log(` Found existing project: ${project.name}`);
|
|
7075
7214
|
const matchingEnv = ((await api.getProject(project.projectId)).environments ?? []).find((e) => e.name.toLowerCase() === stage.toLowerCase());
|
|
7076
7215
|
if (matchingEnv) {
|
|
7077
7216
|
environmentId = matchingEnv.environmentId;
|
|
7078
|
-
logger$
|
|
7217
|
+
logger$4.log(` Using environment: ${matchingEnv.name}`);
|
|
7079
7218
|
} else {
|
|
7080
|
-
logger$
|
|
7219
|
+
logger$4.log(` Creating "${stage}" environment...`);
|
|
7081
7220
|
environmentId = (await api.createEnvironment(project.projectId, stage)).environmentId;
|
|
7082
|
-
logger$
|
|
7221
|
+
logger$4.log(` ✓ Created environment: ${stage}`);
|
|
7083
7222
|
}
|
|
7084
7223
|
} else {
|
|
7085
|
-
logger$
|
|
7224
|
+
logger$4.log(` Creating project: ${projectName}`);
|
|
7086
7225
|
const result = await api.createProject(projectName);
|
|
7087
7226
|
project = result.project;
|
|
7088
7227
|
if (result.environment.name.toLowerCase() !== stage.toLowerCase()) {
|
|
7089
|
-
logger$
|
|
7228
|
+
logger$4.log(` Creating "${stage}" environment...`);
|
|
7090
7229
|
environmentId = (await api.createEnvironment(project.projectId, stage)).environmentId;
|
|
7091
7230
|
} else environmentId = result.environment.environmentId;
|
|
7092
|
-
logger$
|
|
7231
|
+
logger$4.log(` ✓ Created project: ${project.projectId}`);
|
|
7093
7232
|
}
|
|
7094
|
-
logger$
|
|
7233
|
+
logger$4.log("\n📋 Loading deploy state...");
|
|
7095
7234
|
const stateProvider = await createStateProvider({
|
|
7096
7235
|
config: workspace.state,
|
|
7097
7236
|
workspaceRoot: workspace.root,
|
|
@@ -7099,27 +7238,27 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7099
7238
|
});
|
|
7100
7239
|
let state = await stateProvider.read(stage);
|
|
7101
7240
|
if (state) {
|
|
7102
|
-
logger$
|
|
7241
|
+
logger$4.log(` Found existing state for stage "${stage}"`);
|
|
7103
7242
|
if (state.projectId !== project.projectId) {
|
|
7104
|
-
logger$
|
|
7243
|
+
logger$4.log(` ⚠ Project ID changed, updating state`);
|
|
7105
7244
|
state.projectId = project.projectId;
|
|
7106
7245
|
}
|
|
7107
7246
|
if (state.environmentId !== environmentId) {
|
|
7108
|
-
logger$
|
|
7247
|
+
logger$4.log(` ⚠ Environment ID changed, updating state`);
|
|
7109
7248
|
state.environmentId = environmentId;
|
|
7110
7249
|
}
|
|
7111
7250
|
} else {
|
|
7112
|
-
logger$
|
|
7251
|
+
logger$4.log(` Creating new state for stage "${stage}"`);
|
|
7113
7252
|
state = createEmptyState(stage, project.projectId, environmentId);
|
|
7114
7253
|
}
|
|
7115
|
-
logger$
|
|
7254
|
+
logger$4.log("\n🐳 Checking registry...");
|
|
7116
7255
|
let registryId = await getDokployRegistryId();
|
|
7117
7256
|
const registry = workspace.deploy.dokploy?.registry;
|
|
7118
7257
|
if (registryId) try {
|
|
7119
7258
|
const reg = await api.getRegistry(registryId);
|
|
7120
|
-
logger$
|
|
7259
|
+
logger$4.log(` Using registry: ${reg.registryName}`);
|
|
7121
7260
|
} catch {
|
|
7122
|
-
logger$
|
|
7261
|
+
logger$4.log(" ⚠ Stored registry not found, clearing...");
|
|
7123
7262
|
registryId = void 0;
|
|
7124
7263
|
await storeDokployRegistryId("");
|
|
7125
7264
|
}
|
|
@@ -7128,26 +7267,26 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7128
7267
|
if (registries.length > 0) {
|
|
7129
7268
|
registryId = registries[0].registryId;
|
|
7130
7269
|
await storeDokployRegistryId(registryId);
|
|
7131
|
-
logger$
|
|
7270
|
+
logger$4.log(` Using registry: ${registries[0].registryName}`);
|
|
7132
7271
|
} else if (registry) {
|
|
7133
|
-
logger$
|
|
7134
|
-
logger$
|
|
7272
|
+
logger$4.log(" No registries found in Dokploy. Let's create one.");
|
|
7273
|
+
logger$4.log(` Registry URL: ${registry}`);
|
|
7135
7274
|
const username = await prompt("Registry username: ");
|
|
7136
7275
|
const password = await prompt("Registry password/token: ", true);
|
|
7137
7276
|
registryId = (await api.createRegistry("Default Registry", registry, username, password)).registryId;
|
|
7138
7277
|
await storeDokployRegistryId(registryId);
|
|
7139
|
-
logger$
|
|
7140
|
-
} else logger$
|
|
7278
|
+
logger$4.log(` ✓ Registry created: ${registryId}`);
|
|
7279
|
+
} else logger$4.log(" ⚠ No registry configured. Set deploy.dokploy.registry in workspace config");
|
|
7141
7280
|
}
|
|
7142
7281
|
const skippedMobileApps = appsToDeployNames.filter((name) => workspace.apps[name].type === "mobile");
|
|
7143
|
-
if (skippedMobileApps.length > 0) logger$
|
|
7282
|
+
if (skippedMobileApps.length > 0) logger$4.log(`\n📱 Skipping ${skippedMobileApps.length} mobile app(s) — deploy via framework toolchain: ${skippedMobileApps.join(", ")}`);
|
|
7144
7283
|
const backendApps = appsToDeployNames.filter((name) => workspace.apps[name].type === "backend");
|
|
7145
7284
|
const frontendApps = appsToDeployNames.filter((name) => workspace.apps[name].type === "web");
|
|
7146
7285
|
const dokployConfig = workspace.deploy.dokploy;
|
|
7147
7286
|
let declaredEnv = {};
|
|
7148
7287
|
let declaredClusters = {};
|
|
7149
7288
|
{
|
|
7150
|
-
logger$
|
|
7289
|
+
logger$4.log("\n📦 Provisioning declared constructs...");
|
|
7151
7290
|
const appUrls = {};
|
|
7152
7291
|
for (const appName of backendApps) {
|
|
7153
7292
|
const app = workspace.apps[appName];
|
|
@@ -7165,20 +7304,20 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7165
7304
|
});
|
|
7166
7305
|
declaredEnv = declared.env;
|
|
7167
7306
|
declaredClusters = declared.clusters;
|
|
7168
|
-
if (Object.keys(declaredEnv).length > 0) logger$
|
|
7307
|
+
if (Object.keys(declaredEnv).length > 0) logger$4.log(` 🔌 Resolved ${Object.keys(declaredEnv).length} declared URL(s)`);
|
|
7169
7308
|
if (declared.statements.length > 0) {
|
|
7170
7309
|
const serverHostname = getServerHostname(creds.endpoint);
|
|
7171
7310
|
for (const [databaseName, cluster] of Object.entries(declared.clusters)) {
|
|
7172
7311
|
const statements = declared.statements.filter((statement) => statement.database === databaseName);
|
|
7173
7312
|
if (statements.length === 0) continue;
|
|
7174
7313
|
const created = await applyDeclaredStatements(api, cluster, serverHostname, statements);
|
|
7175
|
-
logger$
|
|
7314
|
+
logger$4.log(` 🗄️ ${databaseName}: applied ${statements.length} statement(s), ${created} new`);
|
|
7176
7315
|
}
|
|
7177
7316
|
}
|
|
7178
7317
|
}
|
|
7179
7318
|
const backupCluster = Object.values(declaredClusters)[0];
|
|
7180
7319
|
if (workspace.deploy?.backups && backupCluster) {
|
|
7181
|
-
logger$
|
|
7320
|
+
logger$4.log("\n💾 Provisioning backup destination...");
|
|
7182
7321
|
const { provisionBackupDestination } = await Promise.resolve().then(() => require("./backup-provisioner-BmhIO_Yr.cjs"));
|
|
7183
7322
|
const backupState = await provisionBackupDestination({
|
|
7184
7323
|
api,
|
|
@@ -7187,13 +7326,13 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7187
7326
|
stage,
|
|
7188
7327
|
config: workspace.deploy.backups,
|
|
7189
7328
|
existingState: getBackupState(state),
|
|
7190
|
-
logger: logger$
|
|
7329
|
+
logger: logger$4
|
|
7191
7330
|
});
|
|
7192
7331
|
setBackupState(state, backupState);
|
|
7193
7332
|
if (!backupState.postgresBackupId) {
|
|
7194
7333
|
const backupSchedule = workspace.deploy.backups.schedule ?? "0 2 * * *";
|
|
7195
7334
|
const backupRetention = workspace.deploy.backups.retention ?? 30;
|
|
7196
|
-
logger$
|
|
7335
|
+
logger$4.log(" Creating postgres backup schedule...");
|
|
7197
7336
|
const backup = await api.createPostgresBackup({
|
|
7198
7337
|
schedule: backupSchedule,
|
|
7199
7338
|
prefix: `${stage}/postgres`,
|
|
@@ -7204,8 +7343,8 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7204
7343
|
keepLatestCount: backupRetention
|
|
7205
7344
|
});
|
|
7206
7345
|
setPostgresBackupId(state, backup.backupId);
|
|
7207
|
-
logger$
|
|
7208
|
-
} else logger$
|
|
7346
|
+
logger$4.log(` ✓ Postgres backup schedule created (${backupSchedule})`);
|
|
7347
|
+
} else logger$4.log(" ✓ Using existing postgres backup schedule");
|
|
7209
7348
|
}
|
|
7210
7349
|
const publicUrls = {};
|
|
7211
7350
|
const results = [];
|
|
@@ -7218,25 +7357,25 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7218
7357
|
frontendUrls.push(`https://${hostname}`);
|
|
7219
7358
|
}
|
|
7220
7359
|
if (backendApps.length > 0) {
|
|
7221
|
-
logger$
|
|
7360
|
+
logger$4.log("\n📦 PHASE 1: Deploying backend applications...");
|
|
7222
7361
|
for (const appName of backendApps) {
|
|
7223
7362
|
const app = workspace.apps[appName];
|
|
7224
|
-
logger$
|
|
7363
|
+
logger$4.log(`\n ⚙️ Deploying ${appName}...`);
|
|
7225
7364
|
try {
|
|
7226
7365
|
const dokployAppName = applicationName(stage, workspace.name, appName);
|
|
7227
7366
|
let application = null;
|
|
7228
7367
|
const cachedAppId = getApplicationId(state, appName);
|
|
7229
7368
|
if (cachedAppId) {
|
|
7230
|
-
logger$
|
|
7369
|
+
logger$4.log(` Using cached ID: ${cachedAppId}`);
|
|
7231
7370
|
application = await api.getApplication(cachedAppId);
|
|
7232
|
-
if (application) logger$
|
|
7233
|
-
else logger$
|
|
7371
|
+
if (application) logger$4.log(` ✓ Application found: ${application.applicationId}`);
|
|
7372
|
+
else logger$4.log(` ⚠ Cached ID invalid, will create new`);
|
|
7234
7373
|
}
|
|
7235
7374
|
if (!application) {
|
|
7236
7375
|
const result = await api.findOrCreateApplication(dokployAppName, project.projectId, environmentId);
|
|
7237
7376
|
application = result.application;
|
|
7238
|
-
if (result.created) logger$
|
|
7239
|
-
else logger$
|
|
7377
|
+
if (result.created) logger$4.log(` Created application: ${application.applicationId}`);
|
|
7378
|
+
else logger$4.log(` Found existing application: ${application.applicationId}`);
|
|
7240
7379
|
}
|
|
7241
7380
|
setApplicationId(state, appName, application.applicationId);
|
|
7242
7381
|
const appSecrets = encryptedSecrets.get(appName);
|
|
@@ -7244,11 +7383,11 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7244
7383
|
if (appSecrets && appSecrets.secretCount > 0) {
|
|
7245
7384
|
buildArgs.push(`GKM_ENCRYPTED_CREDENTIALS=${appSecrets.payload.encrypted}`);
|
|
7246
7385
|
buildArgs.push(`GKM_CREDENTIALS_IV=${appSecrets.payload.iv}`);
|
|
7247
|
-
logger$
|
|
7386
|
+
logger$4.log(` Encrypted ${appSecrets.secretCount} secrets`);
|
|
7248
7387
|
}
|
|
7249
7388
|
const imageName = `${workspace.name}-${appName}`;
|
|
7250
7389
|
const imageRef = registry ? `${registry}/${imageName}:${imageTag}` : `${imageName}:${imageTag}`;
|
|
7251
|
-
logger$
|
|
7390
|
+
logger$4.log(` Building Docker image: ${imageRef}`);
|
|
7252
7391
|
await deployDocker({
|
|
7253
7392
|
stage,
|
|
7254
7393
|
tag: imageTag,
|
|
@@ -7289,17 +7428,17 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7289
7428
|
...declaredEnv
|
|
7290
7429
|
};
|
|
7291
7430
|
const envVars = Object.entries(withDeclared).map(([key, value]) => `${key}=${value}`);
|
|
7292
|
-
if (Object.keys(withDeclared).length > 0) logger$
|
|
7431
|
+
if (Object.keys(withDeclared).length > 0) logger$4.log(` Resolved ${Object.keys(withDeclared).length} env vars: ${Object.keys(withDeclared).sort().join(", ")}`);
|
|
7293
7432
|
await api.saveDockerProvider(application.applicationId, imageRef, { registryId });
|
|
7294
7433
|
await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
|
|
7295
|
-
logger$
|
|
7434
|
+
logger$4.log(` Deploying to Dokploy...`);
|
|
7296
7435
|
await api.deployApplication(application.applicationId);
|
|
7297
7436
|
const existingDomain = (await api.getDomainsByApplicationId(application.applicationId)).find((d) => d.host === backendHost);
|
|
7298
7437
|
if (existingDomain) {
|
|
7299
7438
|
appHostnames.set(appName, backendHost);
|
|
7300
7439
|
appDomainIds.set(appName, existingDomain.domainId);
|
|
7301
7440
|
publicUrls[appName] = `https://${backendHost}`;
|
|
7302
|
-
logger$
|
|
7441
|
+
logger$4.log(` ✓ Domain: https://${backendHost} (existing)`);
|
|
7303
7442
|
} else try {
|
|
7304
7443
|
const domain = await api.createDomain({
|
|
7305
7444
|
host: backendHost,
|
|
@@ -7311,10 +7450,10 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7311
7450
|
appHostnames.set(appName, backendHost);
|
|
7312
7451
|
appDomainIds.set(appName, domain.domainId);
|
|
7313
7452
|
publicUrls[appName] = `https://${backendHost}`;
|
|
7314
|
-
logger$
|
|
7453
|
+
logger$4.log(` ✓ Domain: https://${backendHost} (created)`);
|
|
7315
7454
|
} catch (domainError) {
|
|
7316
7455
|
const message = domainError instanceof Error ? domainError.message : "Unknown error";
|
|
7317
|
-
logger$
|
|
7456
|
+
logger$4.log(` ⚠ Domain creation failed: ${message}`);
|
|
7318
7457
|
appHostnames.set(appName, backendHost);
|
|
7319
7458
|
publicUrls[appName] = `https://${backendHost}`;
|
|
7320
7459
|
}
|
|
@@ -7325,10 +7464,10 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7325
7464
|
applicationId: application.applicationId,
|
|
7326
7465
|
imageRef
|
|
7327
7466
|
});
|
|
7328
|
-
logger$
|
|
7467
|
+
logger$4.log(` ✓ ${appName} deployed successfully`);
|
|
7329
7468
|
} catch (error) {
|
|
7330
7469
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
7331
|
-
logger$
|
|
7470
|
+
logger$4.log(` ✗ Failed to deploy ${appName}: ${message}`);
|
|
7332
7471
|
results.push({
|
|
7333
7472
|
appName,
|
|
7334
7473
|
type: app.type,
|
|
@@ -7340,25 +7479,25 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7340
7479
|
}
|
|
7341
7480
|
}
|
|
7342
7481
|
if (frontendApps.length > 0) {
|
|
7343
|
-
logger$
|
|
7482
|
+
logger$4.log("\n🌐 PHASE 2: Deploying frontend applications...");
|
|
7344
7483
|
for (const appName of frontendApps) {
|
|
7345
7484
|
const app = workspace.apps[appName];
|
|
7346
|
-
logger$
|
|
7485
|
+
logger$4.log(`\n 🌐 Deploying ${appName}...`);
|
|
7347
7486
|
try {
|
|
7348
7487
|
const dokployAppName = applicationName(stage, workspace.name, appName);
|
|
7349
7488
|
let application = null;
|
|
7350
7489
|
const cachedAppId = getApplicationId(state, appName);
|
|
7351
7490
|
if (cachedAppId) {
|
|
7352
|
-
logger$
|
|
7491
|
+
logger$4.log(` Using cached ID: ${cachedAppId}`);
|
|
7353
7492
|
application = await api.getApplication(cachedAppId);
|
|
7354
|
-
if (application) logger$
|
|
7355
|
-
else logger$
|
|
7493
|
+
if (application) logger$4.log(` ✓ Application found: ${application.applicationId}`);
|
|
7494
|
+
else logger$4.log(` ⚠ Cached ID invalid, will create new`);
|
|
7356
7495
|
}
|
|
7357
7496
|
if (!application) {
|
|
7358
7497
|
const result = await api.findOrCreateApplication(dokployAppName, project.projectId, environmentId);
|
|
7359
7498
|
application = result.application;
|
|
7360
|
-
if (result.created) logger$
|
|
7361
|
-
else logger$
|
|
7499
|
+
if (result.created) logger$4.log(` Created application: ${application.applicationId}`);
|
|
7500
|
+
else logger$4.log(` Found existing application: ${application.applicationId}`);
|
|
7362
7501
|
}
|
|
7363
7502
|
setApplicationId(state, appName, application.applicationId);
|
|
7364
7503
|
const dependencyUrls = {};
|
|
@@ -7378,7 +7517,7 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7378
7517
|
};
|
|
7379
7518
|
const { valid, missing, resolved } = validateEnvVars(requiredOf(sniffedApps.get(appName)), envContext);
|
|
7380
7519
|
if (!valid) throw new Error(formatMissingVarsError(appName, missing, stage));
|
|
7381
|
-
if (Object.keys(resolved).length > 0) logger$
|
|
7520
|
+
if (Object.keys(resolved).length > 0) logger$4.log(` Resolved ${Object.keys(resolved).length} env vars: ${Object.keys(resolved).join(", ")}`);
|
|
7382
7521
|
const publicPrefix = require_workspace.getPublicEnvPrefix(app.framework);
|
|
7383
7522
|
const buildArgs = [];
|
|
7384
7523
|
const publicUrlArgNames = [];
|
|
@@ -7388,10 +7527,10 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7388
7527
|
publicUrlArgNames.push(key);
|
|
7389
7528
|
}
|
|
7390
7529
|
}
|
|
7391
|
-
if (buildArgs.length > 0) logger$
|
|
7530
|
+
if (buildArgs.length > 0) logger$4.log(` Build args: ${publicUrlArgNames.join(", ")}`);
|
|
7392
7531
|
const imageName = `${workspace.name}-${appName}`;
|
|
7393
7532
|
const imageRef = registry ? `${registry}/${imageName}:${imageTag}` : `${imageName}:${imageTag}`;
|
|
7394
|
-
logger$
|
|
7533
|
+
logger$4.log(` Building Docker image: ${imageRef}`);
|
|
7395
7534
|
await deployDocker({
|
|
7396
7535
|
stage,
|
|
7397
7536
|
tag: imageTag,
|
|
@@ -7412,14 +7551,14 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7412
7551
|
for (const [key, value] of Object.entries(resolved)) envVars.push(`${key}=${value}`);
|
|
7413
7552
|
await api.saveDockerProvider(application.applicationId, imageRef, { registryId });
|
|
7414
7553
|
await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
|
|
7415
|
-
logger$
|
|
7554
|
+
logger$4.log(` Deploying to Dokploy...`);
|
|
7416
7555
|
await api.deployApplication(application.applicationId);
|
|
7417
7556
|
const existingFrontendDomain = (await api.getDomainsByApplicationId(application.applicationId)).find((d) => d.host === frontendHost);
|
|
7418
7557
|
if (existingFrontendDomain) {
|
|
7419
7558
|
appHostnames.set(appName, frontendHost);
|
|
7420
7559
|
appDomainIds.set(appName, existingFrontendDomain.domainId);
|
|
7421
7560
|
publicUrls[appName] = `https://${frontendHost}`;
|
|
7422
|
-
logger$
|
|
7561
|
+
logger$4.log(` ✓ Domain: https://${frontendHost} (existing)`);
|
|
7423
7562
|
} else try {
|
|
7424
7563
|
const domain = await api.createDomain({
|
|
7425
7564
|
host: frontendHost,
|
|
@@ -7431,10 +7570,10 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7431
7570
|
appHostnames.set(appName, frontendHost);
|
|
7432
7571
|
appDomainIds.set(appName, domain.domainId);
|
|
7433
7572
|
publicUrls[appName] = `https://${frontendHost}`;
|
|
7434
|
-
logger$
|
|
7573
|
+
logger$4.log(` ✓ Domain: https://${frontendHost} (created)`);
|
|
7435
7574
|
} catch (domainError) {
|
|
7436
7575
|
const message = domainError instanceof Error ? domainError.message : "Unknown error";
|
|
7437
|
-
logger$
|
|
7576
|
+
logger$4.log(` ⚠ Domain creation failed: ${message}`);
|
|
7438
7577
|
appHostnames.set(appName, frontendHost);
|
|
7439
7578
|
publicUrls[appName] = `https://${frontendHost}`;
|
|
7440
7579
|
}
|
|
@@ -7445,10 +7584,10 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7445
7584
|
applicationId: application.applicationId,
|
|
7446
7585
|
imageRef
|
|
7447
7586
|
});
|
|
7448
|
-
logger$
|
|
7587
|
+
logger$4.log(` ✓ ${appName} deployed successfully`);
|
|
7449
7588
|
} catch (error) {
|
|
7450
7589
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
7451
|
-
logger$
|
|
7590
|
+
logger$4.log(` ✗ Failed to deploy ${appName}: ${message}`);
|
|
7452
7591
|
results.push({
|
|
7453
7592
|
appName,
|
|
7454
7593
|
type: app.type,
|
|
@@ -7458,9 +7597,9 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7458
7597
|
}
|
|
7459
7598
|
}
|
|
7460
7599
|
}
|
|
7461
|
-
logger$
|
|
7600
|
+
logger$4.log("\n📋 Saving deploy state...");
|
|
7462
7601
|
await stateProvider.write(stage, state);
|
|
7463
|
-
logger$
|
|
7602
|
+
logger$4.log(" ✓ State saved");
|
|
7464
7603
|
const dnsConfig = workspace.deploy.dns;
|
|
7465
7604
|
if (dnsConfig && appHostnames.size > 0) {
|
|
7466
7605
|
const dnsResult = await orchestrateDns(appHostnames, dnsConfig, creds.endpoint);
|
|
@@ -7469,27 +7608,27 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7469
7608
|
await stateProvider.write(stage, state);
|
|
7470
7609
|
}
|
|
7471
7610
|
if (dnsResult?.success && appHostnames.size > 0) {
|
|
7472
|
-
logger$
|
|
7611
|
+
logger$4.log("\n🔒 Validating domains for SSL certificates...");
|
|
7473
7612
|
for (const [appName, hostname] of appHostnames) try {
|
|
7474
7613
|
const result = await api.validateDomain(hostname);
|
|
7475
|
-
if (result.isValid) logger$
|
|
7476
|
-
else logger$
|
|
7614
|
+
if (result.isValid) logger$4.log(` ✓ ${appName}: ${hostname} → ${result.resolvedIp}`);
|
|
7615
|
+
else logger$4.log(` ⚠ ${appName}: ${hostname} not valid`);
|
|
7477
7616
|
} catch (validationError) {
|
|
7478
7617
|
const message = validationError instanceof Error ? validationError.message : "Unknown error";
|
|
7479
|
-
logger$
|
|
7618
|
+
logger$4.log(` ⚠ ${appName}: validation failed - ${message}`);
|
|
7480
7619
|
}
|
|
7481
7620
|
}
|
|
7482
7621
|
}
|
|
7483
7622
|
const successCount = results.filter((r) => r.success).length;
|
|
7484
7623
|
const failedCount = results.filter((r) => !r.success).length;
|
|
7485
|
-
logger$
|
|
7486
|
-
logger$
|
|
7487
|
-
logger$
|
|
7488
|
-
logger$
|
|
7489
|
-
if (failedCount > 0) logger$
|
|
7624
|
+
logger$4.log(`\n${"─".repeat(50)}`);
|
|
7625
|
+
logger$4.log(`\n✅ Workspace deployment complete!`);
|
|
7626
|
+
logger$4.log(` Project: ${project.projectId}`);
|
|
7627
|
+
logger$4.log(` Successful: ${successCount}`);
|
|
7628
|
+
if (failedCount > 0) logger$4.log(` Failed: ${failedCount}`);
|
|
7490
7629
|
if (Object.keys(publicUrls).length > 0) {
|
|
7491
|
-
logger$
|
|
7492
|
-
for (const [name, url] of Object.entries(publicUrls)) logger$
|
|
7630
|
+
logger$4.log("\n 📡 Deployed URLs:");
|
|
7631
|
+
for (const [name, url] of Object.entries(publicUrls)) logger$4.log(` ${name}: ${url}`);
|
|
7493
7632
|
}
|
|
7494
7633
|
return {
|
|
7495
7634
|
apps: results,
|
|
@@ -7508,7 +7647,7 @@ async function deployCommand(options) {
|
|
|
7508
7647
|
}
|
|
7509
7648
|
//#endregion
|
|
7510
7649
|
//#region src/deploy/init.ts
|
|
7511
|
-
const logger$
|
|
7650
|
+
const logger$3 = console;
|
|
7512
7651
|
/**
|
|
7513
7652
|
* Get the Dokploy API token from stored credentials or environment
|
|
7514
7653
|
*/
|
|
@@ -7554,28 +7693,28 @@ function unsafeToRewrite(content) {
|
|
|
7554
7693
|
async function updateConfig(config, cwd = process.cwd()) {
|
|
7555
7694
|
const configPath = (0, node_path.join)(cwd, "gkm.config.ts");
|
|
7556
7695
|
if (!(0, node_fs.existsSync)(configPath)) {
|
|
7557
|
-
logger$
|
|
7558
|
-
logger$
|
|
7559
|
-
logger$
|
|
7560
|
-
logger$
|
|
7561
|
-
logger$
|
|
7562
|
-
logger$
|
|
7563
|
-
logger$
|
|
7564
|
-
logger$
|
|
7696
|
+
logger$3.warn("\n gkm.config.ts not found. Add this configuration manually:\n");
|
|
7697
|
+
logger$3.log(` providers: {`);
|
|
7698
|
+
logger$3.log(` dokploy: {`);
|
|
7699
|
+
logger$3.log(` endpoint: '${config.endpoint}',`);
|
|
7700
|
+
logger$3.log(` projectId: '${config.projectId}',`);
|
|
7701
|
+
logger$3.log(` applicationId: '${config.applicationId}',`);
|
|
7702
|
+
logger$3.log(` },`);
|
|
7703
|
+
logger$3.log(` },`);
|
|
7565
7704
|
return;
|
|
7566
7705
|
}
|
|
7567
7706
|
const content = await (0, node_fs_promises.readFile)(configPath, "utf-8");
|
|
7568
7707
|
if (unsafeToRewrite(content)) {
|
|
7569
|
-
logger$
|
|
7570
|
-
logger$
|
|
7571
|
-
logger$
|
|
7572
|
-
logger$
|
|
7573
|
-
if (config.registryId) logger$
|
|
7708
|
+
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.");
|
|
7709
|
+
logger$3.log(`\n Discovered, if you want them written down:`);
|
|
7710
|
+
logger$3.log(` projectId: '${config.projectId}'`);
|
|
7711
|
+
logger$3.log(` applicationId: '${config.applicationId}'`);
|
|
7712
|
+
if (config.registryId) logger$3.log(` registryId: '${config.registryId}'`);
|
|
7574
7713
|
return;
|
|
7575
7714
|
}
|
|
7576
7715
|
if (content.includes("dokploy:") && content.includes("applicationId:")) {
|
|
7577
|
-
logger$
|
|
7578
|
-
logger$
|
|
7716
|
+
logger$3.log("\n Dokploy config already exists in gkm.config.ts");
|
|
7717
|
+
logger$3.log(" Updating with new values...");
|
|
7579
7718
|
}
|
|
7580
7719
|
const registryLine = config.registryId ? `\n\t\t\tregistryId: '${config.registryId}',` : "";
|
|
7581
7720
|
const dokployConfigStr = `dokploy: {
|
|
@@ -7593,7 +7732,7 @@ async function updateConfig(config, cwd = process.cwd()) {
|
|
|
7593
7732
|
},
|
|
7594
7733
|
});`);
|
|
7595
7734
|
await (0, node_fs_promises.writeFile)(configPath, newContent);
|
|
7596
|
-
logger$
|
|
7735
|
+
logger$3.log("\n ✓ Updated gkm.config.ts with Dokploy configuration");
|
|
7597
7736
|
}
|
|
7598
7737
|
/**
|
|
7599
7738
|
* Initialize Dokploy deployment configuration
|
|
@@ -7602,22 +7741,22 @@ async function deployInitCommand(options) {
|
|
|
7602
7741
|
const { projectName, appName, projectId: existingProjectId, registryId } = options;
|
|
7603
7742
|
const endpoint = await getEndpoint(options.endpoint);
|
|
7604
7743
|
const api = await createApi(endpoint);
|
|
7605
|
-
logger$
|
|
7606
|
-
logger$
|
|
7744
|
+
logger$3.log(`\n🚀 Initializing Dokploy deployment...`);
|
|
7745
|
+
logger$3.log(` Endpoint: ${endpoint}`);
|
|
7607
7746
|
let projectId;
|
|
7608
7747
|
if (existingProjectId) {
|
|
7609
7748
|
projectId = existingProjectId;
|
|
7610
|
-
logger$
|
|
7749
|
+
logger$3.log(`\n📁 Using existing project: ${projectId}`);
|
|
7611
7750
|
} else {
|
|
7612
|
-
logger$
|
|
7751
|
+
logger$3.log(`\n📁 Looking for project: ${projectName}`);
|
|
7613
7752
|
const existingProject = (await api.listProjects()).find((p) => p.name.toLowerCase() === projectName.toLowerCase());
|
|
7614
7753
|
if (existingProject) {
|
|
7615
7754
|
projectId = existingProject.projectId;
|
|
7616
|
-
logger$
|
|
7755
|
+
logger$3.log(` Found existing project: ${projectId}`);
|
|
7617
7756
|
} else {
|
|
7618
|
-
logger$
|
|
7757
|
+
logger$3.log(` Creating new project...`);
|
|
7619
7758
|
projectId = (await api.createProject(projectName)).project.projectId;
|
|
7620
|
-
logger$
|
|
7759
|
+
logger$3.log(` ✓ Created project: ${projectId}`);
|
|
7621
7760
|
}
|
|
7622
7761
|
}
|
|
7623
7762
|
const project = await api.getProject(projectId);
|
|
@@ -7625,22 +7764,22 @@ async function deployInitCommand(options) {
|
|
|
7625
7764
|
const firstEnv = project.environments?.[0];
|
|
7626
7765
|
if (firstEnv) environmentId = firstEnv.environmentId;
|
|
7627
7766
|
else {
|
|
7628
|
-
logger$
|
|
7767
|
+
logger$3.log(` Creating production environment...`);
|
|
7629
7768
|
environmentId = (await api.createEnvironment(projectId, "production")).environmentId;
|
|
7630
7769
|
}
|
|
7631
|
-
logger$
|
|
7770
|
+
logger$3.log(`\n📦 Creating application: ${appName}`);
|
|
7632
7771
|
const application = await api.createApplication(appName, projectId, environmentId);
|
|
7633
|
-
logger$
|
|
7772
|
+
logger$3.log(` ✓ Created application: ${application.applicationId}`);
|
|
7634
7773
|
if (registryId) {
|
|
7635
|
-
logger$
|
|
7774
|
+
logger$3.log(`\n🔧 Configuring registry: ${registryId}`);
|
|
7636
7775
|
await api.updateApplication(application.applicationId, { registryId });
|
|
7637
|
-
logger$
|
|
7776
|
+
logger$3.log(` ✓ Registry configured`);
|
|
7638
7777
|
} else try {
|
|
7639
7778
|
const registries = await api.listRegistries();
|
|
7640
7779
|
if (registries.length > 0) {
|
|
7641
|
-
logger$
|
|
7642
|
-
for (const reg of registries) logger$
|
|
7643
|
-
logger$
|
|
7780
|
+
logger$3.log(`\n📋 Available registries:`);
|
|
7781
|
+
for (const reg of registries) logger$3.log(` - ${reg.registryName}: ${reg.registryUrl} (${reg.registryId})`);
|
|
7782
|
+
logger$3.log(`\n To use a registry, run with --registry-id <id>`);
|
|
7644
7783
|
}
|
|
7645
7784
|
} catch {}
|
|
7646
7785
|
const config = {
|
|
@@ -7649,14 +7788,14 @@ async function deployInitCommand(options) {
|
|
|
7649
7788
|
applicationId: application.applicationId
|
|
7650
7789
|
};
|
|
7651
7790
|
await updateConfig(config);
|
|
7652
|
-
logger$
|
|
7653
|
-
logger$
|
|
7654
|
-
logger$
|
|
7655
|
-
logger$
|
|
7656
|
-
logger$
|
|
7657
|
-
logger$
|
|
7658
|
-
logger$
|
|
7659
|
-
logger$
|
|
7791
|
+
logger$3.log(`\n✅ Dokploy deployment initialized!`);
|
|
7792
|
+
logger$3.log(`\n📋 Configuration:`);
|
|
7793
|
+
logger$3.log(` Project ID: ${projectId}`);
|
|
7794
|
+
logger$3.log(` Application ID: ${application.applicationId}`);
|
|
7795
|
+
logger$3.log(`\n🔗 View in Dokploy: ${endpoint}/project/${projectId}`);
|
|
7796
|
+
logger$3.log(`\n📝 Next steps:`);
|
|
7797
|
+
logger$3.log(` 1. Initialize secrets: gkm secrets:init --stage production`);
|
|
7798
|
+
logger$3.log(` 2. Deploy: gkm deploy --provider dokploy --stage production`);
|
|
7660
7799
|
return config;
|
|
7661
7800
|
}
|
|
7662
7801
|
/**
|
|
@@ -7667,31 +7806,31 @@ async function deployListCommand(options) {
|
|
|
7667
7806
|
const api = await createApi(endpoint);
|
|
7668
7807
|
const { resource } = options;
|
|
7669
7808
|
if (resource === "projects") {
|
|
7670
|
-
logger$
|
|
7809
|
+
logger$3.log(`\n📁 Projects in ${endpoint}:`);
|
|
7671
7810
|
const projects = await api.listProjects();
|
|
7672
7811
|
if (projects.length === 0) {
|
|
7673
|
-
logger$
|
|
7812
|
+
logger$3.log(" No projects found");
|
|
7674
7813
|
return;
|
|
7675
7814
|
}
|
|
7676
7815
|
for (const project of projects) {
|
|
7677
|
-
logger$
|
|
7678
|
-
if (project.description) logger$
|
|
7816
|
+
logger$3.log(`\n ${project.name} (${project.projectId})`);
|
|
7817
|
+
if (project.description) logger$3.log(` ${project.description}`);
|
|
7679
7818
|
}
|
|
7680
7819
|
} else if (resource === "registries") {
|
|
7681
|
-
logger$
|
|
7820
|
+
logger$3.log(`\n🐳 Registries in ${endpoint}:`);
|
|
7682
7821
|
const registries = await api.listRegistries();
|
|
7683
7822
|
if (registries.length === 0) {
|
|
7684
|
-
logger$
|
|
7685
|
-
logger$
|
|
7823
|
+
logger$3.log(" No registries configured");
|
|
7824
|
+
logger$3.log(" Run \"gkm registry:setup\" to configure a registry");
|
|
7686
7825
|
return;
|
|
7687
7826
|
}
|
|
7688
7827
|
const storedRegistryId = await getDokployRegistryId();
|
|
7689
7828
|
for (const registry of registries) {
|
|
7690
7829
|
const marker = registry.registryId === storedRegistryId ? " (default)" : "";
|
|
7691
|
-
logger$
|
|
7692
|
-
logger$
|
|
7693
|
-
logger$
|
|
7694
|
-
if (registry.imagePrefix) logger$
|
|
7830
|
+
logger$3.log(`\n ${registry.registryName}${marker} (${registry.registryId})`);
|
|
7831
|
+
logger$3.log(` URL: ${registry.registryUrl}`);
|
|
7832
|
+
logger$3.log(` Username: ${registry.username}`);
|
|
7833
|
+
if (registry.imagePrefix) logger$3.log(` Prefix: ${registry.imagePrefix}`);
|
|
7695
7834
|
}
|
|
7696
7835
|
}
|
|
7697
7836
|
}
|
|
@@ -8552,26 +8691,26 @@ function loadPackageJson() {
|
|
|
8552
8691
|
* Run: pnpm --filter @geekmidas/cli sync-versions
|
|
8553
8692
|
*/
|
|
8554
8693
|
const GEEKMIDAS_VERSIONS = {
|
|
8555
|
-
"@geekmidas/audit": "~10.0.0-alpha.
|
|
8556
|
-
"@geekmidas/auth": "~10.0.0-alpha.
|
|
8557
|
-
"@geekmidas/cache": "~10.0.0-alpha.
|
|
8558
|
-
"@geekmidas/client": "~10.0.0-alpha.
|
|
8559
|
-
"@geekmidas/cloud": "~10.0.0-alpha.
|
|
8560
|
-
"@geekmidas/constructs": "~10.0.0-alpha.
|
|
8561
|
-
"@geekmidas/db": "~10.0.0-alpha.
|
|
8562
|
-
"@geekmidas/emailkit": "~10.0.0-alpha.
|
|
8563
|
-
"@geekmidas/envkit": "~10.0.0-alpha.
|
|
8564
|
-
"@geekmidas/errors": "~10.0.0-alpha.
|
|
8565
|
-
"@geekmidas/events": "~10.0.0-alpha.
|
|
8566
|
-
"@geekmidas/logger": "~10.0.0-alpha.
|
|
8567
|
-
"@geekmidas/manifest": "~10.0.0-alpha.
|
|
8568
|
-
"@geekmidas/rate-limit": "~10.0.0-alpha.
|
|
8569
|
-
"@geekmidas/schema": "~10.0.0-alpha.
|
|
8570
|
-
"@geekmidas/services": "~10.0.0-alpha.
|
|
8571
|
-
"@geekmidas/storage": "~10.0.0-alpha.
|
|
8572
|
-
"@geekmidas/studio": "~10.0.0-alpha.
|
|
8573
|
-
"@geekmidas/telescope": "~10.0.0-alpha.
|
|
8574
|
-
"@geekmidas/testkit": "~10.0.0-alpha.
|
|
8694
|
+
"@geekmidas/audit": "~10.0.0-alpha.28",
|
|
8695
|
+
"@geekmidas/auth": "~10.0.0-alpha.28",
|
|
8696
|
+
"@geekmidas/cache": "~10.0.0-alpha.28",
|
|
8697
|
+
"@geekmidas/client": "~10.0.0-alpha.28",
|
|
8698
|
+
"@geekmidas/cloud": "~10.0.0-alpha.28",
|
|
8699
|
+
"@geekmidas/constructs": "~10.0.0-alpha.28",
|
|
8700
|
+
"@geekmidas/db": "~10.0.0-alpha.28",
|
|
8701
|
+
"@geekmidas/emailkit": "~10.0.0-alpha.28",
|
|
8702
|
+
"@geekmidas/envkit": "~10.0.0-alpha.28",
|
|
8703
|
+
"@geekmidas/errors": "~10.0.0-alpha.28",
|
|
8704
|
+
"@geekmidas/events": "~10.0.0-alpha.28",
|
|
8705
|
+
"@geekmidas/logger": "~10.0.0-alpha.28",
|
|
8706
|
+
"@geekmidas/manifest": "~10.0.0-alpha.28",
|
|
8707
|
+
"@geekmidas/rate-limit": "~10.0.0-alpha.28",
|
|
8708
|
+
"@geekmidas/schema": "~10.0.0-alpha.28",
|
|
8709
|
+
"@geekmidas/services": "~10.0.0-alpha.28",
|
|
8710
|
+
"@geekmidas/storage": "~10.0.0-alpha.28",
|
|
8711
|
+
"@geekmidas/studio": "~10.0.0-alpha.28",
|
|
8712
|
+
"@geekmidas/telescope": "~10.0.0-alpha.28",
|
|
8713
|
+
"@geekmidas/testkit": "~10.0.0-alpha.28",
|
|
8575
8714
|
"@geekmidas/cli": `~${loadPackageJson().version}`
|
|
8576
8715
|
};
|
|
8577
8716
|
//#endregion
|
|
@@ -14650,7 +14789,7 @@ var NotAnAwsRegion = class extends Error {
|
|
|
14650
14789
|
};
|
|
14651
14790
|
//#endregion
|
|
14652
14791
|
//#region src/secrets/index.ts
|
|
14653
|
-
const logger$
|
|
14792
|
+
const logger$2 = console;
|
|
14654
14793
|
/**
|
|
14655
14794
|
* Initialize secrets for a stage.
|
|
14656
14795
|
* Generates secure random passwords for configured services.
|
|
@@ -14658,7 +14797,7 @@ const logger$3 = console;
|
|
|
14658
14797
|
async function secretsInitCommand(options) {
|
|
14659
14798
|
const { stage, force } = options;
|
|
14660
14799
|
if (!force && require_storage.secretsExist(stage)) {
|
|
14661
|
-
logger$
|
|
14800
|
+
logger$2.error(`Secrets already exist for stage "${stage}". Use --force to overwrite.`);
|
|
14662
14801
|
process.exit(1);
|
|
14663
14802
|
}
|
|
14664
14803
|
let projectName;
|
|
@@ -14668,21 +14807,21 @@ async function secretsInitCommand(options) {
|
|
|
14668
14807
|
projectName = loaded.workspace.name;
|
|
14669
14808
|
if (Object.keys(loaded.workspace.apps).length > 1) {
|
|
14670
14809
|
workspaceSecrets = require_reconcile.generateFullstackCustomSecrets(loaded.workspace);
|
|
14671
|
-
logger$
|
|
14810
|
+
logger$2.log(" Detected workspace mode — generating per-app secrets");
|
|
14672
14811
|
}
|
|
14673
14812
|
} catch {}
|
|
14674
14813
|
const secrets = require_reconcile.createStageSecrets(stage, [], { projectName });
|
|
14675
14814
|
if (workspaceSecrets) secrets.custom = workspaceSecrets;
|
|
14676
14815
|
await require_storage.writeStageSecrets(secrets);
|
|
14677
|
-
logger$
|
|
14678
|
-
logger$
|
|
14679
|
-
if (secrets.urls.DATABASE_URL) logger$
|
|
14680
|
-
if (secrets.urls.REDIS_URL) logger$
|
|
14681
|
-
if (secrets.urls.RABBITMQ_URL) logger$
|
|
14682
|
-
if (secrets.urls.STORAGE_ENDPOINT) logger$
|
|
14683
|
-
if (Object.keys(secrets.custom).length > 0) logger$
|
|
14684
|
-
logger$
|
|
14685
|
-
logger$
|
|
14816
|
+
logger$2.log(`\n✓ Secrets initialized for stage "${stage}"`);
|
|
14817
|
+
logger$2.log(` Location: .gkm/secrets/${stage}.json`);
|
|
14818
|
+
if (secrets.urls.DATABASE_URL) logger$2.log(`\n DATABASE_URL: ${maskUrl(secrets.urls.DATABASE_URL)}`);
|
|
14819
|
+
if (secrets.urls.REDIS_URL) logger$2.log(` REDIS_URL: ${maskUrl(secrets.urls.REDIS_URL)}`);
|
|
14820
|
+
if (secrets.urls.RABBITMQ_URL) logger$2.log(` RABBITMQ_URL: ${maskUrl(secrets.urls.RABBITMQ_URL)}`);
|
|
14821
|
+
if (secrets.urls.STORAGE_ENDPOINT) logger$2.log(` STORAGE_ENDPOINT: ${secrets.urls.STORAGE_ENDPOINT}`);
|
|
14822
|
+
if (Object.keys(secrets.custom).length > 0) logger$2.log(`\n Custom secrets: ${Object.keys(secrets.custom).length}`);
|
|
14823
|
+
logger$2.log(`\n Use "gkm secrets:show --stage ${stage}" to view secrets`);
|
|
14824
|
+
logger$2.log(" Use \"gkm secrets:set <KEY> <VALUE> --stage " + stage + "\" to add custom secrets");
|
|
14686
14825
|
}
|
|
14687
14826
|
/**
|
|
14688
14827
|
* Read all data from stdin.
|
|
@@ -14701,21 +14840,21 @@ async function secretsSetCommand(key, value, options) {
|
|
|
14701
14840
|
let secretValue = value;
|
|
14702
14841
|
if (!secretValue) {
|
|
14703
14842
|
if (process.stdin.isTTY) {
|
|
14704
|
-
logger$
|
|
14705
|
-
logger$
|
|
14843
|
+
logger$2.error("No value provided. Use: gkm secrets:set KEY VALUE --stage <stage>");
|
|
14844
|
+
logger$2.error("Or pipe from stdin: echo \"value\" | gkm secrets:set KEY --stage <stage>");
|
|
14706
14845
|
process.exit(1);
|
|
14707
14846
|
}
|
|
14708
14847
|
secretValue = await readStdin();
|
|
14709
14848
|
if (!secretValue) {
|
|
14710
|
-
logger$
|
|
14849
|
+
logger$2.error("No value received from stdin");
|
|
14711
14850
|
process.exit(1);
|
|
14712
14851
|
}
|
|
14713
14852
|
}
|
|
14714
14853
|
try {
|
|
14715
14854
|
await require_storage.setCustomSecret(stage, key, secretValue);
|
|
14716
|
-
logger$
|
|
14855
|
+
logger$2.log(`\n✓ Secret "${key}" set for stage "${stage}"`);
|
|
14717
14856
|
} catch (error) {
|
|
14718
|
-
logger$
|
|
14857
|
+
logger$2.error(error instanceof Error ? error.message : "Failed to set secret");
|
|
14719
14858
|
process.exit(1);
|
|
14720
14859
|
}
|
|
14721
14860
|
}
|
|
@@ -14726,33 +14865,33 @@ async function secretsShowCommand(options) {
|
|
|
14726
14865
|
const { stage, reveal } = options;
|
|
14727
14866
|
const secrets = await require_storage.readStageSecrets(stage);
|
|
14728
14867
|
if (!secrets) {
|
|
14729
|
-
logger$
|
|
14868
|
+
logger$2.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
14730
14869
|
process.exit(1);
|
|
14731
14870
|
}
|
|
14732
|
-
logger$
|
|
14733
|
-
logger$
|
|
14734
|
-
logger$
|
|
14735
|
-
logger$
|
|
14871
|
+
logger$2.log(`\nSecrets for stage "${stage}":`);
|
|
14872
|
+
logger$2.log(` Created: ${secrets.createdAt}`);
|
|
14873
|
+
logger$2.log(` Updated: ${secrets.updatedAt}`);
|
|
14874
|
+
logger$2.log("\nService Credentials:");
|
|
14736
14875
|
for (const [service, creds] of Object.entries(secrets.services)) if (creds) {
|
|
14737
|
-
logger$
|
|
14738
|
-
logger$
|
|
14739
|
-
logger$
|
|
14740
|
-
logger$
|
|
14741
|
-
logger$
|
|
14742
|
-
if (creds.database) logger$
|
|
14743
|
-
if (creds.vhost) logger$
|
|
14744
|
-
if (creds.bucket) logger$
|
|
14745
|
-
}
|
|
14746
|
-
logger$
|
|
14747
|
-
if (secrets.urls.DATABASE_URL) logger$
|
|
14748
|
-
if (secrets.urls.REDIS_URL) logger$
|
|
14749
|
-
if (secrets.urls.RABBITMQ_URL) logger$
|
|
14750
|
-
if (secrets.urls.STORAGE_ENDPOINT) logger$
|
|
14876
|
+
logger$2.log(`\n ${service}:`);
|
|
14877
|
+
logger$2.log(` host: ${creds.host}`);
|
|
14878
|
+
logger$2.log(` port: ${creds.port}`);
|
|
14879
|
+
logger$2.log(` username: ${creds.username}`);
|
|
14880
|
+
logger$2.log(` password: ${reveal ? creds.password : require_storage.maskPassword(creds.password)}`);
|
|
14881
|
+
if (creds.database) logger$2.log(` database: ${creds.database}`);
|
|
14882
|
+
if (creds.vhost) logger$2.log(` vhost: ${creds.vhost}`);
|
|
14883
|
+
if (creds.bucket) logger$2.log(` bucket: ${creds.bucket}`);
|
|
14884
|
+
}
|
|
14885
|
+
logger$2.log("\nConnection URLs:");
|
|
14886
|
+
if (secrets.urls.DATABASE_URL) logger$2.log(` DATABASE_URL: ${reveal ? secrets.urls.DATABASE_URL : maskUrl(secrets.urls.DATABASE_URL)}`);
|
|
14887
|
+
if (secrets.urls.REDIS_URL) logger$2.log(` REDIS_URL: ${reveal ? secrets.urls.REDIS_URL : maskUrl(secrets.urls.REDIS_URL)}`);
|
|
14888
|
+
if (secrets.urls.RABBITMQ_URL) logger$2.log(` RABBITMQ_URL: ${reveal ? secrets.urls.RABBITMQ_URL : maskUrl(secrets.urls.RABBITMQ_URL)}`);
|
|
14889
|
+
if (secrets.urls.STORAGE_ENDPOINT) logger$2.log(` STORAGE_ENDPOINT: ${secrets.urls.STORAGE_ENDPOINT}`);
|
|
14751
14890
|
if (Object.keys(secrets.custom).length > 0) {
|
|
14752
|
-
logger$
|
|
14753
|
-
for (const [key, value] of Object.entries(secrets.custom)) logger$
|
|
14891
|
+
logger$2.log("\nCustom Secrets:");
|
|
14892
|
+
for (const [key, value] of Object.entries(secrets.custom)) logger$2.log(` ${key}: ${reveal ? value : require_storage.maskPassword(value)}`);
|
|
14754
14893
|
}
|
|
14755
|
-
if (!reveal) logger$
|
|
14894
|
+
if (!reveal) logger$2.log("\nUse --reveal to show actual values");
|
|
14756
14895
|
}
|
|
14757
14896
|
/**
|
|
14758
14897
|
* Rotate passwords for services.
|
|
@@ -14761,25 +14900,25 @@ async function secretsRotateCommand(options) {
|
|
|
14761
14900
|
const { stage, service } = options;
|
|
14762
14901
|
const secrets = await require_storage.readStageSecrets(stage);
|
|
14763
14902
|
if (!secrets) {
|
|
14764
|
-
logger$
|
|
14903
|
+
logger$2.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
14765
14904
|
process.exit(1);
|
|
14766
14905
|
}
|
|
14767
14906
|
if (service) {
|
|
14768
14907
|
if (!secrets.services[service]) {
|
|
14769
|
-
logger$
|
|
14908
|
+
logger$2.error(`Service "${service}" not configured in stage "${stage}"`);
|
|
14770
14909
|
process.exit(1);
|
|
14771
14910
|
}
|
|
14772
14911
|
const updated = require_reconcile.rotateServicePassword(secrets, service);
|
|
14773
14912
|
await require_storage.writeStageSecrets(updated);
|
|
14774
|
-
logger$
|
|
14913
|
+
logger$2.log(`\n✓ Password rotated for ${service} in stage "${stage}"`);
|
|
14775
14914
|
} else {
|
|
14776
14915
|
let updated = secrets;
|
|
14777
14916
|
const services = Object.keys(secrets.services);
|
|
14778
14917
|
for (const svc of services) updated = require_reconcile.rotateServicePassword(updated, svc);
|
|
14779
14918
|
await require_storage.writeStageSecrets(updated);
|
|
14780
|
-
logger$
|
|
14919
|
+
logger$2.log(`\n✓ Passwords rotated for all services in stage "${stage}": ${services.join(", ")}`);
|
|
14781
14920
|
}
|
|
14782
|
-
logger$
|
|
14921
|
+
logger$2.log(`\nUse "gkm secrets:show --stage ${stage}" to view new values`);
|
|
14783
14922
|
}
|
|
14784
14923
|
/**
|
|
14785
14924
|
* Import secrets from a JSON file.
|
|
@@ -14787,7 +14926,7 @@ async function secretsRotateCommand(options) {
|
|
|
14787
14926
|
async function secretsImportCommand(file, options) {
|
|
14788
14927
|
const { stage, merge = true } = options;
|
|
14789
14928
|
if (!(0, node_fs.existsSync)(file)) {
|
|
14790
|
-
logger$
|
|
14929
|
+
logger$2.error(`File not found: ${file}`);
|
|
14791
14930
|
process.exit(1);
|
|
14792
14931
|
}
|
|
14793
14932
|
let importedSecrets;
|
|
@@ -14797,12 +14936,12 @@ async function secretsImportCommand(file, options) {
|
|
|
14797
14936
|
if (typeof importedSecrets !== "object" || importedSecrets === null) throw new Error("JSON must be an object");
|
|
14798
14937
|
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}`);
|
|
14799
14938
|
} catch (error) {
|
|
14800
|
-
logger$
|
|
14939
|
+
logger$2.error(`Failed to parse JSON file: ${error instanceof Error ? error.message : "Invalid JSON"}`);
|
|
14801
14940
|
process.exit(1);
|
|
14802
14941
|
}
|
|
14803
14942
|
const secrets = await require_storage.readStageSecrets(stage);
|
|
14804
14943
|
if (!secrets) {
|
|
14805
|
-
logger$
|
|
14944
|
+
logger$2.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
14806
14945
|
process.exit(1);
|
|
14807
14946
|
}
|
|
14808
14947
|
const updatedCustom = merge ? {
|
|
@@ -14817,10 +14956,10 @@ async function secretsImportCommand(file, options) {
|
|
|
14817
14956
|
await require_storage.writeStageSecrets(updated);
|
|
14818
14957
|
const importedCount = Object.keys(importedSecrets).length;
|
|
14819
14958
|
const totalCount = Object.keys(updatedCustom).length;
|
|
14820
|
-
logger$
|
|
14821
|
-
if (merge && totalCount > importedCount) logger$
|
|
14822
|
-
logger$
|
|
14823
|
-
for (const key of Object.keys(importedSecrets)) logger$
|
|
14959
|
+
logger$2.log(`\n✓ Imported ${importedCount} secrets for stage "${stage}"`);
|
|
14960
|
+
if (merge && totalCount > importedCount) logger$2.log(` Total custom secrets: ${totalCount}`);
|
|
14961
|
+
logger$2.log("\n Imported keys:");
|
|
14962
|
+
for (const key of Object.keys(importedSecrets)) logger$2.log(` - ${key}`);
|
|
14824
14963
|
}
|
|
14825
14964
|
/**
|
|
14826
14965
|
* Mask password in a URL for display.
|
|
@@ -14835,175 +14974,120 @@ function maskUrl(url) {
|
|
|
14835
14974
|
}
|
|
14836
14975
|
}
|
|
14837
14976
|
//#endregion
|
|
14838
|
-
//#region src/
|
|
14839
|
-
/**
|
|
14840
|
-
|
|
14841
|
-
|
|
14842
|
-
|
|
14843
|
-
|
|
14844
|
-
* reads none of that: it asks the operating system, so until the root is in the
|
|
14845
|
-
* system store a local `https://` address is "not secure", which is true and
|
|
14846
|
-
* unhelpful.
|
|
14847
|
-
*
|
|
14848
|
-
* A separate command rather than part of `gkm setup`, for one reason: this
|
|
14849
|
-
* needs `sudo`, and a setup that silently escalates on every run is a worse
|
|
14850
|
-
* trade than one that tells you what to type. Caddy's own `caddy trust` draws
|
|
14851
|
-
* the line in the same place.
|
|
14852
|
-
*/
|
|
14853
|
-
const logger$2 = console;
|
|
14854
|
-
const run = (0, node_util.promisify)(node_child_process.execFile);
|
|
14855
|
-
/**
|
|
14856
|
-
* How each platform is told about a new authority.
|
|
14857
|
-
*
|
|
14858
|
-
* Separate rather than shelled through one script because the failure modes
|
|
14859
|
-
* differ and the message should name the real one: macOS refuses without an
|
|
14860
|
-
* admin password, Debian needs the file in a specific directory before its
|
|
14861
|
-
* updater will look at it.
|
|
14862
|
-
*/
|
|
14863
|
-
function installation(platform, certificate) {
|
|
14864
|
-
if (platform === "darwin") return {
|
|
14865
|
-
describe: "the System keychain",
|
|
14866
|
-
steps: [["sudo", [
|
|
14867
|
-
"security",
|
|
14868
|
-
"add-trusted-cert",
|
|
14869
|
-
"-d",
|
|
14870
|
-
"-r",
|
|
14871
|
-
"trustRoot",
|
|
14872
|
-
"-k",
|
|
14873
|
-
"/Library/Keychains/System.keychain",
|
|
14874
|
-
certificate
|
|
14875
|
-
]]]
|
|
14876
|
-
};
|
|
14877
|
-
if (platform === "linux") {
|
|
14878
|
-
const debian = (0, node_fs.existsSync)("/usr/local/share/ca-certificates");
|
|
14879
|
-
return {
|
|
14880
|
-
describe: debian ? "the system CA bundle" : "the system trust anchors",
|
|
14881
|
-
steps: debian ? [["sudo", [
|
|
14882
|
-
"cp",
|
|
14883
|
-
certificate,
|
|
14884
|
-
"/usr/local/share/ca-certificates/gkm-local-ca.crt"
|
|
14885
|
-
]], ["sudo", ["update-ca-certificates"]]] : [["sudo", [
|
|
14886
|
-
"cp",
|
|
14887
|
-
certificate,
|
|
14888
|
-
"/etc/pki/ca-trust/source/anchors/gkm-local-ca.crt"
|
|
14889
|
-
]], ["sudo", ["update-ca-trust"]]]
|
|
14890
|
-
};
|
|
14977
|
+
//#region src/secrets/store.ts
|
|
14978
|
+
/** The encrypted file under `.gkm/secrets/`, and its key under `~/.gkm/`. */
|
|
14979
|
+
var FileSecretsStore = class {
|
|
14980
|
+
root;
|
|
14981
|
+
constructor(root) {
|
|
14982
|
+
this.root = root;
|
|
14891
14983
|
}
|
|
14892
|
-
|
|
14893
|
-
|
|
14894
|
-
/**
|
|
14895
|
-
* Trust the certificate authority this project's edge issues from.
|
|
14896
|
-
*
|
|
14897
|
-
* Idempotent in effect: installing a root that is already trusted replaces the
|
|
14898
|
-
* same entry, so running it twice is not an error.
|
|
14899
|
-
*/
|
|
14900
|
-
async function trustCommand(options = {}) {
|
|
14901
|
-
const { workspace } = await require_config.loadWorkspaceConfig();
|
|
14902
|
-
const certificate = (0, node_path.join)(workspace.root, require_workspace$1.LOCAL_CA_PATH);
|
|
14903
|
-
if (!(0, node_fs.existsSync)(certificate)) throw new NoLocalAuthority(certificate);
|
|
14904
|
-
const { describe, steps } = installation(process.platform, certificate);
|
|
14905
|
-
logger$2.log(`\n🔐 Trusting the local authority in ${describe}`);
|
|
14906
|
-
logger$2.log(` ${certificate}\n`);
|
|
14907
|
-
if (options.dryRun) {
|
|
14908
|
-
for (const [command, args] of steps) logger$2.log(` ${command} ${args.join(" ")}`);
|
|
14909
|
-
return;
|
|
14984
|
+
pull(stage) {
|
|
14985
|
+
return require_storage.readStageSecrets(stage, this.root);
|
|
14910
14986
|
}
|
|
14911
|
-
|
|
14912
|
-
|
|
14913
|
-
|
|
14914
|
-
}
|
|
14987
|
+
push(_stage, secrets) {
|
|
14988
|
+
return require_storage.writeStageSecrets(secrets, this.root);
|
|
14989
|
+
}
|
|
14990
|
+
};
|
|
14915
14991
|
/**
|
|
14916
|
-
*
|
|
14992
|
+
* The store a stage's secrets live in.
|
|
14917
14993
|
*
|
|
14918
|
-
*
|
|
14919
|
-
*
|
|
14920
|
-
|
|
14921
|
-
|
|
14922
|
-
|
|
14923
|
-
|
|
14924
|
-
|
|
14925
|
-
|
|
14926
|
-
|
|
14927
|
-
|
|
14928
|
-
|
|
14929
|
-
|
|
14930
|
-
|
|
14931
|
-
return;
|
|
14932
|
-
}
|
|
14933
|
-
if (await declined(root)) return;
|
|
14934
|
-
const { install } = await (0, prompts.default)({
|
|
14935
|
-
type: "confirm",
|
|
14936
|
-
name: "install",
|
|
14937
|
-
message: "Trust this project's local certificate authority, so a browser accepts its https addresses? (needs sudo)",
|
|
14938
|
-
initial: false
|
|
14939
|
-
});
|
|
14940
|
-
if (!install) {
|
|
14941
|
-
await recordDeclined(root);
|
|
14942
|
-
logger$2.log(" Skipped. Run \"gkm trust\" later.");
|
|
14943
|
-
return;
|
|
14944
|
-
}
|
|
14945
|
-
}
|
|
14946
|
-
await trustCommand();
|
|
14994
|
+
* The local stage is always the file: its secrets belong to the machine
|
|
14995
|
+
* running `gkm dev`. A deployed stage uses `secrets.store`.
|
|
14996
|
+
*/
|
|
14997
|
+
async function secretsStoreFor(workspace, stage, options = {}) {
|
|
14998
|
+
const configured = workspace.secrets.store ?? "file";
|
|
14999
|
+
if (stage === workspace.stages.local || configured === "file") return new FileSecretsStore(workspace.root);
|
|
15000
|
+
if (configured.provider !== "ssm") return configured.provider;
|
|
15001
|
+
const { SsmSecretsStore } = await Promise.resolve().then(() => require("./ssm-FxeSAt35.cjs"));
|
|
15002
|
+
return new SsmSecretsStore({
|
|
15003
|
+
project: workspace.name,
|
|
15004
|
+
region: configured.region,
|
|
15005
|
+
...options.profile ? { profile: options.profile } : {}
|
|
15006
|
+
});
|
|
14947
15007
|
}
|
|
14948
|
-
/**
|
|
14949
|
-
|
|
14950
|
-
|
|
14951
|
-
|
|
14952
|
-
const raw = await (0, node_fs_promises.readFile)((0, node_path.join)(root, TRUST_STATE), "utf-8");
|
|
14953
|
-
return JSON.parse(raw).declined === true;
|
|
14954
|
-
} catch {
|
|
14955
|
-
return false;
|
|
14956
|
-
}
|
|
14957
|
-
}
|
|
14958
|
-
async function recordDeclined(root) {
|
|
14959
|
-
const path = (0, node_path.join)(root, TRUST_STATE);
|
|
14960
|
-
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(path), { recursive: true });
|
|
14961
|
-
await (0, node_fs_promises.writeFile)(path, `${JSON.stringify({ declined: true }, null, 2)}\n`);
|
|
15008
|
+
/** Whether a stage's secrets live somewhere other than this machine. */
|
|
15009
|
+
function isRemoteStore(workspace, stage) {
|
|
15010
|
+
const configured = workspace.secrets.store ?? "file";
|
|
15011
|
+
return stage !== workspace.stages.local && configured !== "file";
|
|
14962
15012
|
}
|
|
15013
|
+
//#endregion
|
|
15014
|
+
//#region src/secrets/transfer.ts
|
|
14963
15015
|
/**
|
|
14964
|
-
*
|
|
14965
|
-
*
|
|
14966
|
-
* Asked by connecting rather than by reading a keychain: what matters is
|
|
14967
|
-
* whether a client with no special configuration verifies the edge, and that is
|
|
14968
|
-
* one platform-neutral question instead of three platform-specific ones. It is
|
|
14969
|
-
* also the exact thing a browser is about to do.
|
|
15016
|
+
* Moving a deployed stage's secrets between this machine and its store.
|
|
14970
15017
|
*
|
|
14971
|
-
*
|
|
14972
|
-
*
|
|
14973
|
-
*
|
|
14974
|
-
|
|
14975
|
-
|
|
14976
|
-
|
|
14977
|
-
|
|
14978
|
-
|
|
14979
|
-
|
|
14980
|
-
|
|
14981
|
-
|
|
14982
|
-
|
|
14983
|
-
|
|
14984
|
-
|
|
15018
|
+
* `push` sends the encrypted local copy to the stage's store; `pull` brings it
|
|
15019
|
+
* back and writes the local copy a deploy reads — generating a key for it if
|
|
15020
|
+
* the machine has none, which is what a fresh CI runner needs. Both reconcile
|
|
15021
|
+
* the keys the workspace now derives, so a store never lags a new construct.
|
|
15022
|
+
*/
|
|
15023
|
+
var transfer_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
|
|
15024
|
+
NoLocalSecrets: () => NoLocalSecrets,
|
|
15025
|
+
NoRemoteSecretsStore: () => NoRemoteSecretsStore,
|
|
15026
|
+
NoStoredSecrets: () => NoStoredSecrets,
|
|
15027
|
+
pullStageSecrets: () => pullStageSecrets,
|
|
15028
|
+
pushStageSecrets: () => pushStageSecrets
|
|
15029
|
+
});
|
|
15030
|
+
/** A push or pull for a stage with nowhere to push or pull from. */
|
|
15031
|
+
var NoRemoteSecretsStore = class extends Error {
|
|
15032
|
+
stage;
|
|
15033
|
+
constructor(stage) {
|
|
15034
|
+
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.`);
|
|
15035
|
+
this.stage = stage;
|
|
15036
|
+
this.name = "NoRemoteSecretsStore";
|
|
14985
15037
|
}
|
|
14986
|
-
}
|
|
14987
|
-
/**
|
|
14988
|
-
var
|
|
14989
|
-
|
|
14990
|
-
constructor(
|
|
14991
|
-
super(`No
|
|
14992
|
-
this.
|
|
14993
|
-
this.name = "
|
|
15038
|
+
};
|
|
15039
|
+
/** A push for a stage whose secrets this machine does not have. */
|
|
15040
|
+
var NoLocalSecrets = class extends Error {
|
|
15041
|
+
stage;
|
|
15042
|
+
constructor(stage) {
|
|
15043
|
+
super(`No secrets for stage "${stage}" on this machine. Run gkm secrets:init --stage ${stage} first.`);
|
|
15044
|
+
this.stage = stage;
|
|
15045
|
+
this.name = "NoLocalSecrets";
|
|
14994
15046
|
}
|
|
14995
15047
|
};
|
|
14996
|
-
/** A
|
|
14997
|
-
var
|
|
14998
|
-
|
|
14999
|
-
|
|
15000
|
-
|
|
15001
|
-
|
|
15002
|
-
this.
|
|
15003
|
-
this.certificate = certificate;
|
|
15004
|
-
this.name = "UnsupportedPlatform";
|
|
15048
|
+
/** A pull for a stage the store holds nothing for. */
|
|
15049
|
+
var NoStoredSecrets = class extends Error {
|
|
15050
|
+
stage;
|
|
15051
|
+
constructor(stage) {
|
|
15052
|
+
super(`The store holds no secrets for stage "${stage}". Push them first: gkm secrets:push --stage ${stage}.`);
|
|
15053
|
+
this.stage = stage;
|
|
15054
|
+
this.name = "NoStoredSecrets";
|
|
15005
15055
|
}
|
|
15006
15056
|
};
|
|
15057
|
+
async function reconcile(secrets, workspace, stage) {
|
|
15058
|
+
const { derivedContainers } = await Promise.resolve().then(() => require("./workspace-Cv48QwPK.cjs")).then((n) => n.workspace_exports);
|
|
15059
|
+
const result = require_reconcile.reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
|
|
15060
|
+
return result ? {
|
|
15061
|
+
secrets: result.secrets,
|
|
15062
|
+
addedKeys: result.addedKeys
|
|
15063
|
+
} : {
|
|
15064
|
+
secrets,
|
|
15065
|
+
addedKeys: []
|
|
15066
|
+
};
|
|
15067
|
+
}
|
|
15068
|
+
function remoteOnly(workspace, stage) {
|
|
15069
|
+
require_workspace.assertDeployedStage(workspace.stages, stage);
|
|
15070
|
+
if (!isRemoteStore(workspace, stage)) throw new NoRemoteSecretsStore(stage);
|
|
15071
|
+
}
|
|
15072
|
+
/** Send this machine's secrets for a deployed stage to its store. */
|
|
15073
|
+
async function pushStageSecrets(workspace, stage, options = {}) {
|
|
15074
|
+
remoteOnly(workspace, stage);
|
|
15075
|
+
const local = await require_storage.readStageSecrets(stage, workspace.root);
|
|
15076
|
+
if (!local) throw new NoLocalSecrets(stage);
|
|
15077
|
+
const result = await reconcile(local, workspace, stage);
|
|
15078
|
+
if (result.addedKeys.length) await require_storage.writeStageSecrets(result.secrets, workspace.root);
|
|
15079
|
+
await (await secretsStoreFor(workspace, stage, options)).push(stage, result.secrets);
|
|
15080
|
+
return result;
|
|
15081
|
+
}
|
|
15082
|
+
/** Bring a deployed stage's secrets from its store into the local copy. */
|
|
15083
|
+
async function pullStageSecrets(workspace, stage, options = {}) {
|
|
15084
|
+
remoteOnly(workspace, stage);
|
|
15085
|
+
const stored = await (await secretsStoreFor(workspace, stage, options)).pull(stage);
|
|
15086
|
+
if (!stored) throw new NoStoredSecrets(stage);
|
|
15087
|
+
const result = await reconcile(stored, workspace, stage);
|
|
15088
|
+
await require_storage.writeStageSecrets(result.secrets, workspace.root);
|
|
15089
|
+
return result;
|
|
15090
|
+
}
|
|
15007
15091
|
//#endregion
|
|
15008
15092
|
//#region src/setup/index.ts
|
|
15009
15093
|
var setup_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
|
|
@@ -15072,8 +15156,7 @@ async function reconcileLocal(workspace, stage, options) {
|
|
|
15072
15156
|
}
|
|
15073
15157
|
logger$1.log(`🐳 Services: ${result.plan.containers.join(", ")}`);
|
|
15074
15158
|
for (const [container, address] of Object.entries(result.addresses)) logger$1.log(` ${container}: ${address}`);
|
|
15075
|
-
const
|
|
15076
|
-
const url = served ? result.env[served.envKey] : void 0;
|
|
15159
|
+
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://"));
|
|
15077
15160
|
if (url) await ensureTrusted(workspace.root, url, { ...options.yes ? { assumeYes: true } : {} });
|
|
15078
15161
|
}
|
|
15079
15162
|
/**
|
|
@@ -15098,14 +15181,14 @@ async function resolveSecrets(stage, workspace, options) {
|
|
|
15098
15181
|
return reconciled ?? secrets;
|
|
15099
15182
|
}
|
|
15100
15183
|
}
|
|
15101
|
-
if (
|
|
15184
|
+
if (isRemoteStore(workspace, stage)) {
|
|
15102
15185
|
logger$1.log("☁️ Checking the secrets store...");
|
|
15103
15186
|
try {
|
|
15104
|
-
const { secrets } = await
|
|
15187
|
+
const { secrets } = await pullStageSecrets(workspace, stage);
|
|
15105
15188
|
logger$1.log("✅ Pulled secrets from the store");
|
|
15106
15189
|
return secrets;
|
|
15107
15190
|
} catch (error) {
|
|
15108
|
-
if (error instanceof
|
|
15191
|
+
if (error instanceof NoStoredSecrets) logger$1.log(" The store holds none yet");
|
|
15109
15192
|
else logger$1.warn(`⚠️ Could not pull from the store: ${error.message}`);
|
|
15110
15193
|
}
|
|
15111
15194
|
}
|
|
@@ -15239,7 +15322,7 @@ async function generateFreshSecrets(stage, workspace, options) {
|
|
|
15239
15322
|
const secrets = createFreshWorkspaceSecrets(stage, workspace, await require_workspace$1.derivedContainers(workspace, stage));
|
|
15240
15323
|
await require_storage.writeStageSecrets(secrets, workspace.root);
|
|
15241
15324
|
logger$1.log(` Secrets written to .gkm/secrets/${stage}.json`);
|
|
15242
|
-
if (
|
|
15325
|
+
if (isRemoteStore(workspace, stage) && !options.yes) {
|
|
15243
15326
|
const { shouldPush } = await (0, prompts.default)({
|
|
15244
15327
|
type: "confirm",
|
|
15245
15328
|
name: "shouldPush",
|
|
@@ -15247,7 +15330,7 @@ async function generateFreshSecrets(stage, workspace, options) {
|
|
|
15247
15330
|
initial: true
|
|
15248
15331
|
});
|
|
15249
15332
|
if (shouldPush) try {
|
|
15250
|
-
await
|
|
15333
|
+
await pushStageSecrets(workspace, stage);
|
|
15251
15334
|
logger$1.log("☁️ Secrets pushed to the store");
|
|
15252
15335
|
} catch (error) {
|
|
15253
15336
|
logger$1.warn(`⚠️ Could not push to the store: ${error.message}`);
|
|
@@ -16120,7 +16203,7 @@ program.command("secrets:push").description("Send a deployed stage's secrets to
|
|
|
16120
16203
|
const globalOptions = program.opts();
|
|
16121
16204
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16122
16205
|
const { loadWorkspaceConfig } = await Promise.resolve().then(() => require("./config.cjs"));
|
|
16123
|
-
const { pushStageSecrets } = await Promise.resolve().then(() =>
|
|
16206
|
+
const { pushStageSecrets } = await Promise.resolve().then(() => transfer_exports);
|
|
16124
16207
|
const { workspace } = await loadWorkspaceConfig();
|
|
16125
16208
|
const { addedKeys } = await pushStageSecrets(workspace, options.stage, { profile: options.profile });
|
|
16126
16209
|
for (const key of addedKeys) console.log(` + ${key} (reconciled)`);
|
|
@@ -16135,7 +16218,7 @@ program.command("secrets:pull").description("Bring a deployed stage's secrets fr
|
|
|
16135
16218
|
const globalOptions = program.opts();
|
|
16136
16219
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16137
16220
|
const { loadWorkspaceConfig } = await Promise.resolve().then(() => require("./config.cjs"));
|
|
16138
|
-
const { pullStageSecrets } = await Promise.resolve().then(() =>
|
|
16221
|
+
const { pullStageSecrets } = await Promise.resolve().then(() => transfer_exports);
|
|
16139
16222
|
const { workspace } = await loadWorkspaceConfig();
|
|
16140
16223
|
const { addedKeys } = await pullStageSecrets(workspace, options.stage, { profile: options.profile });
|
|
16141
16224
|
for (const key of addedKeys) console.log(` + ${key} (reconciled)`);
|
|
@@ -16159,7 +16242,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
|
|
|
16159
16242
|
console.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
16160
16243
|
process.exit(1);
|
|
16161
16244
|
}
|
|
16162
|
-
const { derivedContainers } = await Promise.resolve().then(() => require("./workspace-
|
|
16245
|
+
const { derivedContainers } = await Promise.resolve().then(() => require("./workspace-Cv48QwPK.cjs")).then((n) => n.workspace_exports);
|
|
16163
16246
|
const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
|
|
16164
16247
|
if (!result) {
|
|
16165
16248
|
console.log(`\n✓ Secrets for stage "${stage}" are up-to-date`);
|
|
@@ -16218,7 +16301,7 @@ program.command("deploy:github").description("Let GitHub Actions deploy a stage:
|
|
|
16218
16301
|
try {
|
|
16219
16302
|
const globalOptions = program.opts();
|
|
16220
16303
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16221
|
-
const { deployGithubCommand } = await Promise.resolve().then(() => require("./github-
|
|
16304
|
+
const { deployGithubCommand } = await Promise.resolve().then(() => require("./github-BUCXEl2v.cjs"));
|
|
16222
16305
|
await deployGithubCommand(options);
|
|
16223
16306
|
} catch (error) {
|
|
16224
16307
|
console.error(formatError(error));
|
|
@@ -16350,5 +16433,7 @@ Object.defineProperty(exports, "encryption_exports", {
|
|
|
16350
16433
|
}
|
|
16351
16434
|
});
|
|
16352
16435
|
exports.getHostingerToken = getHostingerToken;
|
|
16436
|
+
exports.isRemoteStore = isRemoteStore;
|
|
16437
|
+
exports.pushStageSecrets = pushStageSecrets;
|
|
16353
16438
|
|
|
16354
16439
|
//# sourceMappingURL=index.cjs.map
|