@geekmidas/cli 10.0.0-alpha.26 → 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-Dn9DW2Fz.cjs → config-BTFLHfRb.cjs} +25 -4
- package/dist/{config-Dn9DW2Fz.cjs.map → config-BTFLHfRb.cjs.map} +1 -1
- package/dist/{config-DBGpRTUy.mjs → config-Dqxa-_Ge.mjs} +20 -5
- package/dist/{config-DBGpRTUy.mjs.map → config-Dqxa-_Ge.mjs.map} +1 -1
- package/dist/config.cjs +3 -2
- package/dist/config.d.cts +12 -1
- package/dist/config.d.cts.map +1 -1
- package/dist/config.d.mts +12 -1
- package/dist/config.d.mts.map +1 -1
- package/dist/config.mjs +3 -3
- 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-D58lt7RR.cjs → github-BUCXEl2v.cjs} +6 -6
- package/dist/{github-D58lt7RR.cjs.map → github-BUCXEl2v.cjs.map} +1 -1
- package/dist/{github-D5SBlOx5.mjs → github-zkzu-ka2.mjs} +4 -4
- package/dist/{github-D5SBlOx5.mjs.map → github-zkzu-ka2.mjs.map} +1 -1
- package/dist/{index-RCUizGxL.d.cts → index-BPN7kghQ.d.cts} +2 -12
- package/dist/{index-B4H4XF7A.d.mts.map → index-BPN7kghQ.d.cts.map} +1 -1
- package/dist/{index-B4H4XF7A.d.mts → index-BqWKWRm0.d.mts} +2 -12
- package/dist/{index-RCUizGxL.d.cts.map → index-BqWKWRm0.d.mts.map} +1 -1
- package/dist/index.cjs +805 -790
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +754 -741
- package/dist/index.mjs.map +1 -1
- package/dist/{openapi-DHQlstv6.mjs → openapi-D-Lk5Hr5.mjs} +3 -3
- package/dist/{openapi-DHQlstv6.mjs.map → openapi-D-Lk5Hr5.mjs.map} +1 -1
- package/dist/{openapi-BAKQLho-.cjs → openapi-K4H5tHzA.cjs} +3 -3
- package/dist/{openapi-BAKQLho-.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/{generator-LfZU7NlU.mjs → reconcile-DhbCvh4K.mjs} +111 -2
- package/dist/reconcile-DhbCvh4K.mjs.map +1 -0
- package/dist/{generator-Wdfp0jCS.cjs → reconcile-szmJw_Sb.cjs} +130 -3
- package/dist/reconcile-szmJw_Sb.cjs.map +1 -0
- package/dist/workspace/index.cjs +1 -2
- package/dist/workspace/index.d.cts +2 -2
- package/dist/workspace/index.d.mts +2 -2
- package/dist/workspace/index.mjs +2 -2
- package/dist/{workspace-CRD9YinV.cjs → workspace-Bav3TVrP.cjs} +47 -78
- package/dist/workspace-Bav3TVrP.cjs.map +1 -0
- package/dist/{credentials-fQSAe4Nb.cjs → workspace-Cv48QwPK.cjs} +347 -516
- package/dist/workspace-Cv48QwPK.cjs.map +1 -0
- package/dist/{workspace-Bu3xNCej.mjs → workspace-T4IVGSoB.mjs} +48 -73
- package/dist/workspace-T4IVGSoB.mjs.map +1 -0
- package/dist/{credentials-DI9kJcaP.mjs → workspace-cBobQVoA.mjs} +349 -500
- package/dist/workspace-cBobQVoA.mjs.map +1 -0
- package/package.json +14 -14
- package/dist/credentials-DI9kJcaP.mjs.map +0 -1
- package/dist/credentials-fQSAe4Nb.cjs.map +0 -1
- package/dist/generator-LfZU7NlU.mjs.map +0 -1
- package/dist/generator-Wdfp0jCS.cjs.map +0 -1
- package/dist/reconcile-gb1lz1Dk.mjs +0 -113
- package/dist/reconcile-gb1lz1Dk.mjs.map +0 -1
- package/dist/reconcile-hx_vBvux.cjs +0 -136
- package/dist/reconcile-hx_vBvux.cjs.map +0 -1
- package/dist/transfer-CAJs_vhu.mjs +0 -122
- package/dist/transfer-CAJs_vhu.mjs.map +0 -1
- package/dist/transfer-D2qLFXsw.cjs +0 -151
- package/dist/transfer-D2qLFXsw.cjs.map +0 -1
- package/dist/workspace-Bu3xNCej.mjs.map +0 -1
- package/dist/workspace-CRD9YinV.cjs.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,16 +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-
|
|
6
|
-
const require_workspace = require("./workspace-
|
|
7
|
-
const require_credentials = require("./credentials-fQSAe4Nb.cjs");
|
|
8
|
-
const require_generator = require("./generator-Wdfp0jCS.cjs");
|
|
4
|
+
const require_discover = require("./discover-BTh87DOO.cjs");
|
|
5
|
+
const require_config = require("./config-BTFLHfRb.cjs");
|
|
6
|
+
const require_workspace = require("./workspace-Bav3TVrP.cjs");
|
|
9
7
|
const require_storage = require("./storage-DgypfSD6.cjs");
|
|
10
|
-
const
|
|
8
|
+
const require_workspace$1 = require("./workspace-Cv48QwPK.cjs");
|
|
9
|
+
const require_openapi = require("./openapi-K4H5tHzA.cjs");
|
|
11
10
|
const require_emit = require("./emit-BdoPFjZf.cjs");
|
|
12
|
-
const require_reconcile = require("./reconcile-
|
|
13
|
-
const require_transfer = require("./transfer-D2qLFXsw.cjs");
|
|
11
|
+
const require_reconcile = require("./reconcile-szmJw_Sb.cjs");
|
|
14
12
|
let commander = require("commander");
|
|
15
13
|
let node_process = require("node:process");
|
|
16
14
|
let node_readline_promises = require("node:readline/promises");
|
|
@@ -23,28 +21,28 @@ let node_child_process = require("node:child_process");
|
|
|
23
21
|
let _geekmidas_manifest = require("@geekmidas/manifest");
|
|
24
22
|
let fast_glob = require("fast-glob");
|
|
25
23
|
fast_glob = require_rolldown_runtime.__toESM(fast_glob, 1);
|
|
24
|
+
let node_module = require("node:module");
|
|
26
25
|
let chokidar = require("chokidar");
|
|
27
26
|
chokidar = require_rolldown_runtime.__toESM(chokidar, 1);
|
|
28
|
-
let yaml = require("yaml");
|
|
29
27
|
let node_crypto = require("node:crypto");
|
|
28
|
+
let node_url = require("node:url");
|
|
30
29
|
let _geekmidas_db_pg_roles = require("@geekmidas/db/pg/roles");
|
|
31
30
|
let pg = require("pg");
|
|
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
|
|
34
|
+
let prompts = require("prompts");
|
|
35
|
+
prompts = require_rolldown_runtime.__toESM(prompts, 1);
|
|
35
36
|
let _geekmidas_constructs_crons = require("@geekmidas/constructs/crons");
|
|
36
37
|
let _geekmidas_constructs_functions = require("@geekmidas/constructs/functions");
|
|
37
38
|
let _geekmidas_constructs_queue = require("@geekmidas/constructs/queue");
|
|
38
39
|
let _geekmidas_constructs_subscribers = require("@geekmidas/constructs/subscribers");
|
|
39
40
|
let _geekmidas_constructs_topic = require("@geekmidas/constructs/topic");
|
|
40
41
|
let node_dns_promises = require("node:dns/promises");
|
|
41
|
-
let node_module = require("node:module");
|
|
42
|
-
let prompts = require("prompts");
|
|
43
|
-
prompts = require_rolldown_runtime.__toESM(prompts, 1);
|
|
44
42
|
let _geekmidas_envkit = require("@geekmidas/envkit");
|
|
45
43
|
let _geekmidas_services = require("@geekmidas/services");
|
|
46
44
|
//#region package.json
|
|
47
|
-
var version = "10.0.0-alpha.
|
|
45
|
+
var version = "10.0.0-alpha.28";
|
|
48
46
|
//#endregion
|
|
49
47
|
//#region src/auth/credentials.ts
|
|
50
48
|
/**
|
|
@@ -411,8 +409,182 @@ function normalizeRoutes(routes) {
|
|
|
411
409
|
return Array.isArray(routes) ? routes : [routes];
|
|
412
410
|
}
|
|
413
411
|
//#endregion
|
|
414
|
-
//#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
|
+
*/
|
|
415
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;
|
|
416
588
|
/** `gkm exec` was given nothing to run. */
|
|
417
589
|
var NoCommandSpecified = class extends Error {
|
|
418
590
|
constructor() {
|
|
@@ -434,21 +606,18 @@ var NoCommandSpecified = class extends Error {
|
|
|
434
606
|
async function execCommand(commandArgs, options = {}) {
|
|
435
607
|
const cwd = options.cwd ?? process.cwd();
|
|
436
608
|
if (!commandArgs[0]) throw new NoCommandSpecified();
|
|
437
|
-
const defaultEnv =
|
|
438
|
-
if (defaultEnv.loaded.length > 0) logger$
|
|
439
|
-
const { credentials, secretsJsonPath, appName } = await
|
|
440
|
-
|
|
441
|
-
resolveDockerPorts: "readonly"
|
|
442
|
-
});
|
|
443
|
-
if (appName) logger$12.log(`📦 App: ${appName}`);
|
|
609
|
+
const defaultEnv = require_workspace$1.loadEnvFiles(".env");
|
|
610
|
+
if (defaultEnv.loaded.length > 0) logger$11.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
611
|
+
const { credentials, secretsJsonPath, appName } = await require_workspace$1.prepareEntryCredentials({ cwd });
|
|
612
|
+
if (appName) logger$11.log(`📦 App: ${appName}`);
|
|
444
613
|
const secretCount = Object.keys(credentials).filter((k) => k !== "PORT").length;
|
|
445
|
-
if (secretCount > 0) logger$
|
|
614
|
+
if (secretCount > 0) logger$11.log(`🔐 Loaded ${secretCount} secret(s)`);
|
|
446
615
|
const preloadDir = (0, node_path.join)(cwd, ".gkm");
|
|
447
616
|
await (0, node_fs_promises.mkdir)(preloadDir, { recursive: true });
|
|
448
617
|
const preloadPath = (0, node_path.join)(preloadDir, "credentials-preload.mjs");
|
|
449
|
-
await
|
|
618
|
+
await require_workspace$1.createCredentialsPreload(preloadPath, secretsJsonPath);
|
|
450
619
|
const [cmd, ...args] = commandArgs;
|
|
451
|
-
logger$
|
|
620
|
+
logger$11.log(`🚀 Running: ${[cmd, ...args].join(" ")}`);
|
|
452
621
|
const nodeOptions = [(process.env.NODE_OPTIONS ?? "").replace(/--import[= ]\S+/g, "").trim(), `--import=${preloadPath}`].filter(Boolean).join(" ");
|
|
453
622
|
const child = (0, node_child_process.spawn)(cmd, args, {
|
|
454
623
|
cwd,
|
|
@@ -462,7 +631,7 @@ async function execCommand(commandArgs, options = {}) {
|
|
|
462
631
|
const exitCode = await new Promise((resolve) => {
|
|
463
632
|
child.on("close", (code) => resolve(code ?? 0));
|
|
464
633
|
child.on("error", (error) => {
|
|
465
|
-
logger$
|
|
634
|
+
logger$11.error(`Failed to run command: ${error.message}`);
|
|
466
635
|
resolve(1);
|
|
467
636
|
});
|
|
468
637
|
});
|
|
@@ -470,7 +639,7 @@ async function execCommand(commandArgs, options = {}) {
|
|
|
470
639
|
}
|
|
471
640
|
//#endregion
|
|
472
641
|
//#region src/dev/index.ts
|
|
473
|
-
const logger$
|
|
642
|
+
const logger$10 = console;
|
|
474
643
|
/**
|
|
475
644
|
* Normalize telescope configuration
|
|
476
645
|
* @internal Exported for testing
|
|
@@ -563,8 +732,8 @@ function getProductionConfigFromGkm(config) {
|
|
|
563
732
|
}
|
|
564
733
|
async function devCommand(options) {
|
|
565
734
|
if (options.entry) return entryDevCommand(options);
|
|
566
|
-
const defaultEnv =
|
|
567
|
-
if (defaultEnv.loaded.length > 0) logger$
|
|
735
|
+
const defaultEnv = require_workspace$1.loadEnvFiles(".env");
|
|
736
|
+
if (defaultEnv.loaded.length > 0) logger$10.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
568
737
|
const appName = require_config.getAppNameFromCwd();
|
|
569
738
|
let config;
|
|
570
739
|
let appRoot = process.cwd();
|
|
@@ -580,9 +749,8 @@ async function devCommand(options) {
|
|
|
580
749
|
workspaceAppName = appConfig.appName;
|
|
581
750
|
workspaceAppPort = appConfig.app.port;
|
|
582
751
|
workspace = appConfig.workspace;
|
|
583
|
-
logger$11.log(`📦 Running app: ${appConfig.appName} on port ${workspaceAppPort}`);
|
|
584
752
|
if (appConfig.app.entry) {
|
|
585
|
-
logger$
|
|
753
|
+
logger$10.log(`📄 Using entry point: ${appConfig.app.entry}`);
|
|
586
754
|
return entryDevCommand({
|
|
587
755
|
...options,
|
|
588
756
|
entry: appConfig.app.entry,
|
|
@@ -592,73 +760,54 @@ async function devCommand(options) {
|
|
|
592
760
|
}
|
|
593
761
|
} catch {
|
|
594
762
|
const loadedConfig = await require_config.loadWorkspaceConfig();
|
|
595
|
-
if (loadedConfig.type === "workspace")
|
|
596
|
-
logger$11.log("📦 Detected workspace configuration");
|
|
597
|
-
return workspaceDevCommand(loadedConfig.workspace, options);
|
|
598
|
-
}
|
|
763
|
+
if (loadedConfig.type === "workspace") return workspaceDevCommand(loadedConfig.workspace, options);
|
|
599
764
|
config = loadedConfig.raw;
|
|
765
|
+
workspace = loadedConfig.workspace;
|
|
600
766
|
}
|
|
601
767
|
else {
|
|
602
768
|
const loadedConfig = await require_config.loadWorkspaceConfig();
|
|
603
|
-
if (loadedConfig.type === "workspace")
|
|
604
|
-
logger$11.log("📦 Detected workspace configuration");
|
|
605
|
-
return workspaceDevCommand(loadedConfig.workspace, options);
|
|
606
|
-
}
|
|
769
|
+
if (loadedConfig.type === "workspace") return workspaceDevCommand(loadedConfig.workspace, options);
|
|
607
770
|
config = loadedConfig.raw;
|
|
608
771
|
workspace = loadedConfig.workspace;
|
|
609
772
|
}
|
|
610
773
|
if (config.env) {
|
|
611
|
-
const { loaded, missing } =
|
|
612
|
-
if (loaded.length > 0) logger$
|
|
613
|
-
if (missing.length > 0) logger$
|
|
774
|
+
const { loaded, missing } = require_workspace$1.loadEnvFiles(config.env, appRoot);
|
|
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(", ")}`);
|
|
614
777
|
}
|
|
615
778
|
const resolved = resolveProviders(config, { provider: "server" });
|
|
616
|
-
logger$11.log("🚀 Starting development server...");
|
|
617
|
-
logger$11.log(`Loading constructs from: ${config.constructs}`);
|
|
618
779
|
const telescope = normalizeTelescopeConfig(config.telescope);
|
|
619
|
-
if (telescope) logger$11.log(`🔭 Telescope enabled at ${telescope.path}`);
|
|
620
780
|
const studio = normalizeStudioConfig(config.studio);
|
|
621
|
-
if (studio) logger$11.log(`🗄️ Studio enabled at ${studio.path}`);
|
|
622
781
|
const hooks = normalizeHooksConfig(config.hooks, appRoot);
|
|
623
|
-
if (hooks) logger$11.log(`🪝 Server hooks enabled from ${config.hooks?.server}`);
|
|
624
782
|
const enableOpenApi = require_openapi.resolveOpenApiConfig(config).enabled || resolved.enableOpenApi;
|
|
625
|
-
|
|
626
|
-
const build = (bustCache = false) => buildApp({
|
|
783
|
+
const build = (bustCache = false) => quietly(() => buildApp({
|
|
627
784
|
config,
|
|
628
785
|
workspaceRoot: workspace?.root ?? secretsRoot,
|
|
629
786
|
appRoot,
|
|
630
787
|
providers: ["server"],
|
|
631
788
|
enableOpenApi,
|
|
632
|
-
cacheBackend:
|
|
789
|
+
cacheBackend: require_workspace$1.cacheBackendFor(require_workspace$1.providerOf(workspace ?? config)),
|
|
633
790
|
telescope,
|
|
634
791
|
studio,
|
|
635
792
|
hooks,
|
|
636
793
|
skipBundle: true,
|
|
637
794
|
bustCache,
|
|
638
795
|
serveEmpty: true
|
|
639
|
-
});
|
|
796
|
+
}));
|
|
640
797
|
const initial = await build();
|
|
641
798
|
const runtime = config.runtime ?? "node";
|
|
642
799
|
let secretsJsonPath;
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
800
|
+
let publicUrl;
|
|
801
|
+
const appSecrets = await require_workspace$1.loadSecretsForApp(secretsRoot, config.stages.local, workspaceAppName);
|
|
802
|
+
if (workspace) {
|
|
803
|
+
const reconciled = await require_workspace$1.reconcileWorkspace(workspace, { stage: workspace.stages.local });
|
|
646
804
|
if (reconciled.changed && reconciled.plan.containers.length > 0) {
|
|
647
|
-
logger$
|
|
648
|
-
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}`);
|
|
649
807
|
}
|
|
650
808
|
Object.assign(appSecrets, reconciled.env);
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
if (Object.keys(resolvedPorts.ports).length > 0) {
|
|
654
|
-
const rewritten = require_credentials.rewriteUrlsWithPorts(appSecrets, resolvedPorts);
|
|
655
|
-
Object.assign(appSecrets, rewritten);
|
|
656
|
-
logger$11.log(`🔌 Applied ${Object.keys(resolvedPorts.ports).length} port mapping(s)`);
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
if (workspace && workspaceAppName) {
|
|
660
|
-
const depEnv = require_workspace.getDependencyEnvVars(workspace, workspaceAppName);
|
|
661
|
-
for (const [key, value] of Object.entries(depEnv)) appSecrets[key] ??= value;
|
|
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;
|
|
662
811
|
}
|
|
663
812
|
if (Object.keys(appSecrets).length > 0) {
|
|
664
813
|
const secretsDir = (0, node_path.join)(secretsRoot, ".gkm");
|
|
@@ -666,13 +815,13 @@ async function devCommand(options) {
|
|
|
666
815
|
const secretsFileName = workspaceAppName ? `dev-secrets-${workspaceAppName}.json` : "dev-secrets.json";
|
|
667
816
|
secretsJsonPath = (0, node_path.join)(secretsDir, secretsFileName);
|
|
668
817
|
await (0, node_fs_promises.writeFile)(secretsJsonPath, JSON.stringify(appSecrets, null, 2));
|
|
669
|
-
logger$11.log(`🔐 Loaded ${Object.keys(appSecrets).length} secret(s)`);
|
|
670
818
|
}
|
|
671
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;
|
|
672
822
|
await devServer.start();
|
|
673
823
|
const hooksFile = (config.hooks?.server?.split("#"))?.[0];
|
|
674
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);
|
|
675
|
-
logger$11.log(`👀 Watching for changes in: ${normalizedPatterns.join(", ")}`);
|
|
676
825
|
const resolvedFiles = await (0, fast_glob.default)(normalizedPatterns, {
|
|
677
826
|
cwd: appRoot,
|
|
678
827
|
absolute: false,
|
|
@@ -681,34 +830,28 @@ async function devCommand(options) {
|
|
|
681
830
|
const dirsToWatch = [...new Set(resolvedFiles.map((f) => {
|
|
682
831
|
return f.split("/").slice(0, -1).join("/");
|
|
683
832
|
}))];
|
|
684
|
-
logger$11.log(`📁 Found ${resolvedFiles.length} files in ${dirsToWatch.length} directories`);
|
|
685
833
|
const watcher = chokidar.default.watch([...resolvedFiles, ...dirsToWatch], {
|
|
686
834
|
ignored: /(^|[/\\])\../,
|
|
687
835
|
persistent: true,
|
|
688
836
|
ignoreInitial: true,
|
|
689
837
|
cwd: appRoot
|
|
690
838
|
});
|
|
691
|
-
watcher.on("ready", () => {
|
|
692
|
-
logger$11.log("🔍 File watcher ready");
|
|
693
|
-
});
|
|
694
839
|
watcher.on("error", (error) => {
|
|
695
|
-
logger$
|
|
840
|
+
logger$10.error("❌ Watcher error:", error);
|
|
696
841
|
});
|
|
697
842
|
let rebuildTimeout = null;
|
|
698
843
|
watcher.on("change", async (path) => {
|
|
699
|
-
logger$
|
|
844
|
+
logger$10.log(`🔄 ${path} changed — rebuilding`);
|
|
700
845
|
if (rebuildTimeout) clearTimeout(rebuildTimeout);
|
|
701
846
|
rebuildTimeout = setTimeout(async () => {
|
|
702
847
|
try {
|
|
703
|
-
logger$11.log("🔄 Rebuilding...");
|
|
704
848
|
const rebuilt = await build(true);
|
|
705
|
-
logger$11.log("✅ Rebuild complete, restarting server...");
|
|
706
849
|
devServer.selfServing = rebuilt.selfServing !== void 0;
|
|
707
850
|
await devServer.restart();
|
|
708
851
|
} catch (error) {
|
|
709
852
|
const err = error;
|
|
710
|
-
logger$
|
|
711
|
-
if (err.stack) logger$
|
|
853
|
+
logger$10.error(`❌ Rebuild failed: ${err.message}`);
|
|
854
|
+
if (err.stack) logger$10.error(err.stack);
|
|
712
855
|
}
|
|
713
856
|
}, 300);
|
|
714
857
|
});
|
|
@@ -716,9 +859,9 @@ async function devCommand(options) {
|
|
|
716
859
|
const shutdown = () => {
|
|
717
860
|
if (isShuttingDown) return;
|
|
718
861
|
isShuttingDown = true;
|
|
719
|
-
logger$
|
|
862
|
+
logger$10.log("\n🛑 Shutting down...");
|
|
720
863
|
Promise.all([watcher.close(), devServer.stop()]).catch((err) => {
|
|
721
|
-
logger$
|
|
864
|
+
logger$10.error("Error during shutdown:", err);
|
|
722
865
|
}).finally(() => {
|
|
723
866
|
process.exit(0);
|
|
724
867
|
});
|
|
@@ -727,19 +870,6 @@ async function devCommand(options) {
|
|
|
727
870
|
process.on("SIGTERM", shutdown);
|
|
728
871
|
}
|
|
729
872
|
/**
|
|
730
|
-
* Generate all dependency environment variables for all apps.
|
|
731
|
-
* Returns a flat object with all {APP_NAME}_URL variables.
|
|
732
|
-
* @internal Exported for testing
|
|
733
|
-
*/
|
|
734
|
-
function generateAllDependencyEnvVars(workspace, urlPrefix = "http://localhost") {
|
|
735
|
-
const env = {};
|
|
736
|
-
for (const appName of Object.keys(workspace.apps)) {
|
|
737
|
-
const appEnv = require_workspace.getDependencyEnvVars(workspace, appName, urlPrefix);
|
|
738
|
-
Object.assign(env, appEnv);
|
|
739
|
-
}
|
|
740
|
-
return env;
|
|
741
|
-
}
|
|
742
|
-
/**
|
|
743
873
|
* Check for port conflicts across all apps.
|
|
744
874
|
* Returns list of conflicts if any ports are duplicated.
|
|
745
875
|
* @internal Exported for testing
|
|
@@ -917,12 +1047,8 @@ async function loadDevSecrets(workspace) {
|
|
|
917
1047
|
const stage = workspace.stages.local;
|
|
918
1048
|
if (require_storage.secretsExist(stage, workspace.root)) {
|
|
919
1049
|
const secrets = await require_storage.readStageSecrets(stage, workspace.root);
|
|
920
|
-
if (secrets)
|
|
921
|
-
logger$11.log(`🔐 Loading secrets from stage: ${stage}`);
|
|
922
|
-
return require_storage.toEmbeddableSecrets(secrets);
|
|
923
|
-
}
|
|
1050
|
+
if (secrets) return require_storage.toEmbeddableSecrets(secrets);
|
|
924
1051
|
}
|
|
925
|
-
logger$11.warn(`⚠️ Secrets enabled but no "${stage}" secrets found. Run "gkm setup" to initialize the local stage`);
|
|
926
1052
|
return {};
|
|
927
1053
|
}
|
|
928
1054
|
/**
|
|
@@ -936,66 +1062,43 @@ async function loadDevSecrets(workspace) {
|
|
|
936
1062
|
*/
|
|
937
1063
|
async function workspaceDevCommand(workspace, options) {
|
|
938
1064
|
const appCount = Object.keys(workspace.apps).length;
|
|
939
|
-
const backendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "backend");
|
|
940
1065
|
const frontendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "web");
|
|
941
|
-
const mobileApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "mobile");
|
|
942
|
-
logger$11.log(`\n🚀 Starting workspace: ${workspace.name}`);
|
|
943
|
-
const counts = [`${backendApps.length} backend`, `${frontendApps.length} web`];
|
|
944
|
-
if (mobileApps.length > 0) counts.push(`${mobileApps.length} mobile`);
|
|
945
|
-
logger$11.log(` ${counts.join(", ")} app(s)`);
|
|
946
1066
|
const conflicts = checkPortConflicts(workspace);
|
|
947
1067
|
if (conflicts.length > 0) {
|
|
948
|
-
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}`);
|
|
949
1069
|
throw new Error("Port conflicts detected. Please assign unique ports to each app.");
|
|
950
1070
|
}
|
|
951
1071
|
if (frontendApps.length > 0) {
|
|
952
|
-
logger$11.log("\n🔍 Validating frontend apps...");
|
|
953
1072
|
const validationResults = await validateFrontendApps(workspace);
|
|
954
1073
|
let hasErrors = false;
|
|
955
1074
|
for (const result of validationResults) {
|
|
956
1075
|
if (!result.valid) {
|
|
957
1076
|
hasErrors = true;
|
|
958
|
-
logger$
|
|
959
|
-
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}`);
|
|
960
1079
|
}
|
|
961
|
-
for (const warning of result.warnings) logger$
|
|
1080
|
+
for (const warning of result.warnings) logger$10.warn(` ⚠️ ${result.appName}: ${warning}`);
|
|
962
1081
|
}
|
|
963
1082
|
if (hasErrors) throw new Error("Frontend app validation failed. Fix the issues above and try again.");
|
|
964
|
-
logger$11.log("✅ Frontend apps validated");
|
|
965
1083
|
}
|
|
966
1084
|
const rawSecrets = await loadDevSecrets(workspace);
|
|
967
|
-
|
|
1085
|
+
const reconciled = await require_workspace$1.reconcileWorkspace(workspace, { stage: workspace.stages.local });
|
|
1086
|
+
if (reconciled.changed && reconciled.plan.containers.length > 0) {
|
|
1087
|
+
logger$10.log(`🐳 Services: ${reconciled.plan.containers.join(", ")}`);
|
|
1088
|
+
for (const [container, address] of Object.entries(reconciled.addresses)) logger$10.log(` ${container}: ${address}`);
|
|
1089
|
+
}
|
|
1090
|
+
const secretsEnv = {
|
|
1091
|
+
...rawSecrets,
|
|
1092
|
+
...reconciled.env
|
|
1093
|
+
};
|
|
968
1094
|
const appUrls = {};
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
for (const [container, address] of Object.entries(reconciled.addresses)) logger$11.log(` ${container}: ${address}`);
|
|
974
|
-
}
|
|
975
|
-
secretsEnv = {
|
|
976
|
-
...rawSecrets,
|
|
977
|
-
...reconciled.env
|
|
978
|
-
};
|
|
979
|
-
for (const resource of reconciled.plan.resources) {
|
|
980
|
-
if (resource.kind !== "rest-api" && resource.kind !== "site") continue;
|
|
981
|
-
const address = reconciled.env[resource.envKey];
|
|
982
|
-
if (address) appUrls[resource.id] = address;
|
|
983
|
-
}
|
|
984
|
-
} else {
|
|
985
|
-
const resolvedPorts = await require_credentials.resolveServicePorts(workspace.root);
|
|
986
|
-
await require_credentials.startWorkspaceServices(workspace, resolvedPorts.dockerEnv, rawSecrets);
|
|
987
|
-
secretsEnv = require_credentials.rewriteUrlsWithPorts(rawSecrets, resolvedPorts);
|
|
988
|
-
}
|
|
989
|
-
if (Object.keys(secretsEnv).length > 0) logger$11.log(` Loaded ${Object.keys(secretsEnv).length} secret(s)`);
|
|
990
|
-
if (Object.keys(appUrls).length > 0) {
|
|
991
|
-
logger$11.log("🔒 App URLs:");
|
|
992
|
-
for (const [id, address] of Object.entries(appUrls)) logger$11.log(` ${id}: ${address}`);
|
|
993
|
-
}
|
|
994
|
-
const dependencyEnv = Object.fromEntries(Object.entries(generateAllDependencyEnvVars(workspace)).filter(([key]) => secretsEnv[key] === void 0));
|
|
995
|
-
if (Object.keys(dependencyEnv).length > 0) {
|
|
996
|
-
logger$11.log("📡 Dependency URLs:");
|
|
997
|
-
for (const [key, value] of Object.entries(dependencyEnv)) logger$11.log(` ${key}=${value}`);
|
|
1095
|
+
for (const resource of reconciled.plan.resources) {
|
|
1096
|
+
if (resource.kind !== "rest-api" && resource.kind !== "site") continue;
|
|
1097
|
+
const address = reconciled.env[resource.envKey];
|
|
1098
|
+
if (address) appUrls[resource.id] = address;
|
|
998
1099
|
}
|
|
1100
|
+
const firstUrl = Object.values(appUrls).find((url) => url.startsWith("https://"));
|
|
1101
|
+
if (firstUrl) await ensureTrusted(workspace.root, firstUrl);
|
|
999
1102
|
let turboFilter = [];
|
|
1000
1103
|
if (options.app) {
|
|
1001
1104
|
if (!workspace.apps[options.app]) {
|
|
@@ -1003,25 +1106,23 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1003
1106
|
throw new Error(`App "${options.app}" not found. Available apps: ${appNames}`);
|
|
1004
1107
|
}
|
|
1005
1108
|
turboFilter = ["--filter", appPackageName(workspace, options.app) ?? options.app];
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
turboFilter = ["--filter", options.filter];
|
|
1009
|
-
logger$11.log(`\n🔍 Using filter: ${options.filter}`);
|
|
1010
|
-
} else {
|
|
1109
|
+
} else if (options.filter) turboFilter = ["--filter", options.filter];
|
|
1110
|
+
else {
|
|
1011
1111
|
const { filters, unpackaged } = turboFilters(workspace);
|
|
1012
1112
|
turboFilter = filters.flatMap((name) => ["--filter", name]);
|
|
1013
|
-
if (unpackaged.length > 0) logger$
|
|
1014
|
-
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"}.`);
|
|
1015
1114
|
}
|
|
1016
1115
|
const buildOrder = require_workspace.getAppBuildOrder(workspace);
|
|
1017
|
-
|
|
1116
|
+
const width = Math.max(...buildOrder.map((name) => name.length));
|
|
1117
|
+
logger$10.log(`\n${workspace.name}: ${appCount} app(s)`);
|
|
1018
1118
|
for (const appName of buildOrder) {
|
|
1019
1119
|
const app = workspace.apps[appName];
|
|
1020
1120
|
if (!app) continue;
|
|
1021
|
-
const
|
|
1022
|
-
const
|
|
1023
|
-
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}`);
|
|
1024
1124
|
}
|
|
1125
|
+
logger$10.log("");
|
|
1025
1126
|
const configFiles = [
|
|
1026
1127
|
"gkm.config.ts",
|
|
1027
1128
|
"gkm.config.js",
|
|
@@ -1038,7 +1139,6 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1038
1139
|
const turboEnv = {
|
|
1039
1140
|
...process.env,
|
|
1040
1141
|
...secretsEnv,
|
|
1041
|
-
...dependencyEnv,
|
|
1042
1142
|
NODE_ENV: "development",
|
|
1043
1143
|
...configPath ? { GKM_CONFIG_PATH: configPath } : {}
|
|
1044
1144
|
};
|
|
@@ -1046,14 +1146,13 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1046
1146
|
const held = [];
|
|
1047
1147
|
for (const appName of scoped) {
|
|
1048
1148
|
const port = workspace.apps[appName]?.port;
|
|
1049
|
-
if (port && !await
|
|
1149
|
+
if (port && !await require_workspace$1.isPortAvailable(port)) held.push({
|
|
1050
1150
|
app: appName,
|
|
1051
1151
|
port,
|
|
1052
1152
|
holder: listenerOn(port)
|
|
1053
1153
|
});
|
|
1054
1154
|
}
|
|
1055
1155
|
if (held.length > 0) throw new WorkspacePortsInUse(held);
|
|
1056
|
-
logger$11.log("\n🏃 Starting turbo run dev...\n");
|
|
1057
1156
|
const turboProcess = (0, node_child_process.spawn)("pnpm", [
|
|
1058
1157
|
"turbo",
|
|
1059
1158
|
"run",
|
|
@@ -1069,7 +1168,7 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1069
1168
|
const shutdown = () => {
|
|
1070
1169
|
if (isShuttingDown) return;
|
|
1071
1170
|
isShuttingDown = true;
|
|
1072
|
-
logger$
|
|
1171
|
+
logger$10.log("\n🛑 Shutting down workspace...");
|
|
1073
1172
|
const pid = turboProcess.pid;
|
|
1074
1173
|
if (pid) try {
|
|
1075
1174
|
process.kill(-pid, "SIGTERM");
|
|
@@ -1093,7 +1192,7 @@ async function workspaceDevCommand(workspace, options) {
|
|
|
1093
1192
|
process.on("SIGTERM", shutdown);
|
|
1094
1193
|
return new Promise((resolve, reject) => {
|
|
1095
1194
|
turboProcess.on("error", (error) => {
|
|
1096
|
-
logger$
|
|
1195
|
+
logger$10.error("❌ Turbo error:", error);
|
|
1097
1196
|
reject(error);
|
|
1098
1197
|
});
|
|
1099
1198
|
turboProcess.on("exit", (code) => {
|
|
@@ -1111,23 +1210,23 @@ async function entryDevCommand(options) {
|
|
|
1111
1210
|
if (!entry) throw new Error("--entry requires a file path");
|
|
1112
1211
|
const entryPath = (0, node_path.resolve)(process.cwd(), entry);
|
|
1113
1212
|
if (!(0, node_fs.existsSync)(entryPath)) throw new Error(`Entry file not found: ${entryPath}`);
|
|
1114
|
-
const defaultEnv =
|
|
1115
|
-
if (defaultEnv.loaded.length > 0) logger$
|
|
1116
|
-
const { credentials, resolvedPort, secretsJsonPath, appName } = await
|
|
1117
|
-
if (appName) logger$
|
|
1118
|
-
logger$
|
|
1119
|
-
if (Object.keys(credentials).length > 1) logger$
|
|
1213
|
+
const defaultEnv = require_workspace$1.loadEnvFiles(".env");
|
|
1214
|
+
if (defaultEnv.loaded.length > 0) logger$10.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
1215
|
+
const { credentials, resolvedPort, secretsJsonPath, appName } = await require_workspace$1.prepareEntryCredentials({ explicitPort: options.portExplicit ? options.port : void 0 });
|
|
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`);
|
|
1120
1219
|
const wrapperDir = (0, node_path.join)(process.cwd(), ".gkm");
|
|
1121
1220
|
await (0, node_fs_promises.mkdir)(wrapperDir, { recursive: true });
|
|
1122
1221
|
const wrapperPath = (0, node_path.join)(wrapperDir, "entry-wrapper.ts");
|
|
1123
|
-
await
|
|
1222
|
+
await require_workspace$1.createEntryWrapper(wrapperPath, entryPath, secretsJsonPath);
|
|
1124
1223
|
const runner = new EntryRunner(wrapperPath, entryPath, watch, resolvedPort);
|
|
1125
1224
|
await runner.start();
|
|
1126
1225
|
let isShuttingDown = false;
|
|
1127
1226
|
const shutdown = () => {
|
|
1128
1227
|
if (isShuttingDown) return;
|
|
1129
1228
|
isShuttingDown = true;
|
|
1130
|
-
logger$
|
|
1229
|
+
logger$10.log("\n🛑 Shutting down...");
|
|
1131
1230
|
runner.stop();
|
|
1132
1231
|
process.exit(0);
|
|
1133
1232
|
};
|
|
@@ -1163,14 +1262,14 @@ var EntryRunner = class {
|
|
|
1163
1262
|
});
|
|
1164
1263
|
let restartTimeout = null;
|
|
1165
1264
|
this.watcher.on("change", (path) => {
|
|
1166
|
-
logger$
|
|
1265
|
+
logger$10.log(`📝 File changed: ${path}`);
|
|
1167
1266
|
if (restartTimeout) clearTimeout(restartTimeout);
|
|
1168
1267
|
restartTimeout = setTimeout(async () => {
|
|
1169
|
-
logger$
|
|
1268
|
+
logger$10.log("🔄 Restarting...");
|
|
1170
1269
|
await this.restart();
|
|
1171
1270
|
}, 300);
|
|
1172
1271
|
});
|
|
1173
|
-
logger$
|
|
1272
|
+
logger$10.log(`👀 Watching for changes in: ${watchDir}`);
|
|
1174
1273
|
}
|
|
1175
1274
|
}
|
|
1176
1275
|
async runProcess() {
|
|
@@ -1178,24 +1277,24 @@ var EntryRunner = class {
|
|
|
1178
1277
|
...process.env,
|
|
1179
1278
|
PORT: String(this.port)
|
|
1180
1279
|
};
|
|
1181
|
-
this.childProcess = (0, node_child_process.spawn)(
|
|
1280
|
+
this.childProcess = (0, node_child_process.spawn)(...tsxCommand((0, node_path.dirname)(this.wrapperPath), [this.wrapperPath]), {
|
|
1182
1281
|
stdio: "inherit",
|
|
1183
1282
|
env,
|
|
1184
1283
|
detached: true
|
|
1185
1284
|
});
|
|
1186
1285
|
this.isRunning = true;
|
|
1187
1286
|
this.childProcess.on("error", (error) => {
|
|
1188
|
-
logger$
|
|
1287
|
+
logger$10.error("❌ Process error:", error);
|
|
1189
1288
|
});
|
|
1190
1289
|
this.childProcess.on("exit", (code) => {
|
|
1191
|
-
if (code !== null && code !== 0 && code !== 143) logger$
|
|
1290
|
+
if (code !== null && code !== 0 && code !== 143) logger$10.error(`❌ Process exited with code ${code}`);
|
|
1192
1291
|
this.isRunning = false;
|
|
1193
1292
|
});
|
|
1194
1293
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
1195
1294
|
if (this.isRunning) {
|
|
1196
|
-
logger$
|
|
1197
|
-
logger$
|
|
1198
|
-
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("");
|
|
1199
1298
|
}
|
|
1200
1299
|
}
|
|
1201
1300
|
async restart() {
|
|
@@ -1281,7 +1380,6 @@ ${credentialsInjection}${parentWatch}const port = process.argv.includes('--port'
|
|
|
1281
1380
|
const { app } = await import('${appImportPath}');
|
|
1282
1381
|
|
|
1283
1382
|
${serveCode.replace(/^ {4}/gm, "")}
|
|
1284
|
-
console.log(\`Server started on port \${port}\`);
|
|
1285
1383
|
`;
|
|
1286
1384
|
return `#!/usr/bin/env node
|
|
1287
1385
|
/**
|
|
@@ -1310,6 +1408,31 @@ start({
|
|
|
1310
1408
|
});
|
|
1311
1409
|
`;
|
|
1312
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
|
+
}
|
|
1313
1436
|
/** A port this app must serve on is held by something else. */
|
|
1314
1437
|
var DevPortInUse = class extends Error {
|
|
1315
1438
|
port;
|
|
@@ -1376,25 +1499,30 @@ var DevServer = class {
|
|
|
1376
1499
|
this.selfServing = selfServing;
|
|
1377
1500
|
this.actualPort = requestedPort;
|
|
1378
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;
|
|
1379
1509
|
async start() {
|
|
1380
1510
|
this.startTime = Date.now();
|
|
1381
1511
|
if (this.isRunning) await this.stop();
|
|
1382
1512
|
if (this.portExplicit) {
|
|
1383
|
-
if (!await
|
|
1513
|
+
if (!await require_workspace$1.isPortAvailable(this.requestedPort)) throw new DevPortInUse(this.requestedPort, listenerOn(this.requestedPort));
|
|
1384
1514
|
this.actualPort = this.requestedPort;
|
|
1385
1515
|
} else {
|
|
1386
|
-
this.actualPort = await
|
|
1387
|
-
if (this.actualPort !== this.requestedPort) logger$
|
|
1516
|
+
this.actualPort = await require_workspace$1.findAvailablePort(this.requestedPort);
|
|
1517
|
+
if (this.actualPort !== this.requestedPort) logger$10.log(`ℹ️ Port ${this.requestedPort} was in use, using port ${this.actualPort} instead`);
|
|
1388
1518
|
}
|
|
1389
1519
|
const serverEntryPath = (0, node_path.join)(this.appRoot, ".gkm", this.provider, "server.ts");
|
|
1390
1520
|
await this.createServerEntry();
|
|
1391
|
-
|
|
1392
|
-
this.serverProcess = (0, node_child_process.spawn)("npx", [
|
|
1393
|
-
"tsx",
|
|
1521
|
+
this.serverProcess = (0, node_child_process.spawn)(...tsxCommand(this.appRoot, [
|
|
1394
1522
|
serverEntryPath,
|
|
1395
1523
|
"--port",
|
|
1396
1524
|
this.actualPort.toString()
|
|
1397
|
-
], {
|
|
1525
|
+
]), {
|
|
1398
1526
|
stdio: "inherit",
|
|
1399
1527
|
env: {
|
|
1400
1528
|
...process.env,
|
|
@@ -1405,29 +1533,24 @@ var DevServer = class {
|
|
|
1405
1533
|
});
|
|
1406
1534
|
this.isRunning = true;
|
|
1407
1535
|
this.serverProcess.on("error", (error) => {
|
|
1408
|
-
logger$
|
|
1536
|
+
logger$10.error("❌ Server error:", error);
|
|
1409
1537
|
});
|
|
1410
1538
|
this.serverProcess.on("exit", (code, signal) => {
|
|
1411
|
-
if (code !== null && code !== 0 && signal !== "SIGTERM") logger$
|
|
1539
|
+
if (code !== null && code !== 0 && signal !== "SIGTERM") logger$10.error(`❌ Server exited with code ${code}`);
|
|
1412
1540
|
this.isRunning = false;
|
|
1413
1541
|
});
|
|
1414
1542
|
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
1415
1543
|
if (this.isRunning) {
|
|
1416
|
-
const
|
|
1417
|
-
const
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
logger$
|
|
1426
|
-
logger$11.log("");
|
|
1427
|
-
logger$11.log(` ┌${border}┐`);
|
|
1428
|
-
for (const line of lines) logger$11.log(` │ ${pad(line)} │`);
|
|
1429
|
-
logger$11.log(` └${border}┘`);
|
|
1430
|
-
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(" · ")}`);
|
|
1431
1554
|
}
|
|
1432
1555
|
}
|
|
1433
1556
|
async stop() {
|
|
@@ -1456,7 +1579,7 @@ var DevServer = class {
|
|
|
1456
1579
|
await this.stop();
|
|
1457
1580
|
let attempts = 0;
|
|
1458
1581
|
while (attempts < 30) {
|
|
1459
|
-
if (await
|
|
1582
|
+
if (await require_workspace$1.isPortAvailable(portToReuse)) break;
|
|
1460
1583
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1461
1584
|
attempts++;
|
|
1462
1585
|
}
|
|
@@ -2242,7 +2365,7 @@ var TopicGenerator = class extends require_discover.ConstructGenerator {
|
|
|
2242
2365
|
};
|
|
2243
2366
|
//#endregion
|
|
2244
2367
|
//#region src/build/manifests.ts
|
|
2245
|
-
const logger$
|
|
2368
|
+
const logger$9 = console;
|
|
2246
2369
|
function isPartitioned(field) {
|
|
2247
2370
|
return !Array.isArray(field);
|
|
2248
2371
|
}
|
|
@@ -2339,8 +2462,8 @@ export type RoutePath = Route['path'];
|
|
|
2339
2462
|
`;
|
|
2340
2463
|
const manifestPath = (0, node_path.join)(manifestDir, "aws.ts");
|
|
2341
2464
|
await (0, node_fs_promises.writeFile)(manifestPath, content);
|
|
2342
|
-
logger$
|
|
2343
|
-
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)}`);
|
|
2344
2467
|
}
|
|
2345
2468
|
async function generateServerManifest(outputDir, appInfo, routes, subscribers, queues = [], topics = [], constructs = {}, backends = {}) {
|
|
2346
2469
|
const manifestDir = (0, node_path.join)(outputDir, "manifest");
|
|
@@ -2395,8 +2518,8 @@ export type RoutePath = Route['path'];
|
|
|
2395
2518
|
`;
|
|
2396
2519
|
const manifestPath = (0, node_path.join)(manifestDir, "server.ts");
|
|
2397
2520
|
await (0, node_fs_promises.writeFile)(manifestPath, content);
|
|
2398
|
-
logger$
|
|
2399
|
-
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)}`);
|
|
2400
2523
|
}
|
|
2401
2524
|
/**
|
|
2402
2525
|
* Filter out 'ALL' method routes from a manifest field (flat or partitioned).
|
|
@@ -2601,7 +2724,7 @@ export default app;
|
|
|
2601
2724
|
}
|
|
2602
2725
|
//#endregion
|
|
2603
2726
|
//#region src/build/index.ts
|
|
2604
|
-
const logger$
|
|
2727
|
+
const logger$8 = console;
|
|
2605
2728
|
/**
|
|
2606
2729
|
* What a build run at the workspace root reads.
|
|
2607
2730
|
*
|
|
@@ -2626,24 +2749,24 @@ async function buildCommand(options) {
|
|
|
2626
2749
|
const isAtWorkspaceRoot = cwd === workspaceRoot;
|
|
2627
2750
|
const someAppIsElsewhere = Object.values(loadedConfig.workspace.apps).some((app) => (0, node_path.resolve)(workspaceRoot, app.path) !== workspaceRoot);
|
|
2628
2751
|
if (isAtWorkspaceRoot && someAppIsElsewhere) {
|
|
2629
|
-
logger$
|
|
2752
|
+
logger$8.log("📦 Detected workspace configuration");
|
|
2630
2753
|
return workspaceBuildCommand(loadedConfig.workspace, options);
|
|
2631
2754
|
}
|
|
2632
2755
|
}
|
|
2633
2756
|
const config = loadedConfig.type !== "workspace" ? await require_config.loadConfig() : (0, node_path.resolve)(process.cwd()) === (0, node_path.resolve)(loadedConfig.workspace.root) ? rootGkmConfig(loadedConfig.workspace) : (await require_config.loadAppConfig()).gkmConfig;
|
|
2634
2757
|
const resolved = resolveProviders(config, options);
|
|
2635
|
-
const cacheBackend =
|
|
2758
|
+
const cacheBackend = require_workspace$1.cacheBackendFor(require_workspace$1.providerOf(loadedConfig.workspace));
|
|
2636
2759
|
const productionConfigFromGkm = getProductionConfigFromGkm(config);
|
|
2637
2760
|
const production = normalizeProductionConfig(options.production ?? false, productionConfigFromGkm);
|
|
2638
|
-
if (production) logger$
|
|
2639
|
-
logger$
|
|
2640
|
-
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)}`);
|
|
2641
2764
|
const telescope = production ? void 0 : normalizeTelescopeConfig(config.telescope);
|
|
2642
|
-
if (telescope) logger$
|
|
2765
|
+
if (telescope) logger$8.log(`🔭 Telescope enabled at ${telescope.path}`);
|
|
2643
2766
|
const studio = production ? void 0 : normalizeStudioConfig(config.studio);
|
|
2644
|
-
if (studio) logger$
|
|
2767
|
+
if (studio) logger$8.log(`🗄️ Studio enabled at ${studio.path}`);
|
|
2645
2768
|
const hooks = normalizeHooksConfig(config.hooks);
|
|
2646
|
-
if (hooks) logger$
|
|
2769
|
+
if (hooks) logger$8.log(`🪝 Server hooks enabled`);
|
|
2647
2770
|
return buildApp({
|
|
2648
2771
|
config,
|
|
2649
2772
|
workspaceRoot: loadedConfig.workspace.root,
|
|
@@ -2686,7 +2809,7 @@ async function buildApp(input) {
|
|
|
2686
2809
|
});
|
|
2687
2810
|
if (selfServing) {
|
|
2688
2811
|
await writeSurfaceEntry((0, node_path.join)(appRoot, ".gkm", "server"), selfServing.source);
|
|
2689
|
-
logger$
|
|
2812
|
+
logger$8.log(`Generated a server for ${selfServing.id} from its own declaration`);
|
|
2690
2813
|
return { selfServing: selfServing.id };
|
|
2691
2814
|
}
|
|
2692
2815
|
const derived = ownersContext({
|
|
@@ -2726,14 +2849,14 @@ async function buildApp(input) {
|
|
|
2726
2849
|
topicGenerator.load(code, appRoot, bustCache)
|
|
2727
2850
|
]);
|
|
2728
2851
|
const allEndpoints = servedBy(loadedEndpoints, derived.surface);
|
|
2729
|
-
logger$
|
|
2730
|
-
logger$
|
|
2731
|
-
logger$
|
|
2732
|
-
logger$
|
|
2733
|
-
logger$
|
|
2734
|
-
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`);
|
|
2735
2858
|
if (!input.serveEmpty && allEndpoints.length === 0 && allFunctions.length === 0 && allCrons.length === 0 && allSubscribers.length === 0 && allQueues.length === 0 && allTopics.length === 0) {
|
|
2736
|
-
logger$
|
|
2859
|
+
logger$8.log("No endpoints, functions, crons, subscribers, queues, or topics found to process");
|
|
2737
2860
|
return {};
|
|
2738
2861
|
}
|
|
2739
2862
|
const rootOutputDir = (0, node_path.join)(appRoot, ".gkm");
|
|
@@ -2750,7 +2873,7 @@ async function buildForProvider(provider, context, appRoot, discovered, endpoint
|
|
|
2750
2873
|
const rootOutputDir = (0, node_path.join)(appRoot, ".gkm");
|
|
2751
2874
|
const outputDir = (0, node_path.join)(rootOutputDir, provider);
|
|
2752
2875
|
await (0, node_fs_promises.mkdir)(outputDir, { recursive: true });
|
|
2753
|
-
logger$
|
|
2876
|
+
logger$8.log(`\nGenerating handlers for provider: ${provider}`);
|
|
2754
2877
|
const [routes, functionInfos, cronInfos, subscriberInfos, queueInfos, topicInfos] = await Promise.all([
|
|
2755
2878
|
endpointGenerator.build(context, endpoints, outputDir, {
|
|
2756
2879
|
provider,
|
|
@@ -2762,7 +2885,7 @@ async function buildForProvider(provider, context, appRoot, discovered, endpoint
|
|
|
2762
2885
|
queueGenerator.build(context, queues, outputDir, { provider }),
|
|
2763
2886
|
topicGenerator.build(context, topics, outputDir, { provider })
|
|
2764
2887
|
]);
|
|
2765
|
-
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}`);
|
|
2766
2889
|
const manifestRoutes = assembleManifestField(routes, endpoints);
|
|
2767
2890
|
const manifestFunctions = assembleManifestField(functionInfos, functions);
|
|
2768
2891
|
const manifestCrons = assembleManifestField(cronInfos, crons);
|
|
@@ -2782,7 +2905,7 @@ async function buildForProvider(provider, context, appRoot, discovered, endpoint
|
|
|
2782
2905
|
}, serverRouteField, manifestSubscribers, manifestQueues, manifestTopics);
|
|
2783
2906
|
let masterKey;
|
|
2784
2907
|
if (context.production?.bundle && !skipBundle) {
|
|
2785
|
-
logger$
|
|
2908
|
+
logger$8.log(`\n📦 Bundling production server...`);
|
|
2786
2909
|
const { bundleServer } = await Promise.resolve().then(() => require("./bundler-mjqGAvu1.cjs"));
|
|
2787
2910
|
const allConstructs = [
|
|
2788
2911
|
...endpoints.map((e) => e.construct),
|
|
@@ -2800,10 +2923,10 @@ async function buildForProvider(provider, context, appRoot, discovered, endpoint
|
|
|
2800
2923
|
stage,
|
|
2801
2924
|
constructs: allConstructs
|
|
2802
2925
|
})).masterKey;
|
|
2803
|
-
logger$
|
|
2926
|
+
logger$8.log(`✅ Bundle complete: .gkm/server/dist/server.mjs`);
|
|
2804
2927
|
if (masterKey) {
|
|
2805
|
-
logger$
|
|
2806
|
-
logger$
|
|
2928
|
+
logger$8.log(`\n🔐 Secrets encrypted for deployment`);
|
|
2929
|
+
logger$8.log(` Deploy with: GKM_MASTER_KEY=${masterKey}`);
|
|
2807
2930
|
}
|
|
2808
2931
|
}
|
|
2809
2932
|
return { masterKey };
|
|
@@ -2890,19 +3013,19 @@ async function workspaceBuildCommand(workspace, options) {
|
|
|
2890
3013
|
const apps = Object.entries(workspace.apps);
|
|
2891
3014
|
const backendApps = apps.filter(([, app]) => app.type === "backend");
|
|
2892
3015
|
const frontendApps = apps.filter(([, app]) => app.type === "web");
|
|
2893
|
-
logger$
|
|
2894
|
-
logger$
|
|
2895
|
-
logger$
|
|
2896
|
-
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`);
|
|
2897
3020
|
const buildOrder = require_workspace.getAppBuildOrder(workspace);
|
|
2898
|
-
logger$
|
|
3021
|
+
logger$8.log(` Build order: ${buildOrder.join(" → ")}`);
|
|
2899
3022
|
const pm = detectPackageManager$2();
|
|
2900
|
-
logger$
|
|
3023
|
+
logger$8.log(`\n📦 Using ${pm} with Turbo for parallel builds...\n`);
|
|
2901
3024
|
try {
|
|
2902
3025
|
const { filters, unpackaged } = turboFilters(workspace);
|
|
2903
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.`);
|
|
2904
3027
|
const turboCommand = getTurboCommand(pm, filters);
|
|
2905
|
-
logger$
|
|
3028
|
+
logger$8.log(`Running: ${turboCommand}`);
|
|
2906
3029
|
await new Promise((resolve, reject) => {
|
|
2907
3030
|
const child = (0, node_child_process.spawn)(turboCommand, {
|
|
2908
3031
|
shell: true,
|
|
@@ -2930,15 +3053,15 @@ async function workspaceBuildCommand(workspace, options) {
|
|
|
2930
3053
|
outputPath
|
|
2931
3054
|
});
|
|
2932
3055
|
}
|
|
2933
|
-
logger$
|
|
2934
|
-
logger$
|
|
3056
|
+
logger$8.log(`\n✅ Workspace build complete!`);
|
|
3057
|
+
logger$8.log(`\n📋 Build Summary:`);
|
|
2935
3058
|
for (const result of results) {
|
|
2936
3059
|
const icon = result.type === "backend" ? "⚙️" : result.type === "mobile" ? "📱" : "🌐";
|
|
2937
|
-
logger$
|
|
3060
|
+
logger$8.log(` ${icon} ${result.appName}: ${result.outputPath || "built"}`);
|
|
2938
3061
|
}
|
|
2939
3062
|
} catch (error) {
|
|
2940
3063
|
const errorMessage = error instanceof Error ? error.message : "Build failed";
|
|
2941
|
-
logger$
|
|
3064
|
+
logger$8.log(`\n❌ Build failed: ${errorMessage}`);
|
|
2942
3065
|
for (const [appName, app] of apps) results.push({
|
|
2943
3066
|
appName,
|
|
2944
3067
|
type: app.type,
|
|
@@ -3066,7 +3189,7 @@ const PROVISIONERS = {
|
|
|
3066
3189
|
*/
|
|
3067
3190
|
database: async (declaration, context) => {
|
|
3068
3191
|
if (declaration.kind !== "database") throw new WrongKind(declaration.kind);
|
|
3069
|
-
const name =
|
|
3192
|
+
const name = require_workspace$1.resourceName(declaration.id, declaration.kind, context.stage);
|
|
3070
3193
|
const { postgres } = await context.api.findOrCreatePostgres(serviceName({
|
|
3071
3194
|
stage: context.stage,
|
|
3072
3195
|
app: context.project
|
|
@@ -3102,7 +3225,7 @@ const PROVISIONERS = {
|
|
|
3102
3225
|
if (declaration.kind !== "database-schema") throw new WrongKind(declaration.kind);
|
|
3103
3226
|
const parent = context.provisioned[declaration.of];
|
|
3104
3227
|
if (!parent) throw new UnresolvedParent(declaration.id, declaration.of);
|
|
3105
|
-
const runtime =
|
|
3228
|
+
const runtime = require_workspace$1.resourceName(declaration.id, declaration.kind, context.stage);
|
|
3106
3229
|
const owner = (0, _geekmidas_db_pg_roles.ownerRole)(runtime);
|
|
3107
3230
|
const reads = readsFrom(context.manifest, declaration.id);
|
|
3108
3231
|
const parentUrl = new URL(parent.provides[(0, _geekmidas_manifest.provideKey)(declaration.of, "url")] ?? "");
|
|
@@ -3204,7 +3327,7 @@ const PROVISIONERS = {
|
|
|
3204
3327
|
stage: context.stage,
|
|
3205
3328
|
app: context.project
|
|
3206
3329
|
}, declaration.id);
|
|
3207
|
-
const bucket =
|
|
3330
|
+
const bucket = require_workspace$1.resourceName(declaration.id, declaration.kind, context.stage);
|
|
3208
3331
|
const user = `${bucket}-root`;
|
|
3209
3332
|
const password = derivedPassword(context, `objects:${declaration.id}`);
|
|
3210
3333
|
const { compose } = await context.api.findOrCreateCompose(service, context.projectId, context.environmentId, minioCompose({
|
|
@@ -3572,10 +3695,6 @@ var UnprovisionableBucket = class extends Error {
|
|
|
3572
3695
|
*/
|
|
3573
3696
|
/**
|
|
3574
3697
|
* Provision everything the workspace declares, and return what it resolved.
|
|
3575
|
-
*
|
|
3576
|
-
* Nothing happens for a project that has not adopted the model: `usesConstructs`
|
|
3577
|
-
* is the same hard switch reconcile reads, so an existing deploy is untouched
|
|
3578
|
-
* until it declares something.
|
|
3579
3698
|
*/
|
|
3580
3699
|
async function provisionDeclared(options) {
|
|
3581
3700
|
const { workspace } = options;
|
|
@@ -3585,9 +3704,8 @@ async function provisionDeclared(options) {
|
|
|
3585
3704
|
provisioned: {},
|
|
3586
3705
|
clusters: {}
|
|
3587
3706
|
};
|
|
3588
|
-
if (!require_credentials.usesConstructs(workspace)) return empty;
|
|
3589
3707
|
const manifest = options.manifest ?? await require_discover.discover({
|
|
3590
|
-
patterns:
|
|
3708
|
+
patterns: require_workspace$1.constructGlobs(workspace),
|
|
3591
3709
|
cwd: workspace.root
|
|
3592
3710
|
});
|
|
3593
3711
|
if (Object.keys(manifest).length === 0) return empty;
|
|
@@ -3599,9 +3717,9 @@ async function provisionDeclared(options) {
|
|
|
3599
3717
|
environmentId: options.environmentId,
|
|
3600
3718
|
stage: options.stage,
|
|
3601
3719
|
project: workspace.name,
|
|
3602
|
-
cache:
|
|
3603
|
-
events:
|
|
3604
|
-
storage:
|
|
3720
|
+
cache: require_workspace$1.cacheBackendFor(require_workspace$1.providerOf(workspace)),
|
|
3721
|
+
events: require_workspace$1.eventsBackendFor(require_workspace$1.providerOf(workspace)),
|
|
3722
|
+
storage: require_workspace$1.storageBackendFor(require_workspace$1.providerOf(workspace)),
|
|
3605
3723
|
addresses: surfaceAddresses(workspace, manifest, options.appUrls),
|
|
3606
3724
|
...options.secrets ? { secrets: options.secrets } : {},
|
|
3607
3725
|
deferred: [],
|
|
@@ -3638,7 +3756,7 @@ async function provisionDeclared(options) {
|
|
|
3638
3756
|
* run again.
|
|
3639
3757
|
*/
|
|
3640
3758
|
async function applyDeclared(client, statements) {
|
|
3641
|
-
return (await
|
|
3759
|
+
return (await require_workspace$1.applyPostgres(client, statements)).filter((entry) => entry.created).length;
|
|
3642
3760
|
}
|
|
3643
3761
|
/**
|
|
3644
3762
|
* The provisioning order, with queues and topics moved to the end.
|
|
@@ -3827,7 +3945,7 @@ async function createDnsProvider(options) {
|
|
|
3827
3945
|
*
|
|
3828
3946
|
* Handles automatic DNS record creation for deployed applications.
|
|
3829
3947
|
*/
|
|
3830
|
-
const logger$
|
|
3948
|
+
const logger$7 = console;
|
|
3831
3949
|
/**
|
|
3832
3950
|
* Check if DNS config is legacy format (single domain with `domain` property)
|
|
3833
3951
|
*/
|
|
@@ -3864,7 +3982,7 @@ function groupHostnamesByDomain(appHostnames, dnsConfig) {
|
|
|
3864
3982
|
for (const [appName, hostname] of appHostnames) {
|
|
3865
3983
|
const rootDomain = findRootDomain(hostname, dnsConfig);
|
|
3866
3984
|
if (!rootDomain) {
|
|
3867
|
-
logger$
|
|
3985
|
+
logger$7.log(` ⚠ No DNS config found for hostname: ${hostname}`);
|
|
3868
3986
|
continue;
|
|
3869
3987
|
}
|
|
3870
3988
|
if (!grouped.has(rootDomain)) grouped.set(rootDomain, /* @__PURE__ */ new Map());
|
|
@@ -3914,10 +4032,10 @@ function generateRequiredRecords(appHostnames, rootDomain, serverIp) {
|
|
|
3914
4032
|
* Print DNS records table
|
|
3915
4033
|
*/
|
|
3916
4034
|
function printDnsRecordsTable(records, rootDomain) {
|
|
3917
|
-
logger$
|
|
3918
|
-
logger$
|
|
3919
|
-
logger$
|
|
3920
|
-
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(" ├─────────────────────────────────────┼──────┼─────────────────┼────────┤");
|
|
3921
4039
|
for (const record of records) {
|
|
3922
4040
|
const subdomain = record.subdomain.padEnd(35);
|
|
3923
4041
|
const type = record.type.padEnd(4);
|
|
@@ -3927,18 +4045,18 @@ function printDnsRecordsTable(records, rootDomain) {
|
|
|
3927
4045
|
else if (record.created) status = "✓ new";
|
|
3928
4046
|
else if (record.existed) status = "✓";
|
|
3929
4047
|
else status = "?";
|
|
3930
|
-
logger$
|
|
4048
|
+
logger$7.log(` │ ${subdomain} │ ${type} │ ${value} │ ${status.padEnd(6)} │`);
|
|
3931
4049
|
}
|
|
3932
|
-
logger$
|
|
4050
|
+
logger$7.log(" └─────────────────────────────────────┴──────┴─────────────────┴────────┘");
|
|
3933
4051
|
}
|
|
3934
4052
|
/**
|
|
3935
4053
|
* Print DNS records in a simple format for manual setup
|
|
3936
4054
|
*/
|
|
3937
4055
|
function printDnsRecordsSimple(records, rootDomain) {
|
|
3938
|
-
logger$
|
|
3939
|
-
logger$
|
|
3940
|
-
for (const record of records) logger$
|
|
3941
|
-
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("");
|
|
3942
4060
|
}
|
|
3943
4061
|
/**
|
|
3944
4062
|
* Create DNS records for a single domain using its configured provider
|
|
@@ -3950,7 +4068,7 @@ async function createDnsRecordsForDomain(records, rootDomain, providerConfig) {
|
|
|
3950
4068
|
provider = await createDnsProvider({ config: providerConfig });
|
|
3951
4069
|
} catch (error) {
|
|
3952
4070
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
3953
|
-
logger$
|
|
4071
|
+
logger$7.log(` ⚠ Failed to create DNS provider for ${rootDomain}: ${message}`);
|
|
3954
4072
|
return records.map((r) => ({
|
|
3955
4073
|
...r,
|
|
3956
4074
|
error: message
|
|
@@ -4004,7 +4122,7 @@ async function createDnsRecordsForDomain(records, rootDomain, providerConfig) {
|
|
|
4004
4122
|
}
|
|
4005
4123
|
} catch (error) {
|
|
4006
4124
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4007
|
-
logger$
|
|
4125
|
+
logger$7.log(` ⚠ Failed to create DNS records for ${rootDomain}: ${message}`);
|
|
4008
4126
|
return records.map((r) => ({
|
|
4009
4127
|
hostname: r.hostname,
|
|
4010
4128
|
subdomain: r.subdomain,
|
|
@@ -4031,20 +4149,20 @@ async function createDnsRecordsForDomain(records, rootDomain, providerConfig) {
|
|
|
4031
4149
|
async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint, state) {
|
|
4032
4150
|
if (!dnsConfig) return null;
|
|
4033
4151
|
const normalizedConfig = normalizeDnsConfig(dnsConfig);
|
|
4034
|
-
logger$
|
|
4152
|
+
logger$7.log("\n🌐 Setting up DNS records...");
|
|
4035
4153
|
let serverIp;
|
|
4036
4154
|
try {
|
|
4037
4155
|
const endpointUrl = new URL(dokployEndpoint);
|
|
4038
4156
|
serverIp = await resolveHostnameToIp(endpointUrl.hostname);
|
|
4039
|
-
logger$
|
|
4157
|
+
logger$7.log(` Server IP: ${serverIp} (from ${endpointUrl.hostname})`);
|
|
4040
4158
|
} catch (error) {
|
|
4041
4159
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4042
|
-
logger$
|
|
4160
|
+
logger$7.log(` ⚠ Failed to resolve server IP: ${message}`);
|
|
4043
4161
|
return null;
|
|
4044
4162
|
}
|
|
4045
4163
|
const groupedHostnames = groupHostnamesByDomain(appHostnames, normalizedConfig);
|
|
4046
4164
|
if (groupedHostnames.size === 0) {
|
|
4047
|
-
logger$
|
|
4165
|
+
logger$7.log(" No DNS records needed (no hostnames match configured domains)");
|
|
4048
4166
|
return {
|
|
4049
4167
|
records: [],
|
|
4050
4168
|
success: true,
|
|
@@ -4056,22 +4174,22 @@ async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint, state) {
|
|
|
4056
4174
|
for (const [rootDomain, domainHostnames] of groupedHostnames) {
|
|
4057
4175
|
const providerConfig = normalizedConfig[rootDomain];
|
|
4058
4176
|
if (!providerConfig) {
|
|
4059
|
-
logger$
|
|
4177
|
+
logger$7.log(` ⚠ No provider config for ${rootDomain}`);
|
|
4060
4178
|
continue;
|
|
4061
4179
|
}
|
|
4062
4180
|
const providerName = typeof providerConfig.provider === "string" ? providerConfig.provider : "custom";
|
|
4063
4181
|
const requiredRecords = generateRequiredRecords(domainHostnames, rootDomain, serverIp);
|
|
4064
4182
|
if (requiredRecords.length === 0) continue;
|
|
4065
|
-
logger$
|
|
4183
|
+
logger$7.log(` Creating DNS records for ${rootDomain} (${providerName})...`);
|
|
4066
4184
|
const domainRecords = await createDnsRecordsForDomain(requiredRecords, rootDomain, providerConfig);
|
|
4067
4185
|
allRecords.push(...domainRecords);
|
|
4068
4186
|
const created = domainRecords.filter((r) => r.created).length;
|
|
4069
4187
|
const existed = domainRecords.filter((r) => r.existed).length;
|
|
4070
4188
|
const failed = domainRecords.filter((r) => r.error).length;
|
|
4071
|
-
if (created > 0) logger$
|
|
4072
|
-
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}`);
|
|
4073
4191
|
if (failed > 0) {
|
|
4074
|
-
logger$
|
|
4192
|
+
logger$7.log(` ⚠ ${failed} record(s) failed for ${rootDomain}`);
|
|
4075
4193
|
hasFailures = true;
|
|
4076
4194
|
}
|
|
4077
4195
|
if (state) {
|
|
@@ -4108,10 +4226,10 @@ async function orchestrateDns(appHostnames, dnsConfig, dokployEndpoint, state) {
|
|
|
4108
4226
|
*/
|
|
4109
4227
|
async function verifyDnsRecords(appHostnames, serverIp, state) {
|
|
4110
4228
|
const results = [];
|
|
4111
|
-
logger$
|
|
4229
|
+
logger$7.log("\n🔍 Verifying DNS records...");
|
|
4112
4230
|
for (const [appName, hostname] of appHostnames) {
|
|
4113
4231
|
if (isDnsVerified(state, hostname, serverIp)) {
|
|
4114
|
-
logger$
|
|
4232
|
+
logger$7.log(` ✓ ${hostname} (previously verified)`);
|
|
4115
4233
|
results.push({
|
|
4116
4234
|
hostname,
|
|
4117
4235
|
appName,
|
|
@@ -4125,7 +4243,7 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
|
|
|
4125
4243
|
const resolvedIp = await resolveHostnameToIp(hostname);
|
|
4126
4244
|
if (resolvedIp === serverIp) {
|
|
4127
4245
|
setDnsVerification(state, hostname, serverIp);
|
|
4128
|
-
logger$
|
|
4246
|
+
logger$7.log(` ✓ ${hostname} → ${resolvedIp}`);
|
|
4129
4247
|
results.push({
|
|
4130
4248
|
hostname,
|
|
4131
4249
|
appName,
|
|
@@ -4134,7 +4252,7 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
|
|
|
4134
4252
|
expectedIp: serverIp
|
|
4135
4253
|
});
|
|
4136
4254
|
} else {
|
|
4137
|
-
logger$
|
|
4255
|
+
logger$7.log(` ⚠ ${hostname} resolves to ${resolvedIp}, expected ${serverIp}`);
|
|
4138
4256
|
results.push({
|
|
4139
4257
|
hostname,
|
|
4140
4258
|
appName,
|
|
@@ -4145,7 +4263,7 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
|
|
|
4145
4263
|
}
|
|
4146
4264
|
} catch (error) {
|
|
4147
4265
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
4148
|
-
logger$
|
|
4266
|
+
logger$7.log(` ⚠ ${hostname} DNS not propagated (${message})`);
|
|
4149
4267
|
results.push({
|
|
4150
4268
|
hostname,
|
|
4151
4269
|
appName,
|
|
@@ -4159,9 +4277,9 @@ async function verifyDnsRecords(appHostnames, serverIp, state) {
|
|
|
4159
4277
|
const skipped = results.filter((r) => r.skipped).length;
|
|
4160
4278
|
const pending = results.filter((r) => !r.verified).length;
|
|
4161
4279
|
if (pending > 0) {
|
|
4162
|
-
logger$
|
|
4163
|
-
logger$
|
|
4164
|
-
} 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)`);
|
|
4165
4283
|
return results;
|
|
4166
4284
|
}
|
|
4167
4285
|
//#endregion
|
|
@@ -4194,43 +4312,19 @@ function resolveHost(appName, app, stage, dokployConfig, isMainFrontend) {
|
|
|
4194
4312
|
return `${appName}.${baseDomain}`;
|
|
4195
4313
|
}
|
|
4196
4314
|
/**
|
|
4197
|
-
*
|
|
4198
|
-
*
|
|
4199
|
-
* Three rules, in order, and the last one is the point:
|
|
4200
|
-
*
|
|
4201
|
-
* 1. **One site** — it is the root. Unambiguous, so nothing has to be said.
|
|
4202
|
-
* 2. **Named `web`** — the convention wins, because people already rely on it.
|
|
4203
|
-
* 3. **Declared `root: true`** — the site says so itself.
|
|
4204
|
-
*
|
|
4205
|
-
* Anything else throws. It used to take the *first* `type: 'web'` it iterated,
|
|
4206
|
-
* and that object is built from the manifest, which is built in glob traversal
|
|
4207
|
-
* order — so two sites with neither named `web` meant renaming a file could
|
|
4208
|
-
* move the production root domain, silently. The same shape as
|
|
4209
|
-
* `CacheIsAmbiguous`: unambiguous with one and arbitrary with two, so two is an
|
|
4210
|
-
* 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.
|
|
4211
4317
|
*
|
|
4212
4318
|
* @throws {AmbiguousRootSite} when several sites could be the root and none says it is
|
|
4213
4319
|
*/
|
|
4214
4320
|
function isMainFrontendApp(appName, app, allApps) {
|
|
4215
4321
|
if (app.type !== "web") return false;
|
|
4216
|
-
const sites = Object.entries(allApps).filter(([, a]) => a.type === "web")
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
}
|
|
4223
|
-
/** Several sites could hold the base domain, and none of them says it does. */
|
|
4224
|
-
var AmbiguousRootSite = class extends Error {
|
|
4225
|
-
sites;
|
|
4226
|
-
tooMany;
|
|
4227
|
-
constructor(sites, tooMany = false) {
|
|
4228
|
-
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.`);
|
|
4229
|
-
this.sites = sites;
|
|
4230
|
-
this.tooMany = tooMany;
|
|
4231
|
-
this.name = "AmbiguousRootSite";
|
|
4232
|
-
}
|
|
4233
|
-
};
|
|
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
|
+
}
|
|
4234
4328
|
/**
|
|
4235
4329
|
* Get public URL arg names from app dependencies.
|
|
4236
4330
|
*
|
|
@@ -5093,7 +5187,7 @@ CMD ["nginx", "-g", "daemon off;"]
|
|
|
5093
5187
|
}
|
|
5094
5188
|
//#endregion
|
|
5095
5189
|
//#region src/docker/index.ts
|
|
5096
|
-
const logger$
|
|
5190
|
+
const logger$6 = console;
|
|
5097
5191
|
/**
|
|
5098
5192
|
* Docker command implementation
|
|
5099
5193
|
* Generates Dockerfile, docker-compose.yml, and related files
|
|
@@ -5104,7 +5198,7 @@ const logger$7 = console;
|
|
|
5104
5198
|
async function dockerCommand(options) {
|
|
5105
5199
|
const loadedConfig = await require_config.loadWorkspaceConfig();
|
|
5106
5200
|
if (loadedConfig.type === "workspace") {
|
|
5107
|
-
logger$
|
|
5201
|
+
logger$6.log("📦 Detected workspace configuration");
|
|
5108
5202
|
return workspaceDockerCommand(loadedConfig.workspace);
|
|
5109
5203
|
}
|
|
5110
5204
|
const config = await require_config.loadConfig();
|
|
@@ -5124,7 +5218,7 @@ async function dockerCommand(options) {
|
|
|
5124
5218
|
if (inMonorepo && !useSlim) {
|
|
5125
5219
|
if (hasTurbo) {
|
|
5126
5220
|
useTurbo = true;
|
|
5127
|
-
logger$
|
|
5221
|
+
logger$6.log(" Detected monorepo with turbo.json - using turbo prune");
|
|
5128
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");
|
|
5129
5223
|
}
|
|
5130
5224
|
let turboPackage = options.turboPackage ?? dockerConfig.imageName;
|
|
@@ -5132,7 +5226,7 @@ async function dockerCommand(options) {
|
|
|
5132
5226
|
const pkg = require(`${process.cwd()}/package.json`);
|
|
5133
5227
|
if (pkg.name) {
|
|
5134
5228
|
turboPackage = pkg.name;
|
|
5135
|
-
logger$
|
|
5229
|
+
logger$6.log(` Turbo package: ${turboPackage}`);
|
|
5136
5230
|
}
|
|
5137
5231
|
} catch {}
|
|
5138
5232
|
const templateOptions = {
|
|
@@ -5149,16 +5243,16 @@ async function dockerCommand(options) {
|
|
|
5149
5243
|
const dockerMode = useSlim ? "slim" : useTurbo ? "turbo" : "multi-stage";
|
|
5150
5244
|
const dockerfilePath = (0, node_path.join)(dockerDir, "Dockerfile");
|
|
5151
5245
|
await (0, node_fs_promises.writeFile)(dockerfilePath, dockerfile);
|
|
5152
|
-
logger$
|
|
5246
|
+
logger$6.log(`Generated: .gkm/docker/Dockerfile (${dockerMode}, ${packageManager})`);
|
|
5153
5247
|
const composePath = await writeConstructsCompose(loadedConfig.workspace);
|
|
5154
5248
|
const dockerignore = generateDockerignore();
|
|
5155
5249
|
const dockerignorePath = (0, node_path.join)(process.cwd(), ".dockerignore");
|
|
5156
5250
|
await (0, node_fs_promises.writeFile)(dockerignorePath, dockerignore);
|
|
5157
|
-
logger$
|
|
5251
|
+
logger$6.log("Generated: .dockerignore (project root)");
|
|
5158
5252
|
const entrypoint = generateDockerEntrypoint();
|
|
5159
5253
|
const entrypointPath = (0, node_path.join)(dockerDir, "docker-entrypoint.sh");
|
|
5160
5254
|
await (0, node_fs_promises.writeFile)(entrypointPath, entrypoint);
|
|
5161
|
-
logger$
|
|
5255
|
+
logger$6.log("Generated: .gkm/docker/docker-entrypoint.sh");
|
|
5162
5256
|
const result = {
|
|
5163
5257
|
dockerfile: dockerfilePath,
|
|
5164
5258
|
dockerCompose: composePath,
|
|
@@ -5177,13 +5271,13 @@ async function dockerCommand(options) {
|
|
|
5177
5271
|
function ensureLockfile(cwd) {
|
|
5178
5272
|
const lockfilePath = findLockfilePath(cwd);
|
|
5179
5273
|
if (!lockfilePath) {
|
|
5180
|
-
logger$
|
|
5274
|
+
logger$6.warn("\n⚠️ No lockfile found. Docker build may fail or use stale dependencies.");
|
|
5181
5275
|
return null;
|
|
5182
5276
|
}
|
|
5183
5277
|
const lockfileName = (0, node_path.basename)(lockfilePath);
|
|
5184
5278
|
const localLockfile = (0, node_path.join)(cwd, lockfileName);
|
|
5185
5279
|
if (lockfilePath === localLockfile) return null;
|
|
5186
|
-
logger$
|
|
5280
|
+
logger$6.log(` Copying ${lockfileName} from monorepo root...`);
|
|
5187
5281
|
(0, node_fs.copyFileSync)(lockfilePath, localLockfile);
|
|
5188
5282
|
return () => {
|
|
5189
5283
|
try {
|
|
@@ -5199,7 +5293,7 @@ async function buildDockerImage(imageName, options) {
|
|
|
5199
5293
|
const tag = options.tag ?? "latest";
|
|
5200
5294
|
const registry = options.registry;
|
|
5201
5295
|
const fullImageName = registry ? `${registry}/${imageName}:${tag}` : `${imageName}:${tag}`;
|
|
5202
|
-
logger$
|
|
5296
|
+
logger$6.log(`\n🐳 Building Docker image: ${fullImageName}`);
|
|
5203
5297
|
const cwd = process.cwd();
|
|
5204
5298
|
const cleanup = ensureLockfile(cwd);
|
|
5205
5299
|
try {
|
|
@@ -5211,7 +5305,7 @@ async function buildDockerImage(imageName, options) {
|
|
|
5211
5305
|
DOCKER_BUILDKIT: "1"
|
|
5212
5306
|
}
|
|
5213
5307
|
});
|
|
5214
|
-
logger$
|
|
5308
|
+
logger$6.log(`✅ Docker image built: ${fullImageName}`);
|
|
5215
5309
|
} catch (error) {
|
|
5216
5310
|
throw new Error(`Failed to build Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
5217
5311
|
} finally {
|
|
@@ -5226,13 +5320,13 @@ async function pushDockerImage(imageName, options) {
|
|
|
5226
5320
|
const registry = options.registry;
|
|
5227
5321
|
if (!registry) throw new Error("Registry is required to push Docker image. Use --registry or configure docker.registry in gkm.config.ts");
|
|
5228
5322
|
const fullImageName = `${registry}/${imageName}:${tag}`;
|
|
5229
|
-
logger$
|
|
5323
|
+
logger$6.log(`\n🚀 Pushing Docker image: ${fullImageName}`);
|
|
5230
5324
|
try {
|
|
5231
5325
|
(0, node_child_process.execSync)(`docker push ${fullImageName}`, {
|
|
5232
5326
|
cwd: process.cwd(),
|
|
5233
5327
|
stdio: "inherit"
|
|
5234
5328
|
});
|
|
5235
|
-
logger$
|
|
5329
|
+
logger$6.log(`✅ Docker image pushed: ${fullImageName}`);
|
|
5236
5330
|
} catch (error) {
|
|
5237
5331
|
throw new Error(`Failed to push Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
5238
5332
|
}
|
|
@@ -5257,21 +5351,21 @@ function getAppPackageName(appPath) {
|
|
|
5257
5351
|
async function workspaceDockerCommand(workspace) {
|
|
5258
5352
|
const results = [];
|
|
5259
5353
|
const apps = Object.entries(workspace.apps);
|
|
5260
|
-
logger$
|
|
5354
|
+
logger$6.log(`\n🐳 Generating Dockerfiles for workspace: ${workspace.name}`);
|
|
5261
5355
|
const dockerDir = (0, node_path.join)(workspace.root, ".gkm", "docker");
|
|
5262
5356
|
await (0, node_fs_promises.mkdir)(dockerDir, { recursive: true });
|
|
5263
5357
|
const packageManager = detectPackageManager$1(workspace.root);
|
|
5264
|
-
logger$
|
|
5358
|
+
logger$6.log(` Package manager: ${packageManager}`);
|
|
5265
5359
|
for (const [appName, app] of apps) {
|
|
5266
5360
|
if (app.type === "mobile") {
|
|
5267
|
-
logger$
|
|
5361
|
+
logger$6.log(`\n 📱 Skipping Docker for ${appName} (mobile app — deploy via framework toolchain)`);
|
|
5268
5362
|
continue;
|
|
5269
5363
|
}
|
|
5270
5364
|
const appPath = app.path;
|
|
5271
5365
|
const turboPackage = getAppPackageName((0, node_path.join)(workspace.root, appPath)) ?? appName;
|
|
5272
5366
|
const imageName = appName;
|
|
5273
5367
|
const buildType = !!app.entry ? "entry" : app.type;
|
|
5274
|
-
logger$
|
|
5368
|
+
logger$6.log(`\n 📄 Generating Dockerfile for ${appName} (${buildType})`);
|
|
5275
5369
|
let dockerfile;
|
|
5276
5370
|
if (app.type === "web") {
|
|
5277
5371
|
const publicUrlArgs = getPublicUrlArgNames(app);
|
|
@@ -5315,7 +5409,7 @@ async function workspaceDockerCommand(workspace) {
|
|
|
5315
5409
|
});
|
|
5316
5410
|
const dockerfilePath = (0, node_path.join)(dockerDir, `Dockerfile.${appName}`);
|
|
5317
5411
|
await (0, node_fs_promises.writeFile)(dockerfilePath, dockerfile);
|
|
5318
|
-
logger$
|
|
5412
|
+
logger$6.log(` Generated: .gkm/docker/Dockerfile.${appName}`);
|
|
5319
5413
|
results.push({
|
|
5320
5414
|
appName,
|
|
5321
5415
|
type: app.type,
|
|
@@ -5326,13 +5420,13 @@ async function workspaceDockerCommand(workspace) {
|
|
|
5326
5420
|
const dockerignore = generateDockerignore();
|
|
5327
5421
|
const dockerignorePath = (0, node_path.join)(workspace.root, ".dockerignore");
|
|
5328
5422
|
await (0, node_fs_promises.writeFile)(dockerignorePath, dockerignore);
|
|
5329
|
-
logger$
|
|
5423
|
+
logger$6.log(`\n Generated: .dockerignore (workspace root)`);
|
|
5330
5424
|
const composePath = await writeConstructsCompose(workspace);
|
|
5331
|
-
logger$
|
|
5332
|
-
logger$
|
|
5425
|
+
logger$6.log(`\n✅ Generated ${results.length} Dockerfile(s) + ${require_workspace$1.COMPOSE_PATH}`);
|
|
5426
|
+
logger$6.log("\n📋 Build commands:");
|
|
5333
5427
|
for (const result of results) {
|
|
5334
5428
|
const icon = result.type === "backend" ? "⚙️" : result.type === "mobile" ? "📱" : "🌐";
|
|
5335
|
-
logger$
|
|
5429
|
+
logger$6.log(` ${icon} docker build -f .gkm/docker/Dockerfile.${result.appName} -t ${result.imageName} .`);
|
|
5336
5430
|
}
|
|
5337
5431
|
printRunInstructions(workspace);
|
|
5338
5432
|
return {
|
|
@@ -5349,22 +5443,22 @@ async function workspaceDockerCommand(workspace) {
|
|
|
5349
5443
|
* describes and what `gkm dev` runs cannot disagree. Nothing is started.
|
|
5350
5444
|
*/
|
|
5351
5445
|
async function writeConstructsCompose(workspace) {
|
|
5352
|
-
await
|
|
5446
|
+
await require_workspace$1.reconcileWorkspace(workspace, {
|
|
5353
5447
|
stage: workspace.stages.local,
|
|
5354
5448
|
start: false
|
|
5355
5449
|
});
|
|
5356
|
-
logger$
|
|
5357
|
-
return (0, node_path.join)(workspace.root,
|
|
5450
|
+
logger$6.log(` Generated: ${require_workspace$1.COMPOSE_PATH} (project root)`);
|
|
5451
|
+
return (0, node_path.join)(workspace.root, require_workspace$1.COMPOSE_PATH);
|
|
5358
5452
|
}
|
|
5359
5453
|
function printRunInstructions(workspace) {
|
|
5360
5454
|
const project = (0, node_fs.existsSync)((0, node_path.join)(workspace.root, "docker-compose.yml")) ? " -f docker-compose.yml" : "";
|
|
5361
|
-
logger$
|
|
5362
|
-
logger$
|
|
5363
|
-
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`);
|
|
5364
5458
|
}
|
|
5365
5459
|
//#endregion
|
|
5366
5460
|
//#region src/deploy/docker.ts
|
|
5367
|
-
const logger$
|
|
5461
|
+
const logger$5 = console;
|
|
5368
5462
|
/**
|
|
5369
5463
|
* Get the full image reference
|
|
5370
5464
|
*/
|
|
@@ -5379,9 +5473,9 @@ function getImageRef(registry, imageName, tag) {
|
|
|
5379
5473
|
* @param buildArgs - Build arguments to pass to docker build
|
|
5380
5474
|
*/
|
|
5381
5475
|
async function buildImage(imageRef, _appName, buildArgs) {
|
|
5382
|
-
logger$
|
|
5476
|
+
logger$5.log(`\n🔨 Building Docker image: ${imageRef}`);
|
|
5383
5477
|
const cwd = process.cwd();
|
|
5384
|
-
logger$
|
|
5478
|
+
logger$5.log(" Generating Dockerfile for the pre-built bundle...");
|
|
5385
5479
|
await dockerCommand({ slim: true });
|
|
5386
5480
|
const dockerfilePath = (0, node_path.join)(cwd, ".gkm", "docker", "Dockerfile");
|
|
5387
5481
|
const buildCwd = cwd;
|
|
@@ -5403,7 +5497,7 @@ async function buildImage(imageRef, _appName, buildArgs) {
|
|
|
5403
5497
|
DOCKER_BUILDKIT: "1"
|
|
5404
5498
|
}
|
|
5405
5499
|
});
|
|
5406
|
-
logger$
|
|
5500
|
+
logger$5.log(`✅ Image built: ${imageRef}`);
|
|
5407
5501
|
} catch (error) {
|
|
5408
5502
|
throw new Error(`Failed to build Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
5409
5503
|
}
|
|
@@ -5412,13 +5506,13 @@ async function buildImage(imageRef, _appName, buildArgs) {
|
|
|
5412
5506
|
* Push Docker image to registry
|
|
5413
5507
|
*/
|
|
5414
5508
|
async function pushImage(imageRef) {
|
|
5415
|
-
logger$
|
|
5509
|
+
logger$5.log(`\n☁️ Pushing image: ${imageRef}`);
|
|
5416
5510
|
try {
|
|
5417
5511
|
(0, node_child_process.execSync)(`docker push ${imageRef}`, {
|
|
5418
5512
|
cwd: process.cwd(),
|
|
5419
5513
|
stdio: "inherit"
|
|
5420
5514
|
});
|
|
5421
|
-
logger$
|
|
5515
|
+
logger$5.log(`✅ Image pushed: ${imageRef}`);
|
|
5422
5516
|
} catch (error) {
|
|
5423
5517
|
throw new Error(`Failed to push Docker image: ${error instanceof Error ? error.message : "Unknown error"}`);
|
|
5424
5518
|
}
|
|
@@ -5432,18 +5526,18 @@ async function deployDocker(options) {
|
|
|
5432
5526
|
const imageRef = getImageRef(config.registry, imageName, tag);
|
|
5433
5527
|
await buildImage(imageRef, config.appName, buildArgs);
|
|
5434
5528
|
if (!skipPush) {
|
|
5435
|
-
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");
|
|
5436
5530
|
else await pushImage(imageRef);
|
|
5437
5531
|
}
|
|
5438
|
-
logger$
|
|
5439
|
-
logger$
|
|
5440
|
-
logger$
|
|
5441
|
-
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}`);
|
|
5442
5536
|
if (masterKey) {
|
|
5443
|
-
logger$
|
|
5444
|
-
logger$
|
|
5445
|
-
logger$
|
|
5446
|
-
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}`);
|
|
5447
5541
|
}
|
|
5448
5542
|
return {
|
|
5449
5543
|
imageRef,
|
|
@@ -6822,7 +6916,7 @@ async function sniffAllApps(apps, workspacePath, options = {}) {
|
|
|
6822
6916
|
*
|
|
6823
6917
|
* @module deploy
|
|
6824
6918
|
*/
|
|
6825
|
-
const logger$
|
|
6919
|
+
const logger$4 = console;
|
|
6826
6920
|
/**
|
|
6827
6921
|
* Prompt for input
|
|
6828
6922
|
*/
|
|
@@ -6896,7 +6990,7 @@ async function waitForPostgres(host, port, user, password, database, maxRetries
|
|
|
6896
6990
|
return;
|
|
6897
6991
|
} catch {
|
|
6898
6992
|
if (i < maxRetries - 1) {
|
|
6899
|
-
logger$
|
|
6993
|
+
logger$4.log(` Waiting for Postgres... (${i + 1}/${maxRetries})`);
|
|
6900
6994
|
await new Promise((r) => setTimeout(r, retryIntervalMs));
|
|
6901
6995
|
}
|
|
6902
6996
|
}
|
|
@@ -6939,10 +7033,10 @@ async function applyDeclaredStatements(api, postgres, serverHostname, statements
|
|
|
6939
7033
|
break;
|
|
6940
7034
|
} catch (error) {
|
|
6941
7035
|
if (!(error instanceof Error ? error.message : String(error)).includes("already in use")) throw error;
|
|
6942
|
-
logger$
|
|
7036
|
+
logger$4.log(` Port ${candidate} is taken; trying another...`);
|
|
6943
7037
|
}
|
|
6944
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.`);
|
|
6945
|
-
logger$
|
|
7039
|
+
logger$4.log(` Publishing ${postgres.appName} on ${externalPort}...`);
|
|
6946
7040
|
}
|
|
6947
7041
|
await api.deployPostgres(postgres.postgresId);
|
|
6948
7042
|
await waitForPostgres(serverHostname, externalPort, postgres.databaseUser, postgres.databasePassword, postgres.databaseName);
|
|
@@ -6966,7 +7060,7 @@ async function applyDeclaredStatements(api, postgres, serverHostname, statements
|
|
|
6966
7060
|
const unpublish = async () => {
|
|
6967
7061
|
if (!opened) return;
|
|
6968
7062
|
await api.savePostgresExternalPort(postgres.postgresId, null).then(() => api.deployPostgres(postgres.postgresId)).catch(() => {
|
|
6969
|
-
logger$
|
|
7063
|
+
logger$4.log(` ⚠ Could not close external port ${externalPort} on ${postgres.appName} — close it in Dokploy.`);
|
|
6970
7064
|
});
|
|
6971
7065
|
};
|
|
6972
7066
|
let lastError;
|
|
@@ -6977,7 +7071,7 @@ async function applyDeclaredStatements(api, postgres, serverHostname, statements
|
|
|
6977
7071
|
lastError = error;
|
|
6978
7072
|
if (attempt === 3) break;
|
|
6979
7073
|
const message = error instanceof Error ? error.message : String(error);
|
|
6980
|
-
logger$
|
|
7074
|
+
logger$4.log(` ⏳ Cluster still settling (${message}); retrying (${attempt}/2)...`);
|
|
6981
7075
|
await new Promise((resolve) => setTimeout(resolve, 1e4));
|
|
6982
7076
|
await waitForPostgres(serverHostname, externalPort, postgres.databaseUser, postgres.databasePassword, postgres.databaseName).catch(() => {});
|
|
6983
7077
|
}
|
|
@@ -7043,7 +7137,7 @@ function requiredOf(requirements) {
|
|
|
7043
7137
|
async function workspaceDeployCommand(configured, options) {
|
|
7044
7138
|
const { provider, stage, tag, apps: selectedApps } = options;
|
|
7045
7139
|
const manifest = await require_discover.discover({
|
|
7046
|
-
patterns:
|
|
7140
|
+
patterns: require_workspace$1.constructGlobs(configured),
|
|
7047
7141
|
cwd: configured.root
|
|
7048
7142
|
});
|
|
7049
7143
|
const units = deployUnits(manifest, configured);
|
|
@@ -7052,91 +7146,91 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7052
7146
|
apps: units
|
|
7053
7147
|
} : configured;
|
|
7054
7148
|
if (provider !== "dokploy") throw new Error(`Workspace deployment only supports Dokploy. Got: ${provider}`);
|
|
7055
|
-
logger$
|
|
7056
|
-
logger$
|
|
7149
|
+
logger$4.log(`\n🚀 Deploying workspace "${workspace.name}" to Dokploy...`);
|
|
7150
|
+
logger$4.log(` Stage: ${stage}`);
|
|
7057
7151
|
const imageTag = tag ?? generateTag(stage);
|
|
7058
|
-
logger$
|
|
7152
|
+
logger$4.log(` Tag: ${imageTag}`);
|
|
7059
7153
|
const buildOrder = require_workspace.getAppBuildOrder(workspace);
|
|
7060
7154
|
let appsToDeployNames = buildOrder;
|
|
7061
7155
|
if (selectedApps && selectedApps.length > 0) {
|
|
7062
7156
|
const invalidApps = selectedApps.filter((name) => !workspace.apps[name]);
|
|
7063
7157
|
if (invalidApps.length > 0) throw new Error(`Unknown apps: ${invalidApps.join(", ")}\nAvailable apps: ${Object.keys(workspace.apps).join(", ")}`);
|
|
7064
7158
|
appsToDeployNames = buildOrder.filter((name) => selectedApps.includes(name));
|
|
7065
|
-
logger$
|
|
7066
|
-
} else logger$
|
|
7159
|
+
logger$4.log(` Deploying apps: ${appsToDeployNames.join(", ")}`);
|
|
7160
|
+
} else logger$4.log(` Deploying all apps: ${appsToDeployNames.join(", ")}`);
|
|
7067
7161
|
const dokployApps = appsToDeployNames.filter((name) => {
|
|
7068
7162
|
const target = workspace.apps[name].resolvedDeployTarget;
|
|
7069
7163
|
if (!require_workspace.isDeployTargetSupported(target)) {
|
|
7070
|
-
logger$
|
|
7164
|
+
logger$4.log(` ⚠️ Skipping ${name}: ${require_workspace.getDeployTargetError(target, name)}`);
|
|
7071
7165
|
return false;
|
|
7072
7166
|
}
|
|
7073
7167
|
return true;
|
|
7074
7168
|
});
|
|
7075
7169
|
if (dokployApps.length === 0) throw new Error("No apps to deploy. All selected apps have unsupported deploy targets.");
|
|
7076
7170
|
appsToDeployNames = dokployApps;
|
|
7077
|
-
logger$
|
|
7171
|
+
logger$4.log("\n🔐 Loading secrets and analyzing environment requirements...");
|
|
7078
7172
|
const stageSecrets = await require_storage.readStageSecrets(stage, workspace.root);
|
|
7079
7173
|
if (!stageSecrets) {
|
|
7080
|
-
logger$
|
|
7081
|
-
logger$
|
|
7174
|
+
logger$4.log(` ⚠️ No secrets found for stage "${stage}"`);
|
|
7175
|
+
logger$4.log(` Run "gkm secrets:init --stage ${stage}" to create secrets`);
|
|
7082
7176
|
}
|
|
7083
7177
|
const sniffedApps = await sniffAllApps(workspace.apps, workspace.root);
|
|
7084
7178
|
const encryptedSecrets = stageSecrets ? prepareSecretsForAllApps(stageSecrets, sniffedApps) : /* @__PURE__ */ new Map();
|
|
7085
7179
|
if (stageSecrets) {
|
|
7086
7180
|
const report = generateSecretsReport(encryptedSecrets, sniffedApps);
|
|
7087
|
-
if (report.appsWithSecrets.length > 0) logger$
|
|
7088
|
-
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(", ")}`);
|
|
7089
7183
|
}
|
|
7090
7184
|
let creds = await getDokployCredentials();
|
|
7091
7185
|
if (!creds) {
|
|
7092
|
-
logger$
|
|
7186
|
+
logger$4.log("\n📋 Dokploy credentials not found. Let's set them up.");
|
|
7093
7187
|
const normalizedEndpoint = (await prompt("Dokploy URL (e.g., https://dokploy.example.com): ")).replace(/\/$/, "");
|
|
7094
7188
|
try {
|
|
7095
7189
|
new URL(normalizedEndpoint);
|
|
7096
7190
|
} catch {
|
|
7097
7191
|
throw new Error("Invalid URL format");
|
|
7098
7192
|
}
|
|
7099
|
-
logger$
|
|
7193
|
+
logger$4.log(`\nGenerate a token at: ${normalizedEndpoint}/settings/profile\n`);
|
|
7100
7194
|
const token = await prompt("API Token: ", true);
|
|
7101
|
-
logger$
|
|
7195
|
+
logger$4.log("\nValidating credentials...");
|
|
7102
7196
|
if (!await validateDokployToken(normalizedEndpoint, token)) throw new Error("Invalid credentials. Please check your token.");
|
|
7103
7197
|
await storeDokployCredentials(token, normalizedEndpoint);
|
|
7104
7198
|
creds = {
|
|
7105
7199
|
token,
|
|
7106
7200
|
endpoint: normalizedEndpoint
|
|
7107
7201
|
};
|
|
7108
|
-
logger$
|
|
7202
|
+
logger$4.log("✓ Credentials saved");
|
|
7109
7203
|
}
|
|
7110
7204
|
const api = new DokployApi({
|
|
7111
7205
|
baseUrl: creds.endpoint,
|
|
7112
7206
|
token: creds.token
|
|
7113
7207
|
});
|
|
7114
|
-
logger$
|
|
7208
|
+
logger$4.log("\n📁 Setting up Dokploy project...");
|
|
7115
7209
|
const projectName = workspace.name;
|
|
7116
7210
|
let project = (await api.listProjects()).find((p) => p.name.toLowerCase() === projectName.toLowerCase());
|
|
7117
7211
|
let environmentId;
|
|
7118
7212
|
if (project) {
|
|
7119
|
-
logger$
|
|
7213
|
+
logger$4.log(` Found existing project: ${project.name}`);
|
|
7120
7214
|
const matchingEnv = ((await api.getProject(project.projectId)).environments ?? []).find((e) => e.name.toLowerCase() === stage.toLowerCase());
|
|
7121
7215
|
if (matchingEnv) {
|
|
7122
7216
|
environmentId = matchingEnv.environmentId;
|
|
7123
|
-
logger$
|
|
7217
|
+
logger$4.log(` Using environment: ${matchingEnv.name}`);
|
|
7124
7218
|
} else {
|
|
7125
|
-
logger$
|
|
7219
|
+
logger$4.log(` Creating "${stage}" environment...`);
|
|
7126
7220
|
environmentId = (await api.createEnvironment(project.projectId, stage)).environmentId;
|
|
7127
|
-
logger$
|
|
7221
|
+
logger$4.log(` ✓ Created environment: ${stage}`);
|
|
7128
7222
|
}
|
|
7129
7223
|
} else {
|
|
7130
|
-
logger$
|
|
7224
|
+
logger$4.log(` Creating project: ${projectName}`);
|
|
7131
7225
|
const result = await api.createProject(projectName);
|
|
7132
7226
|
project = result.project;
|
|
7133
7227
|
if (result.environment.name.toLowerCase() !== stage.toLowerCase()) {
|
|
7134
|
-
logger$
|
|
7228
|
+
logger$4.log(` Creating "${stage}" environment...`);
|
|
7135
7229
|
environmentId = (await api.createEnvironment(project.projectId, stage)).environmentId;
|
|
7136
7230
|
} else environmentId = result.environment.environmentId;
|
|
7137
|
-
logger$
|
|
7231
|
+
logger$4.log(` ✓ Created project: ${project.projectId}`);
|
|
7138
7232
|
}
|
|
7139
|
-
logger$
|
|
7233
|
+
logger$4.log("\n📋 Loading deploy state...");
|
|
7140
7234
|
const stateProvider = await createStateProvider({
|
|
7141
7235
|
config: workspace.state,
|
|
7142
7236
|
workspaceRoot: workspace.root,
|
|
@@ -7144,27 +7238,27 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7144
7238
|
});
|
|
7145
7239
|
let state = await stateProvider.read(stage);
|
|
7146
7240
|
if (state) {
|
|
7147
|
-
logger$
|
|
7241
|
+
logger$4.log(` Found existing state for stage "${stage}"`);
|
|
7148
7242
|
if (state.projectId !== project.projectId) {
|
|
7149
|
-
logger$
|
|
7243
|
+
logger$4.log(` ⚠ Project ID changed, updating state`);
|
|
7150
7244
|
state.projectId = project.projectId;
|
|
7151
7245
|
}
|
|
7152
7246
|
if (state.environmentId !== environmentId) {
|
|
7153
|
-
logger$
|
|
7247
|
+
logger$4.log(` ⚠ Environment ID changed, updating state`);
|
|
7154
7248
|
state.environmentId = environmentId;
|
|
7155
7249
|
}
|
|
7156
7250
|
} else {
|
|
7157
|
-
logger$
|
|
7251
|
+
logger$4.log(` Creating new state for stage "${stage}"`);
|
|
7158
7252
|
state = createEmptyState(stage, project.projectId, environmentId);
|
|
7159
7253
|
}
|
|
7160
|
-
logger$
|
|
7254
|
+
logger$4.log("\n🐳 Checking registry...");
|
|
7161
7255
|
let registryId = await getDokployRegistryId();
|
|
7162
7256
|
const registry = workspace.deploy.dokploy?.registry;
|
|
7163
7257
|
if (registryId) try {
|
|
7164
7258
|
const reg = await api.getRegistry(registryId);
|
|
7165
|
-
logger$
|
|
7259
|
+
logger$4.log(` Using registry: ${reg.registryName}`);
|
|
7166
7260
|
} catch {
|
|
7167
|
-
logger$
|
|
7261
|
+
logger$4.log(" ⚠ Stored registry not found, clearing...");
|
|
7168
7262
|
registryId = void 0;
|
|
7169
7263
|
await storeDokployRegistryId("");
|
|
7170
7264
|
}
|
|
@@ -7173,26 +7267,26 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7173
7267
|
if (registries.length > 0) {
|
|
7174
7268
|
registryId = registries[0].registryId;
|
|
7175
7269
|
await storeDokployRegistryId(registryId);
|
|
7176
|
-
logger$
|
|
7270
|
+
logger$4.log(` Using registry: ${registries[0].registryName}`);
|
|
7177
7271
|
} else if (registry) {
|
|
7178
|
-
logger$
|
|
7179
|
-
logger$
|
|
7272
|
+
logger$4.log(" No registries found in Dokploy. Let's create one.");
|
|
7273
|
+
logger$4.log(` Registry URL: ${registry}`);
|
|
7180
7274
|
const username = await prompt("Registry username: ");
|
|
7181
7275
|
const password = await prompt("Registry password/token: ", true);
|
|
7182
7276
|
registryId = (await api.createRegistry("Default Registry", registry, username, password)).registryId;
|
|
7183
7277
|
await storeDokployRegistryId(registryId);
|
|
7184
|
-
logger$
|
|
7185
|
-
} else logger$
|
|
7278
|
+
logger$4.log(` ✓ Registry created: ${registryId}`);
|
|
7279
|
+
} else logger$4.log(" ⚠ No registry configured. Set deploy.dokploy.registry in workspace config");
|
|
7186
7280
|
}
|
|
7187
7281
|
const skippedMobileApps = appsToDeployNames.filter((name) => workspace.apps[name].type === "mobile");
|
|
7188
|
-
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(", ")}`);
|
|
7189
7283
|
const backendApps = appsToDeployNames.filter((name) => workspace.apps[name].type === "backend");
|
|
7190
7284
|
const frontendApps = appsToDeployNames.filter((name) => workspace.apps[name].type === "web");
|
|
7191
7285
|
const dokployConfig = workspace.deploy.dokploy;
|
|
7192
7286
|
let declaredEnv = {};
|
|
7193
7287
|
let declaredClusters = {};
|
|
7194
7288
|
{
|
|
7195
|
-
logger$
|
|
7289
|
+
logger$4.log("\n📦 Provisioning declared constructs...");
|
|
7196
7290
|
const appUrls = {};
|
|
7197
7291
|
for (const appName of backendApps) {
|
|
7198
7292
|
const app = workspace.apps[appName];
|
|
@@ -7210,20 +7304,20 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7210
7304
|
});
|
|
7211
7305
|
declaredEnv = declared.env;
|
|
7212
7306
|
declaredClusters = declared.clusters;
|
|
7213
|
-
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)`);
|
|
7214
7308
|
if (declared.statements.length > 0) {
|
|
7215
7309
|
const serverHostname = getServerHostname(creds.endpoint);
|
|
7216
7310
|
for (const [databaseName, cluster] of Object.entries(declared.clusters)) {
|
|
7217
7311
|
const statements = declared.statements.filter((statement) => statement.database === databaseName);
|
|
7218
7312
|
if (statements.length === 0) continue;
|
|
7219
7313
|
const created = await applyDeclaredStatements(api, cluster, serverHostname, statements);
|
|
7220
|
-
logger$
|
|
7314
|
+
logger$4.log(` 🗄️ ${databaseName}: applied ${statements.length} statement(s), ${created} new`);
|
|
7221
7315
|
}
|
|
7222
7316
|
}
|
|
7223
7317
|
}
|
|
7224
7318
|
const backupCluster = Object.values(declaredClusters)[0];
|
|
7225
7319
|
if (workspace.deploy?.backups && backupCluster) {
|
|
7226
|
-
logger$
|
|
7320
|
+
logger$4.log("\n💾 Provisioning backup destination...");
|
|
7227
7321
|
const { provisionBackupDestination } = await Promise.resolve().then(() => require("./backup-provisioner-BmhIO_Yr.cjs"));
|
|
7228
7322
|
const backupState = await provisionBackupDestination({
|
|
7229
7323
|
api,
|
|
@@ -7232,13 +7326,13 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7232
7326
|
stage,
|
|
7233
7327
|
config: workspace.deploy.backups,
|
|
7234
7328
|
existingState: getBackupState(state),
|
|
7235
|
-
logger: logger$
|
|
7329
|
+
logger: logger$4
|
|
7236
7330
|
});
|
|
7237
7331
|
setBackupState(state, backupState);
|
|
7238
7332
|
if (!backupState.postgresBackupId) {
|
|
7239
7333
|
const backupSchedule = workspace.deploy.backups.schedule ?? "0 2 * * *";
|
|
7240
7334
|
const backupRetention = workspace.deploy.backups.retention ?? 30;
|
|
7241
|
-
logger$
|
|
7335
|
+
logger$4.log(" Creating postgres backup schedule...");
|
|
7242
7336
|
const backup = await api.createPostgresBackup({
|
|
7243
7337
|
schedule: backupSchedule,
|
|
7244
7338
|
prefix: `${stage}/postgres`,
|
|
@@ -7249,8 +7343,8 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7249
7343
|
keepLatestCount: backupRetention
|
|
7250
7344
|
});
|
|
7251
7345
|
setPostgresBackupId(state, backup.backupId);
|
|
7252
|
-
logger$
|
|
7253
|
-
} else logger$
|
|
7346
|
+
logger$4.log(` ✓ Postgres backup schedule created (${backupSchedule})`);
|
|
7347
|
+
} else logger$4.log(" ✓ Using existing postgres backup schedule");
|
|
7254
7348
|
}
|
|
7255
7349
|
const publicUrls = {};
|
|
7256
7350
|
const results = [];
|
|
@@ -7263,25 +7357,25 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7263
7357
|
frontendUrls.push(`https://${hostname}`);
|
|
7264
7358
|
}
|
|
7265
7359
|
if (backendApps.length > 0) {
|
|
7266
|
-
logger$
|
|
7360
|
+
logger$4.log("\n📦 PHASE 1: Deploying backend applications...");
|
|
7267
7361
|
for (const appName of backendApps) {
|
|
7268
7362
|
const app = workspace.apps[appName];
|
|
7269
|
-
logger$
|
|
7363
|
+
logger$4.log(`\n ⚙️ Deploying ${appName}...`);
|
|
7270
7364
|
try {
|
|
7271
7365
|
const dokployAppName = applicationName(stage, workspace.name, appName);
|
|
7272
7366
|
let application = null;
|
|
7273
7367
|
const cachedAppId = getApplicationId(state, appName);
|
|
7274
7368
|
if (cachedAppId) {
|
|
7275
|
-
logger$
|
|
7369
|
+
logger$4.log(` Using cached ID: ${cachedAppId}`);
|
|
7276
7370
|
application = await api.getApplication(cachedAppId);
|
|
7277
|
-
if (application) logger$
|
|
7278
|
-
else logger$
|
|
7371
|
+
if (application) logger$4.log(` ✓ Application found: ${application.applicationId}`);
|
|
7372
|
+
else logger$4.log(` ⚠ Cached ID invalid, will create new`);
|
|
7279
7373
|
}
|
|
7280
7374
|
if (!application) {
|
|
7281
7375
|
const result = await api.findOrCreateApplication(dokployAppName, project.projectId, environmentId);
|
|
7282
7376
|
application = result.application;
|
|
7283
|
-
if (result.created) logger$
|
|
7284
|
-
else logger$
|
|
7377
|
+
if (result.created) logger$4.log(` Created application: ${application.applicationId}`);
|
|
7378
|
+
else logger$4.log(` Found existing application: ${application.applicationId}`);
|
|
7285
7379
|
}
|
|
7286
7380
|
setApplicationId(state, appName, application.applicationId);
|
|
7287
7381
|
const appSecrets = encryptedSecrets.get(appName);
|
|
@@ -7289,11 +7383,11 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7289
7383
|
if (appSecrets && appSecrets.secretCount > 0) {
|
|
7290
7384
|
buildArgs.push(`GKM_ENCRYPTED_CREDENTIALS=${appSecrets.payload.encrypted}`);
|
|
7291
7385
|
buildArgs.push(`GKM_CREDENTIALS_IV=${appSecrets.payload.iv}`);
|
|
7292
|
-
logger$
|
|
7386
|
+
logger$4.log(` Encrypted ${appSecrets.secretCount} secrets`);
|
|
7293
7387
|
}
|
|
7294
7388
|
const imageName = `${workspace.name}-${appName}`;
|
|
7295
7389
|
const imageRef = registry ? `${registry}/${imageName}:${imageTag}` : `${imageName}:${imageTag}`;
|
|
7296
|
-
logger$
|
|
7390
|
+
logger$4.log(` Building Docker image: ${imageRef}`);
|
|
7297
7391
|
await deployDocker({
|
|
7298
7392
|
stage,
|
|
7299
7393
|
tag: imageTag,
|
|
@@ -7334,17 +7428,17 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7334
7428
|
...declaredEnv
|
|
7335
7429
|
};
|
|
7336
7430
|
const envVars = Object.entries(withDeclared).map(([key, value]) => `${key}=${value}`);
|
|
7337
|
-
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(", ")}`);
|
|
7338
7432
|
await api.saveDockerProvider(application.applicationId, imageRef, { registryId });
|
|
7339
7433
|
await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
|
|
7340
|
-
logger$
|
|
7434
|
+
logger$4.log(` Deploying to Dokploy...`);
|
|
7341
7435
|
await api.deployApplication(application.applicationId);
|
|
7342
7436
|
const existingDomain = (await api.getDomainsByApplicationId(application.applicationId)).find((d) => d.host === backendHost);
|
|
7343
7437
|
if (existingDomain) {
|
|
7344
7438
|
appHostnames.set(appName, backendHost);
|
|
7345
7439
|
appDomainIds.set(appName, existingDomain.domainId);
|
|
7346
7440
|
publicUrls[appName] = `https://${backendHost}`;
|
|
7347
|
-
logger$
|
|
7441
|
+
logger$4.log(` ✓ Domain: https://${backendHost} (existing)`);
|
|
7348
7442
|
} else try {
|
|
7349
7443
|
const domain = await api.createDomain({
|
|
7350
7444
|
host: backendHost,
|
|
@@ -7356,10 +7450,10 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7356
7450
|
appHostnames.set(appName, backendHost);
|
|
7357
7451
|
appDomainIds.set(appName, domain.domainId);
|
|
7358
7452
|
publicUrls[appName] = `https://${backendHost}`;
|
|
7359
|
-
logger$
|
|
7453
|
+
logger$4.log(` ✓ Domain: https://${backendHost} (created)`);
|
|
7360
7454
|
} catch (domainError) {
|
|
7361
7455
|
const message = domainError instanceof Error ? domainError.message : "Unknown error";
|
|
7362
|
-
logger$
|
|
7456
|
+
logger$4.log(` ⚠ Domain creation failed: ${message}`);
|
|
7363
7457
|
appHostnames.set(appName, backendHost);
|
|
7364
7458
|
publicUrls[appName] = `https://${backendHost}`;
|
|
7365
7459
|
}
|
|
@@ -7370,10 +7464,10 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7370
7464
|
applicationId: application.applicationId,
|
|
7371
7465
|
imageRef
|
|
7372
7466
|
});
|
|
7373
|
-
logger$
|
|
7467
|
+
logger$4.log(` ✓ ${appName} deployed successfully`);
|
|
7374
7468
|
} catch (error) {
|
|
7375
7469
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
7376
|
-
logger$
|
|
7470
|
+
logger$4.log(` ✗ Failed to deploy ${appName}: ${message}`);
|
|
7377
7471
|
results.push({
|
|
7378
7472
|
appName,
|
|
7379
7473
|
type: app.type,
|
|
@@ -7385,25 +7479,25 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7385
7479
|
}
|
|
7386
7480
|
}
|
|
7387
7481
|
if (frontendApps.length > 0) {
|
|
7388
|
-
logger$
|
|
7482
|
+
logger$4.log("\n🌐 PHASE 2: Deploying frontend applications...");
|
|
7389
7483
|
for (const appName of frontendApps) {
|
|
7390
7484
|
const app = workspace.apps[appName];
|
|
7391
|
-
logger$
|
|
7485
|
+
logger$4.log(`\n 🌐 Deploying ${appName}...`);
|
|
7392
7486
|
try {
|
|
7393
7487
|
const dokployAppName = applicationName(stage, workspace.name, appName);
|
|
7394
7488
|
let application = null;
|
|
7395
7489
|
const cachedAppId = getApplicationId(state, appName);
|
|
7396
7490
|
if (cachedAppId) {
|
|
7397
|
-
logger$
|
|
7491
|
+
logger$4.log(` Using cached ID: ${cachedAppId}`);
|
|
7398
7492
|
application = await api.getApplication(cachedAppId);
|
|
7399
|
-
if (application) logger$
|
|
7400
|
-
else logger$
|
|
7493
|
+
if (application) logger$4.log(` ✓ Application found: ${application.applicationId}`);
|
|
7494
|
+
else logger$4.log(` ⚠ Cached ID invalid, will create new`);
|
|
7401
7495
|
}
|
|
7402
7496
|
if (!application) {
|
|
7403
7497
|
const result = await api.findOrCreateApplication(dokployAppName, project.projectId, environmentId);
|
|
7404
7498
|
application = result.application;
|
|
7405
|
-
if (result.created) logger$
|
|
7406
|
-
else logger$
|
|
7499
|
+
if (result.created) logger$4.log(` Created application: ${application.applicationId}`);
|
|
7500
|
+
else logger$4.log(` Found existing application: ${application.applicationId}`);
|
|
7407
7501
|
}
|
|
7408
7502
|
setApplicationId(state, appName, application.applicationId);
|
|
7409
7503
|
const dependencyUrls = {};
|
|
@@ -7423,7 +7517,7 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7423
7517
|
};
|
|
7424
7518
|
const { valid, missing, resolved } = validateEnvVars(requiredOf(sniffedApps.get(appName)), envContext);
|
|
7425
7519
|
if (!valid) throw new Error(formatMissingVarsError(appName, missing, stage));
|
|
7426
|
-
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(", ")}`);
|
|
7427
7521
|
const publicPrefix = require_workspace.getPublicEnvPrefix(app.framework);
|
|
7428
7522
|
const buildArgs = [];
|
|
7429
7523
|
const publicUrlArgNames = [];
|
|
@@ -7433,10 +7527,10 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7433
7527
|
publicUrlArgNames.push(key);
|
|
7434
7528
|
}
|
|
7435
7529
|
}
|
|
7436
|
-
if (buildArgs.length > 0) logger$
|
|
7530
|
+
if (buildArgs.length > 0) logger$4.log(` Build args: ${publicUrlArgNames.join(", ")}`);
|
|
7437
7531
|
const imageName = `${workspace.name}-${appName}`;
|
|
7438
7532
|
const imageRef = registry ? `${registry}/${imageName}:${imageTag}` : `${imageName}:${imageTag}`;
|
|
7439
|
-
logger$
|
|
7533
|
+
logger$4.log(` Building Docker image: ${imageRef}`);
|
|
7440
7534
|
await deployDocker({
|
|
7441
7535
|
stage,
|
|
7442
7536
|
tag: imageTag,
|
|
@@ -7457,14 +7551,14 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7457
7551
|
for (const [key, value] of Object.entries(resolved)) envVars.push(`${key}=${value}`);
|
|
7458
7552
|
await api.saveDockerProvider(application.applicationId, imageRef, { registryId });
|
|
7459
7553
|
await api.saveApplicationEnv(application.applicationId, envVars.join("\n"));
|
|
7460
|
-
logger$
|
|
7554
|
+
logger$4.log(` Deploying to Dokploy...`);
|
|
7461
7555
|
await api.deployApplication(application.applicationId);
|
|
7462
7556
|
const existingFrontendDomain = (await api.getDomainsByApplicationId(application.applicationId)).find((d) => d.host === frontendHost);
|
|
7463
7557
|
if (existingFrontendDomain) {
|
|
7464
7558
|
appHostnames.set(appName, frontendHost);
|
|
7465
7559
|
appDomainIds.set(appName, existingFrontendDomain.domainId);
|
|
7466
7560
|
publicUrls[appName] = `https://${frontendHost}`;
|
|
7467
|
-
logger$
|
|
7561
|
+
logger$4.log(` ✓ Domain: https://${frontendHost} (existing)`);
|
|
7468
7562
|
} else try {
|
|
7469
7563
|
const domain = await api.createDomain({
|
|
7470
7564
|
host: frontendHost,
|
|
@@ -7476,10 +7570,10 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7476
7570
|
appHostnames.set(appName, frontendHost);
|
|
7477
7571
|
appDomainIds.set(appName, domain.domainId);
|
|
7478
7572
|
publicUrls[appName] = `https://${frontendHost}`;
|
|
7479
|
-
logger$
|
|
7573
|
+
logger$4.log(` ✓ Domain: https://${frontendHost} (created)`);
|
|
7480
7574
|
} catch (domainError) {
|
|
7481
7575
|
const message = domainError instanceof Error ? domainError.message : "Unknown error";
|
|
7482
|
-
logger$
|
|
7576
|
+
logger$4.log(` ⚠ Domain creation failed: ${message}`);
|
|
7483
7577
|
appHostnames.set(appName, frontendHost);
|
|
7484
7578
|
publicUrls[appName] = `https://${frontendHost}`;
|
|
7485
7579
|
}
|
|
@@ -7490,10 +7584,10 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7490
7584
|
applicationId: application.applicationId,
|
|
7491
7585
|
imageRef
|
|
7492
7586
|
});
|
|
7493
|
-
logger$
|
|
7587
|
+
logger$4.log(` ✓ ${appName} deployed successfully`);
|
|
7494
7588
|
} catch (error) {
|
|
7495
7589
|
const message = error instanceof Error ? error.message : "Unknown error";
|
|
7496
|
-
logger$
|
|
7590
|
+
logger$4.log(` ✗ Failed to deploy ${appName}: ${message}`);
|
|
7497
7591
|
results.push({
|
|
7498
7592
|
appName,
|
|
7499
7593
|
type: app.type,
|
|
@@ -7503,9 +7597,9 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7503
7597
|
}
|
|
7504
7598
|
}
|
|
7505
7599
|
}
|
|
7506
|
-
logger$
|
|
7600
|
+
logger$4.log("\n📋 Saving deploy state...");
|
|
7507
7601
|
await stateProvider.write(stage, state);
|
|
7508
|
-
logger$
|
|
7602
|
+
logger$4.log(" ✓ State saved");
|
|
7509
7603
|
const dnsConfig = workspace.deploy.dns;
|
|
7510
7604
|
if (dnsConfig && appHostnames.size > 0) {
|
|
7511
7605
|
const dnsResult = await orchestrateDns(appHostnames, dnsConfig, creds.endpoint);
|
|
@@ -7514,27 +7608,27 @@ async function workspaceDeployCommand(configured, options) {
|
|
|
7514
7608
|
await stateProvider.write(stage, state);
|
|
7515
7609
|
}
|
|
7516
7610
|
if (dnsResult?.success && appHostnames.size > 0) {
|
|
7517
|
-
logger$
|
|
7611
|
+
logger$4.log("\n🔒 Validating domains for SSL certificates...");
|
|
7518
7612
|
for (const [appName, hostname] of appHostnames) try {
|
|
7519
7613
|
const result = await api.validateDomain(hostname);
|
|
7520
|
-
if (result.isValid) logger$
|
|
7521
|
-
else logger$
|
|
7614
|
+
if (result.isValid) logger$4.log(` ✓ ${appName}: ${hostname} → ${result.resolvedIp}`);
|
|
7615
|
+
else logger$4.log(` ⚠ ${appName}: ${hostname} not valid`);
|
|
7522
7616
|
} catch (validationError) {
|
|
7523
7617
|
const message = validationError instanceof Error ? validationError.message : "Unknown error";
|
|
7524
|
-
logger$
|
|
7618
|
+
logger$4.log(` ⚠ ${appName}: validation failed - ${message}`);
|
|
7525
7619
|
}
|
|
7526
7620
|
}
|
|
7527
7621
|
}
|
|
7528
7622
|
const successCount = results.filter((r) => r.success).length;
|
|
7529
7623
|
const failedCount = results.filter((r) => !r.success).length;
|
|
7530
|
-
logger$
|
|
7531
|
-
logger$
|
|
7532
|
-
logger$
|
|
7533
|
-
logger$
|
|
7534
|
-
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}`);
|
|
7535
7629
|
if (Object.keys(publicUrls).length > 0) {
|
|
7536
|
-
logger$
|
|
7537
|
-
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}`);
|
|
7538
7632
|
}
|
|
7539
7633
|
return {
|
|
7540
7634
|
apps: results,
|
|
@@ -7553,7 +7647,7 @@ async function deployCommand(options) {
|
|
|
7553
7647
|
}
|
|
7554
7648
|
//#endregion
|
|
7555
7649
|
//#region src/deploy/init.ts
|
|
7556
|
-
const logger$
|
|
7650
|
+
const logger$3 = console;
|
|
7557
7651
|
/**
|
|
7558
7652
|
* Get the Dokploy API token from stored credentials or environment
|
|
7559
7653
|
*/
|
|
@@ -7599,28 +7693,28 @@ function unsafeToRewrite(content) {
|
|
|
7599
7693
|
async function updateConfig(config, cwd = process.cwd()) {
|
|
7600
7694
|
const configPath = (0, node_path.join)(cwd, "gkm.config.ts");
|
|
7601
7695
|
if (!(0, node_fs.existsSync)(configPath)) {
|
|
7602
|
-
logger$
|
|
7603
|
-
logger$
|
|
7604
|
-
logger$
|
|
7605
|
-
logger$
|
|
7606
|
-
logger$
|
|
7607
|
-
logger$
|
|
7608
|
-
logger$
|
|
7609
|
-
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(` },`);
|
|
7610
7704
|
return;
|
|
7611
7705
|
}
|
|
7612
7706
|
const content = await (0, node_fs_promises.readFile)(configPath, "utf-8");
|
|
7613
7707
|
if (unsafeToRewrite(content)) {
|
|
7614
|
-
logger$
|
|
7615
|
-
logger$
|
|
7616
|
-
logger$
|
|
7617
|
-
logger$
|
|
7618
|
-
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}'`);
|
|
7619
7713
|
return;
|
|
7620
7714
|
}
|
|
7621
7715
|
if (content.includes("dokploy:") && content.includes("applicationId:")) {
|
|
7622
|
-
logger$
|
|
7623
|
-
logger$
|
|
7716
|
+
logger$3.log("\n Dokploy config already exists in gkm.config.ts");
|
|
7717
|
+
logger$3.log(" Updating with new values...");
|
|
7624
7718
|
}
|
|
7625
7719
|
const registryLine = config.registryId ? `\n\t\t\tregistryId: '${config.registryId}',` : "";
|
|
7626
7720
|
const dokployConfigStr = `dokploy: {
|
|
@@ -7638,7 +7732,7 @@ async function updateConfig(config, cwd = process.cwd()) {
|
|
|
7638
7732
|
},
|
|
7639
7733
|
});`);
|
|
7640
7734
|
await (0, node_fs_promises.writeFile)(configPath, newContent);
|
|
7641
|
-
logger$
|
|
7735
|
+
logger$3.log("\n ✓ Updated gkm.config.ts with Dokploy configuration");
|
|
7642
7736
|
}
|
|
7643
7737
|
/**
|
|
7644
7738
|
* Initialize Dokploy deployment configuration
|
|
@@ -7647,22 +7741,22 @@ async function deployInitCommand(options) {
|
|
|
7647
7741
|
const { projectName, appName, projectId: existingProjectId, registryId } = options;
|
|
7648
7742
|
const endpoint = await getEndpoint(options.endpoint);
|
|
7649
7743
|
const api = await createApi(endpoint);
|
|
7650
|
-
logger$
|
|
7651
|
-
logger$
|
|
7744
|
+
logger$3.log(`\n🚀 Initializing Dokploy deployment...`);
|
|
7745
|
+
logger$3.log(` Endpoint: ${endpoint}`);
|
|
7652
7746
|
let projectId;
|
|
7653
7747
|
if (existingProjectId) {
|
|
7654
7748
|
projectId = existingProjectId;
|
|
7655
|
-
logger$
|
|
7749
|
+
logger$3.log(`\n📁 Using existing project: ${projectId}`);
|
|
7656
7750
|
} else {
|
|
7657
|
-
logger$
|
|
7751
|
+
logger$3.log(`\n📁 Looking for project: ${projectName}`);
|
|
7658
7752
|
const existingProject = (await api.listProjects()).find((p) => p.name.toLowerCase() === projectName.toLowerCase());
|
|
7659
7753
|
if (existingProject) {
|
|
7660
7754
|
projectId = existingProject.projectId;
|
|
7661
|
-
logger$
|
|
7755
|
+
logger$3.log(` Found existing project: ${projectId}`);
|
|
7662
7756
|
} else {
|
|
7663
|
-
logger$
|
|
7757
|
+
logger$3.log(` Creating new project...`);
|
|
7664
7758
|
projectId = (await api.createProject(projectName)).project.projectId;
|
|
7665
|
-
logger$
|
|
7759
|
+
logger$3.log(` ✓ Created project: ${projectId}`);
|
|
7666
7760
|
}
|
|
7667
7761
|
}
|
|
7668
7762
|
const project = await api.getProject(projectId);
|
|
@@ -7670,22 +7764,22 @@ async function deployInitCommand(options) {
|
|
|
7670
7764
|
const firstEnv = project.environments?.[0];
|
|
7671
7765
|
if (firstEnv) environmentId = firstEnv.environmentId;
|
|
7672
7766
|
else {
|
|
7673
|
-
logger$
|
|
7767
|
+
logger$3.log(` Creating production environment...`);
|
|
7674
7768
|
environmentId = (await api.createEnvironment(projectId, "production")).environmentId;
|
|
7675
7769
|
}
|
|
7676
|
-
logger$
|
|
7770
|
+
logger$3.log(`\n📦 Creating application: ${appName}`);
|
|
7677
7771
|
const application = await api.createApplication(appName, projectId, environmentId);
|
|
7678
|
-
logger$
|
|
7772
|
+
logger$3.log(` ✓ Created application: ${application.applicationId}`);
|
|
7679
7773
|
if (registryId) {
|
|
7680
|
-
logger$
|
|
7774
|
+
logger$3.log(`\n🔧 Configuring registry: ${registryId}`);
|
|
7681
7775
|
await api.updateApplication(application.applicationId, { registryId });
|
|
7682
|
-
logger$
|
|
7776
|
+
logger$3.log(` ✓ Registry configured`);
|
|
7683
7777
|
} else try {
|
|
7684
7778
|
const registries = await api.listRegistries();
|
|
7685
7779
|
if (registries.length > 0) {
|
|
7686
|
-
logger$
|
|
7687
|
-
for (const reg of registries) logger$
|
|
7688
|
-
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>`);
|
|
7689
7783
|
}
|
|
7690
7784
|
} catch {}
|
|
7691
7785
|
const config = {
|
|
@@ -7694,14 +7788,14 @@ async function deployInitCommand(options) {
|
|
|
7694
7788
|
applicationId: application.applicationId
|
|
7695
7789
|
};
|
|
7696
7790
|
await updateConfig(config);
|
|
7697
|
-
logger$
|
|
7698
|
-
logger$
|
|
7699
|
-
logger$
|
|
7700
|
-
logger$
|
|
7701
|
-
logger$
|
|
7702
|
-
logger$
|
|
7703
|
-
logger$
|
|
7704
|
-
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`);
|
|
7705
7799
|
return config;
|
|
7706
7800
|
}
|
|
7707
7801
|
/**
|
|
@@ -7712,31 +7806,31 @@ async function deployListCommand(options) {
|
|
|
7712
7806
|
const api = await createApi(endpoint);
|
|
7713
7807
|
const { resource } = options;
|
|
7714
7808
|
if (resource === "projects") {
|
|
7715
|
-
logger$
|
|
7809
|
+
logger$3.log(`\n📁 Projects in ${endpoint}:`);
|
|
7716
7810
|
const projects = await api.listProjects();
|
|
7717
7811
|
if (projects.length === 0) {
|
|
7718
|
-
logger$
|
|
7812
|
+
logger$3.log(" No projects found");
|
|
7719
7813
|
return;
|
|
7720
7814
|
}
|
|
7721
7815
|
for (const project of projects) {
|
|
7722
|
-
logger$
|
|
7723
|
-
if (project.description) logger$
|
|
7816
|
+
logger$3.log(`\n ${project.name} (${project.projectId})`);
|
|
7817
|
+
if (project.description) logger$3.log(` ${project.description}`);
|
|
7724
7818
|
}
|
|
7725
7819
|
} else if (resource === "registries") {
|
|
7726
|
-
logger$
|
|
7820
|
+
logger$3.log(`\n🐳 Registries in ${endpoint}:`);
|
|
7727
7821
|
const registries = await api.listRegistries();
|
|
7728
7822
|
if (registries.length === 0) {
|
|
7729
|
-
logger$
|
|
7730
|
-
logger$
|
|
7823
|
+
logger$3.log(" No registries configured");
|
|
7824
|
+
logger$3.log(" Run \"gkm registry:setup\" to configure a registry");
|
|
7731
7825
|
return;
|
|
7732
7826
|
}
|
|
7733
7827
|
const storedRegistryId = await getDokployRegistryId();
|
|
7734
7828
|
for (const registry of registries) {
|
|
7735
7829
|
const marker = registry.registryId === storedRegistryId ? " (default)" : "";
|
|
7736
|
-
logger$
|
|
7737
|
-
logger$
|
|
7738
|
-
logger$
|
|
7739
|
-
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}`);
|
|
7740
7834
|
}
|
|
7741
7835
|
}
|
|
7742
7836
|
}
|
|
@@ -8597,26 +8691,26 @@ function loadPackageJson() {
|
|
|
8597
8691
|
* Run: pnpm --filter @geekmidas/cli sync-versions
|
|
8598
8692
|
*/
|
|
8599
8693
|
const GEEKMIDAS_VERSIONS = {
|
|
8600
|
-
"@geekmidas/audit": "~10.0.0-alpha.
|
|
8601
|
-
"@geekmidas/auth": "~10.0.0-alpha.
|
|
8602
|
-
"@geekmidas/cache": "~10.0.0-alpha.
|
|
8603
|
-
"@geekmidas/client": "~10.0.0-alpha.
|
|
8604
|
-
"@geekmidas/cloud": "~10.0.0-alpha.
|
|
8605
|
-
"@geekmidas/constructs": "~10.0.0-alpha.
|
|
8606
|
-
"@geekmidas/db": "~10.0.0-alpha.
|
|
8607
|
-
"@geekmidas/emailkit": "~10.0.0-alpha.
|
|
8608
|
-
"@geekmidas/envkit": "~10.0.0-alpha.
|
|
8609
|
-
"@geekmidas/errors": "~10.0.0-alpha.
|
|
8610
|
-
"@geekmidas/events": "~10.0.0-alpha.
|
|
8611
|
-
"@geekmidas/logger": "~10.0.0-alpha.
|
|
8612
|
-
"@geekmidas/manifest": "~10.0.0-alpha.
|
|
8613
|
-
"@geekmidas/rate-limit": "~10.0.0-alpha.
|
|
8614
|
-
"@geekmidas/schema": "~10.0.0-alpha.
|
|
8615
|
-
"@geekmidas/services": "~10.0.0-alpha.
|
|
8616
|
-
"@geekmidas/storage": "~10.0.0-alpha.
|
|
8617
|
-
"@geekmidas/studio": "~10.0.0-alpha.
|
|
8618
|
-
"@geekmidas/telescope": "~10.0.0-alpha.
|
|
8619
|
-
"@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",
|
|
8620
8714
|
"@geekmidas/cli": `~${loadPackageJson().version}`
|
|
8621
8715
|
};
|
|
8622
8716
|
//#endregion
|
|
@@ -14531,7 +14625,7 @@ async function initCommand(projectName, options = {}) {
|
|
|
14531
14625
|
}
|
|
14532
14626
|
console.log("🔐 Initializing encrypted secrets...\n");
|
|
14533
14627
|
const local = templateOptions.stages.local;
|
|
14534
|
-
const devSecrets =
|
|
14628
|
+
const devSecrets = require_reconcile.createStageSecrets(local, [], { projectName: name });
|
|
14535
14629
|
const customSecrets = {
|
|
14536
14630
|
PORT: "3000",
|
|
14537
14631
|
LOG_LEVEL: "debug",
|
|
@@ -14695,7 +14789,7 @@ var NotAnAwsRegion = class extends Error {
|
|
|
14695
14789
|
};
|
|
14696
14790
|
//#endregion
|
|
14697
14791
|
//#region src/secrets/index.ts
|
|
14698
|
-
const logger$
|
|
14792
|
+
const logger$2 = console;
|
|
14699
14793
|
/**
|
|
14700
14794
|
* Initialize secrets for a stage.
|
|
14701
14795
|
* Generates secure random passwords for configured services.
|
|
@@ -14703,7 +14797,7 @@ const logger$3 = console;
|
|
|
14703
14797
|
async function secretsInitCommand(options) {
|
|
14704
14798
|
const { stage, force } = options;
|
|
14705
14799
|
if (!force && require_storage.secretsExist(stage)) {
|
|
14706
|
-
logger$
|
|
14800
|
+
logger$2.error(`Secrets already exist for stage "${stage}". Use --force to overwrite.`);
|
|
14707
14801
|
process.exit(1);
|
|
14708
14802
|
}
|
|
14709
14803
|
let projectName;
|
|
@@ -14713,21 +14807,21 @@ async function secretsInitCommand(options) {
|
|
|
14713
14807
|
projectName = loaded.workspace.name;
|
|
14714
14808
|
if (Object.keys(loaded.workspace.apps).length > 1) {
|
|
14715
14809
|
workspaceSecrets = require_reconcile.generateFullstackCustomSecrets(loaded.workspace);
|
|
14716
|
-
logger$
|
|
14810
|
+
logger$2.log(" Detected workspace mode — generating per-app secrets");
|
|
14717
14811
|
}
|
|
14718
14812
|
} catch {}
|
|
14719
|
-
const secrets =
|
|
14813
|
+
const secrets = require_reconcile.createStageSecrets(stage, [], { projectName });
|
|
14720
14814
|
if (workspaceSecrets) secrets.custom = workspaceSecrets;
|
|
14721
14815
|
await require_storage.writeStageSecrets(secrets);
|
|
14722
|
-
logger$
|
|
14723
|
-
logger$
|
|
14724
|
-
if (secrets.urls.DATABASE_URL) logger$
|
|
14725
|
-
if (secrets.urls.REDIS_URL) logger$
|
|
14726
|
-
if (secrets.urls.RABBITMQ_URL) logger$
|
|
14727
|
-
if (secrets.urls.STORAGE_ENDPOINT) logger$
|
|
14728
|
-
if (Object.keys(secrets.custom).length > 0) logger$
|
|
14729
|
-
logger$
|
|
14730
|
-
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");
|
|
14731
14825
|
}
|
|
14732
14826
|
/**
|
|
14733
14827
|
* Read all data from stdin.
|
|
@@ -14746,21 +14840,21 @@ async function secretsSetCommand(key, value, options) {
|
|
|
14746
14840
|
let secretValue = value;
|
|
14747
14841
|
if (!secretValue) {
|
|
14748
14842
|
if (process.stdin.isTTY) {
|
|
14749
|
-
logger$
|
|
14750
|
-
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>");
|
|
14751
14845
|
process.exit(1);
|
|
14752
14846
|
}
|
|
14753
14847
|
secretValue = await readStdin();
|
|
14754
14848
|
if (!secretValue) {
|
|
14755
|
-
logger$
|
|
14849
|
+
logger$2.error("No value received from stdin");
|
|
14756
14850
|
process.exit(1);
|
|
14757
14851
|
}
|
|
14758
14852
|
}
|
|
14759
14853
|
try {
|
|
14760
14854
|
await require_storage.setCustomSecret(stage, key, secretValue);
|
|
14761
|
-
logger$
|
|
14855
|
+
logger$2.log(`\n✓ Secret "${key}" set for stage "${stage}"`);
|
|
14762
14856
|
} catch (error) {
|
|
14763
|
-
logger$
|
|
14857
|
+
logger$2.error(error instanceof Error ? error.message : "Failed to set secret");
|
|
14764
14858
|
process.exit(1);
|
|
14765
14859
|
}
|
|
14766
14860
|
}
|
|
@@ -14771,33 +14865,33 @@ async function secretsShowCommand(options) {
|
|
|
14771
14865
|
const { stage, reveal } = options;
|
|
14772
14866
|
const secrets = await require_storage.readStageSecrets(stage);
|
|
14773
14867
|
if (!secrets) {
|
|
14774
|
-
logger$
|
|
14868
|
+
logger$2.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
14775
14869
|
process.exit(1);
|
|
14776
14870
|
}
|
|
14777
|
-
logger$
|
|
14778
|
-
logger$
|
|
14779
|
-
logger$
|
|
14780
|
-
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:");
|
|
14781
14875
|
for (const [service, creds] of Object.entries(secrets.services)) if (creds) {
|
|
14782
|
-
logger$
|
|
14783
|
-
logger$
|
|
14784
|
-
logger$
|
|
14785
|
-
logger$
|
|
14786
|
-
logger$
|
|
14787
|
-
if (creds.database) logger$
|
|
14788
|
-
if (creds.vhost) logger$
|
|
14789
|
-
if (creds.bucket) logger$
|
|
14790
|
-
}
|
|
14791
|
-
logger$
|
|
14792
|
-
if (secrets.urls.DATABASE_URL) logger$
|
|
14793
|
-
if (secrets.urls.REDIS_URL) logger$
|
|
14794
|
-
if (secrets.urls.RABBITMQ_URL) logger$
|
|
14795
|
-
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}`);
|
|
14796
14890
|
if (Object.keys(secrets.custom).length > 0) {
|
|
14797
|
-
logger$
|
|
14798
|
-
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)}`);
|
|
14799
14893
|
}
|
|
14800
|
-
if (!reveal) logger$
|
|
14894
|
+
if (!reveal) logger$2.log("\nUse --reveal to show actual values");
|
|
14801
14895
|
}
|
|
14802
14896
|
/**
|
|
14803
14897
|
* Rotate passwords for services.
|
|
@@ -14806,25 +14900,25 @@ async function secretsRotateCommand(options) {
|
|
|
14806
14900
|
const { stage, service } = options;
|
|
14807
14901
|
const secrets = await require_storage.readStageSecrets(stage);
|
|
14808
14902
|
if (!secrets) {
|
|
14809
|
-
logger$
|
|
14903
|
+
logger$2.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
14810
14904
|
process.exit(1);
|
|
14811
14905
|
}
|
|
14812
14906
|
if (service) {
|
|
14813
14907
|
if (!secrets.services[service]) {
|
|
14814
|
-
logger$
|
|
14908
|
+
logger$2.error(`Service "${service}" not configured in stage "${stage}"`);
|
|
14815
14909
|
process.exit(1);
|
|
14816
14910
|
}
|
|
14817
|
-
const updated =
|
|
14911
|
+
const updated = require_reconcile.rotateServicePassword(secrets, service);
|
|
14818
14912
|
await require_storage.writeStageSecrets(updated);
|
|
14819
|
-
logger$
|
|
14913
|
+
logger$2.log(`\n✓ Password rotated for ${service} in stage "${stage}"`);
|
|
14820
14914
|
} else {
|
|
14821
14915
|
let updated = secrets;
|
|
14822
14916
|
const services = Object.keys(secrets.services);
|
|
14823
|
-
for (const svc of services) updated =
|
|
14917
|
+
for (const svc of services) updated = require_reconcile.rotateServicePassword(updated, svc);
|
|
14824
14918
|
await require_storage.writeStageSecrets(updated);
|
|
14825
|
-
logger$
|
|
14919
|
+
logger$2.log(`\n✓ Passwords rotated for all services in stage "${stage}": ${services.join(", ")}`);
|
|
14826
14920
|
}
|
|
14827
|
-
logger$
|
|
14921
|
+
logger$2.log(`\nUse "gkm secrets:show --stage ${stage}" to view new values`);
|
|
14828
14922
|
}
|
|
14829
14923
|
/**
|
|
14830
14924
|
* Import secrets from a JSON file.
|
|
@@ -14832,7 +14926,7 @@ async function secretsRotateCommand(options) {
|
|
|
14832
14926
|
async function secretsImportCommand(file, options) {
|
|
14833
14927
|
const { stage, merge = true } = options;
|
|
14834
14928
|
if (!(0, node_fs.existsSync)(file)) {
|
|
14835
|
-
logger$
|
|
14929
|
+
logger$2.error(`File not found: ${file}`);
|
|
14836
14930
|
process.exit(1);
|
|
14837
14931
|
}
|
|
14838
14932
|
let importedSecrets;
|
|
@@ -14842,12 +14936,12 @@ async function secretsImportCommand(file, options) {
|
|
|
14842
14936
|
if (typeof importedSecrets !== "object" || importedSecrets === null) throw new Error("JSON must be an object");
|
|
14843
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}`);
|
|
14844
14938
|
} catch (error) {
|
|
14845
|
-
logger$
|
|
14939
|
+
logger$2.error(`Failed to parse JSON file: ${error instanceof Error ? error.message : "Invalid JSON"}`);
|
|
14846
14940
|
process.exit(1);
|
|
14847
14941
|
}
|
|
14848
14942
|
const secrets = await require_storage.readStageSecrets(stage);
|
|
14849
14943
|
if (!secrets) {
|
|
14850
|
-
logger$
|
|
14944
|
+
logger$2.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
14851
14945
|
process.exit(1);
|
|
14852
14946
|
}
|
|
14853
14947
|
const updatedCustom = merge ? {
|
|
@@ -14862,10 +14956,10 @@ async function secretsImportCommand(file, options) {
|
|
|
14862
14956
|
await require_storage.writeStageSecrets(updated);
|
|
14863
14957
|
const importedCount = Object.keys(importedSecrets).length;
|
|
14864
14958
|
const totalCount = Object.keys(updatedCustom).length;
|
|
14865
|
-
logger$
|
|
14866
|
-
if (merge && totalCount > importedCount) logger$
|
|
14867
|
-
logger$
|
|
14868
|
-
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}`);
|
|
14869
14963
|
}
|
|
14870
14964
|
/**
|
|
14871
14965
|
* Mask password in a URL for display.
|
|
@@ -14880,175 +14974,120 @@ function maskUrl(url) {
|
|
|
14880
14974
|
}
|
|
14881
14975
|
}
|
|
14882
14976
|
//#endregion
|
|
14883
|
-
//#region src/
|
|
14884
|
-
/**
|
|
14885
|
-
|
|
14886
|
-
|
|
14887
|
-
|
|
14888
|
-
|
|
14889
|
-
* reads none of that: it asks the operating system, so until the root is in the
|
|
14890
|
-
* system store a local `https://` address is "not secure", which is true and
|
|
14891
|
-
* unhelpful.
|
|
14892
|
-
*
|
|
14893
|
-
* A separate command rather than part of `gkm setup`, for one reason: this
|
|
14894
|
-
* needs `sudo`, and a setup that silently escalates on every run is a worse
|
|
14895
|
-
* trade than one that tells you what to type. Caddy's own `caddy trust` draws
|
|
14896
|
-
* the line in the same place.
|
|
14897
|
-
*/
|
|
14898
|
-
const logger$2 = console;
|
|
14899
|
-
const run = (0, node_util.promisify)(node_child_process.execFile);
|
|
14900
|
-
/**
|
|
14901
|
-
* How each platform is told about a new authority.
|
|
14902
|
-
*
|
|
14903
|
-
* Separate rather than shelled through one script because the failure modes
|
|
14904
|
-
* differ and the message should name the real one: macOS refuses without an
|
|
14905
|
-
* admin password, Debian needs the file in a specific directory before its
|
|
14906
|
-
* updater will look at it.
|
|
14907
|
-
*/
|
|
14908
|
-
function installation(platform, certificate) {
|
|
14909
|
-
if (platform === "darwin") return {
|
|
14910
|
-
describe: "the System keychain",
|
|
14911
|
-
steps: [["sudo", [
|
|
14912
|
-
"security",
|
|
14913
|
-
"add-trusted-cert",
|
|
14914
|
-
"-d",
|
|
14915
|
-
"-r",
|
|
14916
|
-
"trustRoot",
|
|
14917
|
-
"-k",
|
|
14918
|
-
"/Library/Keychains/System.keychain",
|
|
14919
|
-
certificate
|
|
14920
|
-
]]]
|
|
14921
|
-
};
|
|
14922
|
-
if (platform === "linux") {
|
|
14923
|
-
const debian = (0, node_fs.existsSync)("/usr/local/share/ca-certificates");
|
|
14924
|
-
return {
|
|
14925
|
-
describe: debian ? "the system CA bundle" : "the system trust anchors",
|
|
14926
|
-
steps: debian ? [["sudo", [
|
|
14927
|
-
"cp",
|
|
14928
|
-
certificate,
|
|
14929
|
-
"/usr/local/share/ca-certificates/gkm-local-ca.crt"
|
|
14930
|
-
]], ["sudo", ["update-ca-certificates"]]] : [["sudo", [
|
|
14931
|
-
"cp",
|
|
14932
|
-
certificate,
|
|
14933
|
-
"/etc/pki/ca-trust/source/anchors/gkm-local-ca.crt"
|
|
14934
|
-
]], ["sudo", ["update-ca-trust"]]]
|
|
14935
|
-
};
|
|
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;
|
|
14936
14983
|
}
|
|
14937
|
-
|
|
14938
|
-
|
|
14939
|
-
/**
|
|
14940
|
-
* Trust the certificate authority this project's edge issues from.
|
|
14941
|
-
*
|
|
14942
|
-
* Idempotent in effect: installing a root that is already trusted replaces the
|
|
14943
|
-
* same entry, so running it twice is not an error.
|
|
14944
|
-
*/
|
|
14945
|
-
async function trustCommand(options = {}) {
|
|
14946
|
-
const { workspace } = await require_config.loadWorkspaceConfig();
|
|
14947
|
-
const certificate = (0, node_path.join)(workspace.root, require_credentials.LOCAL_CA_PATH);
|
|
14948
|
-
if (!(0, node_fs.existsSync)(certificate)) throw new NoLocalAuthority(certificate);
|
|
14949
|
-
const { describe, steps } = installation(process.platform, certificate);
|
|
14950
|
-
logger$2.log(`\n🔐 Trusting the local authority in ${describe}`);
|
|
14951
|
-
logger$2.log(` ${certificate}\n`);
|
|
14952
|
-
if (options.dryRun) {
|
|
14953
|
-
for (const [command, args] of steps) logger$2.log(` ${command} ${args.join(" ")}`);
|
|
14954
|
-
return;
|
|
14984
|
+
pull(stage) {
|
|
14985
|
+
return require_storage.readStageSecrets(stage, this.root);
|
|
14955
14986
|
}
|
|
14956
|
-
|
|
14957
|
-
|
|
14958
|
-
|
|
14959
|
-
}
|
|
14987
|
+
push(_stage, secrets) {
|
|
14988
|
+
return require_storage.writeStageSecrets(secrets, this.root);
|
|
14989
|
+
}
|
|
14990
|
+
};
|
|
14960
14991
|
/**
|
|
14961
|
-
*
|
|
14992
|
+
* The store a stage's secrets live in.
|
|
14962
14993
|
*
|
|
14963
|
-
*
|
|
14964
|
-
*
|
|
14965
|
-
|
|
14966
|
-
|
|
14967
|
-
|
|
14968
|
-
|
|
14969
|
-
|
|
14970
|
-
|
|
14971
|
-
|
|
14972
|
-
|
|
14973
|
-
|
|
14974
|
-
|
|
14975
|
-
|
|
14976
|
-
return;
|
|
14977
|
-
}
|
|
14978
|
-
if (await declined(root)) return;
|
|
14979
|
-
const { install } = await (0, prompts.default)({
|
|
14980
|
-
type: "confirm",
|
|
14981
|
-
name: "install",
|
|
14982
|
-
message: "Trust this project's local certificate authority, so a browser accepts its https addresses? (needs sudo)",
|
|
14983
|
-
initial: false
|
|
14984
|
-
});
|
|
14985
|
-
if (!install) {
|
|
14986
|
-
await recordDeclined(root);
|
|
14987
|
-
logger$2.log(" Skipped. Run \"gkm trust\" later.");
|
|
14988
|
-
return;
|
|
14989
|
-
}
|
|
14990
|
-
}
|
|
14991
|
-
await trustCommand();
|
|
14992
|
-
}
|
|
14993
|
-
/** Where a declined answer is remembered, so setup asks once and not again. */
|
|
14994
|
-
const TRUST_STATE = ".gkm/trust.json";
|
|
14995
|
-
async function declined(root) {
|
|
14996
|
-
try {
|
|
14997
|
-
const raw = await (0, node_fs_promises.readFile)((0, node_path.join)(root, TRUST_STATE), "utf-8");
|
|
14998
|
-
return JSON.parse(raw).declined === true;
|
|
14999
|
-
} catch {
|
|
15000
|
-
return false;
|
|
15001
|
-
}
|
|
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
|
+
});
|
|
15002
15007
|
}
|
|
15003
|
-
|
|
15004
|
-
|
|
15005
|
-
|
|
15006
|
-
|
|
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";
|
|
15007
15012
|
}
|
|
15013
|
+
//#endregion
|
|
15014
|
+
//#region src/secrets/transfer.ts
|
|
15008
15015
|
/**
|
|
15009
|
-
*
|
|
15016
|
+
* Moving a deployed stage's secrets between this machine and its store.
|
|
15010
15017
|
*
|
|
15011
|
-
*
|
|
15012
|
-
*
|
|
15013
|
-
*
|
|
15014
|
-
*
|
|
15015
|
-
|
|
15016
|
-
|
|
15017
|
-
|
|
15018
|
-
|
|
15019
|
-
|
|
15020
|
-
|
|
15021
|
-
|
|
15022
|
-
|
|
15023
|
-
|
|
15024
|
-
|
|
15025
|
-
|
|
15026
|
-
|
|
15027
|
-
|
|
15028
|
-
|
|
15029
|
-
|
|
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";
|
|
15030
15037
|
}
|
|
15031
|
-
}
|
|
15032
|
-
/**
|
|
15033
|
-
var
|
|
15034
|
-
|
|
15035
|
-
constructor(
|
|
15036
|
-
super(`No
|
|
15037
|
-
this.
|
|
15038
|
-
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";
|
|
15039
15046
|
}
|
|
15040
15047
|
};
|
|
15041
|
-
/** A
|
|
15042
|
-
var
|
|
15043
|
-
|
|
15044
|
-
|
|
15045
|
-
|
|
15046
|
-
|
|
15047
|
-
this.
|
|
15048
|
-
this.certificate = certificate;
|
|
15049
|
-
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";
|
|
15050
15055
|
}
|
|
15051
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
|
+
}
|
|
15052
15091
|
//#endregion
|
|
15053
15092
|
//#region src/setup/index.ts
|
|
15054
15093
|
var setup_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
|
|
@@ -15087,7 +15126,7 @@ async function setupCommand(options = {}) {
|
|
|
15087
15126
|
logger$1.error("❌ Failed to resolve secrets. Exiting.");
|
|
15088
15127
|
process.exit(1);
|
|
15089
15128
|
}
|
|
15090
|
-
const containers = await
|
|
15129
|
+
const containers = await require_workspace$1.derivedContainers(workspace, stage);
|
|
15091
15130
|
if (isMultiApp && containers.includes("postgres")) {
|
|
15092
15131
|
await require_reconcile.writeDockerEnvFromSecrets(secrets, workspace.root);
|
|
15093
15132
|
logger$1.log("📄 Generated docker/.env with database passwords");
|
|
@@ -15106,7 +15145,7 @@ async function setupCommand(options = {}) {
|
|
|
15106
15145
|
* free and quiet.
|
|
15107
15146
|
*/
|
|
15108
15147
|
async function reconcileLocal(workspace, stage, options) {
|
|
15109
|
-
const result = await
|
|
15148
|
+
const result = await require_workspace$1.reconcileWorkspace(workspace, { stage });
|
|
15110
15149
|
if (result.plan.containers.length === 0) {
|
|
15111
15150
|
logger$1.log("🐳 No containers declared");
|
|
15112
15151
|
return;
|
|
@@ -15117,8 +15156,7 @@ async function reconcileLocal(workspace, stage, options) {
|
|
|
15117
15156
|
}
|
|
15118
15157
|
logger$1.log(`🐳 Services: ${result.plan.containers.join(", ")}`);
|
|
15119
15158
|
for (const [container, address] of Object.entries(result.addresses)) logger$1.log(` ${container}: ${address}`);
|
|
15120
|
-
const
|
|
15121
|
-
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://"));
|
|
15122
15160
|
if (url) await ensureTrusted(workspace.root, url, { ...options.yes ? { assumeYes: true } : {} });
|
|
15123
15161
|
}
|
|
15124
15162
|
/**
|
|
@@ -15138,19 +15176,19 @@ async function resolveSecrets(stage, workspace, options) {
|
|
|
15138
15176
|
logger$1.log("🔐 Using existing local secrets");
|
|
15139
15177
|
const secrets = await require_storage.readStageSecrets(stage, workspace.root);
|
|
15140
15178
|
if (secrets) {
|
|
15141
|
-
const reconciled = reconcileSecrets(secrets, workspace, await
|
|
15179
|
+
const reconciled = reconcileSecrets(secrets, workspace, await require_workspace$1.derivedContainers(workspace, stage));
|
|
15142
15180
|
if (reconciled) await require_storage.writeStageSecrets(reconciled, workspace.root);
|
|
15143
15181
|
return reconciled ?? secrets;
|
|
15144
15182
|
}
|
|
15145
15183
|
}
|
|
15146
|
-
if (
|
|
15184
|
+
if (isRemoteStore(workspace, stage)) {
|
|
15147
15185
|
logger$1.log("☁️ Checking the secrets store...");
|
|
15148
15186
|
try {
|
|
15149
|
-
const { secrets } = await
|
|
15187
|
+
const { secrets } = await pullStageSecrets(workspace, stage);
|
|
15150
15188
|
logger$1.log("✅ Pulled secrets from the store");
|
|
15151
15189
|
return secrets;
|
|
15152
15190
|
} catch (error) {
|
|
15153
|
-
if (error instanceof
|
|
15191
|
+
if (error instanceof NoStoredSecrets) logger$1.log(" The store holds none yet");
|
|
15154
15192
|
else logger$1.warn(`⚠️ Could not pull from the store: ${error.message}`);
|
|
15155
15193
|
}
|
|
15156
15194
|
}
|
|
@@ -15186,7 +15224,7 @@ function reconcileSecrets(secrets, workspace, containers = []) {
|
|
|
15186
15224
|
let changed = false;
|
|
15187
15225
|
let result = { ...secrets };
|
|
15188
15226
|
for (const name of credentialedServices(containers)) if (!result.services[name]) {
|
|
15189
|
-
const creds =
|
|
15227
|
+
const creds = require_reconcile.generateServiceCredentials(name);
|
|
15190
15228
|
if (name === "minio") {
|
|
15191
15229
|
creds.bucket = workspace.name;
|
|
15192
15230
|
creds.username = workspace.name;
|
|
@@ -15198,7 +15236,7 @@ function reconcileSecrets(secrets, workspace, containers = []) {
|
|
|
15198
15236
|
[name]: creds
|
|
15199
15237
|
}
|
|
15200
15238
|
};
|
|
15201
|
-
result.urls =
|
|
15239
|
+
result.urls = require_reconcile.generateConnectionUrls(result.services, result.eventsBackend);
|
|
15202
15240
|
logger$1.log(` 🔄 Adding missing service credentials: ${name}`);
|
|
15203
15241
|
changed = true;
|
|
15204
15242
|
}
|
|
@@ -15211,12 +15249,12 @@ function reconcileSecrets(secrets, workspace, containers = []) {
|
|
|
15211
15249
|
host: result.services.postgres.host,
|
|
15212
15250
|
port: result.services.postgres.port,
|
|
15213
15251
|
username: "pgboss",
|
|
15214
|
-
password:
|
|
15252
|
+
password: require_reconcile.generateSecurePassword(),
|
|
15215
15253
|
database: result.services.postgres.database ?? "app"
|
|
15216
15254
|
}
|
|
15217
15255
|
}
|
|
15218
15256
|
};
|
|
15219
|
-
result.urls =
|
|
15257
|
+
result.urls = require_reconcile.generateConnectionUrls(result.services, result.eventsBackend);
|
|
15220
15258
|
logger$1.log(" 🔄 Adding missing service credentials: pgboss");
|
|
15221
15259
|
changed = true;
|
|
15222
15260
|
}
|
|
@@ -15251,7 +15289,7 @@ function reconcileSecrets(secrets, workspace, containers = []) {
|
|
|
15251
15289
|
*/
|
|
15252
15290
|
function createFreshWorkspaceSecrets(stage, workspace, containers = []) {
|
|
15253
15291
|
const serviceNames = credentialedServices(containers);
|
|
15254
|
-
const secrets =
|
|
15292
|
+
const secrets = require_reconcile.createStageSecrets(stage, serviceNames, { projectName: workspace.name });
|
|
15255
15293
|
if (Object.keys(workspace.apps).length > 1) secrets.custom = require_reconcile.generateFullstackCustomSecrets(workspace, containers);
|
|
15256
15294
|
else secrets.custom = {
|
|
15257
15295
|
PORT: "3000",
|
|
@@ -15273,7 +15311,7 @@ function createFreshWorkspaceSecrets(stage, workspace, containers = []) {
|
|
|
15273
15311
|
async function ensureStageSecrets(stage, cwd = process.cwd()) {
|
|
15274
15312
|
const { workspace } = await require_config.loadWorkspaceConfig(cwd);
|
|
15275
15313
|
if (require_storage.secretsExist(stage, workspace.root)) return false;
|
|
15276
|
-
const secrets = createFreshWorkspaceSecrets(stage, workspace, await
|
|
15314
|
+
const secrets = createFreshWorkspaceSecrets(stage, workspace, await require_workspace$1.derivedContainers(workspace, stage));
|
|
15277
15315
|
await require_storage.writeStageSecrets(secrets, workspace.root);
|
|
15278
15316
|
return true;
|
|
15279
15317
|
}
|
|
@@ -15281,10 +15319,10 @@ async function ensureStageSecrets(stage, cwd = process.cwd()) {
|
|
|
15281
15319
|
* Generate fresh secrets for the workspace.
|
|
15282
15320
|
*/
|
|
15283
15321
|
async function generateFreshSecrets(stage, workspace, options) {
|
|
15284
|
-
const secrets = createFreshWorkspaceSecrets(stage, workspace, await
|
|
15322
|
+
const secrets = createFreshWorkspaceSecrets(stage, workspace, await require_workspace$1.derivedContainers(workspace, stage));
|
|
15285
15323
|
await require_storage.writeStageSecrets(secrets, workspace.root);
|
|
15286
15324
|
logger$1.log(` Secrets written to .gkm/secrets/${stage}.json`);
|
|
15287
|
-
if (
|
|
15325
|
+
if (isRemoteStore(workspace, stage) && !options.yes) {
|
|
15288
15326
|
const { shouldPush } = await (0, prompts.default)({
|
|
15289
15327
|
type: "confirm",
|
|
15290
15328
|
name: "shouldPush",
|
|
@@ -15292,7 +15330,7 @@ async function generateFreshSecrets(stage, workspace, options) {
|
|
|
15292
15330
|
initial: true
|
|
15293
15331
|
});
|
|
15294
15332
|
if (shouldPush) try {
|
|
15295
|
-
await
|
|
15333
|
+
await pushStageSecrets(workspace, stage);
|
|
15296
15334
|
logger$1.log("☁️ Secrets pushed to the store");
|
|
15297
15335
|
} catch (error) {
|
|
15298
15336
|
logger$1.warn(`⚠️ Could not push to the store: ${error.message}`);
|
|
@@ -15556,13 +15594,12 @@ async function testCommand(options = {}) {
|
|
|
15556
15594
|
const { ensureStageSecrets } = await Promise.resolve().then(() => setup_exports);
|
|
15557
15595
|
if (await ensureStageSecrets(stage, cwd)) console.log(` 🔐 Generated fresh ${stage} secrets (auto-setup)`);
|
|
15558
15596
|
}
|
|
15559
|
-
const defaultEnv =
|
|
15597
|
+
const defaultEnv = require_workspace$1.loadEnvFiles(".env");
|
|
15560
15598
|
if (defaultEnv.loaded.length > 0) console.log(` 📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
15561
|
-
const result = await
|
|
15599
|
+
const result = await require_workspace$1.prepareEntryCredentials({
|
|
15562
15600
|
stage,
|
|
15563
15601
|
startDocker: !options.prepare,
|
|
15564
15602
|
secretsFileName: "test-secrets.json",
|
|
15565
|
-
resolveDockerPorts: "full",
|
|
15566
15603
|
reconcileStage: "test"
|
|
15567
15604
|
});
|
|
15568
15605
|
let finalCredentials = { ...result.credentials };
|
|
@@ -15576,18 +15613,17 @@ async function testCommand(options = {}) {
|
|
|
15576
15613
|
console.log(` 🔍 Sniffed ${sniffed.requiredEnvVars.length} required env var(s)` + (result.declaredKeys.length > 0 ? `, kept ${result.declaredKeys.length} declared` : ""));
|
|
15577
15614
|
}
|
|
15578
15615
|
}
|
|
15579
|
-
finalCredentials = rewriteDatabaseUrlForTests(finalCredentials);
|
|
15580
15616
|
console.log("");
|
|
15581
15617
|
await (0, node_fs_promises.writeFile)(result.secretsJsonPath, JSON.stringify(finalCredentials, null, 2));
|
|
15582
15618
|
const gkmDir = (0, node_path.join)(cwd, ".gkm");
|
|
15583
15619
|
const preloadPath = (0, node_path.join)(gkmDir, "test-credentials-preload.ts");
|
|
15584
|
-
await
|
|
15620
|
+
await require_workspace$1.createCredentialsPreload(preloadPath, result.secretsJsonPath);
|
|
15585
15621
|
const workspace = await require_config.loadWorkspaceConfig(cwd).then((loaded) => loaded.workspace).catch(() => void 0);
|
|
15586
|
-
const manifestPath = workspace
|
|
15622
|
+
const manifestPath = workspace ? await writeTestHarness({
|
|
15587
15623
|
root: workspace.root,
|
|
15588
15624
|
targets: [workspace.root, ...Object.values(workspace.apps).map((app) => (0, node_path.isAbsolute)(app.path) ? app.path : (0, node_path.join)(workspace.root, app.path))],
|
|
15589
|
-
patterns:
|
|
15590
|
-
cacheBackend:
|
|
15625
|
+
patterns: require_workspace$1.constructGlobs(workspace),
|
|
15626
|
+
cacheBackend: require_workspace$1.backendsOf(workspace).cache,
|
|
15591
15627
|
stage: require_workspace.TEST_STAGE,
|
|
15592
15628
|
env: finalCredentials
|
|
15593
15629
|
}) : void 0;
|
|
@@ -15627,29 +15663,6 @@ async function testCommand(options = {}) {
|
|
|
15627
15663
|
});
|
|
15628
15664
|
});
|
|
15629
15665
|
}
|
|
15630
|
-
const TEST_DB_SUFFIX = "_test";
|
|
15631
|
-
/**
|
|
15632
|
-
* Rewrite DATABASE_URL to point to a separate test database.
|
|
15633
|
-
* Appends `_test` to the database name (e.g., `app` -> `app_test`).
|
|
15634
|
-
* @internal Exported for testing
|
|
15635
|
-
*/
|
|
15636
|
-
function rewriteDatabaseUrlForTests(env) {
|
|
15637
|
-
const result = { ...env };
|
|
15638
|
-
for (const key of Object.keys(result)) {
|
|
15639
|
-
if (!key.includes("DATABASE_URL")) continue;
|
|
15640
|
-
const value = result[key];
|
|
15641
|
-
try {
|
|
15642
|
-
const url = new URL(value);
|
|
15643
|
-
const dbName = url.pathname.slice(1);
|
|
15644
|
-
if (dbName && !dbName.endsWith(TEST_DB_SUFFIX)) {
|
|
15645
|
-
url.pathname = `/${dbName}${TEST_DB_SUFFIX}`;
|
|
15646
|
-
result[key] = url.toString();
|
|
15647
|
-
console.log(` 🧪 ${key}: using test database "${dbName}${TEST_DB_SUFFIX}"`);
|
|
15648
|
-
}
|
|
15649
|
-
} catch {}
|
|
15650
|
-
}
|
|
15651
|
-
return result;
|
|
15652
|
-
}
|
|
15653
15666
|
/** `gkm test` outside a project that declares stages, with none named. */
|
|
15654
15667
|
var NoStageToTest = class extends Error {
|
|
15655
15668
|
constructor() {
|
|
@@ -16190,7 +16203,7 @@ program.command("secrets:push").description("Send a deployed stage's secrets to
|
|
|
16190
16203
|
const globalOptions = program.opts();
|
|
16191
16204
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16192
16205
|
const { loadWorkspaceConfig } = await Promise.resolve().then(() => require("./config.cjs"));
|
|
16193
|
-
const { pushStageSecrets } = await Promise.resolve().then(() =>
|
|
16206
|
+
const { pushStageSecrets } = await Promise.resolve().then(() => transfer_exports);
|
|
16194
16207
|
const { workspace } = await loadWorkspaceConfig();
|
|
16195
16208
|
const { addedKeys } = await pushStageSecrets(workspace, options.stage, { profile: options.profile });
|
|
16196
16209
|
for (const key of addedKeys) console.log(` + ${key} (reconciled)`);
|
|
@@ -16205,7 +16218,7 @@ program.command("secrets:pull").description("Bring a deployed stage's secrets fr
|
|
|
16205
16218
|
const globalOptions = program.opts();
|
|
16206
16219
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16207
16220
|
const { loadWorkspaceConfig } = await Promise.resolve().then(() => require("./config.cjs"));
|
|
16208
|
-
const { pullStageSecrets } = await Promise.resolve().then(() =>
|
|
16221
|
+
const { pullStageSecrets } = await Promise.resolve().then(() => transfer_exports);
|
|
16209
16222
|
const { workspace } = await loadWorkspaceConfig();
|
|
16210
16223
|
const { addedKeys } = await pullStageSecrets(workspace, options.stage, { profile: options.profile });
|
|
16211
16224
|
for (const key of addedKeys) console.log(` + ${key} (reconciled)`);
|
|
@@ -16220,7 +16233,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
|
|
|
16220
16233
|
const globalOptions = program.opts();
|
|
16221
16234
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16222
16235
|
const { loadWorkspaceConfig } = await Promise.resolve().then(() => require("./config.cjs"));
|
|
16223
|
-
const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-
|
|
16236
|
+
const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-szmJw_Sb.cjs")).then((n) => n.reconcile_exports);
|
|
16224
16237
|
const { readStageSecrets, writeStageSecrets } = await Promise.resolve().then(() => require("./storage-DgypfSD6.cjs")).then((n) => n.storage_exports);
|
|
16225
16238
|
const { workspace } = await loadWorkspaceConfig();
|
|
16226
16239
|
const stage = options.stage ?? workspace.stages.local;
|
|
@@ -16229,7 +16242,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
|
|
|
16229
16242
|
console.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
16230
16243
|
process.exit(1);
|
|
16231
16244
|
}
|
|
16232
|
-
const { derivedContainers } = await Promise.resolve().then(() => require("./
|
|
16245
|
+
const { derivedContainers } = await Promise.resolve().then(() => require("./workspace-Cv48QwPK.cjs")).then((n) => n.workspace_exports);
|
|
16233
16246
|
const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
|
|
16234
16247
|
if (!result) {
|
|
16235
16248
|
console.log(`\n✓ Secrets for stage "${stage}" are up-to-date`);
|
|
@@ -16288,7 +16301,7 @@ program.command("deploy:github").description("Let GitHub Actions deploy a stage:
|
|
|
16288
16301
|
try {
|
|
16289
16302
|
const globalOptions = program.opts();
|
|
16290
16303
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
16291
|
-
const { deployGithubCommand } = await Promise.resolve().then(() => require("./github-
|
|
16304
|
+
const { deployGithubCommand } = await Promise.resolve().then(() => require("./github-BUCXEl2v.cjs"));
|
|
16292
16305
|
await deployGithubCommand(options);
|
|
16293
16306
|
} catch (error) {
|
|
16294
16307
|
console.error(formatError(error));
|
|
@@ -16420,5 +16433,7 @@ Object.defineProperty(exports, "encryption_exports", {
|
|
|
16420
16433
|
}
|
|
16421
16434
|
});
|
|
16422
16435
|
exports.getHostingerToken = getHostingerToken;
|
|
16436
|
+
exports.isRemoteStore = isRemoteStore;
|
|
16437
|
+
exports.pushStageSecrets = pushStageSecrets;
|
|
16423
16438
|
|
|
16424
16439
|
//# sourceMappingURL=index.cjs.map
|