@geekmidas/cli 10.0.0-alpha.63 → 10.0.0-alpha.65
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/README.md +1 -1
- package/dist/{HostingerProvider-mJ7YruxH.cjs → HostingerProvider-Dlmbfass.cjs} +3 -3
- package/dist/{HostingerProvider-mJ7YruxH.cjs.map → HostingerProvider-Dlmbfass.cjs.map} +1 -1
- package/dist/{HostingerProvider-DBBwpDjn.mjs → HostingerProvider-x9gQTAGZ.mjs} +2 -2
- package/dist/{HostingerProvider-DBBwpDjn.mjs.map → HostingerProvider-x9gQTAGZ.mjs.map} +1 -1
- package/dist/{LegacyStateStore-CP7SZwTP.cjs → LegacyStateStore-BGoo__sO.cjs} +2 -2
- package/dist/{LegacyStateStore-CP7SZwTP.cjs.map → LegacyStateStore-BGoo__sO.cjs.map} +1 -1
- package/dist/{LegacyStateStore-Q3GmKOdw.mjs → LegacyStateStore-BUu1nQ5u.mjs} +2 -2
- package/dist/{LegacyStateStore-Q3GmKOdw.mjs.map → LegacyStateStore-BUu1nQ5u.mjs.map} +1 -1
- package/dist/{LocalStateStore-sS8mvLeq.cjs → LocalStateStore-93urXyQe.cjs} +2 -2
- package/dist/{LocalStateStore-sS8mvLeq.cjs.map → LocalStateStore-93urXyQe.cjs.map} +1 -1
- package/dist/{LocalStateStore-BGIXcrvX.mjs → LocalStateStore-BRhMQRuy.mjs} +2 -2
- package/dist/{LocalStateStore-BGIXcrvX.mjs.map → LocalStateStore-BRhMQRuy.mjs.map} +1 -1
- package/dist/{OpenApiTsGenerator-CjHWFRDd.cjs → OpenApiTsGenerator-BWuDRHe9.cjs} +2 -2
- package/dist/{OpenApiTsGenerator-CjHWFRDd.cjs.map → OpenApiTsGenerator-BWuDRHe9.cjs.map} +1 -1
- package/dist/{OpenApiTsGenerator-CnAwhGxi.mjs → OpenApiTsGenerator-C6v-IeHt.mjs} +2 -2
- package/dist/{OpenApiTsGenerator-CnAwhGxi.mjs.map → OpenApiTsGenerator-C6v-IeHt.mjs.map} +1 -1
- package/dist/{S3StateStore-6jqITH2F.cjs → S3StateStore-F4dxw6kS.cjs} +2 -2
- package/dist/{S3StateStore-6jqITH2F.cjs.map → S3StateStore-F4dxw6kS.cjs.map} +1 -1
- package/dist/{S3StateStore-D7l_lWJ3.mjs → S3StateStore-pw1GhWwt.mjs} +2 -2
- package/dist/{S3StateStore-D7l_lWJ3.mjs.map → S3StateStore-pw1GhWwt.mjs.map} +1 -1
- package/dist/{SSMStateStore-B__F7IJa.cjs → SSMStateStore-Bt9acuAF.cjs} +2 -2
- package/dist/{SSMStateStore-B__F7IJa.cjs.map → SSMStateStore-Bt9acuAF.cjs.map} +1 -1
- package/dist/{SSMStateStore-CAQDVpoc.mjs → SSMStateStore-Dmj7zzFO.mjs} +2 -2
- package/dist/{SSMStateStore-CAQDVpoc.mjs.map → SSMStateStore-Dmj7zzFO.mjs.map} +1 -1
- package/dist/{bundler-DLfOn0ls.cjs → bundler-D6MlIfBT.cjs} +3 -3
- package/dist/{bundler-DLfOn0ls.cjs.map → bundler-D6MlIfBT.cjs.map} +1 -1
- package/dist/{bundler-DyWI41Iy.mjs → bundler-D_14JLZF.mjs} +3 -3
- package/dist/{bundler-DyWI41Iy.mjs.map → bundler-D_14JLZF.mjs.map} +1 -1
- package/dist/{cli-D-ZM-X2s.cjs → cli-BfH1deAT.cjs} +8 -7
- package/dist/{cli-D-ZM-X2s.cjs.map → cli-BfH1deAT.cjs.map} +1 -1
- package/dist/{cli-PkVF28LM.mjs → cli-P56vvrDQ.mjs} +5 -4
- package/dist/{cli-PkVF28LM.mjs.map → cli-P56vvrDQ.mjs.map} +1 -1
- package/dist/compose-391oEKj1.cjs +1982 -0
- package/dist/compose-391oEKj1.cjs.map +1 -0
- package/dist/compose-DprE3ASm.mjs +1862 -0
- package/dist/compose-DprE3ASm.mjs.map +1 -0
- package/dist/{config-C2BUosLm.d.mts → config-BonvTW8p.d.mts} +3 -3
- package/dist/{config-C2BUosLm.d.mts.map → config-BonvTW8p.d.mts.map} +1 -1
- package/dist/{config-CemHVcIr.d.cts → config-V_X_BMpm.d.cts} +3 -3
- package/dist/{config-CemHVcIr.d.cts.map → config-V_X_BMpm.d.cts.map} +1 -1
- package/dist/config.cjs +416 -17
- package/dist/config.cjs.map +1 -0
- package/dist/config.d.cts +3 -3
- package/dist/config.d.mts +3 -3
- package/dist/config.mjs +401 -2
- package/dist/config.mjs.map +1 -0
- package/dist/credentials-Bzbg23n8.cjs +298 -0
- package/dist/credentials-Bzbg23n8.cjs.map +1 -0
- package/dist/credentials-Dq1MnWQf.mjs +233 -0
- package/dist/credentials-Dq1MnWQf.mjs.map +1 -0
- package/dist/{define-CHBIEc5j.mjs → define-DR8xGYfL.mjs} +6 -3
- package/dist/define-DR8xGYfL.mjs.map +1 -0
- package/dist/{define-Cv_QvDrg.cjs → define-DzRz8FgH.cjs} +6 -3
- package/dist/define-DzRz8FgH.cjs.map +1 -0
- package/dist/deploy/public.cjs +39 -33
- package/dist/deploy/public.cjs.map +1 -1
- package/dist/deploy/public.d.cts +44 -5
- package/dist/deploy/public.d.cts.map +1 -1
- package/dist/deploy/public.d.mts +44 -5
- package/dist/deploy/public.d.mts.map +1 -1
- package/dist/deploy/public.mjs +15 -12
- package/dist/deploy/public.mjs.map +1 -1
- package/dist/{deploy-CGW-QY9D.mjs → deploy-9MBG_QLd.mjs} +11 -9
- package/dist/deploy-9MBG_QLd.mjs.map +1 -0
- package/dist/{deploy-BiMrP9i7.cjs → deploy-B80idPUZ.cjs} +17 -15
- package/dist/deploy-B80idPUZ.cjs.map +1 -0
- package/dist/{deploy-CG8bfFij.mjs → deploy-CZ5nlpsu.mjs} +16 -119
- package/dist/deploy-CZ5nlpsu.mjs.map +1 -0
- package/dist/{deploy-ABBI0lf1.cjs → deploy-Cy8igpQF.cjs} +48 -151
- package/dist/deploy-Cy8igpQF.cjs.map +1 -0
- package/dist/derive-B4vDpzgP.mjs +150 -0
- package/dist/derive-B4vDpzgP.mjs.map +1 -0
- package/dist/derive-DAbo49sm.cjs +161 -0
- package/dist/derive-DAbo49sm.cjs.map +1 -0
- package/dist/{discover-C_6COHEv.cjs → discover-DHdECueV.cjs} +6 -157
- package/dist/discover-DHdECueV.cjs.map +1 -0
- package/dist/{discover-BaT6bpnA.mjs → discover-LMrqsWSU.mjs} +8 -117
- package/dist/discover-LMrqsWSU.mjs.map +1 -0
- package/dist/{dokploy-CwdmlyQL.mjs → dokploy-BdtBePjp.mjs} +3 -3
- package/dist/{dokploy-CwdmlyQL.mjs.map → dokploy-BdtBePjp.mjs.map} +1 -1
- package/dist/{dokploy-QJxMhxdf.cjs → dokploy-iFY8MY-L.cjs} +3 -3
- package/dist/{dokploy-QJxMhxdf.cjs.map → dokploy-iFY8MY-L.cjs.map} +1 -1
- package/dist/{encryption-DXw3jj0c.cjs → encryption-B1wJwgae.cjs} +6 -6
- package/dist/{encryption-DXw3jj0c.cjs.map → encryption-B1wJwgae.cjs.map} +1 -1
- package/dist/{encryption-CbQG_ayW.mjs → encryption-DF_hgIpl.mjs} +5 -5
- package/dist/{encryption-CbQG_ayW.mjs.map → encryption-DF_hgIpl.mjs.map} +1 -1
- package/dist/{github-ciwbIwVa.mjs → github-B0YYE98x.mjs} +4 -4
- package/dist/{github-ciwbIwVa.mjs.map → github-B0YYE98x.mjs.map} +1 -1
- package/dist/{github-BH_mETav.cjs → github-CYcmTdFs.cjs} +4 -4
- package/dist/{github-BH_mETav.cjs.map → github-CYcmTdFs.cjs.map} +1 -1
- package/dist/{index-DrKzpHl-.d.mts → index-DPUAGJaK.d.cts} +16 -3
- package/dist/index-DPUAGJaK.d.cts.map +1 -0
- package/dist/{index-BtXDZQuF.d.cts → index-DwKD_8lt.d.mts} +16 -3
- package/dist/index-DwKD_8lt.d.mts.map +1 -0
- package/dist/index.cjs +280 -1849
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +221 -1774
- package/dist/index.mjs.map +1 -1
- package/dist/local-BdH4XN0E.cjs +409 -0
- package/dist/local-BdH4XN0E.cjs.map +1 -0
- package/dist/local-CWR4Y_kW.mjs +350 -0
- package/dist/local-CWR4Y_kW.mjs.map +1 -0
- package/dist/{migrate-ImBKTBcW.mjs → migrate-B2qL5wj4.mjs} +8 -7
- package/dist/{migrate-ImBKTBcW.mjs.map → migrate-B2qL5wj4.mjs.map} +1 -1
- package/dist/{migrate-6oa9y_f2.cjs → migrate-q4pPAXtO.cjs} +21 -20
- package/dist/{migrate-6oa9y_f2.cjs.map → migrate-q4pPAXtO.cjs.map} +1 -1
- package/dist/openapi.cjs +2 -2
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/openapi.mjs +2 -2
- package/dist/provider-C9_-45d5.cjs +56 -0
- package/dist/provider-C9_-45d5.cjs.map +1 -0
- package/dist/provider-q4lsYzYG.mjs +45 -0
- package/dist/provider-q4lsYzYG.mjs.map +1 -0
- package/dist/{ready-Bpdnn2ON.mjs → ready-bQfu9GES.mjs} +3 -3
- package/dist/{ready-Bpdnn2ON.mjs.map → ready-bQfu9GES.mjs.map} +1 -1
- package/dist/{ready-BiG0rt4h.cjs → ready-brEhh68f.cjs} +3 -3
- package/dist/{ready-BiG0rt4h.cjs.map → ready-brEhh68f.cjs.map} +1 -1
- package/dist/reconcile/public.cjs +1 -1
- package/dist/reconcile/public.d.cts +1 -1
- package/dist/reconcile/public.d.mts +1 -1
- package/dist/reconcile/public.mjs +1 -1
- package/dist/resolve-DLkIXTct.mjs +262 -0
- package/dist/resolve-DLkIXTct.mjs.map +1 -0
- package/dist/resolve-N_i4XNd-.cjs +315 -0
- package/dist/resolve-N_i4XNd-.cjs.map +1 -0
- package/dist/{runtime-Dn_n-29S.d.cts → runtime-BnD7Nn96.d.cts} +2 -2
- package/dist/{runtime-CXPlkZzs.d.mts.map → runtime-BnD7Nn96.d.cts.map} +1 -1
- package/dist/{runtime-CXPlkZzs.d.mts → runtime-j-tmFL65.d.mts} +2 -2
- package/dist/{runtime-Dn_n-29S.d.cts.map → runtime-j-tmFL65.d.mts.map} +1 -1
- package/dist/sandbox/discover-worker.cjs +1 -1
- package/dist/sandbox/discover-worker.mjs +1 -1
- package/dist/sandbox-CL--ZDui.cjs +164 -0
- package/dist/sandbox-CL--ZDui.cjs.map +1 -0
- package/dist/sandbox-Dm247NGx.mjs +117 -0
- package/dist/sandbox-Dm247NGx.mjs.map +1 -0
- package/dist/sniffer-BSwaL-dX.cjs +2236 -0
- package/dist/sniffer-BSwaL-dX.cjs.map +1 -0
- package/dist/sniffer-BZ-OJO4Y.mjs +2068 -0
- package/dist/sniffer-BZ-OJO4Y.mjs.map +1 -0
- package/dist/sst-B2HbrDR3.mjs +430 -0
- package/dist/sst-B2HbrDR3.mjs.map +1 -0
- package/dist/sst-DcfsVH6x.cjs +453 -0
- package/dist/sst-DcfsVH6x.cjs.map +1 -0
- package/dist/{sniffer-aO_-s5vq.mjs → state-B8YwmUZU.mjs} +1204 -3236
- package/dist/state-B8YwmUZU.mjs.map +1 -0
- package/dist/{sniffer-D_SYc_pO.cjs → state-DF26e_lo.cjs} +1251 -3494
- package/dist/state-DF26e_lo.cjs.map +1 -0
- package/dist/{store-KvNQwYaB.cjs → store-CJF1Tqkq.cjs} +4 -4
- package/dist/{store-KvNQwYaB.cjs.map → store-CJF1Tqkq.cjs.map} +1 -1
- package/dist/{store-BS8A7NcA.mjs → store-CLiHnqOY.mjs} +2 -2
- package/dist/{store-BS8A7NcA.mjs.map → store-CLiHnqOY.mjs.map} +1 -1
- package/dist/target/public.cjs +6 -6
- package/dist/target/public.d.cts +3 -3
- package/dist/target/public.d.mts +3 -3
- package/dist/target/public.mjs +3 -3
- package/dist/terminal-Bs5E1oxm.cjs +88 -0
- package/dist/terminal-Bs5E1oxm.cjs.map +1 -0
- package/dist/terminal-CfbiYUbZ.mjs +84 -0
- package/dist/terminal-CfbiYUbZ.mjs.map +1 -0
- package/dist/{types-DIA7Li-J.d.cts → types-BCzMWBb6.d.mts} +84 -17
- package/dist/types-BCzMWBb6.d.mts.map +1 -0
- package/dist/{types-CfLKGfC_.d.mts → types-ByMWkXKP.d.cts} +85 -16
- package/dist/types-ByMWkXKP.d.cts.map +1 -0
- package/dist/utils-D2Nk1Nv2.cjs +205 -0
- package/dist/utils-D2Nk1Nv2.cjs.map +1 -0
- package/dist/utils-D_fxUqvU.mjs +156 -0
- package/dist/utils-D_fxUqvU.mjs.map +1 -0
- package/dist/vitest.cjs +1 -1
- package/dist/vitest.mjs +1 -1
- package/dist/workspace/index.cjs +3 -1
- package/dist/workspace/index.d.cts +3 -3
- package/dist/workspace/index.d.mts +3 -3
- package/dist/workspace/index.mjs +2 -2
- package/dist/{workspace-Kl4NG9IH.cjs → workspace-BewAHZIo.cjs} +41 -6
- package/dist/workspace-BewAHZIo.cjs.map +1 -0
- package/dist/{workspace-Dh7rr7l4.mjs → workspace-C06_pW4g.mjs} +30 -7
- package/dist/workspace-C06_pW4g.mjs.map +1 -0
- package/package.json +13 -13
- package/dist/config-BEJYKd4s.cjs +0 -648
- package/dist/config-BEJYKd4s.cjs.map +0 -1
- package/dist/config-LKKCVCnC.mjs +0 -547
- package/dist/config-LKKCVCnC.mjs.map +0 -1
- package/dist/define-CHBIEc5j.mjs.map +0 -1
- package/dist/define-Cv_QvDrg.cjs.map +0 -1
- package/dist/deploy-ABBI0lf1.cjs.map +0 -1
- package/dist/deploy-BiMrP9i7.cjs.map +0 -1
- package/dist/deploy-CG8bfFij.mjs.map +0 -1
- package/dist/deploy-CGW-QY9D.mjs.map +0 -1
- package/dist/discover-BaT6bpnA.mjs.map +0 -1
- package/dist/discover-C_6COHEv.cjs.map +0 -1
- package/dist/home-DOzBaWaL.cjs +0 -33
- package/dist/home-DOzBaWaL.cjs.map +0 -1
- package/dist/home-GI7pJl1M.mjs +0 -22
- package/dist/home-GI7pJl1M.mjs.map +0 -1
- package/dist/index-BtXDZQuF.d.cts.map +0 -1
- package/dist/index-DrKzpHl-.d.mts.map +0 -1
- package/dist/resolve-B1T0hUaR.mjs +0 -170
- package/dist/resolve-B1T0hUaR.mjs.map +0 -1
- package/dist/resolve-xytBTtG4.cjs +0 -217
- package/dist/resolve-xytBTtG4.cjs.map +0 -1
- package/dist/runtime-CMwtq0AL.cjs +0 -163
- package/dist/runtime-CMwtq0AL.cjs.map +0 -1
- package/dist/runtime-DbakJS8u.mjs +0 -134
- package/dist/runtime-DbakJS8u.mjs.map +0 -1
- package/dist/sniffer-D_SYc_pO.cjs.map +0 -1
- package/dist/sniffer-aO_-s5vq.mjs.map +0 -1
- package/dist/terminal-D0f1u0LG.cjs +0 -576
- package/dist/terminal-D0f1u0LG.cjs.map +0 -1
- package/dist/terminal-gMxAo9Dj.mjs +0 -517
- package/dist/terminal-gMxAo9Dj.mjs.map +0 -1
- package/dist/types-CfLKGfC_.d.mts.map +0 -1
- package/dist/types-DIA7Li-J.d.cts.map +0 -1
- package/dist/workspace-Dh7rr7l4.mjs.map +0 -1
- package/dist/workspace-Kl4NG9IH.cjs.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
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_sniffer = require("./sniffer-
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
4
|
+
const require_sniffer = require("./sniffer-BSwaL-dX.cjs");
|
|
5
|
+
const require_local = require("./local-BdH4XN0E.cjs");
|
|
6
|
+
const require_credentials = require("./credentials-Bzbg23n8.cjs");
|
|
7
|
+
const require_discover = require("./discover-DHdECueV.cjs");
|
|
8
|
+
const require_derive = require("./derive-DAbo49sm.cjs");
|
|
9
|
+
const require_workspace = require("./workspace-BewAHZIo.cjs");
|
|
10
|
+
const require_config = require("./config.cjs");
|
|
8
11
|
const require_storage = require("./storage-CSGoC3bK.cjs");
|
|
9
|
-
const require_store = require("./store-
|
|
10
|
-
const
|
|
12
|
+
const require_store = require("./store-CJF1Tqkq.cjs");
|
|
13
|
+
const require_state = require("./state-DF26e_lo.cjs");
|
|
14
|
+
const require_OpenApiTsGenerator = require("./OpenApiTsGenerator-BWuDRHe9.cjs");
|
|
11
15
|
const require_openapi = require("./openapi.cjs");
|
|
12
|
-
const require_fakes = require("./fakes-zW-jNAbQ.cjs");
|
|
13
16
|
const require_appPorts = require("./appPorts-Cu79hJF6.cjs");
|
|
14
|
-
const require_ready = require("./ready-
|
|
17
|
+
const require_ready = require("./ready-brEhh68f.cjs");
|
|
15
18
|
const require_emit = require("./emit-Dmc1nvdS.cjs");
|
|
16
|
-
const
|
|
19
|
+
const require_deploy = require("./deploy-B80idPUZ.cjs");
|
|
20
|
+
const require_compose = require("./compose-391oEKj1.cjs");
|
|
21
|
+
const require_LocalStateStore = require("./LocalStateStore-93urXyQe.cjs");
|
|
22
|
+
const require_utils = require("./utils-D2Nk1Nv2.cjs");
|
|
17
23
|
let node_path = require("node:path");
|
|
18
24
|
let commander = require("commander");
|
|
19
25
|
let node_fs = require("node:fs");
|
|
@@ -30,8 +36,6 @@ let chokidar = require("chokidar");
|
|
|
30
36
|
chokidar = require_rolldown_runtime.__toESM(chokidar, 1);
|
|
31
37
|
let node_net = require("node:net");
|
|
32
38
|
let node_crypto = require("node:crypto");
|
|
33
|
-
let pg = require("pg");
|
|
34
|
-
pg = require_rolldown_runtime.__toESM(pg, 1);
|
|
35
39
|
let yaml = require("yaml");
|
|
36
40
|
let prompts = require("prompts");
|
|
37
41
|
prompts = require_rolldown_runtime.__toESM(prompts, 1);
|
|
@@ -47,7 +51,7 @@ let _geekmidas_constructs_functions = require("@geekmidas/constructs/functions")
|
|
|
47
51
|
let _geekmidas_constructs_queue = require("@geekmidas/constructs/queue");
|
|
48
52
|
let _geekmidas_constructs_topic = require("@geekmidas/constructs/topic");
|
|
49
53
|
//#region package.json
|
|
50
|
-
var version = "10.0.0-alpha.
|
|
54
|
+
var version = "10.0.0-alpha.65";
|
|
51
55
|
//#endregion
|
|
52
56
|
//#region src/trust/index.ts
|
|
53
57
|
/**
|
|
@@ -64,7 +68,7 @@ var version = "10.0.0-alpha.63";
|
|
|
64
68
|
* worse trade than one that tells you what to type. Caddy's own `caddy trust`
|
|
65
69
|
* draws the line in the same place.
|
|
66
70
|
*/
|
|
67
|
-
const logger$
|
|
71
|
+
const logger$7 = console;
|
|
68
72
|
const run = (0, node_util.promisify)(node_child_process.execFile);
|
|
69
73
|
/**
|
|
70
74
|
* How each platform is told about a new authority.
|
|
@@ -113,18 +117,18 @@ function installation(platform, certificate) {
|
|
|
113
117
|
*/
|
|
114
118
|
async function trustCommand(options = {}) {
|
|
115
119
|
const { workspace } = await require_config.loadWorkspaceConfig();
|
|
116
|
-
const certificate = (0, node_path.join)(workspace.root,
|
|
120
|
+
const certificate = (0, node_path.join)(workspace.root, require_state.LOCAL_CA_PATH);
|
|
117
121
|
if (!(0, node_fs.existsSync)(certificate)) throw new NoLocalAuthority(certificate);
|
|
118
122
|
const { describe, asks, steps } = installation(process.platform, certificate);
|
|
119
|
-
logger$
|
|
120
|
-
logger$
|
|
123
|
+
logger$7.log(`\n🔐 Trusting the local authority in ${describe}`);
|
|
124
|
+
logger$7.log(` ${certificate}\n`);
|
|
121
125
|
if (options.dryRun) {
|
|
122
|
-
for (const [command, args] of steps) logger$
|
|
126
|
+
for (const [command, args] of steps) logger$7.log(` ${command} ${args.join(" ")}`);
|
|
123
127
|
return;
|
|
124
128
|
}
|
|
125
|
-
logger$
|
|
129
|
+
logger$7.log(` ${asks}\n`);
|
|
126
130
|
for (const [command, args] of steps) await run(command, args, { stdio: "inherit" });
|
|
127
|
-
logger$
|
|
131
|
+
logger$7.log("\n✅ Local https addresses are trusted. Restart your browser.\n");
|
|
128
132
|
}
|
|
129
133
|
/**
|
|
130
134
|
* Trust the authority if this machine lacks it and its owner agrees.
|
|
@@ -141,7 +145,7 @@ async function ensureTrusted(root, url, options = {}) {
|
|
|
141
145
|
if (await isTrusted(url)) return;
|
|
142
146
|
if (!options.assumeYes) {
|
|
143
147
|
if (!process.stdin.isTTY) {
|
|
144
|
-
logger$
|
|
148
|
+
logger$7.log(" Local https addresses are not trusted here. Run \"gkm trust\", or \"gkm setup --yes\".");
|
|
145
149
|
return;
|
|
146
150
|
}
|
|
147
151
|
if (await declined(root)) return;
|
|
@@ -153,7 +157,7 @@ async function ensureTrusted(root, url, options = {}) {
|
|
|
153
157
|
});
|
|
154
158
|
if (!install) {
|
|
155
159
|
await recordDeclined(root);
|
|
156
|
-
logger$
|
|
160
|
+
logger$7.log(" Skipped. Run \"gkm trust\" later.");
|
|
157
161
|
return;
|
|
158
162
|
}
|
|
159
163
|
}
|
|
@@ -1297,7 +1301,7 @@ async function serveFakes(fakes, ports) {
|
|
|
1297
1301
|
const served = [];
|
|
1298
1302
|
for (const [id, local] of Object.entries(fakes)) {
|
|
1299
1303
|
if (local.image) continue;
|
|
1300
|
-
const port = ports[
|
|
1304
|
+
const port = ports[require_state.fakeKey(id)];
|
|
1301
1305
|
if (port === void 0) continue;
|
|
1302
1306
|
const { handler } = (await import((0, node_url.pathToFileURL)(local.file).href)).default;
|
|
1303
1307
|
const server = serve({
|
|
@@ -1318,7 +1322,7 @@ function closeFakes(served) {
|
|
|
1318
1322
|
}
|
|
1319
1323
|
//#endregion
|
|
1320
1324
|
//#region src/exec/index.ts
|
|
1321
|
-
const logger$
|
|
1325
|
+
const logger$6 = console;
|
|
1322
1326
|
/** `gkm exec` was given nothing to run. */
|
|
1323
1327
|
var NoCommandSpecified = class extends Error {
|
|
1324
1328
|
constructor() {
|
|
@@ -1340,18 +1344,18 @@ var NoCommandSpecified = class extends Error {
|
|
|
1340
1344
|
async function execCommand(commandArgs, options = {}) {
|
|
1341
1345
|
const cwd = options.cwd ?? process.cwd();
|
|
1342
1346
|
if (!commandArgs[0]) throw new NoCommandSpecified();
|
|
1343
|
-
const defaultEnv =
|
|
1344
|
-
if (defaultEnv.loaded.length > 0) logger$
|
|
1345
|
-
const { credentials, secretsJsonPath, appName } = await
|
|
1346
|
-
if (appName) logger$
|
|
1347
|
+
const defaultEnv = require_state.loadEnvFiles(".env");
|
|
1348
|
+
if (defaultEnv.loaded.length > 0) logger$6.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
1349
|
+
const { credentials, secretsJsonPath, appName } = await require_state.prepareEntryCredentials({ cwd });
|
|
1350
|
+
if (appName) logger$6.log(`📦 App: ${appName}`);
|
|
1347
1351
|
const secretCount = Object.keys(credentials).filter((k) => k !== "PORT").length;
|
|
1348
|
-
if (secretCount > 0) logger$
|
|
1352
|
+
if (secretCount > 0) logger$6.log(`🔐 Loaded ${secretCount} secret(s)`);
|
|
1349
1353
|
const preloadDir = (0, node_path.join)(cwd, ".gkm");
|
|
1350
1354
|
await (0, node_fs_promises.mkdir)(preloadDir, { recursive: true });
|
|
1351
1355
|
const preloadPath = (0, node_path.join)(preloadDir, "credentials-preload.mjs");
|
|
1352
|
-
await
|
|
1356
|
+
await require_state.createCredentialsPreload(preloadPath, secretsJsonPath);
|
|
1353
1357
|
const [cmd, ...args] = commandArgs;
|
|
1354
|
-
logger$
|
|
1358
|
+
logger$6.log(`🚀 Running: ${[cmd, ...args].join(" ")}`);
|
|
1355
1359
|
const nodeOptions = [(process.env.NODE_OPTIONS ?? "").replace(/--import[= ]\S+/g, "").trim(), `--import=${preloadPath}`].filter(Boolean).join(" ");
|
|
1356
1360
|
const child = (0, node_child_process.spawn)(cmd, args, {
|
|
1357
1361
|
cwd,
|
|
@@ -1365,7 +1369,7 @@ async function execCommand(commandArgs, options = {}) {
|
|
|
1365
1369
|
const exitCode = await new Promise((resolve) => {
|
|
1366
1370
|
child.on("close", (code) => resolve(code ?? 0));
|
|
1367
1371
|
child.on("error", (error) => {
|
|
1368
|
-
logger$
|
|
1372
|
+
logger$6.error(`Failed to run command: ${error.message}`);
|
|
1369
1373
|
resolve(1);
|
|
1370
1374
|
});
|
|
1371
1375
|
});
|
|
@@ -1373,7 +1377,7 @@ async function execCommand(commandArgs, options = {}) {
|
|
|
1373
1377
|
}
|
|
1374
1378
|
//#endregion
|
|
1375
1379
|
//#region src/dev/index.ts
|
|
1376
|
-
const logger$
|
|
1380
|
+
const logger$5 = console;
|
|
1377
1381
|
/**
|
|
1378
1382
|
* Normalize telescope configuration
|
|
1379
1383
|
* @internal Exported for testing
|
|
@@ -1442,14 +1446,14 @@ const SUBSCRIBERS_ENV = "GKM_SUBSCRIBERS";
|
|
|
1442
1446
|
async function devCommand(options) {
|
|
1443
1447
|
if (options.subscribers === false) process.env[SUBSCRIBERS_ENV] = "off";
|
|
1444
1448
|
if (options.entry) return entryDevCommand(options);
|
|
1445
|
-
const defaultEnv =
|
|
1446
|
-
if (defaultEnv.loaded.length > 0) logger$
|
|
1449
|
+
const defaultEnv = require_state.loadEnvFiles(".env");
|
|
1450
|
+
if (defaultEnv.loaded.length > 0) logger$5.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
1447
1451
|
const appConfig = await require_config.loadAppConfig().catch((error) => {
|
|
1448
1452
|
if (error instanceof require_config.NotInAnApp || error instanceof require_config.NotABackendApp) return;
|
|
1449
1453
|
throw error;
|
|
1450
1454
|
});
|
|
1451
1455
|
if (!appConfig) {
|
|
1452
|
-
logger$
|
|
1456
|
+
logger$5.log("🔎 Reading gkm.config.ts and the constructs");
|
|
1453
1457
|
const { workspace: everything } = await require_config.loadWorkspaceConfig();
|
|
1454
1458
|
return workspaceDevCommand(everything, options);
|
|
1455
1459
|
}
|
|
@@ -1458,16 +1462,16 @@ async function devCommand(options) {
|
|
|
1458
1462
|
const secretsRoot = appConfig.workspaceRoot;
|
|
1459
1463
|
const workspaceAppName = appConfig.appName;
|
|
1460
1464
|
const assigned = await require_appPorts.assignAppPorts(appConfig.workspace, [workspaceAppName], {
|
|
1461
|
-
free:
|
|
1465
|
+
free: require_state.isPortAvailable,
|
|
1462
1466
|
holder: require_appPorts.holderOf
|
|
1463
1467
|
});
|
|
1464
1468
|
if (assigned.running.length > 0) throw new WorkspacePortsInUse(assigned.running);
|
|
1465
|
-
for (const { app, from, to, holder } of assigned.moved) logger$
|
|
1469
|
+
for (const { app, from, to, holder } of assigned.moved) logger$5.log(`↪️ ${app}: ${from} is held by ${require_appPorts.describeHolder(holder)}, which is not this workspace's — using ${to}`);
|
|
1466
1470
|
const workspace = require_appPorts.withAppPorts(appConfig.workspace, assigned.ports);
|
|
1467
1471
|
const workspaceAppPort = assigned.ports[workspaceAppName] ?? appConfig.app.port;
|
|
1468
1472
|
process.env[require_appPorts.APP_TAG_ENV] = require_appPorts.appTag(appConfig.workspaceRoot, workspaceAppName);
|
|
1469
1473
|
if (appConfig.app.entry) {
|
|
1470
|
-
logger$
|
|
1474
|
+
logger$5.log(`📄 Using entry point: ${appConfig.app.entry}`);
|
|
1471
1475
|
return entryDevCommand({
|
|
1472
1476
|
...options,
|
|
1473
1477
|
entry: appConfig.app.entry,
|
|
@@ -1476,9 +1480,9 @@ async function devCommand(options) {
|
|
|
1476
1480
|
});
|
|
1477
1481
|
}
|
|
1478
1482
|
if (config.env) {
|
|
1479
|
-
const { loaded, missing } =
|
|
1480
|
-
if (loaded.length > 0) logger$
|
|
1481
|
-
if (missing.length > 0) logger$
|
|
1483
|
+
const { loaded, missing } = require_state.loadEnvFiles(config.env, appRoot);
|
|
1484
|
+
if (loaded.length > 0) logger$5.log(`📦 Loaded env: ${loaded.join(", ")}`);
|
|
1485
|
+
if (missing.length > 0) logger$5.warn(`⚠️ Missing env files: ${missing.join(", ")}`);
|
|
1482
1486
|
}
|
|
1483
1487
|
const telescope = normalizeTelescopeConfig(config.telescope);
|
|
1484
1488
|
const hooks = normalizeHooksConfig(config.hooks, appRoot);
|
|
@@ -1489,7 +1493,7 @@ async function devCommand(options) {
|
|
|
1489
1493
|
appRoot,
|
|
1490
1494
|
target: "server",
|
|
1491
1495
|
enableOpenApi,
|
|
1492
|
-
cacheBackend:
|
|
1496
|
+
cacheBackend: require_state.cacheBackendFor(require_state.providerOf(workspace ?? config)),
|
|
1493
1497
|
telescope,
|
|
1494
1498
|
databaseApi: true,
|
|
1495
1499
|
hooks,
|
|
@@ -1497,25 +1501,25 @@ async function devCommand(options) {
|
|
|
1497
1501
|
bustCache,
|
|
1498
1502
|
serveEmpty: true
|
|
1499
1503
|
}));
|
|
1500
|
-
logger$
|
|
1504
|
+
logger$5.log(`🔨 Building ${workspaceAppName ?? "the app"}`);
|
|
1501
1505
|
const initial = await build();
|
|
1502
1506
|
const runtime = config.runtime ?? "node";
|
|
1503
1507
|
let secretsJsonPath;
|
|
1504
1508
|
let publicUrl;
|
|
1505
1509
|
let manifest;
|
|
1506
|
-
const appSecrets = await
|
|
1510
|
+
const appSecrets = await require_state.loadSecretsForApp(workspace ? await require_store.secretsStoreFor(workspace, config.stages.local) : new require_store.FileSecretsStore(secretsRoot), config.stages.local, workspaceAppName);
|
|
1507
1511
|
if (workspace) {
|
|
1508
|
-
const reconciled = await
|
|
1512
|
+
const reconciled = await require_state.reconcileWorkspace(workspace, {
|
|
1509
1513
|
stage: workspace.stages.local,
|
|
1510
1514
|
...options.fake ? { fake: true } : {},
|
|
1511
|
-
progress: (message) => logger$
|
|
1515
|
+
progress: (message) => logger$5.log(message)
|
|
1512
1516
|
});
|
|
1513
1517
|
if (reconciled.services.length > 0) {
|
|
1514
|
-
logger$
|
|
1515
|
-
for (const line of
|
|
1518
|
+
logger$5.log("🐳 Services");
|
|
1519
|
+
for (const line of require_state.describeServices(reconciled.services)) logger$5.log(line);
|
|
1516
1520
|
}
|
|
1517
1521
|
Object.assign(appSecrets, reconciled.env);
|
|
1518
|
-
const own = reconciled.plan.resources.find((r) => (r.kind === "rest-api" || r.kind === "site") &&
|
|
1522
|
+
const own = reconciled.plan.resources.find((r) => (r.kind === "rest-api" || r.kind === "site") && require_derive.appKey(r.id) === workspaceAppName);
|
|
1519
1523
|
publicUrl = own ? reconciled.env[own.envKey] : void 0;
|
|
1520
1524
|
manifest = reconciled.manifest;
|
|
1521
1525
|
}
|
|
@@ -1579,11 +1583,11 @@ async function devCommand(options) {
|
|
|
1579
1583
|
cwd: appRoot
|
|
1580
1584
|
});
|
|
1581
1585
|
watcher.on("error", (error) => {
|
|
1582
|
-
logger$
|
|
1586
|
+
logger$5.error("❌ Watcher error:", error);
|
|
1583
1587
|
});
|
|
1584
1588
|
let rebuildTimeout = null;
|
|
1585
1589
|
watcher.on("change", async (path) => {
|
|
1586
|
-
logger$
|
|
1590
|
+
logger$5.log(`🔄 ${path} changed — rebuilding`);
|
|
1587
1591
|
if (rebuildTimeout) clearTimeout(rebuildTimeout);
|
|
1588
1592
|
rebuildTimeout = setTimeout(async () => {
|
|
1589
1593
|
try {
|
|
@@ -1592,8 +1596,8 @@ async function devCommand(options) {
|
|
|
1592
1596
|
await devServer.restart();
|
|
1593
1597
|
} catch (error) {
|
|
1594
1598
|
const err = error;
|
|
1595
|
-
logger$
|
|
1596
|
-
if (err.stack) logger$
|
|
1599
|
+
logger$5.error(`❌ Rebuild failed: ${err.message}`);
|
|
1600
|
+
if (err.stack) logger$5.error(err.stack);
|
|
1597
1601
|
}
|
|
1598
1602
|
}, 300);
|
|
1599
1603
|
});
|
|
@@ -1601,13 +1605,13 @@ async function devCommand(options) {
|
|
|
1601
1605
|
const shutdown = () => {
|
|
1602
1606
|
if (isShuttingDown) return;
|
|
1603
1607
|
isShuttingDown = true;
|
|
1604
|
-
logger$
|
|
1608
|
+
logger$5.log("\n🛑 Shutting down...");
|
|
1605
1609
|
Promise.all([
|
|
1606
1610
|
watcher.close(),
|
|
1607
1611
|
devServer.stop(),
|
|
1608
1612
|
discovery.leave()
|
|
1609
1613
|
]).catch((err) => {
|
|
1610
|
-
logger$
|
|
1614
|
+
logger$5.error("Error during shutdown:", err);
|
|
1611
1615
|
}).finally(() => {
|
|
1612
1616
|
process.exit(0);
|
|
1613
1617
|
});
|
|
@@ -1807,17 +1811,17 @@ async function loadDevSecrets(workspace) {
|
|
|
1807
1811
|
async function workspaceDevCommand(configured, options) {
|
|
1808
1812
|
const scoped = options.app ? [options.app] : options.filter ? [] : Object.keys(configured.apps);
|
|
1809
1813
|
const assigned = await require_appPorts.assignAppPorts(configured, scoped, {
|
|
1810
|
-
free:
|
|
1814
|
+
free: require_state.isPortAvailable,
|
|
1811
1815
|
holder: require_appPorts.holderOf
|
|
1812
1816
|
});
|
|
1813
1817
|
if (assigned.running.length > 0) throw new WorkspacePortsInUse(assigned.running);
|
|
1814
|
-
for (const { app, from, to, holder } of assigned.moved) logger$
|
|
1818
|
+
for (const { app, from, to, holder } of assigned.moved) logger$5.log(`↪️ ${app}: ${from} is held by ${require_appPorts.describeHolder(holder)}, which is not this workspace's — using ${to}`);
|
|
1815
1819
|
const workspace = require_appPorts.withAppPorts(configured, assigned.ports);
|
|
1816
1820
|
const appCount = Object.keys(workspace.apps).length;
|
|
1817
1821
|
const frontendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "web");
|
|
1818
1822
|
const conflicts = checkPortConflicts(workspace);
|
|
1819
1823
|
if (conflicts.length > 0) {
|
|
1820
|
-
for (const conflict of conflicts) logger$
|
|
1824
|
+
for (const conflict of conflicts) logger$5.error(`❌ Port conflict: Apps "${conflict.app1}" and "${conflict.app2}" both use port ${conflict.port}`);
|
|
1821
1825
|
throw new Error("Port conflicts detected. Please assign unique ports to each app.");
|
|
1822
1826
|
}
|
|
1823
1827
|
if (frontendApps.length > 0) {
|
|
@@ -1826,35 +1830,35 @@ async function workspaceDevCommand(configured, options) {
|
|
|
1826
1830
|
for (const result of validationResults) {
|
|
1827
1831
|
if (!result.valid) {
|
|
1828
1832
|
hasErrors = true;
|
|
1829
|
-
logger$
|
|
1830
|
-
for (const error of result.errors) logger$
|
|
1833
|
+
logger$5.error(`\n❌ Frontend app "${result.appName}" validation failed:`);
|
|
1834
|
+
for (const error of result.errors) logger$5.error(` • ${error}`);
|
|
1831
1835
|
}
|
|
1832
|
-
for (const warning of result.warnings) logger$
|
|
1836
|
+
for (const warning of result.warnings) logger$5.warn(` ⚠️ ${result.appName}: ${warning}`);
|
|
1833
1837
|
}
|
|
1834
1838
|
if (hasErrors) throw new Error("Frontend app validation failed. Fix the issues above and try again.");
|
|
1835
1839
|
}
|
|
1836
1840
|
const rawSecrets = await loadDevSecrets(workspace);
|
|
1837
|
-
const reconciled = await
|
|
1841
|
+
const reconciled = await require_state.reconcileWorkspace(workspace, {
|
|
1838
1842
|
stage: workspace.stages.local,
|
|
1839
1843
|
...options.fake ? { fake: true } : {},
|
|
1840
|
-
progress: (message) => logger$
|
|
1844
|
+
progress: (message) => logger$5.log(message)
|
|
1841
1845
|
});
|
|
1842
1846
|
if (reconciled.services.length > 0) {
|
|
1843
|
-
logger$
|
|
1844
|
-
for (const line of
|
|
1847
|
+
logger$5.log("🐳 Services");
|
|
1848
|
+
for (const line of require_state.describeServices(reconciled.services)) logger$5.log(line);
|
|
1845
1849
|
}
|
|
1846
1850
|
const secretsEnv = {
|
|
1847
1851
|
...rawSecrets,
|
|
1848
1852
|
...reconciled.env
|
|
1849
1853
|
};
|
|
1850
1854
|
const fakes = options.fake ? await serveFakes(reconciled.fakes, reconciled.ports) : [];
|
|
1851
|
-
for (const { id, port } of fakes) logger$
|
|
1852
|
-
logger$
|
|
1855
|
+
for (const { id, port } of fakes) logger$5.log(`🎭 ${id} fake: http://localhost:${port}`);
|
|
1856
|
+
logger$5.log(options.migrate || options.seed ? "🗄️ Migrating the databases" : "🗄️ Checking for pending migrations");
|
|
1853
1857
|
if (options.migrate || options.seed) {
|
|
1854
|
-
const { prepareDevDatabases } = await Promise.resolve().then(() => require("./migrate-
|
|
1858
|
+
const { prepareDevDatabases } = await Promise.resolve().then(() => require("./migrate-q4pPAXtO.cjs"));
|
|
1855
1859
|
await prepareDevDatabases(workspace, reconciled.env, { seed: !!options.seed });
|
|
1856
|
-
} else await Promise.resolve().then(() => require("./migrate-
|
|
1857
|
-
logger$
|
|
1860
|
+
} else await Promise.resolve().then(() => require("./migrate-q4pPAXtO.cjs")).then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
|
|
1861
|
+
logger$5.log(`⚠️ Could not check for pending migrations: ${error instanceof Error ? error.message : String(error)}`);
|
|
1858
1862
|
});
|
|
1859
1863
|
const appUrls = {};
|
|
1860
1864
|
for (const resource of reconciled.plan.resources) {
|
|
@@ -1875,26 +1879,26 @@ async function workspaceDevCommand(configured, options) {
|
|
|
1875
1879
|
else {
|
|
1876
1880
|
const { filters, unpackaged } = turboFilters(workspace);
|
|
1877
1881
|
turboFilter = filters.flatMap((name) => ["--filter", name]);
|
|
1878
|
-
if (unpackaged.length > 0) logger$
|
|
1882
|
+
if (unpackaged.length > 0) logger$5.warn(`⚠️ No package.json with a name for: ${unpackaged.join(", ")} — turbo cannot run ${unpackaged.length === 1 ? "it" : "them"}.`);
|
|
1879
1883
|
}
|
|
1880
1884
|
const buildOrder = require_workspace.getAppBuildOrder(workspace);
|
|
1881
1885
|
const width = Math.max(...buildOrder.map((name) => name.length));
|
|
1882
|
-
logger$
|
|
1886
|
+
logger$5.log(`\n${workspace.name}: ${appCount} app(s)`);
|
|
1883
1887
|
for (const appName of buildOrder) {
|
|
1884
1888
|
const app = workspace.apps[appName];
|
|
1885
1889
|
if (!app) continue;
|
|
1886
1890
|
if (app.type === "mobile") {
|
|
1887
|
-
const own = reconciled.plan.resources.find((r) => r.kind === "mobile-app" &&
|
|
1891
|
+
const own = reconciled.plan.resources.find((r) => r.kind === "mobile-app" && require_derive.appKey(r.id) === appName);
|
|
1888
1892
|
const scheme = own ? reconciled.env[own.envKey] : void 0;
|
|
1889
1893
|
const device = Object.entries(reconciled.env).find(([key]) => key.endsWith("_DEVICE_URL"))?.[1];
|
|
1890
|
-
logger$
|
|
1894
|
+
logger$5.log(` ${appName.padEnd(width)} ${scheme ? `${scheme}://` : "mobile"}${device ? ` — devices reach ${new URL(device).hostname}` : ""}`);
|
|
1891
1895
|
continue;
|
|
1892
1896
|
}
|
|
1893
1897
|
const local = `http://localhost:${app.port}`;
|
|
1894
|
-
const url = Object.entries(appUrls).find(([id]) =>
|
|
1895
|
-
logger$
|
|
1898
|
+
const url = Object.entries(appUrls).find(([id]) => require_derive.appKey(id) === appName)?.[1];
|
|
1899
|
+
logger$5.log(` ${appName.padEnd(width)} ${url ? `${url} -> ${local}` : local}`);
|
|
1896
1900
|
}
|
|
1897
|
-
logger$
|
|
1901
|
+
logger$5.log("");
|
|
1898
1902
|
const configFiles = [
|
|
1899
1903
|
"gkm.config.ts",
|
|
1900
1904
|
"gkm.config.js",
|
|
@@ -1921,7 +1925,7 @@ async function workspaceDevCommand(configured, options) {
|
|
|
1921
1925
|
apps: buildOrder.flatMap((name) => {
|
|
1922
1926
|
const app = workspace.apps[name];
|
|
1923
1927
|
if (!app) return [];
|
|
1924
|
-
const publicUrl = Object.entries(appUrls).find(([id]) =>
|
|
1928
|
+
const publicUrl = Object.entries(appUrls).find(([id]) => require_derive.appKey(id) === name)?.[1];
|
|
1925
1929
|
return [{
|
|
1926
1930
|
name,
|
|
1927
1931
|
type: app.type,
|
|
@@ -1939,10 +1943,10 @@ async function workspaceDevCommand(configured, options) {
|
|
|
1939
1943
|
...secretsEnv,
|
|
1940
1944
|
NODE_ENV: "development",
|
|
1941
1945
|
...configPath ? { GKM_CONFIG_PATH: configPath } : {},
|
|
1942
|
-
...options.fake ? { [
|
|
1946
|
+
...options.fake ? { [require_state.FAKE_ENV]: "1" } : {},
|
|
1943
1947
|
[DISCOVERY_QUIET_ENV]: "1"
|
|
1944
1948
|
};
|
|
1945
|
-
logger$
|
|
1949
|
+
logger$5.log(`🚀 Starting ${appCount} app(s) — each builds before it answers, and says so when it is ready`);
|
|
1946
1950
|
const turboProcess = (0, node_child_process.spawn)("pnpm", [
|
|
1947
1951
|
"turbo",
|
|
1948
1952
|
"run",
|
|
@@ -1958,7 +1962,7 @@ async function workspaceDevCommand(configured, options) {
|
|
|
1958
1962
|
const shutdown = () => {
|
|
1959
1963
|
if (isShuttingDown) return;
|
|
1960
1964
|
isShuttingDown = true;
|
|
1961
|
-
logger$
|
|
1965
|
+
logger$5.log("\n🛑 Shutting down workspace...");
|
|
1962
1966
|
closeFakes(fakes);
|
|
1963
1967
|
discovery.leave();
|
|
1964
1968
|
const pid = turboProcess.pid;
|
|
@@ -1984,7 +1988,7 @@ async function workspaceDevCommand(configured, options) {
|
|
|
1984
1988
|
process.on("SIGTERM", shutdown);
|
|
1985
1989
|
return new Promise((resolve, reject) => {
|
|
1986
1990
|
turboProcess.on("error", (error) => {
|
|
1987
|
-
logger$
|
|
1991
|
+
logger$5.error("❌ Turbo error:", error);
|
|
1988
1992
|
reject(error);
|
|
1989
1993
|
});
|
|
1990
1994
|
turboProcess.on("exit", (code) => {
|
|
@@ -2002,23 +2006,23 @@ async function entryDevCommand(options) {
|
|
|
2002
2006
|
if (!entry) throw new Error("--entry requires a file path");
|
|
2003
2007
|
const entryPath = (0, node_path.resolve)(process.cwd(), entry);
|
|
2004
2008
|
if (!(0, node_fs.existsSync)(entryPath)) throw new Error(`Entry file not found: ${entryPath}`);
|
|
2005
|
-
const defaultEnv =
|
|
2006
|
-
if (defaultEnv.loaded.length > 0) logger$
|
|
2007
|
-
const { credentials, resolvedPort, secretsJsonPath, appName } = await
|
|
2008
|
-
if (appName) logger$
|
|
2009
|
-
logger$
|
|
2010
|
-
if (Object.keys(credentials).length > 1) logger$
|
|
2009
|
+
const defaultEnv = require_state.loadEnvFiles(".env");
|
|
2010
|
+
if (defaultEnv.loaded.length > 0) logger$5.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
2011
|
+
const { credentials, resolvedPort, secretsJsonPath, appName } = await require_state.prepareEntryCredentials({ explicitPort: options.portExplicit ? options.port : void 0 });
|
|
2012
|
+
if (appName) logger$5.log(`📦 App: ${appName} (port ${resolvedPort})`);
|
|
2013
|
+
logger$5.log(`🚀 Starting entry file: ${entry} on port ${resolvedPort}`);
|
|
2014
|
+
if (Object.keys(credentials).length > 1) logger$5.log(`🔐 Loaded ${Object.keys(credentials).length - 1} secret(s) + PORT`);
|
|
2011
2015
|
const wrapperDir = (0, node_path.join)(process.cwd(), ".gkm");
|
|
2012
2016
|
await (0, node_fs_promises.mkdir)(wrapperDir, { recursive: true });
|
|
2013
2017
|
const wrapperPath = (0, node_path.join)(wrapperDir, "entry-wrapper.ts");
|
|
2014
|
-
await
|
|
2018
|
+
await require_state.createEntryWrapper(wrapperPath, entryPath, secretsJsonPath);
|
|
2015
2019
|
const runner = new EntryRunner(wrapperPath, entryPath, watch, resolvedPort);
|
|
2016
2020
|
await runner.start();
|
|
2017
2021
|
let isShuttingDown = false;
|
|
2018
2022
|
const shutdown = () => {
|
|
2019
2023
|
if (isShuttingDown) return;
|
|
2020
2024
|
isShuttingDown = true;
|
|
2021
|
-
logger$
|
|
2025
|
+
logger$5.log("\n🛑 Shutting down...");
|
|
2022
2026
|
runner.stop();
|
|
2023
2027
|
process.exit(0);
|
|
2024
2028
|
};
|
|
@@ -2054,14 +2058,14 @@ var EntryRunner = class {
|
|
|
2054
2058
|
});
|
|
2055
2059
|
let restartTimeout = null;
|
|
2056
2060
|
this.watcher.on("change", (path) => {
|
|
2057
|
-
logger$
|
|
2061
|
+
logger$5.log(`📝 File changed: ${path}`);
|
|
2058
2062
|
if (restartTimeout) clearTimeout(restartTimeout);
|
|
2059
2063
|
restartTimeout = setTimeout(async () => {
|
|
2060
|
-
logger$
|
|
2064
|
+
logger$5.log("🔄 Restarting...");
|
|
2061
2065
|
await this.restart();
|
|
2062
2066
|
}, 300);
|
|
2063
2067
|
});
|
|
2064
|
-
logger$
|
|
2068
|
+
logger$5.log(`👀 Watching for changes in: ${watchDir}`);
|
|
2065
2069
|
}
|
|
2066
2070
|
}
|
|
2067
2071
|
async runProcess() {
|
|
@@ -2077,17 +2081,17 @@ var EntryRunner = class {
|
|
|
2077
2081
|
});
|
|
2078
2082
|
this.isRunning = true;
|
|
2079
2083
|
this.childProcess.on("error", (error) => {
|
|
2080
|
-
logger$
|
|
2084
|
+
logger$5.error("❌ Process error:", error);
|
|
2081
2085
|
});
|
|
2082
2086
|
this.childProcess.on("exit", (code) => {
|
|
2083
|
-
if (code !== null && code !== 0 && code !== 143) logger$
|
|
2087
|
+
if (code !== null && code !== 0 && code !== 143) logger$5.error(`❌ Process exited with code ${code}`);
|
|
2084
2088
|
this.isRunning = false;
|
|
2085
2089
|
});
|
|
2086
2090
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2087
2091
|
if (this.isRunning) {
|
|
2088
|
-
logger$
|
|
2089
|
-
logger$
|
|
2090
|
-
logger$
|
|
2092
|
+
logger$5.log("");
|
|
2093
|
+
logger$5.log(` \x1b[32m✓ Ready\x1b[0m at \x1b[36mhttp://localhost:${this.port}\x1b[0m`);
|
|
2094
|
+
logger$5.log("");
|
|
2091
2095
|
}
|
|
2092
2096
|
}
|
|
2093
2097
|
async restart() {
|
|
@@ -2206,7 +2210,7 @@ start({
|
|
|
2206
2210
|
* lets a console read what this machine is running.
|
|
2207
2211
|
*/
|
|
2208
2212
|
function announce(discovery) {
|
|
2209
|
-
logger$
|
|
2213
|
+
logger$5.log(`🧭 Discovery: ${discovery.connectUrl}`);
|
|
2210
2214
|
}
|
|
2211
2215
|
/** Run `fn` with `console.log` muted — warnings and errors still print. */
|
|
2212
2216
|
async function quietly(fn) {
|
|
@@ -2320,11 +2324,11 @@ var DevServer = class {
|
|
|
2320
2324
|
this.startTime = Date.now();
|
|
2321
2325
|
if (this.isRunning) await this.stop();
|
|
2322
2326
|
if (this.portExplicit) {
|
|
2323
|
-
if (!await
|
|
2327
|
+
if (!await require_state.isPortAvailable(this.requestedPort)) throw new DevPortInUse(this.requestedPort, listenerOn(this.requestedPort));
|
|
2324
2328
|
this.actualPort = this.requestedPort;
|
|
2325
2329
|
} else {
|
|
2326
|
-
this.actualPort = await
|
|
2327
|
-
if (this.actualPort !== this.requestedPort) logger$
|
|
2330
|
+
this.actualPort = await require_state.findAvailablePort(this.requestedPort);
|
|
2331
|
+
if (this.actualPort !== this.requestedPort) logger$5.log(`ℹ️ Port ${this.requestedPort} was in use, using port ${this.actualPort} instead`);
|
|
2328
2332
|
}
|
|
2329
2333
|
const serverEntryPath = (0, node_path.join)(this.appRoot, ".gkm", "server", "server.ts");
|
|
2330
2334
|
await this.createServerEntry();
|
|
@@ -2344,11 +2348,11 @@ var DevServer = class {
|
|
|
2344
2348
|
});
|
|
2345
2349
|
this.isRunning = true;
|
|
2346
2350
|
this.serverProcess.on("error", (error) => {
|
|
2347
|
-
logger$
|
|
2351
|
+
logger$5.error("❌ Server error:", error);
|
|
2348
2352
|
});
|
|
2349
2353
|
const child = this.serverProcess;
|
|
2350
2354
|
child.on("exit", (code, signal) => {
|
|
2351
|
-
if (code !== null && code !== 0 && signal !== "SIGTERM") logger$
|
|
2355
|
+
if (code !== null && code !== 0 && signal !== "SIGTERM") logger$5.error(`❌ Server exited with code ${code}`);
|
|
2352
2356
|
this.isRunning = false;
|
|
2353
2357
|
if (this.serverProcess === child) this.onStatus?.("stopped");
|
|
2354
2358
|
});
|
|
@@ -2357,13 +2361,13 @@ var DevServer = class {
|
|
|
2357
2361
|
const local = `http://localhost:${this.actualPort}`;
|
|
2358
2362
|
const address = this.publicUrl ? `${this.publicUrl} -> ${local}` : local;
|
|
2359
2363
|
const seconds = ((Date.now() - this.startTime) / 1e3).toFixed(1);
|
|
2360
|
-
logger$
|
|
2364
|
+
logger$5.log(`\x1b[32m✓\x1b[0m ${this.label} ready in ${seconds}s ${address}`);
|
|
2361
2365
|
const tools = this.selfServing ? [] : [
|
|
2362
2366
|
...this.enableOpenApi ? ["docs /__docs"] : [],
|
|
2363
2367
|
...this.telescope ? [`telescope ${this.telescope.path}`] : [],
|
|
2364
2368
|
...this.databaseApi ? [`db ${this.databaseApi}`] : []
|
|
2365
2369
|
];
|
|
2366
|
-
if (tools.length > 0) logger$
|
|
2370
|
+
if (tools.length > 0) logger$5.log(` ${tools.join(" · ")}`);
|
|
2367
2371
|
this.onStatus?.("ready");
|
|
2368
2372
|
}
|
|
2369
2373
|
}
|
|
@@ -2394,7 +2398,7 @@ var DevServer = class {
|
|
|
2394
2398
|
await this.stop();
|
|
2395
2399
|
let attempts = 0;
|
|
2396
2400
|
while (attempts < 30) {
|
|
2397
|
-
if (await
|
|
2401
|
+
if (await require_state.isPortAvailable(portToReuse)) break;
|
|
2398
2402
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2399
2403
|
attempts++;
|
|
2400
2404
|
}
|
|
@@ -3059,7 +3063,7 @@ ${shutdown}`);
|
|
|
3059
3063
|
}
|
|
3060
3064
|
//#endregion
|
|
3061
3065
|
//#region src/build/index.ts
|
|
3062
|
-
const logger$
|
|
3066
|
+
const logger$4 = console;
|
|
3063
3067
|
/**
|
|
3064
3068
|
* Where `gkm build --stage` leaves the key its bundle's secrets were encrypted
|
|
3065
3069
|
* with, beside the bundle in `.gkm/server/` and readable by its owner alone.
|
|
@@ -3107,12 +3111,12 @@ async function buildCommand(options) {
|
|
|
3107
3111
|
const isAtWorkspaceRoot = cwd === workspaceRoot;
|
|
3108
3112
|
const someAppIsElsewhere = Object.values(loadedConfig.workspace.apps).some((app) => (0, node_path.resolve)(workspaceRoot, app.path) !== workspaceRoot);
|
|
3109
3113
|
if (isAtWorkspaceRoot && someAppIsElsewhere) {
|
|
3110
|
-
logger$
|
|
3114
|
+
logger$4.log("📦 Detected workspace configuration");
|
|
3111
3115
|
return workspaceBuildCommand(loadedConfig.workspace, options);
|
|
3112
3116
|
}
|
|
3113
3117
|
}
|
|
3114
3118
|
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;
|
|
3115
|
-
const target = options.provider ??
|
|
3119
|
+
const target = options.provider ?? require_state.providerOf(loadedConfig.workspace);
|
|
3116
3120
|
const workspaceRoot = loadedConfig.workspace.root;
|
|
3117
3121
|
const appRoot = process.cwd();
|
|
3118
3122
|
const output = await buildOneApp({
|
|
@@ -3144,22 +3148,22 @@ async function declaredIn(config, cwd) {
|
|
|
3144
3148
|
*/
|
|
3145
3149
|
function backendsOf(workspace) {
|
|
3146
3150
|
return {
|
|
3147
|
-
cache:
|
|
3151
|
+
cache: require_state.cacheBackendFor(require_state.providerOf(workspace)),
|
|
3148
3152
|
email: "ses"
|
|
3149
3153
|
};
|
|
3150
3154
|
}
|
|
3151
3155
|
/** One app's build, from its config and the command's options. */
|
|
3152
3156
|
async function buildOneApp(input) {
|
|
3153
3157
|
const { config, options, workspace, appRoot, target } = input;
|
|
3154
|
-
const cacheBackend =
|
|
3158
|
+
const cacheBackend = require_state.cacheBackendFor(require_state.providerOf(workspace));
|
|
3155
3159
|
const production = normalizeProductionConfig(options.production ?? false);
|
|
3156
|
-
if (production) logger$
|
|
3157
|
-
logger$
|
|
3158
|
-
logger$
|
|
3160
|
+
if (production) logger$4.log(`🏭 Building for PRODUCTION`);
|
|
3161
|
+
logger$4.log(`Building for ${target}`);
|
|
3162
|
+
logger$4.log(`Loading constructs from: ${formatRoutes(config.constructs)}`);
|
|
3159
3163
|
const telescope = production ? void 0 : normalizeTelescopeConfig(config.telescope);
|
|
3160
|
-
if (telescope) logger$
|
|
3164
|
+
if (telescope) logger$4.log(`🔭 Telescope enabled at ${telescope.path}`);
|
|
3161
3165
|
const hooks = normalizeHooksConfig(config.hooks, appRoot);
|
|
3162
|
-
if (hooks) logger$
|
|
3166
|
+
if (hooks) logger$4.log(`🪝 Server hooks enabled`);
|
|
3163
3167
|
return buildApp({
|
|
3164
3168
|
config,
|
|
3165
3169
|
workspaceRoot: workspace.root,
|
|
@@ -3201,7 +3205,7 @@ async function writeManifest(input) {
|
|
|
3201
3205
|
});
|
|
3202
3206
|
const path = (0, node_path.join)(manifestDir, `${target}.ts`);
|
|
3203
3207
|
await (0, node_fs_promises.writeFile)(path, require_emit.manifestModule(folded, backends));
|
|
3204
|
-
logger$
|
|
3208
|
+
logger$4.log(`Manifest: ${(0, node_path.relative)(process.cwd(), path)}`);
|
|
3205
3209
|
}
|
|
3206
3210
|
/**
|
|
3207
3211
|
* Turn one app's constructs into its generated entry.
|
|
@@ -3229,11 +3233,11 @@ async function buildApp(input) {
|
|
|
3229
3233
|
});
|
|
3230
3234
|
if (selfServing) {
|
|
3231
3235
|
await writeSurfaceEntry((0, node_path.join)(appRoot, ".gkm", "server"), selfServing.source);
|
|
3232
|
-
logger$
|
|
3236
|
+
logger$4.log(`Generated a server for ${selfServing.id} from its own declaration`);
|
|
3233
3237
|
if (production?.bundle && !input.skipBundle) {
|
|
3234
3238
|
const outputDir = (0, node_path.join)(appRoot, ".gkm", "server");
|
|
3235
3239
|
const entryPoint = await writeSurfaceServer(outputDir, production);
|
|
3236
|
-
const { bundleServer } = await Promise.resolve().then(() => require("./bundler-
|
|
3240
|
+
const { bundleServer } = await Promise.resolve().then(() => require("./bundler-D6MlIfBT.cjs"));
|
|
3237
3241
|
await bundleServer({
|
|
3238
3242
|
entryPoint,
|
|
3239
3243
|
outputDir: (0, node_path.join)(outputDir, "dist"),
|
|
@@ -3242,7 +3246,7 @@ async function buildApp(input) {
|
|
|
3242
3246
|
external: production.external,
|
|
3243
3247
|
...input.stage ? { stage: input.stage } : {}
|
|
3244
3248
|
});
|
|
3245
|
-
logger$
|
|
3249
|
+
logger$4.log(`✅ Bundle complete: .gkm/server/dist/server.mjs`);
|
|
3246
3250
|
}
|
|
3247
3251
|
return { selfServing: selfServing.id };
|
|
3248
3252
|
}
|
|
@@ -3288,14 +3292,14 @@ async function buildApp(input) {
|
|
|
3288
3292
|
topicGenerator.load(code, appRoot, bustCache)
|
|
3289
3293
|
]);
|
|
3290
3294
|
const allEndpoints = servedBy(loadedEndpoints, derived.surface);
|
|
3291
|
-
logger$
|
|
3292
|
-
logger$
|
|
3293
|
-
logger$
|
|
3294
|
-
logger$
|
|
3295
|
-
logger$
|
|
3296
|
-
logger$
|
|
3295
|
+
logger$4.log(`Found ${allEndpoints.length} endpoints`);
|
|
3296
|
+
logger$4.log(`Found ${allFunctions.length} functions`);
|
|
3297
|
+
logger$4.log(`Found ${allCrons.length} crons`);
|
|
3298
|
+
logger$4.log(`Found ${allSubscribers.length} subscribers`);
|
|
3299
|
+
logger$4.log(`Found ${allQueues.length} queues`);
|
|
3300
|
+
logger$4.log(`Found ${allTopics.length} topics`);
|
|
3297
3301
|
if (!input.serveEmpty && allEndpoints.length === 0 && allFunctions.length === 0 && allCrons.length === 0 && allSubscribers.length === 0 && allQueues.length === 0 && allTopics.length === 0) {
|
|
3298
|
-
logger$
|
|
3302
|
+
logger$4.log("No endpoints, functions, crons, subscribers, queues, or topics found to process");
|
|
3299
3303
|
return {};
|
|
3300
3304
|
}
|
|
3301
3305
|
if (production?.subscribers === "include" && derived.surface) {
|
|
@@ -3308,7 +3312,7 @@ async function buildApp(input) {
|
|
|
3308
3312
|
[allQueues.length, "queue consumer"],
|
|
3309
3313
|
[allSubscribers.length, "subscriber"]
|
|
3310
3314
|
].filter(([n]) => n > 0).map(([n, what]) => `${n} ${what}${n === 1 ? "" : "s"}`);
|
|
3311
|
-
if (left.length > 0) logger$
|
|
3315
|
+
if (left.length > 0) logger$4.log(`Serving ${derived.surface.id} only: leaving out ${left.join(", ")} — they run in a Worker's image`);
|
|
3312
3316
|
}
|
|
3313
3317
|
const result = await buildForTarget(target, buildContext, appRoot, workspaceRoot, endpointGenerator, functionGenerator, cronGenerator, subscriberGenerator, queueGenerator, topicGenerator, allEndpoints, allFunctions, allCrons, allSubscribers, allQueues, allTopics, enableOpenApi, input.skipBundle ?? false, input.stage);
|
|
3314
3318
|
await require_openapi.generateOpenApi(allEndpoints.map(({ construct }) => construct), {
|
|
@@ -3331,7 +3335,7 @@ async function buildForTarget(target, context, appRoot, workspaceRoot, endpointG
|
|
|
3331
3335
|
force: true
|
|
3332
3336
|
});
|
|
3333
3337
|
await (0, node_fs_promises.mkdir)(outputDir, { recursive: true });
|
|
3334
|
-
logger$
|
|
3338
|
+
logger$4.log(`\nGenerating handlers for ${target}`);
|
|
3335
3339
|
const options = {
|
|
3336
3340
|
target,
|
|
3337
3341
|
root: workspaceRoot
|
|
@@ -3347,7 +3351,7 @@ async function buildForTarget(target, context, appRoot, workspaceRoot, endpointG
|
|
|
3347
3351
|
queueGenerator.build(context, queues, outputDir, options),
|
|
3348
3352
|
topicGenerator.build(context, topics, outputDir, options)
|
|
3349
3353
|
]);
|
|
3350
|
-
logger$
|
|
3354
|
+
logger$4.log(`Generated ${routes.length} routes, ${functionInfos.length} functions, ${cronInfos.length} crons, ${subscriberInfos.length} subscribers, ${queueInfos.length} queues, ${topicInfos.length} topics for ${target}`);
|
|
3351
3355
|
const fields = {
|
|
3352
3356
|
...context.surface ? { surface: context.surface.id } : {},
|
|
3353
3357
|
functions: functionInfos,
|
|
@@ -3370,8 +3374,8 @@ async function buildForTarget(target, context, appRoot, workspaceRoot, endpointG
|
|
|
3370
3374
|
};
|
|
3371
3375
|
let masterKey;
|
|
3372
3376
|
if (context.production?.bundle && !skipBundle) {
|
|
3373
|
-
logger$
|
|
3374
|
-
const { bundleServer } = await Promise.resolve().then(() => require("./bundler-
|
|
3377
|
+
logger$4.log(`\n📦 Bundling production server...`);
|
|
3378
|
+
const { bundleServer } = await Promise.resolve().then(() => require("./bundler-D6MlIfBT.cjs"));
|
|
3375
3379
|
const allConstructs = [
|
|
3376
3380
|
...endpoints.map((e) => e.construct),
|
|
3377
3381
|
...functions.map((f) => f.construct),
|
|
@@ -3388,13 +3392,13 @@ async function buildForTarget(target, context, appRoot, workspaceRoot, endpointG
|
|
|
3388
3392
|
stage,
|
|
3389
3393
|
constructs: allConstructs
|
|
3390
3394
|
})).masterKey;
|
|
3391
|
-
logger$
|
|
3395
|
+
logger$4.log(`✅ Bundle complete: .gkm/server/dist/server.mjs`);
|
|
3392
3396
|
if (masterKey) {
|
|
3393
3397
|
const keyPath = (0, node_path.join)(outputDir, MASTER_KEY_FILE);
|
|
3394
3398
|
await (0, node_fs_promises.writeFile)(keyPath, `${masterKey}\n`, { mode: 384 });
|
|
3395
3399
|
await (0, node_fs_promises.chmod)(keyPath, 384);
|
|
3396
|
-
logger$
|
|
3397
|
-
logger$
|
|
3400
|
+
logger$4.log(`\n🔐 Secrets encrypted for deployment`);
|
|
3401
|
+
logger$4.log(` Run with GKM_MASTER_KEY set to the key in ${(0, node_path.relative)(appRoot, keyPath)} (fingerprint ${require_sniffer.keyFingerprint(masterKey)})`);
|
|
3398
3402
|
}
|
|
3399
3403
|
}
|
|
3400
3404
|
return {
|
|
@@ -3411,7 +3415,7 @@ async function buildForTarget(target, context, appRoot, workspaceRoot, endpointG
|
|
|
3411
3415
|
* Detect available package manager.
|
|
3412
3416
|
* @internal Exported for testing
|
|
3413
3417
|
*/
|
|
3414
|
-
function detectPackageManager
|
|
3418
|
+
function detectPackageManager() {
|
|
3415
3419
|
if ((0, node_fs.existsSync)("pnpm-lock.yaml")) return "pnpm";
|
|
3416
3420
|
if ((0, node_fs.existsSync)("yarn.lock")) return "yarn";
|
|
3417
3421
|
return "npm";
|
|
@@ -3495,20 +3499,20 @@ async function workspaceBuildCommand(workspace, options) {
|
|
|
3495
3499
|
const apps = Object.entries(workspace.apps);
|
|
3496
3500
|
const backendApps = apps.filter(([, app]) => app.type === "backend");
|
|
3497
3501
|
const frontendApps = apps.filter(([, app]) => app.type === "web");
|
|
3498
|
-
logger$
|
|
3499
|
-
logger$
|
|
3500
|
-
logger$
|
|
3501
|
-
if (options.production) logger$
|
|
3502
|
+
logger$4.log(`\n🏗️ Building workspace: ${workspace.name}`);
|
|
3503
|
+
logger$4.log(` Backend apps: ${backendApps.map(([name]) => name).join(", ") || "none"}`);
|
|
3504
|
+
logger$4.log(` Frontend apps: ${frontendApps.map(([name]) => name).join(", ") || "none"}`);
|
|
3505
|
+
if (options.production) logger$4.log(` 🏭 Production mode enabled`);
|
|
3502
3506
|
const buildOrder = require_workspace.getAppBuildOrder(workspace);
|
|
3503
|
-
logger$
|
|
3507
|
+
logger$4.log(` Build order: ${buildOrder.join(" → ")}`);
|
|
3504
3508
|
try {
|
|
3505
|
-
const target = options.provider ??
|
|
3509
|
+
const target = options.provider ?? require_state.providerOf(workspace);
|
|
3506
3510
|
const builds = [];
|
|
3507
3511
|
for (const appName of buildOrder) {
|
|
3508
3512
|
const app = workspace.apps[appName];
|
|
3509
3513
|
const config = require_workspace.getAppGkmConfig(workspace, appName);
|
|
3510
3514
|
if (!app || app.type !== "backend" || !config) continue;
|
|
3511
|
-
logger$
|
|
3515
|
+
logger$4.log(`\n⚙️ ${appName}`);
|
|
3512
3516
|
const output = await buildOneApp({
|
|
3513
3517
|
config,
|
|
3514
3518
|
options,
|
|
@@ -3530,12 +3534,12 @@ async function workspaceBuildCommand(workspace, options) {
|
|
|
3530
3534
|
});
|
|
3531
3535
|
const { filters, unpackaged } = turboFilters(workspace, { exclude: "backend" });
|
|
3532
3536
|
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.`);
|
|
3533
|
-
const pm = detectPackageManager
|
|
3537
|
+
const pm = detectPackageManager();
|
|
3534
3538
|
const [turboBin, turboArgs] = getTurboArgs(pm, filters, { only: true });
|
|
3535
3539
|
if (filters.length > 0) {
|
|
3536
|
-
logger$
|
|
3537
|
-
logger$
|
|
3538
|
-
const sandbox = options.sandbox ?? new
|
|
3540
|
+
logger$4.log(`\n📦 Using ${pm} with Turbo for the other apps...\n`);
|
|
3541
|
+
logger$4.log(`Running: ${[turboBin, ...turboArgs].join(" ")}`);
|
|
3542
|
+
const sandbox = options.sandbox ?? new require_local.LocalSandbox({
|
|
3539
3543
|
root: workspace.root,
|
|
3540
3544
|
passEnv: ["TURBO_TOKEN"]
|
|
3541
3545
|
});
|
|
@@ -3545,10 +3549,10 @@ async function workspaceBuildCommand(workspace, options) {
|
|
|
3545
3549
|
...sandbox.env,
|
|
3546
3550
|
NODE_ENV: options.production ? "production" : "development"
|
|
3547
3551
|
},
|
|
3548
|
-
timeoutMs:
|
|
3552
|
+
timeoutMs: require_local.DEFAULT_TIMEOUT_MS,
|
|
3549
3553
|
output: "inherit"
|
|
3550
3554
|
});
|
|
3551
|
-
if (built.exitCode !== 0) throw new
|
|
3555
|
+
if (built.exitCode !== 0) throw new require_local.CommandFailed(turboBin, turboArgs, built.exitCode, built.signal);
|
|
3552
3556
|
}
|
|
3553
3557
|
for (const [appName, app] of apps) {
|
|
3554
3558
|
const outputPath = getAppOutputPath(workspace, appName, app);
|
|
@@ -3559,15 +3563,15 @@ async function workspaceBuildCommand(workspace, options) {
|
|
|
3559
3563
|
outputPath
|
|
3560
3564
|
});
|
|
3561
3565
|
}
|
|
3562
|
-
logger$
|
|
3563
|
-
logger$
|
|
3566
|
+
logger$4.log(`\n✅ Workspace build complete!`);
|
|
3567
|
+
logger$4.log(`\n📋 Build Summary:`);
|
|
3564
3568
|
for (const result of results) {
|
|
3565
3569
|
const icon = result.type === "backend" ? "⚙️" : result.type === "mobile" ? "📱" : "🌐";
|
|
3566
|
-
logger$
|
|
3570
|
+
logger$4.log(` ${icon} ${result.appName}: ${result.outputPath || "built"}`);
|
|
3567
3571
|
}
|
|
3568
3572
|
} catch (error) {
|
|
3569
3573
|
const errorMessage = error instanceof Error ? error.message : "Build failed";
|
|
3570
|
-
logger$
|
|
3574
|
+
logger$4.log(`\n❌ Build failed: ${errorMessage}`);
|
|
3571
3575
|
for (const [appName, app] of apps) results.push({
|
|
3572
3576
|
appName,
|
|
3573
3577
|
type: app.type,
|
|
@@ -3597,1169 +3601,17 @@ function formatRoutes(routes) {
|
|
|
3597
3601
|
return Array.isArray(routes) ? routes.join(", ") : routes;
|
|
3598
3602
|
}
|
|
3599
3603
|
//#endregion
|
|
3600
|
-
//#region src/migrate/provider.ts
|
|
3601
|
-
/**
|
|
3602
|
-
* One construct's migrations and seeds, read from its folders.
|
|
3603
|
-
*
|
|
3604
|
-
* Two kinds of file, told apart by extension rather than by configuration: a
|
|
3605
|
-
* script exports `up` (and may export `down`) — or `seed(db, { stage })`, in
|
|
3606
|
-
* `seeds/` — and a
|
|
3607
|
-
* `.sql` file is run whole. The second is what makes a folder independent of
|
|
3608
|
-
* the client the app uses — Better Auth's schema is SQL whatever the
|
|
3609
|
-
* application queries with.
|
|
3610
|
-
*/
|
|
3611
|
-
const SCRIPTS = /* @__PURE__ */ new Set([
|
|
3612
|
-
".ts",
|
|
3613
|
-
".mts",
|
|
3614
|
-
".js",
|
|
3615
|
-
".mjs"
|
|
3616
|
-
]);
|
|
3617
|
-
/** Two files in one folder that would be one migration. */
|
|
3618
|
-
var DuplicateMigration = class extends Error {
|
|
3619
|
-
folder;
|
|
3620
|
-
migration;
|
|
3621
|
-
constructor(folder, migration) {
|
|
3622
|
-
super(`${folder} has two migrations named '${migration}' — a script and a .sql file with the same name. A migration is recorded by name, so only one of them could ever run; rename or remove the other.`);
|
|
3623
|
-
this.folder = folder;
|
|
3624
|
-
this.migration = migration;
|
|
3625
|
-
this.name = "DuplicateMigration";
|
|
3626
|
-
}
|
|
3627
|
-
};
|
|
3628
|
-
/** A script in a migrations folder that exports no `up`. */
|
|
3629
|
-
var MigrationHasNoUp = class extends Error {
|
|
3630
|
-
file;
|
|
3631
|
-
constructor(file) {
|
|
3632
|
-
super(`${file} is in a migrations folder but exports no \`up\` function. Export \`async function up(db: Kysely<unknown>)\`, or move the file out of the folder if it is not a migration.`);
|
|
3633
|
-
this.file = file;
|
|
3634
|
-
this.name = "MigrationHasNoUp";
|
|
3635
|
-
}
|
|
3636
|
-
};
|
|
3637
|
-
/** A script in a seeds folder that exports no `seed`. */
|
|
3638
|
-
var SeedHasNoSeed = class extends Error {
|
|
3639
|
-
file;
|
|
3640
|
-
constructor(file) {
|
|
3641
|
-
super(`${file} is in a seeds folder but exports no \`seed\` function. Export \`async function seed(db, { stage })\`, or move the file out of the folder if it is not a seed.`);
|
|
3642
|
-
this.file = file;
|
|
3643
|
-
this.name = "SeedHasNoSeed";
|
|
3644
|
-
}
|
|
3645
|
-
};
|
|
3646
|
-
/**
|
|
3647
|
-
* The migration files in a folder, by name, without reading them.
|
|
3648
|
-
*
|
|
3649
|
-
* Also what `gkm dev` compares against a construct's history to report what
|
|
3650
|
-
* is pending, which is why it is separate from loading them.
|
|
3651
|
-
*/
|
|
3652
|
-
async function migrationFiles(folder) {
|
|
3653
|
-
const entries = await (0, node_fs_promises.readdir)(folder, { withFileTypes: true }).catch((error) => {
|
|
3654
|
-
if (error.code === "ENOENT") return [];
|
|
3655
|
-
throw error;
|
|
3656
|
-
});
|
|
3657
|
-
const files = /* @__PURE__ */ new Map();
|
|
3658
|
-
for (const entry of entries) {
|
|
3659
|
-
if (!entry.isFile() || !isMigration(entry.name)) continue;
|
|
3660
|
-
const name = entry.name.slice(0, -(0, node_path.extname)(entry.name).length);
|
|
3661
|
-
if (files.has(name)) throw new DuplicateMigration(folder, name);
|
|
3662
|
-
files.set(name, (0, node_path.join)(folder, entry.name));
|
|
3663
|
-
}
|
|
3664
|
-
return files;
|
|
3665
|
-
}
|
|
3666
|
-
/** A Kysely provider over one construct's folder. */
|
|
3667
|
-
function folderProvider(folder, raw) {
|
|
3668
|
-
return { async getMigrations() {
|
|
3669
|
-
const migrations = {};
|
|
3670
|
-
for (const [name, file] of await migrationFiles(folder)) migrations[name] = (0, node_path.extname)(file) === ".sql" ? await sqlMigration(file, raw) : await scriptMigration(file);
|
|
3671
|
-
return migrations;
|
|
3672
|
-
} };
|
|
3673
|
-
}
|
|
3674
|
-
/**
|
|
3675
|
-
* The seeds in a folder, loaded, in name order.
|
|
3676
|
-
*
|
|
3677
|
-
* Every one runs on every pass — there is no history to consult — so the order
|
|
3678
|
-
* is the only thing between two seeds where one reads what another writes.
|
|
3679
|
-
* Name them to sort that way.
|
|
3680
|
-
*/
|
|
3681
|
-
async function loadSeeds(folder, raw) {
|
|
3682
|
-
const files = [...await migrationFiles(folder)].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
3683
|
-
const seeds = [];
|
|
3684
|
-
for (const [name, file] of files) seeds.push({
|
|
3685
|
-
name,
|
|
3686
|
-
run: (0, node_path.extname)(file) === ".sql" ? (await sqlMigration(file, raw)).up : await scriptSeed(file)
|
|
3687
|
-
});
|
|
3688
|
-
return seeds;
|
|
3689
|
-
}
|
|
3690
|
-
/**
|
|
3691
|
-
* A seed script, imported as it is on disk now.
|
|
3692
|
-
*
|
|
3693
|
-
* Keyed by its modification time: a seed is run again after it is edited, and
|
|
3694
|
-
* a process that has imported it once would otherwise run the old one.
|
|
3695
|
-
*/
|
|
3696
|
-
async function scriptSeed(file) {
|
|
3697
|
-
const url = (0, node_url.pathToFileURL)(file);
|
|
3698
|
-
url.searchParams.set("mtime", String((await (0, node_fs_promises.stat)(file)).mtimeMs));
|
|
3699
|
-
const module = await import(url.href);
|
|
3700
|
-
if (typeof module.seed !== "function") throw new SeedHasNoSeed(file);
|
|
3701
|
-
return module.seed;
|
|
3702
|
-
}
|
|
3703
|
-
function isMigration(file) {
|
|
3704
|
-
if (file.endsWith(".d.ts")) return false;
|
|
3705
|
-
if (/\.(spec|test)\.[cm]?[jt]s$/.test(file)) return false;
|
|
3706
|
-
const extension = (0, node_path.extname)(file);
|
|
3707
|
-
return extension === ".sql" || SCRIPTS.has(extension);
|
|
3708
|
-
}
|
|
3709
|
-
async function scriptMigration(file) {
|
|
3710
|
-
const module = await import((0, node_url.pathToFileURL)(file).href);
|
|
3711
|
-
if (typeof module.up !== "function") throw new MigrationHasNoUp(file);
|
|
3712
|
-
return {
|
|
3713
|
-
up: module.up,
|
|
3714
|
-
...typeof module.down === "function" ? { down: module.down } : {}
|
|
3715
|
-
};
|
|
3716
|
-
}
|
|
3717
|
-
/**
|
|
3718
|
-
* A `.sql` file, run whole.
|
|
3719
|
-
*
|
|
3720
|
-
* One query rather than split on `;`: with no parameters, Postgres takes
|
|
3721
|
-
* several statements in one message, and splitting by hand breaks on the first
|
|
3722
|
-
* `;` inside a string or a function body. There is no `down` — a generated
|
|
3723
|
-
* schema is rolled back by a migration that says how, not by guessing.
|
|
3724
|
-
*/
|
|
3725
|
-
async function sqlMigration(file, raw) {
|
|
3726
|
-
const statements = await (0, node_fs_promises.readFile)(file, "utf-8");
|
|
3727
|
-
return { up: async (db) => {
|
|
3728
|
-
await raw.raw(statements).execute(db);
|
|
3729
|
-
} };
|
|
3730
|
-
}
|
|
3731
|
-
//#endregion
|
|
3732
|
-
//#region src/migrate/databases.ts
|
|
3733
|
-
/**
|
|
3734
|
-
* Bring each construct's schema up to its migrations folder.
|
|
3735
|
-
*
|
|
3736
|
-
* Which constructs, in what order, from which folder, is `migrationTargets`'
|
|
3737
|
-
* answer and nobody else's. What is decided here is only how one target is
|
|
3738
|
-
* run: as its owner, in its own schema, with a history of its own.
|
|
3739
|
-
*/
|
|
3740
|
-
/** A folder under `db/` that names no construct. */
|
|
3741
|
-
var UnknownMigrationFolder = class extends Error {
|
|
3742
|
-
folder;
|
|
3743
|
-
known;
|
|
3744
|
-
constructor(folder, known) {
|
|
3745
|
-
super(`${folder} names no database construct, so its migrations would never run. ${known.length > 0 ? `The folders that do: ${known.join(", ")}.` : "This project declares no database."} Rename the folder after the construct it belongs to.`);
|
|
3746
|
-
this.folder = folder;
|
|
3747
|
-
this.known = known;
|
|
3748
|
-
this.name = "UnknownMigrationFolder";
|
|
3749
|
-
}
|
|
3750
|
-
};
|
|
3751
|
-
/** A construct was asked for by a name that takes no migrations. */
|
|
3752
|
-
var NoSuchMigrationTarget = class extends Error {
|
|
3753
|
-
construct;
|
|
3754
|
-
known;
|
|
3755
|
-
constructor(construct, known) {
|
|
3756
|
-
super(`'${construct}' is not a database construct. ` + (known.length > 0 ? `The ones with migrations: ${known.join(", ")}.` : "This project declares no database."));
|
|
3757
|
-
this.construct = construct;
|
|
3758
|
-
this.known = known;
|
|
3759
|
-
this.name = "NoSuchMigrationTarget";
|
|
3760
|
-
}
|
|
3761
|
-
};
|
|
3762
|
-
/**
|
|
3763
|
-
* A migration left directly in a construct's folder, where nothing reads it.
|
|
3764
|
-
*
|
|
3765
|
-
* Migrations moved into `migrations/` so seeds could sit beside them. A file
|
|
3766
|
-
* still at the old level would otherwise never run, and nothing would say so.
|
|
3767
|
-
*/
|
|
3768
|
-
var MigrationsOutsideFolder = class extends Error {
|
|
3769
|
-
folder;
|
|
3770
|
-
files;
|
|
3771
|
-
constructor(folder, files) {
|
|
3772
|
-
super(`${folder} has ${files.join(", ")} directly inside it, where nothing runs them. Migrations live in ${folder}/migrations/ and seeds in ${folder}/seeds/ — move ${files.length === 1 ? "it" : "them"} there.`);
|
|
3773
|
-
this.folder = folder;
|
|
3774
|
-
this.files = files;
|
|
3775
|
-
this.name = "MigrationsOutsideFolder";
|
|
3776
|
-
}
|
|
3777
|
-
};
|
|
3778
|
-
/** A folder inside a construct's folder that is neither `migrations` nor `seeds`. */
|
|
3779
|
-
var UnknownDatabaseFolder = class extends Error {
|
|
3780
|
-
folder;
|
|
3781
|
-
constructor(folder) {
|
|
3782
|
-
super(`${folder} is neither migrations/ nor seeds/, so nothing in it would ever run. A construct's folder holds those two; rename or remove it.`);
|
|
3783
|
-
this.folder = folder;
|
|
3784
|
-
this.name = "UnknownDatabaseFolder";
|
|
3785
|
-
}
|
|
3786
|
-
};
|
|
3787
|
-
/** A seed failed; its own writes are rolled back, the ones before it stay. */
|
|
3788
|
-
var SeedFailed = class extends Error {
|
|
3789
|
-
construct;
|
|
3790
|
-
seed;
|
|
3791
|
-
cause;
|
|
3792
|
-
constructor(construct, seed, cause) {
|
|
3793
|
-
super(`${construct}: seed '${seed}' failed — ${cause instanceof Error ? cause.message : String(cause)}. Its writes were rolled back; fix it and run it again.`);
|
|
3794
|
-
this.construct = construct;
|
|
3795
|
-
this.seed = seed;
|
|
3796
|
-
this.cause = cause;
|
|
3797
|
-
this.name = "SeedFailed";
|
|
3798
|
-
}
|
|
3799
|
-
};
|
|
3800
|
-
/** A construct with roles has no owner URL to migrate with. */
|
|
3801
|
-
var NoOwnerCredential = class extends Error {
|
|
3802
|
-
construct;
|
|
3803
|
-
key;
|
|
3804
|
-
constructor(construct, key) {
|
|
3805
|
-
super(`${construct} has no ${key} to migrate with. Migrations run as the owner role — the runtime one cannot create a table — and nothing resolved its URL. Is the stage reconciled?`);
|
|
3806
|
-
this.construct = construct;
|
|
3807
|
-
this.key = key;
|
|
3808
|
-
this.name = "NoOwnerCredential";
|
|
3809
|
-
}
|
|
3810
|
-
};
|
|
3811
|
-
/** A migration failed; the ones before it in the run stay applied. */
|
|
3812
|
-
var MigrationFailed = class extends Error {
|
|
3813
|
-
construct;
|
|
3814
|
-
migration;
|
|
3815
|
-
cause;
|
|
3816
|
-
constructor(construct, migration, cause) {
|
|
3817
|
-
super(`${construct}: migration ${migration ? `'${migration}' ` : ""}failed — ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
3818
|
-
this.construct = construct;
|
|
3819
|
-
this.migration = migration;
|
|
3820
|
-
this.cause = cause;
|
|
3821
|
-
this.name = "MigrationFailed";
|
|
3822
|
-
}
|
|
3823
|
-
};
|
|
3824
|
-
/** The project's Kysely could not be found from where a database is declared. */
|
|
3825
|
-
var KyselyNotFound = class extends Error {
|
|
3826
|
-
from;
|
|
3827
|
-
constructor(from) {
|
|
3828
|
-
super(`Could not resolve 'kysely' from ${from}. Migrations run with the project's own Kysely — install it where the database is declared.`);
|
|
3829
|
-
this.from = from;
|
|
3830
|
-
this.name = "KyselyNotFound";
|
|
3831
|
-
}
|
|
3832
|
-
};
|
|
3833
|
-
/** Apply every pending migration, construct by construct, parents first. */
|
|
3834
|
-
async function migrateDatabases(options) {
|
|
3835
|
-
const runs = [];
|
|
3836
|
-
for (const target of await targetsFor(options)) {
|
|
3837
|
-
if ((await migrationFiles((0, node_path.join)(options.root, target.migrations))).size === 0) {
|
|
3838
|
-
runs.push({
|
|
3839
|
-
target,
|
|
3840
|
-
applied: []
|
|
3841
|
-
});
|
|
3842
|
-
continue;
|
|
3843
|
-
}
|
|
3844
|
-
const applied = await withMigrator(options, target, async (migrator) => {
|
|
3845
|
-
const { error, results = [] } = await migrator.migrateToLatest();
|
|
3846
|
-
if (error) {
|
|
3847
|
-
const failed = results.find((result) => result.status === "Error");
|
|
3848
|
-
throw new MigrationFailed(target.id, failed?.migrationName, error);
|
|
3849
|
-
}
|
|
3850
|
-
return results.filter((result) => result.status === "Success").map((result) => result.migrationName);
|
|
3851
|
-
});
|
|
3852
|
-
runs.push({
|
|
3853
|
-
target,
|
|
3854
|
-
applied
|
|
3855
|
-
});
|
|
3856
|
-
}
|
|
3857
|
-
return runs;
|
|
3858
|
-
}
|
|
3859
|
-
/**
|
|
3860
|
-
* Run every construct's seeds, parents first — each one, every time, on every
|
|
3861
|
-
* stage, production included. Each is handed the stage, so a seed decides
|
|
3862
|
-
* what belongs where.
|
|
3863
|
-
*
|
|
3864
|
-
* Seeds are reference data kept up to date by being run again, so there is no
|
|
3865
|
-
* history: a seed is an upsert, and a changed one is applied by the next run.
|
|
3866
|
-
* Each runs in its own transaction, as the construct's owner, so a failure
|
|
3867
|
-
* leaves nothing half-written. Assumes the schema is migrated —
|
|
3868
|
-
* {@link migrateAndSeed} is what makes that true.
|
|
3869
|
-
*/
|
|
3870
|
-
async function seedDatabases(options) {
|
|
3871
|
-
const runs = [];
|
|
3872
|
-
for (const target of await targetsFor(options)) {
|
|
3873
|
-
const folder = (0, node_path.join)(options.root, target.seeds);
|
|
3874
|
-
if ((await migrationFiles(folder)).size === 0) {
|
|
3875
|
-
runs.push({
|
|
3876
|
-
target,
|
|
3877
|
-
seeded: []
|
|
3878
|
-
});
|
|
3879
|
-
continue;
|
|
3880
|
-
}
|
|
3881
|
-
const seeded = await withOwner(options, target, async (db, kysely) => {
|
|
3882
|
-
const names = [];
|
|
3883
|
-
for (const seed of await loadSeeds(folder, kysely.sql)) {
|
|
3884
|
-
try {
|
|
3885
|
-
await db.transaction().execute((trx) => seed.run(trx, { stage: options.stage }));
|
|
3886
|
-
} catch (error) {
|
|
3887
|
-
throw new SeedFailed(target.id, seed.name, error);
|
|
3888
|
-
}
|
|
3889
|
-
names.push(seed.name);
|
|
3890
|
-
}
|
|
3891
|
-
return names;
|
|
3892
|
-
});
|
|
3893
|
-
runs.push({
|
|
3894
|
-
target,
|
|
3895
|
-
seeded
|
|
3896
|
-
});
|
|
3897
|
-
}
|
|
3898
|
-
return runs;
|
|
3899
|
-
}
|
|
3900
|
-
/**
|
|
3901
|
-
* Migrate, then seed: a seed only ever runs against the schema it was written
|
|
3902
|
-
* for.
|
|
3903
|
-
*
|
|
3904
|
-
* With `only`, the constructs it lives in are migrated too — a tenant's seed
|
|
3905
|
-
* cannot run before the database that owns its schema exists — and only it is
|
|
3906
|
-
* seeded.
|
|
3907
|
-
*/
|
|
3908
|
-
async function migrateAndSeed(options) {
|
|
3909
|
-
const migrations = [];
|
|
3910
|
-
for (const target of await withParents(options)) migrations.push(...await migrateDatabases({
|
|
3911
|
-
...options,
|
|
3912
|
-
only: target.id
|
|
3913
|
-
}));
|
|
3914
|
-
return {
|
|
3915
|
-
migrations,
|
|
3916
|
-
seeds: await seedDatabases(options)
|
|
3917
|
-
};
|
|
3918
|
-
}
|
|
3919
|
-
/** The targets `only` names, after the ones it lives in; all without it. */
|
|
3920
|
-
async function withParents(options) {
|
|
3921
|
-
const chosen = await targetsFor(options);
|
|
3922
|
-
if (!options.only) return chosen;
|
|
3923
|
-
const all = (0, _geekmidas_manifest.migrationTargets)(options.manifest);
|
|
3924
|
-
const ids = /* @__PURE__ */ new Set();
|
|
3925
|
-
for (const target of chosen) {
|
|
3926
|
-
let current = target;
|
|
3927
|
-
while (current) {
|
|
3928
|
-
ids.add(current.id);
|
|
3929
|
-
const parent = current.of;
|
|
3930
|
-
current = parent ? all.find((t) => t.id === parent) : void 0;
|
|
3931
|
-
}
|
|
3932
|
-
}
|
|
3933
|
-
return all.filter((target) => ids.has(target.id));
|
|
3934
|
-
}
|
|
3935
|
-
/**
|
|
3936
|
-
* What each construct's folder has that its history does not — without
|
|
3937
|
-
* applying any of it. What `gkm dev` reports.
|
|
3938
|
-
*/
|
|
3939
|
-
async function pendingMigrations(options) {
|
|
3940
|
-
const found = [];
|
|
3941
|
-
for (const target of await targetsFor(options)) {
|
|
3942
|
-
if ((await migrationFiles((0, node_path.join)(options.root, target.migrations))).size === 0) continue;
|
|
3943
|
-
const pending = await withMigrator(options, target, async (migrator) => (await migrator.getMigrations()).filter((migration) => migration.executedAt === void 0).map((migration) => migration.name));
|
|
3944
|
-
if (pending.length > 0) found.push({
|
|
3945
|
-
target,
|
|
3946
|
-
pending
|
|
3947
|
-
});
|
|
3948
|
-
}
|
|
3949
|
-
return found;
|
|
3950
|
-
}
|
|
3951
|
-
/**
|
|
3952
|
-
* The targets this run covers, with every folder under `db/` accounted for.
|
|
3953
|
-
*
|
|
3954
|
-
* A folder that names no construct is an error rather than something to skip:
|
|
3955
|
-
* a typo in its name is otherwise a migration that silently never runs.
|
|
3956
|
-
*/
|
|
3957
|
-
async function targetsFor(options) {
|
|
3958
|
-
const targets = (0, _geekmidas_manifest.migrationTargets)(options.manifest);
|
|
3959
|
-
const folders = targets.map((target) => target.folder);
|
|
3960
|
-
for (const folder of await foldersIn((0, node_path.join)(options.root, _geekmidas_manifest.MIGRATIONS_ROOT))) {
|
|
3961
|
-
const relative = `${_geekmidas_manifest.MIGRATIONS_ROOT}/${folder}`;
|
|
3962
|
-
if (!folders.includes(relative)) throw new UnknownMigrationFolder(relative, folders);
|
|
3963
|
-
await checkLayout(options.root, relative);
|
|
3964
|
-
}
|
|
3965
|
-
if (!options.only) return targets;
|
|
3966
|
-
const id = (0, _geekmidas_manifest.canonicalId)(options.only);
|
|
3967
|
-
const chosen = targets.filter((target) => target.id === id);
|
|
3968
|
-
if (chosen.length === 0) throw new NoSuchMigrationTarget(options.only, targets.map((target) => target.id));
|
|
3969
|
-
return chosen;
|
|
3970
|
-
}
|
|
3971
|
-
/**
|
|
3972
|
-
* A construct's folder holds `migrations/` and `seeds/`, and nothing else —
|
|
3973
|
-
* anything beside them is a file or folder nothing would ever run.
|
|
3974
|
-
*/
|
|
3975
|
-
async function checkLayout(root, folder) {
|
|
3976
|
-
const entries = await (0, node_fs_promises.readdir)((0, node_path.join)(root, folder), { withFileTypes: true });
|
|
3977
|
-
const loose = entries.filter((entry) => entry.isFile() && !entry.name.startsWith(".")).map((entry) => entry.name);
|
|
3978
|
-
if (loose.length > 0) throw new MigrationsOutsideFolder(folder, loose);
|
|
3979
|
-
for (const entry of entries) if (entry.isDirectory() && !_geekmidas_manifest.DATABASE_FOLDERS.includes(entry.name)) throw new UnknownDatabaseFolder(`${folder}/${entry.name}`);
|
|
3980
|
-
}
|
|
3981
|
-
async function foldersIn(dir) {
|
|
3982
|
-
return (await (0, node_fs_promises.readdir)(dir, { withFileTypes: true }).catch((error) => {
|
|
3983
|
-
if (error.code === "ENOENT") return [];
|
|
3984
|
-
throw error;
|
|
3985
|
-
})).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
3986
|
-
}
|
|
3987
|
-
/**
|
|
3988
|
-
* A migrator for one target, connected as its owner.
|
|
3989
|
-
*
|
|
3990
|
-
* No `migrationTableSchema`: the owner role's `search_path` is pinned to the
|
|
3991
|
-
* construct's schema, so the history lands there — `app.kysely_migration` for
|
|
3992
|
-
* the database, `auth_database.kysely_migration` for its tenant — and two
|
|
3993
|
-
* constructs in one Postgres never share one.
|
|
3994
|
-
*
|
|
3995
|
-
* Unordered migrations are allowed. Files are timestamped, so a branch merged
|
|
3996
|
-
* after another's migration ran carries an older one; refusing it would make
|
|
3997
|
-
* every such merge a manual repair.
|
|
3998
|
-
*/
|
|
3999
|
-
async function withMigrator(options, target, run) {
|
|
4000
|
-
return withOwner(options, target, (db, { Migrator, sql }) => run(new Migrator({
|
|
4001
|
-
db,
|
|
4002
|
-
provider: folderProvider((0, node_path.join)(options.root, target.migrations), sql),
|
|
4003
|
-
allowUnorderedMigrations: true
|
|
4004
|
-
})));
|
|
4005
|
-
}
|
|
4006
|
-
/**
|
|
4007
|
-
* One target's database, connected as its owner and closed afterwards — what
|
|
4008
|
-
* migrations and seeds both run as.
|
|
4009
|
-
*/
|
|
4010
|
-
async function withOwner(options, target, run) {
|
|
4011
|
-
const kysely = await kyselyFrom(options.sources?.[target.id]?.file ?? (target.of ? options.sources?.[target.of]?.file : void 0) ?? (0, node_path.join)(options.root, "package.json"));
|
|
4012
|
-
const db = new kysely.Kysely({ dialect: new kysely.PostgresDialect({ pool: new pg.default.Pool(poolConfig(ownerUrl(options, target))) }) });
|
|
4013
|
-
try {
|
|
4014
|
-
return await run(db, kysely);
|
|
4015
|
-
} finally {
|
|
4016
|
-
await db.destroy();
|
|
4017
|
-
}
|
|
4018
|
-
}
|
|
4019
|
-
/**
|
|
4020
|
-
* The owner's URL, with no fallback to the runtime one.
|
|
4021
|
-
*
|
|
4022
|
-
* The runtime role can create nothing, so falling back would only move the
|
|
4023
|
-
* failure to the first `CREATE TABLE` and make it read like a permissions
|
|
4024
|
-
* bug. The one exception is a database declared `roles: false`, where there is
|
|
4025
|
-
* no owner — both URLs are the master credential by design.
|
|
4026
|
-
*/
|
|
4027
|
-
function ownerUrl(options, target) {
|
|
4028
|
-
const key = (0, _geekmidas_manifest.provideKey)(target.id, "ownerUrl");
|
|
4029
|
-
const owner = options.env[key];
|
|
4030
|
-
if (owner) return owner;
|
|
4031
|
-
const database = options.manifest[target.of ?? target.id];
|
|
4032
|
-
if (database?.kind === "database" && database.roles === false) {
|
|
4033
|
-
const url = options.env[(0, _geekmidas_manifest.provideKey)(target.id, "url")];
|
|
4034
|
-
if (url) return url;
|
|
4035
|
-
}
|
|
4036
|
-
throw new NoOwnerCredential(target.id, key);
|
|
4037
|
-
}
|
|
4038
|
-
/**
|
|
4039
|
-
* Pool settings for a URL, with `?search_path=` as the startup option Postgres
|
|
4040
|
-
* reads. Only a `roles: false` URL carries it — a role pins its own.
|
|
4041
|
-
*/
|
|
4042
|
-
function poolConfig(url) {
|
|
4043
|
-
const parsed = new URL(url);
|
|
4044
|
-
const searchPath = parsed.searchParams.get("search_path");
|
|
4045
|
-
if (!searchPath) return { connectionString: url };
|
|
4046
|
-
parsed.searchParams.delete("search_path");
|
|
4047
|
-
return {
|
|
4048
|
-
connectionString: parsed.toString(),
|
|
4049
|
-
options: `-c search_path=${searchPath}`
|
|
4050
|
-
};
|
|
4051
|
-
}
|
|
4052
|
-
/** Kysely as the project resolves it from `from`. */
|
|
4053
|
-
async function kyselyFrom(from) {
|
|
4054
|
-
const require$2 = (0, node_module.createRequire)(from);
|
|
4055
|
-
try {
|
|
4056
|
-
const [main, migration] = await Promise.all([import((0, node_url.pathToFileURL)(require$2.resolve("kysely")).href), import((0, node_url.pathToFileURL)(require$2.resolve("kysely/migration")).href)]);
|
|
4057
|
-
return {
|
|
4058
|
-
Kysely: main.Kysely,
|
|
4059
|
-
PostgresDialect: main.PostgresDialect,
|
|
4060
|
-
sql: main.sql,
|
|
4061
|
-
Migrator: migration.Migrator
|
|
4062
|
-
};
|
|
4063
|
-
} catch {
|
|
4064
|
-
throw new KyselyNotFound(from);
|
|
4065
|
-
}
|
|
4066
|
-
}
|
|
4067
|
-
//#endregion
|
|
4068
|
-
//#region src/compose/docker.ts
|
|
4069
|
-
/**
|
|
4070
|
-
* The Docker operations `gkm compose` needs, behind an interface.
|
|
4071
|
-
*
|
|
4072
|
-
* Small on purpose, like reconcile's: the rules about *when* each runs — the
|
|
4073
|
-
* registry is asked before anything is pulled, nothing is started for a tag
|
|
4074
|
-
* that is missing — are asserted against a fake, and this file is only the
|
|
4075
|
-
* wiring to the real CLI.
|
|
4076
|
-
*
|
|
4077
|
-
* Every call is an argument array. Image refs, tags and paths come from the
|
|
4078
|
-
* workspace, and none of them reaches a shell.
|
|
4079
|
-
*/
|
|
4080
|
-
/** How long a stack has to pass its health checks. */
|
|
4081
|
-
const WAIT_TIMEOUT_S = 180;
|
|
4082
|
-
/** `docker compose port` printed nothing a port can be read from. */
|
|
4083
|
-
var ServicePortUnknown = class extends Error {
|
|
4084
|
-
service;
|
|
4085
|
-
inside;
|
|
4086
|
-
output;
|
|
4087
|
-
constructor(service, inside, output) {
|
|
4088
|
-
super(`'${service}' publishes no host port for ${inside} (docker said: ${output.trim() || "nothing"}). Is it running? \`docker compose ps\` shows the stack.`);
|
|
4089
|
-
this.service = service;
|
|
4090
|
-
this.inside = inside;
|
|
4091
|
-
this.output = output;
|
|
4092
|
-
this.name = "ServicePortUnknown";
|
|
4093
|
-
}
|
|
4094
|
-
};
|
|
4095
|
-
function compose(stack, args) {
|
|
4096
|
-
return [
|
|
4097
|
-
"compose",
|
|
4098
|
-
"-p",
|
|
4099
|
-
stack.project,
|
|
4100
|
-
"-f",
|
|
4101
|
-
stack.file,
|
|
4102
|
-
...args
|
|
4103
|
-
];
|
|
4104
|
-
}
|
|
4105
|
-
/** Run a program and keep everything it said, whatever it exits with. */
|
|
4106
|
-
function capture(command, args, timeoutMs = 6e4) {
|
|
4107
|
-
return new Promise((resolve, reject) => {
|
|
4108
|
-
const child = (0, node_child_process.spawn)(command, [...args], {
|
|
4109
|
-
stdio: [
|
|
4110
|
-
"ignore",
|
|
4111
|
-
"pipe",
|
|
4112
|
-
"pipe"
|
|
4113
|
-
],
|
|
4114
|
-
shell: false
|
|
4115
|
-
});
|
|
4116
|
-
let stdout = "";
|
|
4117
|
-
let stderr = "";
|
|
4118
|
-
child.stdout.on("data", (chunk) => {
|
|
4119
|
-
stdout += chunk.toString();
|
|
4120
|
-
});
|
|
4121
|
-
child.stderr.on("data", (chunk) => {
|
|
4122
|
-
stderr += chunk.toString();
|
|
4123
|
-
});
|
|
4124
|
-
const timer = setTimeout(() => child.kill("SIGKILL"), timeoutMs);
|
|
4125
|
-
timer.unref?.();
|
|
4126
|
-
child.on("error", (error) => {
|
|
4127
|
-
clearTimeout(timer);
|
|
4128
|
-
reject(error);
|
|
4129
|
-
});
|
|
4130
|
-
child.on("close", (code) => {
|
|
4131
|
-
clearTimeout(timer);
|
|
4132
|
-
resolve({
|
|
4133
|
-
code,
|
|
4134
|
-
stdout,
|
|
4135
|
-
stderr
|
|
4136
|
-
});
|
|
4137
|
-
});
|
|
4138
|
-
});
|
|
4139
|
-
}
|
|
4140
|
-
/**
|
|
4141
|
-
* Whether a failed `docker manifest inspect` means the tag is not there, as
|
|
4142
|
-
* opposed to the registry not answering.
|
|
4143
|
-
*
|
|
4144
|
-
* Read off the message because that is all the CLI gives. Anything not
|
|
4145
|
-
* recognisably "no such tag" is treated as the registry being unreachable:
|
|
4146
|
-
* a registry that refused credentials must not be reported as a tag that was
|
|
4147
|
-
* never pushed, or the fix offered would be the wrong one.
|
|
4148
|
-
*/
|
|
4149
|
-
function isMissingManifest(output) {
|
|
4150
|
-
return /no such manifest|manifest unknown|manifest for .* not found|not found: manifest/i.test(output);
|
|
4151
|
-
}
|
|
4152
|
-
const dockerCompose = {
|
|
4153
|
-
async lookup(ref) {
|
|
4154
|
-
const { code, stdout, stderr } = await capture("docker", [
|
|
4155
|
-
"manifest",
|
|
4156
|
-
"inspect",
|
|
4157
|
-
ref
|
|
4158
|
-
]);
|
|
4159
|
-
if (code === 0) return {
|
|
4160
|
-
ref,
|
|
4161
|
-
status: "found"
|
|
4162
|
-
};
|
|
4163
|
-
const output = `${stderr}\n${stdout}`.trim();
|
|
4164
|
-
return isMissingManifest(output) ? {
|
|
4165
|
-
ref,
|
|
4166
|
-
status: "missing"
|
|
4167
|
-
} : {
|
|
4168
|
-
ref,
|
|
4169
|
-
status: "unreachable",
|
|
4170
|
-
detail: output
|
|
4171
|
-
};
|
|
4172
|
-
},
|
|
4173
|
-
async build(stack, services) {
|
|
4174
|
-
await require_sniffer.run("docker", compose(stack, ["build", ...services]), {
|
|
4175
|
-
cwd: stack.cwd,
|
|
4176
|
-
env: {
|
|
4177
|
-
...process.env,
|
|
4178
|
-
DOCKER_BUILDKIT: "1"
|
|
4179
|
-
}
|
|
4180
|
-
});
|
|
4181
|
-
},
|
|
4182
|
-
async pull(stack, services) {
|
|
4183
|
-
await require_sniffer.run("docker", compose(stack, ["pull", ...services]), { cwd: stack.cwd });
|
|
4184
|
-
},
|
|
4185
|
-
async up(stack, services) {
|
|
4186
|
-
await require_sniffer.run("docker", compose(stack, [
|
|
4187
|
-
"up",
|
|
4188
|
-
"-d",
|
|
4189
|
-
"--wait",
|
|
4190
|
-
`--wait-timeout=${WAIT_TIMEOUT_S}`,
|
|
4191
|
-
...services ? [] : ["--remove-orphans"],
|
|
4192
|
-
...services ?? []
|
|
4193
|
-
]), { cwd: stack.cwd });
|
|
4194
|
-
},
|
|
4195
|
-
async down(stack) {
|
|
4196
|
-
await require_sniffer.run("docker", compose(stack, ["down"]), { cwd: stack.cwd });
|
|
4197
|
-
},
|
|
4198
|
-
async port(stack, service, inside) {
|
|
4199
|
-
const output = await require_sniffer.runOutput("docker", compose(stack, [
|
|
4200
|
-
"port",
|
|
4201
|
-
service,
|
|
4202
|
-
String(inside)
|
|
4203
|
-
]), { cwd: stack.cwd });
|
|
4204
|
-
const port = Number(output.trim().split(":").pop());
|
|
4205
|
-
if (!Number.isInteger(port) || port <= 0) throw new ServicePortUnknown(service, inside, output);
|
|
4206
|
-
return port;
|
|
4207
|
-
},
|
|
4208
|
-
async copyOut(stack, service, from, to) {
|
|
4209
|
-
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(to), { recursive: true });
|
|
4210
|
-
await require_sniffer.run("docker", compose(stack, [
|
|
4211
|
-
"cp",
|
|
4212
|
-
`${service}:${from}`,
|
|
4213
|
-
to
|
|
4214
|
-
]), { cwd: stack.cwd });
|
|
4215
|
-
},
|
|
4216
|
-
async digest(ref) {
|
|
4217
|
-
const { code, stdout } = await capture("docker", [
|
|
4218
|
-
"image",
|
|
4219
|
-
"inspect",
|
|
4220
|
-
"--format={{json .RepoDigests}} {{.Id}}",
|
|
4221
|
-
ref
|
|
4222
|
-
]);
|
|
4223
|
-
if (code !== 0) return void 0;
|
|
4224
|
-
const [json = "[]", id] = stdout.trim().split(" ");
|
|
4225
|
-
const repository = ref.split("@")[0].replace(/:[\w][\w.-]*$/, "");
|
|
4226
|
-
try {
|
|
4227
|
-
const digests = JSON.parse(json);
|
|
4228
|
-
const match = Array.isArray(digests) ? digests.find((entry) => typeof entry === "string" && entry.startsWith(`${repository}@`)) : void 0;
|
|
4229
|
-
if (match) return match.slice(repository.length + 1);
|
|
4230
|
-
} catch {}
|
|
4231
|
-
return id || void 0;
|
|
4232
|
-
}
|
|
4233
|
-
};
|
|
4234
|
-
//#endregion
|
|
4235
|
-
//#region src/compose/images.ts
|
|
4236
|
-
/**
|
|
4237
|
-
* The tag a site's image carries for a release.
|
|
4238
|
-
*
|
|
4239
|
-
* A site's public URLs are inlined when it is built, so one bundle serves one
|
|
4240
|
-
* stage: `v1.4.0-production` and `v1.4.0-staging` are two images of the same
|
|
4241
|
-
* commit. A backend reads its URLs at runtime, so one image serves every stage
|
|
4242
|
-
* and its tag is the release's alone.
|
|
4243
|
-
*/
|
|
4244
|
-
function siteTag(tag, stage) {
|
|
4245
|
-
return `${tag}-${stage}`;
|
|
4246
|
-
}
|
|
4247
|
-
/** Images a tag names that the registry does not have. */
|
|
4248
|
-
var ImageTagNotFound = class extends Error {
|
|
4249
|
-
tag;
|
|
4250
|
-
refs;
|
|
4251
|
-
constructor(tag, refs) {
|
|
4252
|
-
super(`The registry has no image for ${refs.length === 1 ? "this app" : `${refs.length} apps`} at '${tag}':\n` + refs.map((ref) => ` - ${ref}`).join("\n") + `\nNothing was pulled or started. Push '${tag}' for every app from CI, pick a tag that exists, or run without --tag to build from this checkout.`);
|
|
4253
|
-
this.tag = tag;
|
|
4254
|
-
this.refs = refs;
|
|
4255
|
-
this.name = "ImageTagNotFound";
|
|
4256
|
-
}
|
|
4257
|
-
};
|
|
4258
|
-
/** The registry did not answer, or did not let this machine in. */
|
|
4259
|
-
var RegistryUnreachable = class extends Error {
|
|
4260
|
-
ref;
|
|
4261
|
-
detail;
|
|
4262
|
-
constructor(ref, detail) {
|
|
4263
|
-
super(`Could not ask the registry about ${ref}: ${detail || "no answer"}\nNothing was pulled or started. Check the registry is reachable and that this machine is logged in to it (\`docker login <registry>\`).`);
|
|
4264
|
-
this.ref = ref;
|
|
4265
|
-
this.detail = detail;
|
|
4266
|
-
this.name = "RegistryUnreachable";
|
|
4267
|
-
}
|
|
4268
|
-
};
|
|
4269
|
-
/**
|
|
4270
|
-
* Assert every image exists, before anything else happens.
|
|
4271
|
-
*
|
|
4272
|
-
* Asked all at once and reported all at once: a release missing two images is
|
|
4273
|
-
* one problem with two names, and finding the second only after fixing the
|
|
4274
|
-
* first is a slower way to learn the same thing.
|
|
4275
|
-
*
|
|
4276
|
-
* @throws {RegistryUnreachable} when the registry cannot say
|
|
4277
|
-
* @throws {ImageTagNotFound} listing every missing image
|
|
4278
|
-
*/
|
|
4279
|
-
async function assertImagesExist(docker, tag, images) {
|
|
4280
|
-
const lookups = await Promise.all(images.map((image) => docker.lookup(image.ref)));
|
|
4281
|
-
const unreachable = lookups.find((lookup) => lookup.status === "unreachable");
|
|
4282
|
-
if (unreachable?.status === "unreachable") throw new RegistryUnreachable(unreachable.ref, unreachable.detail);
|
|
4283
|
-
const missing = lookups.filter((lookup) => lookup.status === "missing").map((lookup) => lookup.ref);
|
|
4284
|
-
if (missing.length > 0) throw new ImageTagNotFound(tag, missing);
|
|
4285
|
-
}
|
|
4286
|
-
//#endregion
|
|
4287
|
-
//#region src/compose/caddyfile.ts
|
|
4288
|
-
/** The Caddyfile for a stack's edge. */
|
|
4289
|
-
function edgeCaddyfile(sites, options) {
|
|
4290
|
-
const global = options.local ? `{
|
|
4291
|
-
# No ACME and no public DNS: the CA is Caddy's own and the names resolve
|
|
4292
|
-
# to this machine.
|
|
4293
|
-
local_certs
|
|
4294
|
-
auto_https disable_redirects
|
|
4295
|
-
}` : options.email ? `{
|
|
4296
|
-
email ${options.email}
|
|
4297
|
-
}` : "";
|
|
4298
|
-
const blocks = sites.map(({ host, upstream }) => `https://${host} {${options.local ? `
|
|
4299
|
-
tls internal
|
|
4300
|
-
` : ""}
|
|
4301
|
-
# Host and X-Forwarded-* are passed through as Caddy does by default, so
|
|
4302
|
-
# an app builds redirects, cookies and links on the address a caller used.
|
|
4303
|
-
reverse_proxy ${upstream} {
|
|
4304
|
-
# Never buffer: a streamed response — server-sent events, a Next.js
|
|
4305
|
-
# RSC payload — reaches the client as each chunk is written.
|
|
4306
|
-
flush_interval -1
|
|
4307
|
-
}
|
|
4308
|
-
}`);
|
|
4309
|
-
return `${HEADER}${global ? `${global}\n\n` : ""}${blocks.join("\n\n")}\n`;
|
|
4310
|
-
}
|
|
4311
|
-
const HEADER = `# Generated by gkm compose from the construct manifest — do not edit.
|
|
4312
|
-
#
|
|
4313
|
-
# One host per API and site, each in front of its own container.
|
|
4314
|
-
`;
|
|
4315
|
-
//#endregion
|
|
4316
|
-
//#region src/compose/stack.ts
|
|
4317
|
-
/**
|
|
4318
|
-
* The stack a workspace runs as for one stage — pure.
|
|
4319
|
-
*
|
|
4320
|
-
* Everything `gkm compose` writes is decided here, as data: the compose file,
|
|
4321
|
-
* the Caddyfile, one env file per backend, and the image each app runs. The
|
|
4322
|
-
* command around it reads the workspace, asks the registry, writes the files
|
|
4323
|
-
* and starts Docker; none of those decide anything, so all of it can be
|
|
4324
|
-
* asserted without a daemon.
|
|
4325
|
-
*
|
|
4326
|
-
* The values come from the same derivations every other target uses. The
|
|
4327
|
-
* plan is reconcile's `planFor`, the keys an app reads are `appEnvKeys`, and
|
|
4328
|
-
* every URL is `envFor`'s — asked twice, once with each app at its public
|
|
4329
|
-
* address and once on the compose network, because a browser and a sibling
|
|
4330
|
-
* service reach the same API by different names.
|
|
4331
|
-
*/
|
|
4332
|
-
/** Where a stack's files are written, relative to the workspace root. */
|
|
4333
|
-
function stackDir(stage) {
|
|
4334
|
-
return `.gkm/compose/${stage}`;
|
|
4335
|
-
}
|
|
4336
|
-
/**
|
|
4337
|
-
* The compose project a stage's stack runs as.
|
|
4338
|
-
*
|
|
4339
|
-
* The deploy identity's scope, so two workspaces with one name in different
|
|
4340
|
-
* namespaces never share containers or volumes — and the stage, so a
|
|
4341
|
-
* machine can run two stages side by side.
|
|
4342
|
-
*/
|
|
4343
|
-
function composeProject(identity) {
|
|
4344
|
-
return `${identity.scope}-${identity.stage}`;
|
|
4345
|
-
}
|
|
4346
|
-
/** The ports the edge is published on, and the variables that move them. */
|
|
4347
|
-
const EDGE_PORT_ENV = {
|
|
4348
|
-
https: "GKM_COMPOSE_HTTPS_PORT",
|
|
4349
|
-
http: "GKM_COMPOSE_HTTP_PORT"
|
|
4350
|
-
};
|
|
4351
|
-
/**
|
|
4352
|
-
* Containers this target does not run.
|
|
4353
|
-
*
|
|
4354
|
-
* Object storage is the stage's own — an externally hosted bucket whose URL
|
|
4355
|
-
* the stage's secrets hold — and so is a deployed stage's mail. The AWS
|
|
4356
|
-
* emulator never applies: a stack is a server target, whose events are
|
|
4357
|
-
* pg-boss.
|
|
4358
|
-
*/
|
|
4359
|
-
const NOT_RUN = {
|
|
4360
|
-
minio: true,
|
|
4361
|
-
localstack: true,
|
|
4362
|
-
caddy: true
|
|
4363
|
-
};
|
|
4364
|
-
/** Kinds whose `<ID>_URL` a stage must be given, because nothing here runs them. */
|
|
4365
|
-
const EXTERNAL_KINDS = {
|
|
4366
|
-
objects: true,
|
|
4367
|
-
"file-server": true
|
|
4368
|
-
};
|
|
4369
|
-
/** The S3 client's credentials, read beside a bucket's URL when it has them. */
|
|
4370
|
-
const STORAGE_KEYS = [
|
|
4371
|
-
"AWS_ACCESS_KEY_ID",
|
|
4372
|
-
"AWS_SECRET_ACCESS_KEY",
|
|
4373
|
-
"AWS_REGION"
|
|
4374
|
-
];
|
|
4375
|
-
/** A bucket the stack needs and the stage was not given. */
|
|
4376
|
-
var BucketNotConfigured = class extends Error {
|
|
4377
|
-
app;
|
|
4378
|
-
id;
|
|
4379
|
-
key;
|
|
4380
|
-
stage;
|
|
4381
|
-
constructor(app, id, key, stage) {
|
|
4382
|
-
super(`'${app}' uses the bucket '${id}', and gkm compose runs no object storage: the stage needs an externally hosted bucket. Set its URL in the stage's secrets: gkm secrets:set ${key} 's3://…' --stage ${stage}`);
|
|
4383
|
-
this.app = app;
|
|
4384
|
-
this.id = id;
|
|
4385
|
-
this.key = key;
|
|
4386
|
-
this.stage = stage;
|
|
4387
|
-
this.name = "BucketNotConfigured";
|
|
4388
|
-
}
|
|
4389
|
-
};
|
|
4390
|
-
/** A value only the stage's secrets can hold, and they do not. */
|
|
4391
|
-
var StageSecretMissing = class extends Error {
|
|
4392
|
-
app;
|
|
4393
|
-
key;
|
|
4394
|
-
stage;
|
|
4395
|
-
constructor(app, key, stage) {
|
|
4396
|
-
super(`'${app}' reads ${key}, and the stage '${stage}' has none. Nothing in the stack can derive it; set it in the stage's secrets: gkm secrets:set ${key} '…' --stage ${stage}`);
|
|
4397
|
-
this.app = app;
|
|
4398
|
-
this.key = key;
|
|
4399
|
-
this.stage = stage;
|
|
4400
|
-
this.name = "StageSecretMissing";
|
|
4401
|
-
}
|
|
4402
|
-
};
|
|
4403
|
-
/** A deployed stage's secrets with no seed to derive its passwords from. */
|
|
4404
|
-
var StageSeedMissing = class extends Error {
|
|
4405
|
-
stage;
|
|
4406
|
-
constructor(stage) {
|
|
4407
|
-
super(`The stage '${stage}' has no seed in its secrets, and a deployed stage's database passwords are derived from it. Run gkm compose without --dry-run once, or gkm deploy, to generate it.`);
|
|
4408
|
-
this.stage = stage;
|
|
4409
|
-
this.name = "StageSeedMissing";
|
|
4410
|
-
}
|
|
4411
|
-
};
|
|
4412
|
-
/** A workspace with no API and no site to run. */
|
|
4413
|
-
var NothingToCompose = class extends Error {
|
|
4414
|
-
root;
|
|
4415
|
-
constructor(root) {
|
|
4416
|
-
super(`${root} declares no RestApi and no site, so a stack would run nothing. Workers and mobile apps are not run by gkm compose yet.`);
|
|
4417
|
-
this.root = root;
|
|
4418
|
-
this.name = "NothingToCompose";
|
|
4419
|
-
}
|
|
4420
|
-
};
|
|
4421
|
-
/** A value that cannot be written to an env file a line at a time. */
|
|
4422
|
-
var EnvValueMultiline = class extends Error {
|
|
4423
|
-
app;
|
|
4424
|
-
key;
|
|
4425
|
-
constructor(app, key) {
|
|
4426
|
-
super(`${key} for '${app}' spans more than one line, and an env file holds one value per line. Store it on one line — base64, or JSON — and decode it where it is read.`);
|
|
4427
|
-
this.app = app;
|
|
4428
|
-
this.key = key;
|
|
4429
|
-
this.name = "EnvValueMultiline";
|
|
4430
|
-
}
|
|
4431
|
-
};
|
|
4432
|
-
/** The services a stack's apps wait on and the edge waits on in turn. */
|
|
4433
|
-
const HEALTHY = { condition: "service_healthy" };
|
|
4434
|
-
/**
|
|
4435
|
-
* An app's health check: its own server, asked on 127.0.0.1 — not
|
|
4436
|
-
* `localhost`, which resolves to `::1` first in an Alpine container while a
|
|
4437
|
-
* server may listen on IPv4 alone.
|
|
4438
|
-
*/
|
|
4439
|
-
function probe(port, path) {
|
|
4440
|
-
return {
|
|
4441
|
-
test: [
|
|
4442
|
-
"CMD",
|
|
4443
|
-
"wget",
|
|
4444
|
-
"-q",
|
|
4445
|
-
"-O",
|
|
4446
|
-
"/dev/null",
|
|
4447
|
-
`http://127.0.0.1:${port}${path}`
|
|
4448
|
-
],
|
|
4449
|
-
interval: "5s",
|
|
4450
|
-
timeout: "3s",
|
|
4451
|
-
retries: 20,
|
|
4452
|
-
start_period: "10s"
|
|
4453
|
-
};
|
|
4454
|
-
}
|
|
4455
|
-
/** The stack for one stage. */
|
|
4456
|
-
function composeStack(input) {
|
|
4457
|
-
const { workspace, manifest, stage, identity } = input;
|
|
4458
|
-
const local = stage === workspace.stages.local;
|
|
4459
|
-
const project = composeProject(identity);
|
|
4460
|
-
const custom = input.secrets?.custom ?? {};
|
|
4461
|
-
const https = input.ports?.https ?? 443;
|
|
4462
|
-
const http = input.ports?.http ?? 80;
|
|
4463
|
-
const plan = require_sniffer.planFor(manifest, stage, (0, _geekmidas_manifest.provisionOrder)(manifest), {
|
|
4464
|
-
localStage: workspace.stages.local,
|
|
4465
|
-
events: require_fakes.DEFAULT_EVENTS.server,
|
|
4466
|
-
cache: require_fakes.DEFAULT_CACHE.server,
|
|
4467
|
-
edge: false
|
|
4468
|
-
});
|
|
4469
|
-
const byId = new Map(plan.resources.map((r) => [r.id, r]));
|
|
4470
|
-
const seed = local ? void 0 : input.secrets?.seed;
|
|
4471
|
-
if (!local && !seed) throw new StageSeedMissing(stage);
|
|
4472
|
-
const credential = {
|
|
4473
|
-
master: local ? "geekmidas" : require_sniffer.localRolePassword(workspace.name, plan, "master", seed),
|
|
4474
|
-
...seed ? { seed } : {}
|
|
4475
|
-
};
|
|
4476
|
-
const infra = plan.containers.filter((c) => !NOT_RUN[c]).filter((c) => local || c !== "mailpit").sort();
|
|
4477
|
-
const apps = stackApps(workspace, manifest, plan, {
|
|
4478
|
-
local,
|
|
4479
|
-
stage,
|
|
4480
|
-
https,
|
|
4481
|
-
identity,
|
|
4482
|
-
images: input.images
|
|
4483
|
-
});
|
|
4484
|
-
if (apps.length === 0) throw new NothingToCompose(workspace.root);
|
|
4485
|
-
const derivation = {
|
|
4486
|
-
project: workspace.name,
|
|
4487
|
-
master: credential.master,
|
|
4488
|
-
...credential.seed ? { seed: credential.seed } : {}
|
|
4489
|
-
};
|
|
4490
|
-
const outside = require_sniffer.networkEnv(plan, {
|
|
4491
|
-
...derivation,
|
|
4492
|
-
addresses: Object.fromEntries(apps.map((app) => [app.id, app.url]))
|
|
4493
|
-
});
|
|
4494
|
-
const inside = require_sniffer.networkEnv(plan, {
|
|
4495
|
-
...derivation,
|
|
4496
|
-
addresses: Object.fromEntries(apps.map((app) => [app.id, `http://${app.name}:${app.port}`]))
|
|
4497
|
-
});
|
|
4498
|
-
const owners = /* @__PURE__ */ new Map();
|
|
4499
|
-
for (const resource of plan.resources) {
|
|
4500
|
-
owners.set(resource.envKey, resource);
|
|
4501
|
-
if (resource.kind === "email") {
|
|
4502
|
-
owners.set((0, _geekmidas_manifest.provideKey)(resource.id, "from"), resource);
|
|
4503
|
-
owners.set((0, _geekmidas_manifest.provideKey)(resource.id, "inboxUrl"), resource);
|
|
4504
|
-
}
|
|
4505
|
-
if (resource.kind === "external-api") owners.set((0, _geekmidas_manifest.provideKey)(resource.id, "credentials"), resource);
|
|
4506
|
-
}
|
|
4507
|
-
const surfaceUrls = new Map(plan.resources.filter((r) => r.kind === "rest-api" || r.kind === "site").map((r) => [r.envKey, r.id]));
|
|
4508
|
-
const appById = new Map(apps.map((app) => [app.id, app]));
|
|
4509
|
-
/**
|
|
4510
|
-
* What a key resolves to for one app, from the stage's secrets where only
|
|
4511
|
-
* they can hold it, and the derivations otherwise.
|
|
4512
|
-
*/
|
|
4513
|
-
const valueFor = (app, key, owner) => {
|
|
4514
|
-
if (owner && EXTERNAL_KINDS[owner.kind] && key === owner.envKey) {
|
|
4515
|
-
const value = custom[key];
|
|
4516
|
-
if (value === void 0) throw new BucketNotConfigured(app.name, owner.id, key, stage);
|
|
4517
|
-
return value;
|
|
4518
|
-
}
|
|
4519
|
-
if (owner && suppliedBy(owner, key, local)) {
|
|
4520
|
-
const value = custom[key];
|
|
4521
|
-
if (value === void 0) throw new StageSecretMissing(app.name, key, stage);
|
|
4522
|
-
return value;
|
|
4523
|
-
}
|
|
4524
|
-
if (custom[key] !== void 0) return custom[key];
|
|
4525
|
-
const surface = surfaceUrls.get(key);
|
|
4526
|
-
if (app.kind === "rest-api" && surface && surface !== app.id) return inside[key];
|
|
4527
|
-
return outside[key];
|
|
4528
|
-
};
|
|
4529
|
-
for (const app of apps) {
|
|
4530
|
-
if (app.kind === "site") {
|
|
4531
|
-
const site = manifest[app.id];
|
|
4532
|
-
if (site?.kind !== "site") continue;
|
|
4533
|
-
const args = {};
|
|
4534
|
-
for (const [key, source] of Object.entries((0, _geekmidas_manifest.publicEnvFor)(site, manifest))) {
|
|
4535
|
-
const value = valueFor(app, source, owners.get(source));
|
|
4536
|
-
if (value !== void 0) args[key] = value;
|
|
4537
|
-
}
|
|
4538
|
-
if (app.build && Object.keys(args).length > 0) app.build.args = args;
|
|
4539
|
-
continue;
|
|
4540
|
-
}
|
|
4541
|
-
const keys = require_sniffer.appEnvKeys(manifest, app.name, input.runnables) ?? /* @__PURE__ */ new Set();
|
|
4542
|
-
const values = {};
|
|
4543
|
-
for (const key of [...keys].sort()) {
|
|
4544
|
-
const owner = owners.get(key) ?? storageOwner(key, plan);
|
|
4545
|
-
if (!local && owner?.kind === "email" && key.endsWith("_INBOX_URL")) continue;
|
|
4546
|
-
if (STORAGE_KEYS.includes(key)) {
|
|
4547
|
-
if (custom[key] !== void 0) values[key] = custom[key];
|
|
4548
|
-
continue;
|
|
4549
|
-
}
|
|
4550
|
-
const value = valueFor(app, key, owner);
|
|
4551
|
-
if (value !== void 0) values[key] = value;
|
|
4552
|
-
}
|
|
4553
|
-
const trusted = (0, _geekmidas_manifest.provideKey)(app.id, "trustedOrigins");
|
|
4554
|
-
if (keys.has(trusted)) {
|
|
4555
|
-
const internal = (byId.get(app.id)?.callers ?? []).map((caller) => appById.get(caller)).filter((caller) => caller?.kind === "rest-api").map((caller) => `http://${caller.name}:${caller.port}`);
|
|
4556
|
-
const origins = [...(values[trusted] ?? "").split(",").filter(Boolean), ...internal];
|
|
4557
|
-
values[trusted] = [...new Set(origins)].join(",");
|
|
4558
|
-
}
|
|
4559
|
-
app.env = {
|
|
4560
|
-
...values,
|
|
4561
|
-
NODE_ENV: "production",
|
|
4562
|
-
PORT: String(app.port),
|
|
4563
|
-
STAGE: stage
|
|
4564
|
-
};
|
|
4565
|
-
for (const [key, value] of Object.entries(app.env)) if (/[\r\n]/.test(value)) throw new EnvValueMultiline(app.name, key);
|
|
4566
|
-
}
|
|
4567
|
-
const compose = stackFile({
|
|
4568
|
-
project,
|
|
4569
|
-
plan,
|
|
4570
|
-
infra,
|
|
4571
|
-
apps,
|
|
4572
|
-
master: credential.master,
|
|
4573
|
-
https,
|
|
4574
|
-
http
|
|
4575
|
-
});
|
|
4576
|
-
const caddyfile = edgeCaddyfile(apps.map((app) => ({
|
|
4577
|
-
host: app.host,
|
|
4578
|
-
upstream: `${app.name}:${app.port}`
|
|
4579
|
-
})), { local });
|
|
4580
|
-
const dockerfiles = {};
|
|
4581
|
-
for (const app of apps) {
|
|
4582
|
-
if (!app.build) continue;
|
|
4583
|
-
dockerfiles[app.build.dockerfile] = app.kind === "site" ? require_sniffer.siteDockerfile(app.name, workspace.apps[app.name], {
|
|
4584
|
-
turboPackage: input.packages?.[app.name] ?? app.name,
|
|
4585
|
-
packageManager: input.packageManager ?? "pnpm",
|
|
4586
|
-
manifest
|
|
4587
|
-
}) : require_sniffer.generateSlimDockerfile({
|
|
4588
|
-
imageName: app.name,
|
|
4589
|
-
baseImage: "node:22-alpine",
|
|
4590
|
-
port: app.port,
|
|
4591
|
-
healthCheckPath: "/health",
|
|
4592
|
-
prebuilt: true,
|
|
4593
|
-
packageManager: input.packageManager ?? "pnpm",
|
|
4594
|
-
bundle: `${app.path}/.gkm/server/dist/server.mjs`
|
|
4595
|
-
});
|
|
4596
|
-
}
|
|
4597
|
-
return {
|
|
4598
|
-
project,
|
|
4599
|
-
stage,
|
|
4600
|
-
local,
|
|
4601
|
-
apps,
|
|
4602
|
-
infra,
|
|
4603
|
-
compose,
|
|
4604
|
-
caddyfile,
|
|
4605
|
-
dockerfiles,
|
|
4606
|
-
plan,
|
|
4607
|
-
credential
|
|
4608
|
-
};
|
|
4609
|
-
/** Whether only the stage's secrets can supply `key` to `owner`. */
|
|
4610
|
-
function suppliedBy(owner, key, isLocal) {
|
|
4611
|
-
if (owner.kind === "credential") return true;
|
|
4612
|
-
if (owner.kind === "external-api") return key === (0, _geekmidas_manifest.provideKey)(owner.id, "credentials");
|
|
4613
|
-
if (isLocal) return false;
|
|
4614
|
-
if (owner.kind === "secret" || owner.kind === "encryption") return true;
|
|
4615
|
-
return owner.kind === "email" && !key.endsWith("_INBOX_URL");
|
|
4616
|
-
}
|
|
4617
|
-
}
|
|
4618
|
-
/** Which bucket an S3 credential key belongs to — the first one declared. */
|
|
4619
|
-
function storageOwner(key, plan) {
|
|
4620
|
-
if (!STORAGE_KEYS.includes(key)) return void 0;
|
|
4621
|
-
return plan.resources.find((r) => r.kind === "objects");
|
|
4622
|
-
}
|
|
4623
|
-
/** The apps a stack runs: every API and every site, at its stage address. */
|
|
4624
|
-
function stackApps(workspace, manifest, plan, options) {
|
|
4625
|
-
const apps = [];
|
|
4626
|
-
const resources = new Map(plan.resources.map((r) => [r.id, r]));
|
|
4627
|
-
for (const [id, declaration] of Object.entries(manifest)) {
|
|
4628
|
-
if (declaration.kind !== "rest-api" && declaration.kind !== "site") continue;
|
|
4629
|
-
const name = require_config.appKey(id);
|
|
4630
|
-
const app = workspace.apps[name];
|
|
4631
|
-
const resource = resources.get(id);
|
|
4632
|
-
if (!app || !resource || !app.port) continue;
|
|
4633
|
-
const host = hostOf(name, app, workspace, resource, options);
|
|
4634
|
-
const tag = declaration.kind === "site" ? siteTag(options.images.tag, options.stage) : options.images.tag;
|
|
4635
|
-
const ref = require_sniffer.validateImageRef(require_store.imageRef(options.identity, name, options.images.registry, tag));
|
|
4636
|
-
apps.push({
|
|
4637
|
-
app: name,
|
|
4638
|
-
name,
|
|
4639
|
-
id,
|
|
4640
|
-
kind: declaration.kind,
|
|
4641
|
-
path: app.path,
|
|
4642
|
-
port: app.port,
|
|
4643
|
-
host,
|
|
4644
|
-
url: `https://${host}${options.local && options.https !== 443 ? `:${options.https}` : ""}`,
|
|
4645
|
-
ref,
|
|
4646
|
-
tag,
|
|
4647
|
-
...options.images.mode === "build" ? { build: { dockerfile: `${stackDir(options.stage)}/Dockerfile.${name}` } } : {}
|
|
4648
|
-
});
|
|
4649
|
-
}
|
|
4650
|
-
return apps.sort((a, b) => a.name.localeCompare(b.name));
|
|
4651
|
-
}
|
|
4652
|
-
/**
|
|
4653
|
-
* The host an app answers on for a stage: the `gkm dev` edge's
|
|
4654
|
-
* `*.localhost` names for the local stage, and the stage's domain — by the
|
|
4655
|
-
* same rule a deploy follows — for any other.
|
|
4656
|
-
*/
|
|
4657
|
-
function hostOf(name, app, workspace, resource, options) {
|
|
4658
|
-
if (options.local) return require_sniffer.hostFor(resource, workspace.name);
|
|
4659
|
-
return require_sniffer.resolveHost(name, app, options.stage, workspace.deploy?.domains, require_sniffer.isMainFrontendApp(name, app, workspace.apps));
|
|
4660
|
-
}
|
|
4661
|
-
/** The compose document: the infrastructure, the apps, and the edge. */
|
|
4662
|
-
function stackFile(options) {
|
|
4663
|
-
const { project, plan, infra, apps } = options;
|
|
4664
|
-
const derived = require_sniffer.composeFor({
|
|
4665
|
-
...plan,
|
|
4666
|
-
containers: [...infra]
|
|
4667
|
-
}, {
|
|
4668
|
-
project,
|
|
4669
|
-
ports: Object.fromEntries(require_sniffer.portKeys(infra, plan.fakes).map((key) => [key, 0]))
|
|
4670
|
-
});
|
|
4671
|
-
const services = {};
|
|
4672
|
-
const volumes = {
|
|
4673
|
-
...derived.volumes,
|
|
4674
|
-
"caddy-data": {},
|
|
4675
|
-
"caddy-config": {}
|
|
4676
|
-
};
|
|
4677
|
-
for (const name of infra) {
|
|
4678
|
-
const { ports: _ports, depends_on, profiles: _profiles, ...service } = derived.services[name];
|
|
4679
|
-
services[name] = {
|
|
4680
|
-
...service,
|
|
4681
|
-
...depends_on?.length ? { depends_on: Object.fromEntries(depends_on.map((dep) => [dep, HEALTHY])) } : {}
|
|
4682
|
-
};
|
|
4683
|
-
}
|
|
4684
|
-
const postgres = services.postgres;
|
|
4685
|
-
if (postgres) {
|
|
4686
|
-
postgres.environment = {
|
|
4687
|
-
...postgres.environment,
|
|
4688
|
-
POSTGRES_PASSWORD: options.master
|
|
4689
|
-
};
|
|
4690
|
-
postgres.ports = ["127.0.0.1::5432"];
|
|
4691
|
-
}
|
|
4692
|
-
for (const app of apps) services[app.name] = {
|
|
4693
|
-
image: app.ref,
|
|
4694
|
-
...app.build ? { build: {
|
|
4695
|
-
context: "../../..",
|
|
4696
|
-
dockerfile: app.build.dockerfile,
|
|
4697
|
-
...app.build.args ? { args: app.build.args } : {}
|
|
4698
|
-
} } : {},
|
|
4699
|
-
restart: "unless-stopped",
|
|
4700
|
-
...app.kind === "rest-api" ? {
|
|
4701
|
-
env_file: [{
|
|
4702
|
-
path: `./${app.name}.env`,
|
|
4703
|
-
format: "raw"
|
|
4704
|
-
}],
|
|
4705
|
-
...infra.length ? { depends_on: Object.fromEntries(infra.map((name) => [name, HEALTHY])) } : {}
|
|
4706
|
-
} : {},
|
|
4707
|
-
healthcheck: probe(app.port, app.kind === "site" ? "/" : "/health")
|
|
4708
|
-
};
|
|
4709
|
-
services.caddy = {
|
|
4710
|
-
image: require_sniffer.DEFAULT_IMAGES.caddy,
|
|
4711
|
-
restart: "unless-stopped",
|
|
4712
|
-
ports: [`${options.https}:443`, `${options.http}:80`],
|
|
4713
|
-
volumes: [
|
|
4714
|
-
"./Caddyfile:/etc/caddy/Caddyfile:ro",
|
|
4715
|
-
"caddy-data:/data",
|
|
4716
|
-
"caddy-config:/config"
|
|
4717
|
-
],
|
|
4718
|
-
depends_on: Object.fromEntries(apps.map((app) => [app.name, HEALTHY])),
|
|
4719
|
-
healthcheck: {
|
|
4720
|
-
test: [
|
|
4721
|
-
"CMD",
|
|
4722
|
-
"caddy",
|
|
4723
|
-
"version"
|
|
4724
|
-
],
|
|
4725
|
-
interval: "10s",
|
|
4726
|
-
timeout: "5s",
|
|
4727
|
-
retries: 5
|
|
4728
|
-
}
|
|
4729
|
-
};
|
|
4730
|
-
return {
|
|
4731
|
-
name: project,
|
|
4732
|
-
services,
|
|
4733
|
-
volumes
|
|
4734
|
-
};
|
|
4735
|
-
}
|
|
4736
|
-
/** Render an app's env file: one `KEY=value` per line, read raw by compose. */
|
|
4737
|
-
function envFile(env) {
|
|
4738
|
-
return `${Object.entries(env).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${key}=${value}`).join("\n")}\n`;
|
|
4739
|
-
}
|
|
4740
|
-
//#endregion
|
|
4741
3604
|
//#region src/compose/index.ts
|
|
4742
3605
|
/**
|
|
4743
3606
|
* `gkm compose` — a workspace's APIs and sites for one stage, as one Docker
|
|
4744
3607
|
* Compose stack behind Caddy.
|
|
4745
3608
|
*
|
|
4746
|
-
*
|
|
4747
|
-
*
|
|
4748
|
-
*
|
|
4749
|
-
*
|
|
4750
|
-
*
|
|
4751
|
-
* - The databases exist before the apps do. Postgres comes up alone, its
|
|
4752
|
-
* databases, roles and grants are created and its migrations applied from
|
|
4753
|
-
* this machine, and only then do the apps start — so an app never boots
|
|
4754
|
-
* against a schema that is not there yet.
|
|
4755
|
-
* - What a stage runs is recorded. Each app's tag and the digest it resolved
|
|
4756
|
-
* to are written to the stage's state, so "what is this stage running" has
|
|
4757
|
-
* an exact answer.
|
|
4758
|
-
*
|
|
4759
|
-
* Docker, the registry, the build and the clock are injected, so the rules are
|
|
4760
|
-
* asserted without a daemon; the defaults are the real ones.
|
|
3609
|
+
* A convenience over `gkm deploy --target compose`: the stage defaults to the
|
|
3610
|
+
* local one, `--build`/`--pull` choose the images whatever `--tag` says, and
|
|
3611
|
+
* `--down` stops the stack. Everything else — the order, the image check, the
|
|
3612
|
+
* state, the health checks — is the compose target's, run through the same
|
|
3613
|
+
* `deploy()` as any other target, so there is one way a stack comes up.
|
|
4761
3614
|
*/
|
|
4762
|
-
const logger$4 = console;
|
|
4763
3615
|
/** `--build` and `--pull` together ask for two different things. */
|
|
4764
3616
|
var ComposeModeConflict = class extends Error {
|
|
4765
3617
|
constructor() {
|
|
@@ -4767,320 +3619,52 @@ var ComposeModeConflict = class extends Error {
|
|
|
4767
3619
|
this.name = "ComposeModeConflict";
|
|
4768
3620
|
}
|
|
4769
3621
|
};
|
|
4770
|
-
/** A build with no commit to tag its images with. */
|
|
4771
|
-
var NoGitRevision = class extends Error {
|
|
4772
|
-
root;
|
|
4773
|
-
constructor(root) {
|
|
4774
|
-
super(`Images built here are tagged with the commit they were built from, and ${root} is not in a git repository with a commit. Commit, or pass --tag to name the images yourself.`);
|
|
4775
|
-
this.root = root;
|
|
4776
|
-
this.name = "NoGitRevision";
|
|
4777
|
-
}
|
|
4778
|
-
};
|
|
4779
|
-
/** A port variable that does not hold a port. */
|
|
4780
|
-
var EdgePortInvalid = class extends Error {
|
|
4781
|
-
variable;
|
|
4782
|
-
value;
|
|
4783
|
-
constructor(variable, value) {
|
|
4784
|
-
super(`${variable}=${value} is not a port. Set it to a number between 1 and 65535, or unset it for the default.`);
|
|
4785
|
-
this.variable = variable;
|
|
4786
|
-
this.value = value;
|
|
4787
|
-
this.name = "EdgePortInvalid";
|
|
4788
|
-
}
|
|
4789
|
-
};
|
|
4790
|
-
/** The commit HEAD is at, short — and `-dirty` when the tree has changes. */
|
|
4791
|
-
async function gitRevision(root) {
|
|
4792
|
-
let sha;
|
|
4793
|
-
try {
|
|
4794
|
-
sha = (await require_sniffer.runOutput("git", [
|
|
4795
|
-
"rev-parse",
|
|
4796
|
-
"--short",
|
|
4797
|
-
"HEAD"
|
|
4798
|
-
], { cwd: root })).trim();
|
|
4799
|
-
} catch {
|
|
4800
|
-
throw new NoGitRevision(root);
|
|
4801
|
-
}
|
|
4802
|
-
if (!sha) throw new NoGitRevision(root);
|
|
4803
|
-
return (await require_sniffer.runOutput("git", ["status", "--porcelain"], { cwd: root }).catch(() => "")).trim() ? `${sha}-dirty` : sha;
|
|
4804
|
-
}
|
|
4805
|
-
/**
|
|
4806
|
-
* Bundle a backend with the gkm that is running — the same build a deploy
|
|
4807
|
-
* makes, so the image holds one file and no dependencies.
|
|
4808
|
-
*/
|
|
4809
|
-
async function bundleWithThisCli(appRoot) {
|
|
4810
|
-
const entry = process.argv[1];
|
|
4811
|
-
if (!entry) return;
|
|
4812
|
-
await require_sniffer.run(process.execPath, [
|
|
4813
|
-
...process.execArgv,
|
|
4814
|
-
entry,
|
|
4815
|
-
"build",
|
|
4816
|
-
"--provider",
|
|
4817
|
-
"server",
|
|
4818
|
-
"--production"
|
|
4819
|
-
], { cwd: appRoot });
|
|
4820
|
-
}
|
|
4821
|
-
const defaultDeps = {
|
|
4822
|
-
docker: dockerCompose,
|
|
4823
|
-
bundle: bundleWithThisCli,
|
|
4824
|
-
revision: gitRevision
|
|
4825
|
-
};
|
|
4826
|
-
/** The edge's ports, from the environment — 443 and 80 when unset. */
|
|
4827
|
-
function edgePorts(env = process.env) {
|
|
4828
|
-
const read = (variable, fallback) => {
|
|
4829
|
-
const value = env[variable];
|
|
4830
|
-
if (value === void 0 || value === "") return fallback;
|
|
4831
|
-
const port = Number(value);
|
|
4832
|
-
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new EdgePortInvalid(variable, value);
|
|
4833
|
-
return port;
|
|
4834
|
-
};
|
|
4835
|
-
return {
|
|
4836
|
-
https: read(EDGE_PORT_ENV.https, 443),
|
|
4837
|
-
http: read(EDGE_PORT_ENV.http, 80)
|
|
4838
|
-
};
|
|
4839
|
-
}
|
|
4840
3622
|
/** `gkm compose`. */
|
|
4841
3623
|
async function composeCommand(options = {}, deps = {}) {
|
|
4842
|
-
const { docker, bundle, revision } = {
|
|
4843
|
-
...defaultDeps,
|
|
4844
|
-
...deps
|
|
4845
|
-
};
|
|
4846
|
-
const env = deps.env ?? process.env;
|
|
4847
3624
|
if (options.build && options.pull) throw new ComposeModeConflict();
|
|
4848
|
-
const
|
|
4849
|
-
const
|
|
3625
|
+
const cwd = (0, node_path.resolve)(options.cwd ?? process.cwd());
|
|
3626
|
+
const { workspace } = await require_config.loadWorkspaceConfig(cwd);
|
|
4850
3627
|
const stage = options.stage ?? workspace.stages.local;
|
|
4851
|
-
if (!(stage === workspace.stages.local)) require_workspace.assertDeployedStage(workspace.stages, stage);
|
|
4852
|
-
const identity = require_store.deployIdentity(workspace, stage);
|
|
4853
|
-
const dir = (0, node_path.join)(root, stackDir(stage));
|
|
4854
|
-
const file = (0, node_path.join)(dir, "docker-compose.yml");
|
|
4855
|
-
const ref = {
|
|
4856
|
-
project: composeProject(identity),
|
|
4857
|
-
file,
|
|
4858
|
-
cwd: root
|
|
4859
|
-
};
|
|
4860
3628
|
if (options.down) {
|
|
4861
|
-
|
|
4862
|
-
|
|
3629
|
+
const ref = {
|
|
3630
|
+
project: require_compose.composeProject(require_store.deployIdentity(workspace, stage)),
|
|
3631
|
+
file: (0, node_path.join)(workspace.root, require_compose.stackDir(stage), "docker-compose.yml"),
|
|
3632
|
+
cwd: workspace.root
|
|
3633
|
+
};
|
|
3634
|
+
await (deps.docker ?? require_compose.dockerCompose).down(ref);
|
|
3635
|
+
console.log(`🛑 Stopped ${ref.project}. Its volumes are kept.`);
|
|
4863
3636
|
return;
|
|
4864
3637
|
}
|
|
4865
|
-
|
|
4866
|
-
const
|
|
4867
|
-
const
|
|
4868
|
-
|
|
4869
|
-
cwd
|
|
4870
|
-
sources,
|
|
4871
|
-
runnables
|
|
4872
|
-
});
|
|
4873
|
-
const mode = options.build || !options.pull && !options.tag ? "build" : "pull";
|
|
4874
|
-
const tag = options.tag ?? (mode === "pull" ? "latest" : await revision(root));
|
|
4875
|
-
const { secrets, keep } = await stageSecrets(workspace, manifest, stage);
|
|
4876
|
-
const packageManager = require_sniffer.detectPackageManager(root);
|
|
4877
|
-
const stack = composeStack({
|
|
4878
|
-
workspace,
|
|
4879
|
-
manifest,
|
|
4880
|
-
runnables,
|
|
3638
|
+
let finished;
|
|
3639
|
+
const mode = options.build ? "build" : options.pull ? "pull" : void 0;
|
|
3640
|
+
const tag = options.tag ?? (options.pull ? "latest" : void 0);
|
|
3641
|
+
const result = await require_deploy.deploy({
|
|
3642
|
+
cwd,
|
|
4881
3643
|
stage,
|
|
4882
|
-
|
|
4883
|
-
|
|
4884
|
-
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
secrets,
|
|
4889
|
-
ports: edgePorts(env),
|
|
4890
|
-
packageManager,
|
|
4891
|
-
packages: Object.fromEntries(Object.entries(workspace.apps).map(([name, app]) => [name, require_sniffer.appPackageName(root, name, app.path)]))
|
|
4892
|
-
});
|
|
4893
|
-
if (mode === "pull" && !options.dryRun) await assertImagesExist(docker, tag, stack.apps);
|
|
4894
|
-
if (options.dryRun) keep.skip();
|
|
4895
|
-
else await keep.write();
|
|
4896
|
-
const files = await writeStack(root, dir, stack);
|
|
4897
|
-
printPlan(stack, mode, files);
|
|
4898
|
-
if (options.dryRun) return {
|
|
4899
|
-
stack,
|
|
4900
|
-
files
|
|
4901
|
-
};
|
|
4902
|
-
const store = deps.state ? await deps.state(workspace) : await require_sniffer.createStateStore({
|
|
4903
|
-
config: workspace.state,
|
|
4904
|
-
workspaceRoot: root,
|
|
4905
|
-
workspaceName: workspace.name
|
|
4906
|
-
});
|
|
4907
|
-
const lock = await store.lock(stage, { operation: "compose" });
|
|
4908
|
-
try {
|
|
4909
|
-
const apps = stack.apps.map((app) => app.name);
|
|
4910
|
-
if (mode === "build") {
|
|
4911
|
-
for (const app of stack.apps.filter((a) => a.kind === "rest-api")) {
|
|
4912
|
-
logger$4.log(`\n📦 Bundling ${app.name}…`);
|
|
4913
|
-
await bundle((0, node_path.join)(root, app.path));
|
|
3644
|
+
target: "compose",
|
|
3645
|
+
targets: { compose: require_compose.composeTarget({
|
|
3646
|
+
...deps,
|
|
3647
|
+
...mode ? { mode } : {},
|
|
3648
|
+
report: (done) => {
|
|
3649
|
+
finished = done;
|
|
4914
3650
|
}
|
|
4915
|
-
|
|
4916
|
-
|
|
4917
|
-
}
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
|
|
4921
|
-
|
|
4922
|
-
logger$4.log(`\n🗄️ Starting ${stack.infra.join(", ")}…`);
|
|
4923
|
-
await docker.up(ref, stack.infra);
|
|
3651
|
+
}) },
|
|
3652
|
+
...tag ? { tag } : {},
|
|
3653
|
+
...options.dryRun ? { dryRun: true } : {},
|
|
3654
|
+
logger: {
|
|
3655
|
+
info: (message) => console.log(message),
|
|
3656
|
+
warn: (message) => console.warn(message),
|
|
3657
|
+
error: (message) => console.error(message)
|
|
4924
3658
|
}
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
ref,
|
|
4928
|
-
sql: deps.sql ?? require_sniffer.pgClient,
|
|
4929
|
-
migrate: deps.migrate ?? migrateDatabases
|
|
4930
|
-
});
|
|
4931
|
-
logger$4.log("\n🚀 Starting the stack…");
|
|
4932
|
-
await docker.up(ref);
|
|
4933
|
-
if (stack.local) await docker.copyOut(ref, "caddy", "/data/caddy/pki/authorities/local/root.crt", (0, node_path.join)(dir, "caddy-root.crt")).catch(() => {});
|
|
4934
|
-
const images = await recordImages(store, stack, docker, mode);
|
|
4935
|
-
logger$4.log(`\n✅ ${ref.project} is running:`);
|
|
4936
|
-
for (const app of stack.apps) logger$4.log(` ${app.name.padEnd(12)} ${app.url}`);
|
|
4937
|
-
if (stack.local) logger$4.log(`\n🔐 Certificates are from Caddy's local CA: NODE_EXTRA_CA_CERTS=${(0, node_path.join)(stackDir(stage), "caddy-root.crt")}`);
|
|
4938
|
-
return {
|
|
4939
|
-
stack,
|
|
4940
|
-
files,
|
|
4941
|
-
images
|
|
4942
|
-
};
|
|
4943
|
-
} finally {
|
|
4944
|
-
await lock.release();
|
|
4945
|
-
}
|
|
4946
|
-
}
|
|
4947
|
-
/**
|
|
4948
|
-
* The stage's secrets — and, for a deployed stage, everything it generates
|
|
4949
|
-
* once: its seed, and each declared secret and keyring. Written back before
|
|
4950
|
-
* anything is derived from them, so the next run derives the same passwords.
|
|
4951
|
-
*/
|
|
4952
|
-
async function stageSecrets(workspace, manifest, stage) {
|
|
4953
|
-
const store = await require_store.secretsStoreFor(workspace, stage);
|
|
4954
|
-
const stored = await store.read(stage);
|
|
4955
|
-
const nothing = {
|
|
4956
|
-
write: async () => {},
|
|
4957
|
-
skip: () => {}
|
|
4958
|
-
};
|
|
4959
|
-
if (stage === workspace.stages.local) return {
|
|
4960
|
-
secrets: stored,
|
|
4961
|
-
keep: nothing
|
|
4962
|
-
};
|
|
4963
|
-
const { secrets, generated } = require_sniffer.withGeneratedSecrets(stored ?? require_storage.initStageSecrets(stage), manifest);
|
|
4964
|
-
if (generated.length === 0) return {
|
|
4965
|
-
secrets,
|
|
4966
|
-
keep: nothing
|
|
4967
|
-
};
|
|
3659
|
+
}).result;
|
|
3660
|
+
if (!finished) return void 0;
|
|
4968
3661
|
return {
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
logger$4.log(`🔑 Generated for "${stage}" (${store.name}): ${generated.join(", ")}`);
|
|
4974
|
-
},
|
|
4975
|
-
skip() {
|
|
4976
|
-
logger$4.log(`🔑 "${stage}" has no ${generated.join(", ")} yet; this dry run used values it did not keep.`);
|
|
4977
|
-
}
|
|
4978
|
-
}
|
|
3662
|
+
stack: finished.stack,
|
|
3663
|
+
files: finished.files,
|
|
3664
|
+
...options.dryRun ? {} : { images: finished.images },
|
|
3665
|
+
deploy: result
|
|
4979
3666
|
};
|
|
4980
3667
|
}
|
|
4981
|
-
/** Write the stack's files: owner-only, since the env files hold secrets. */
|
|
4982
|
-
async function writeStack(root, dir, stack) {
|
|
4983
|
-
await (0, node_fs_promises.mkdir)(dir, {
|
|
4984
|
-
recursive: true,
|
|
4985
|
-
mode: 448
|
|
4986
|
-
});
|
|
4987
|
-
await (0, node_fs_promises.chmod)(dir, 448);
|
|
4988
|
-
const files = [];
|
|
4989
|
-
const write = async (path, content, mode = 420) => {
|
|
4990
|
-
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(path), { recursive: true });
|
|
4991
|
-
await (0, node_fs_promises.writeFile)(path, content, { mode });
|
|
4992
|
-
await (0, node_fs_promises.chmod)(path, mode);
|
|
4993
|
-
files.push(path);
|
|
4994
|
-
};
|
|
4995
|
-
await write((0, node_path.join)(dir, "docker-compose.yml"), composeYaml(stack));
|
|
4996
|
-
await write((0, node_path.join)(dir, "Caddyfile"), stack.caddyfile);
|
|
4997
|
-
for (const app of stack.apps) if (app.env) await write((0, node_path.join)(dir, `${app.name}.env`), envFile(app.env), 384);
|
|
4998
|
-
for (const [path, content] of Object.entries(stack.dockerfiles)) await write((0, node_path.join)(root, path), content);
|
|
4999
|
-
await ignoreStacks(root);
|
|
5000
|
-
return files;
|
|
5001
|
-
}
|
|
5002
|
-
/** Make sure the build context leaves `.gkm/compose` out. */
|
|
5003
|
-
async function ignoreStacks(root) {
|
|
5004
|
-
const path = (0, node_path.join)(root, ".dockerignore");
|
|
5005
|
-
if (!(0, node_fs.existsSync)(path)) {
|
|
5006
|
-
await (0, node_fs_promises.writeFile)(path, require_sniffer.generateDockerignore());
|
|
5007
|
-
return;
|
|
5008
|
-
}
|
|
5009
|
-
const current = await (0, node_fs_promises.readFile)(path, "utf-8");
|
|
5010
|
-
if (/^\/?\.gkm\/compose\/?$|^\/?\.gkm\/?$/m.test(current)) return;
|
|
5011
|
-
await (0, node_fs_promises.appendFile)(path, `${current.endsWith("\n") ? "" : "\n"}\n# gkm compose's stacks: each app's env file holds its stage's secrets\n.gkm/compose\n`);
|
|
5012
|
-
}
|
|
5013
|
-
function composeYaml(stack) {
|
|
5014
|
-
return `# Generated by gkm compose from the construct manifest — do not edit.
|
|
5015
|
-
# The ${stack.stage} stage's APIs and sites behind one Caddy. Each backend
|
|
5016
|
-
# reads exactly the keys in its own env file beside this one.
|
|
5017
|
-
#
|
|
5018
|
-
# gkm compose${stack.local ? "" : ` --stage ${stack.stage}`} start or update it
|
|
5019
|
-
# gkm compose${stack.local ? "" : ` --stage ${stack.stage}`} --down stop it
|
|
5020
|
-
${(0, yaml.stringify)(stack.compose, {
|
|
5021
|
-
lineWidth: 0,
|
|
5022
|
-
aliasDuplicateObjects: false
|
|
5023
|
-
})}`;
|
|
5024
|
-
}
|
|
5025
|
-
/** Create what the plan names in the stack's Postgres, then migrate it. */
|
|
5026
|
-
async function prepareDatabases(stack, workspace, manifest, sources, deps) {
|
|
5027
|
-
const port = await deps.docker.port(deps.ref, "postgres", 5432);
|
|
5028
|
-
const statements = require_sniffer.postgresStatements(stack.plan, workspace.name, stack.credential.seed);
|
|
5029
|
-
if (statements.length > 0) {
|
|
5030
|
-
logger$4.log("🗄️ Creating databases, roles and grants…");
|
|
5031
|
-
await require_sniffer.applyPostgres(deps.sql(port, stack.credential.master), statements);
|
|
5032
|
-
}
|
|
5033
|
-
const env = require_sniffer.envFor(stack.plan, {
|
|
5034
|
-
ports: { [require_sniffer.primaryPortKey("postgres")]: port },
|
|
5035
|
-
project: workspace.name,
|
|
5036
|
-
master: stack.credential.master,
|
|
5037
|
-
...stack.credential.seed ? { seed: stack.credential.seed } : {}
|
|
5038
|
-
});
|
|
5039
|
-
const runs = await deps.migrate({
|
|
5040
|
-
root: workspace.root,
|
|
5041
|
-
manifest,
|
|
5042
|
-
sources,
|
|
5043
|
-
env
|
|
5044
|
-
});
|
|
5045
|
-
for (const { target, applied } of runs) if (applied.length > 0) logger$4.log(`🗄️ ${target.migrations}: applied ${applied.length}`);
|
|
5046
|
-
}
|
|
5047
|
-
/**
|
|
5048
|
-
* Record each app's image in the stage's state: the tag it ran and the digest
|
|
5049
|
-
* the tag resolved to — or, for an image built here and never pushed, its
|
|
5050
|
-
* content id.
|
|
5051
|
-
*/
|
|
5052
|
-
async function recordImages(store, stack, docker, mode) {
|
|
5053
|
-
if (!await store.read(stack.stage)) await store.write(stack.stage, require_sniffer.createEmptyState(stack.stage, "", ""), { expectedVersion: null });
|
|
5054
|
-
const images = {};
|
|
5055
|
-
for (const app of stack.apps) {
|
|
5056
|
-
const digest = await docker.digest(app.ref);
|
|
5057
|
-
images[app.name] = {
|
|
5058
|
-
ref: app.ref,
|
|
5059
|
-
tag: app.tag,
|
|
5060
|
-
...digest ? { digest } : {}
|
|
5061
|
-
};
|
|
5062
|
-
await store.putResource(stack.stage, {
|
|
5063
|
-
key: `compose:${app.name}`,
|
|
5064
|
-
type: "compose-image",
|
|
5065
|
-
status: "ready",
|
|
5066
|
-
...digest ? { id: digest } : {},
|
|
5067
|
-
data: {
|
|
5068
|
-
project: stack.project,
|
|
5069
|
-
ref: app.ref,
|
|
5070
|
-
tag: app.tag,
|
|
5071
|
-
source: mode,
|
|
5072
|
-
...digest ? { digest } : {}
|
|
5073
|
-
}
|
|
5074
|
-
});
|
|
5075
|
-
}
|
|
5076
|
-
return images;
|
|
5077
|
-
}
|
|
5078
|
-
function printPlan(stack, mode, files) {
|
|
5079
|
-
logger$4.log(`\n🧱 ${stack.project} — stage ${stack.stage}`);
|
|
5080
|
-
for (const app of stack.apps) logger$4.log(` ${app.name.padEnd(12)} ${app.url.padEnd(40)} ${mode === "build" ? "build" : "pull"} ${app.ref}`);
|
|
5081
|
-
if (stack.infra.length > 0) logger$4.log(` infrastructure: ${stack.infra.join(", ")}`);
|
|
5082
|
-
logger$4.log(`\n📝 Wrote ${files.length} file(s) under ${stackDir(stack.stage)}/`);
|
|
5083
|
-
}
|
|
5084
3668
|
/**
|
|
5085
3669
|
* Format a fatal error for display.
|
|
5086
3670
|
* Always includes the full stack trace since these are process-ending errors.
|
|
@@ -5105,7 +3689,7 @@ const logger$3 = console;
|
|
|
5105
3689
|
* Get the Dokploy API token from stored credentials or environment
|
|
5106
3690
|
*/
|
|
5107
3691
|
async function getApiToken() {
|
|
5108
|
-
const token = await
|
|
3692
|
+
const token = await require_credentials.getDokployToken();
|
|
5109
3693
|
if (!token) throw new Error("Dokploy credentials not found.\nRun \"gkm login --service dokploy\" to authenticate, or set DOKPLOY_API_TOKEN.");
|
|
5110
3694
|
return token;
|
|
5111
3695
|
}
|
|
@@ -5114,7 +3698,7 @@ async function getApiToken() {
|
|
|
5114
3698
|
*/
|
|
5115
3699
|
async function getEndpoint(providedEndpoint) {
|
|
5116
3700
|
if (providedEndpoint) return providedEndpoint;
|
|
5117
|
-
const stored = await
|
|
3701
|
+
const stored = await require_credentials.getDokployCredentials();
|
|
5118
3702
|
if (stored) return stored.endpoint;
|
|
5119
3703
|
throw new Error("Dokploy endpoint not specified.\nEither run \"gkm login --service dokploy\" first, or provide --endpoint.");
|
|
5120
3704
|
}
|
|
@@ -5217,7 +3801,7 @@ async function deployListCommand(options) {
|
|
|
5217
3801
|
logger$3.log(" Run \"gkm registry:setup\" to configure a registry");
|
|
5218
3802
|
return;
|
|
5219
3803
|
}
|
|
5220
|
-
const storedRegistryId = await
|
|
3804
|
+
const storedRegistryId = await require_credentials.getDokployRegistryId();
|
|
5221
3805
|
for (const registry of registries) {
|
|
5222
3806
|
const marker = registry.registryId === storedRegistryId ? " (default)" : "";
|
|
5223
3807
|
logger$3.log(`\n ${registry.registryName}${marker} (${registry.registryId})`);
|
|
@@ -6486,25 +5070,25 @@ function loadPackageJson() {
|
|
|
6486
5070
|
* Run: pnpm --filter @geekmidas/cli sync-versions
|
|
6487
5071
|
*/
|
|
6488
5072
|
const GEEKMIDAS_VERSIONS = {
|
|
6489
|
-
"@geekmidas/audit": "~10.0.0-alpha.
|
|
6490
|
-
"@geekmidas/auth": "~10.0.0-alpha.
|
|
6491
|
-
"@geekmidas/cache": "~10.0.0-alpha.
|
|
6492
|
-
"@geekmidas/client": "~10.0.0-alpha.
|
|
6493
|
-
"@geekmidas/cloud": "~10.0.0-alpha.
|
|
6494
|
-
"@geekmidas/constructs": "~10.0.0-alpha.
|
|
6495
|
-
"@geekmidas/db": "~10.0.0-alpha.
|
|
6496
|
-
"@geekmidas/emailkit": "~10.0.0-alpha.
|
|
6497
|
-
"@geekmidas/envkit": "~10.0.0-alpha.
|
|
6498
|
-
"@geekmidas/errors": "~10.0.0-alpha.
|
|
6499
|
-
"@geekmidas/events": "~10.0.0-alpha.
|
|
6500
|
-
"@geekmidas/logger": "~10.0.0-alpha.
|
|
6501
|
-
"@geekmidas/manifest": "~10.0.0-alpha.
|
|
6502
|
-
"@geekmidas/rate-limit": "~10.0.0-alpha.
|
|
6503
|
-
"@geekmidas/schema": "~10.0.0-alpha.
|
|
6504
|
-
"@geekmidas/services": "~10.0.0-alpha.
|
|
6505
|
-
"@geekmidas/storage": "~10.0.0-alpha.
|
|
6506
|
-
"@geekmidas/telescope": "~10.0.0-alpha.
|
|
6507
|
-
"@geekmidas/testkit": "~10.0.0-alpha.
|
|
5073
|
+
"@geekmidas/audit": "~10.0.0-alpha.65",
|
|
5074
|
+
"@geekmidas/auth": "~10.0.0-alpha.65",
|
|
5075
|
+
"@geekmidas/cache": "~10.0.0-alpha.65",
|
|
5076
|
+
"@geekmidas/client": "~10.0.0-alpha.65",
|
|
5077
|
+
"@geekmidas/cloud": "~10.0.0-alpha.65",
|
|
5078
|
+
"@geekmidas/constructs": "~10.0.0-alpha.65",
|
|
5079
|
+
"@geekmidas/db": "~10.0.0-alpha.65",
|
|
5080
|
+
"@geekmidas/emailkit": "~10.0.0-alpha.65",
|
|
5081
|
+
"@geekmidas/envkit": "~10.0.0-alpha.65",
|
|
5082
|
+
"@geekmidas/errors": "~10.0.0-alpha.65",
|
|
5083
|
+
"@geekmidas/events": "~10.0.0-alpha.65",
|
|
5084
|
+
"@geekmidas/logger": "~10.0.0-alpha.65",
|
|
5085
|
+
"@geekmidas/manifest": "~10.0.0-alpha.65",
|
|
5086
|
+
"@geekmidas/rate-limit": "~10.0.0-alpha.65",
|
|
5087
|
+
"@geekmidas/schema": "~10.0.0-alpha.65",
|
|
5088
|
+
"@geekmidas/services": "~10.0.0-alpha.65",
|
|
5089
|
+
"@geekmidas/storage": "~10.0.0-alpha.65",
|
|
5090
|
+
"@geekmidas/telescope": "~10.0.0-alpha.65",
|
|
5091
|
+
"@geekmidas/testkit": "~10.0.0-alpha.65",
|
|
6508
5092
|
"@geekmidas/cli": `~${loadPackageJson().version}`
|
|
6509
5093
|
};
|
|
6510
5094
|
//#endregion
|
|
@@ -7187,8 +5771,10 @@ ${pm.setup}
|
|
|
7187
5771
|
- name: Install
|
|
7188
5772
|
run: ${pm.install}
|
|
7189
5773
|
${stageSecrets}
|
|
5774
|
+
# Builds, releases and health-checks the stage through deploy.default
|
|
5775
|
+
# in gkm.config.ts, and fails the job if any of it fails.
|
|
7190
5776
|
- name: Deploy
|
|
7191
|
-
run: ${pm.
|
|
5777
|
+
run: ${pm.exec} gkm deploy --stage "$STAGE"
|
|
7192
5778
|
env:
|
|
7193
5779
|
STAGE: \${{ matrix.stage }}${deployEnv}
|
|
7194
5780
|
`;
|
|
@@ -8041,7 +6627,7 @@ function deployPackage(options) {
|
|
|
8041
6627
|
case "sst": {
|
|
8042
6628
|
const v = GEEKMIDAS_VERSIONS;
|
|
8043
6629
|
return {
|
|
8044
|
-
scripts: perStage(options, (stage) => `gkm
|
|
6630
|
+
scripts: perStage(options, (stage) => `gkm deploy --stage ${stage}`),
|
|
8045
6631
|
dependencies: {
|
|
8046
6632
|
"@geekmidas/cloud": v["@geekmidas/cloud"],
|
|
8047
6633
|
"@geekmidas/db": v["@geekmidas/db"],
|
|
@@ -8084,7 +6670,7 @@ function generateDeployFiles(options) {
|
|
|
8084
6670
|
* constructs the application declares, through \`fromManifest\`. What is here
|
|
8085
6671
|
* is what varies by stage, and the inputs a declaration cannot carry.
|
|
8086
6672
|
*
|
|
8087
|
-
* Deploy with \`
|
|
6673
|
+
* Deploy with \`gkm deploy --stage <stage>\`, which builds the manifest first.
|
|
8088
6674
|
*/
|
|
8089
6675
|
const region = '${options.region}';
|
|
8090
6676
|
|
|
@@ -8118,7 +6704,7 @@ ${hasDatabase ? " const vpc = new sst.aws.Vpc('Vpc', { nat: 'ec2' });\n\n" :
|
|
|
8118
6704
|
hostedZoneId: '',
|
|
8119
6705
|
});
|
|
8120
6706
|
|
|
8121
|
-
|
|
6707
|
+
const provisioned = fromManifest(
|
|
8122
6708
|
new Stack(app, '${(0, _geekmidas_manifest.canonicalId)(options.name)}'),
|
|
8123
6709
|
constructs,
|
|
8124
6710
|
{
|
|
@@ -8126,6 +6712,15 @@ ${inputs.join("\n")}
|
|
|
8126
6712
|
},
|
|
8127
6713
|
backends,
|
|
8128
6714
|
);
|
|
6715
|
+
|
|
6716
|
+
// Each surface's URL, by its id: SST prints these and writes them to
|
|
6717
|
+
// .sst/outputs.json, where \`gkm deploy\` reads them to health-check
|
|
6718
|
+
// what it released.
|
|
6719
|
+
return Object.fromEntries(
|
|
6720
|
+
Object.entries(constructs)
|
|
6721
|
+
.filter(([, c]) => c.kind === 'rest-api' || c.kind === 'site')
|
|
6722
|
+
.map(([id]) => [id, provisioned[id]!.provides().url]),
|
|
6723
|
+
);
|
|
8129
6724
|
},
|
|
8130
6725
|
});
|
|
8131
6726
|
`
|
|
@@ -8536,7 +7131,7 @@ ${workspaceConstructsGlobs(options.routesStructure, (0, node_path.dirname)(optio
|
|
|
8536
7131
|
function deployBlock(options) {
|
|
8537
7132
|
if (options.deployTarget !== "sst") return "";
|
|
8538
7133
|
return `
|
|
8539
|
-
//
|
|
7134
|
+
// \`gkm deploy\` runs \`sst deploy\` on the manifest \`gkm build\` writes to
|
|
8540
7135
|
// \`.gkm/manifest/aws.ts\`. Each construct becomes a managed AWS resource.
|
|
8541
7136
|
deploy: { default: 'sst' },
|
|
8542
7137
|
`;
|
|
@@ -11165,155 +9760,6 @@ function isFullstackTemplate(name) {
|
|
|
11165
9760
|
return name === "fullstack";
|
|
11166
9761
|
}
|
|
11167
9762
|
//#endregion
|
|
11168
|
-
//#region src/init/utils.ts
|
|
11169
|
-
/**
|
|
11170
|
-
* Detect the package manager being used based on lockfiles or npm_config_user_agent
|
|
11171
|
-
*/
|
|
11172
|
-
function detectPackageManager(cwd = process.cwd()) {
|
|
11173
|
-
if ((0, node_fs.existsSync)((0, node_path.join)(cwd, "pnpm-lock.yaml"))) return "pnpm";
|
|
11174
|
-
if ((0, node_fs.existsSync)((0, node_path.join)(cwd, "yarn.lock"))) return "yarn";
|
|
11175
|
-
if ((0, node_fs.existsSync)((0, node_path.join)(cwd, "bun.lockb"))) return "bun";
|
|
11176
|
-
if ((0, node_fs.existsSync)((0, node_path.join)(cwd, "package-lock.json"))) return "npm";
|
|
11177
|
-
const userAgent = process.env.npm_config_user_agent || "";
|
|
11178
|
-
if (userAgent.includes("pnpm")) return "pnpm";
|
|
11179
|
-
if (userAgent.includes("yarn")) return "yarn";
|
|
11180
|
-
if (userAgent.includes("bun")) return "bun";
|
|
11181
|
-
return "npm";
|
|
11182
|
-
}
|
|
11183
|
-
/** `gkm init`'s flags, which a name can swallow when a space goes missing. */
|
|
11184
|
-
const INIT_FLAGS = [
|
|
11185
|
-
"yes",
|
|
11186
|
-
"name",
|
|
11187
|
-
"template",
|
|
11188
|
-
"monorepo",
|
|
11189
|
-
"api-path",
|
|
11190
|
-
"pm",
|
|
11191
|
-
"skip-install"
|
|
11192
|
-
];
|
|
11193
|
-
/**
|
|
11194
|
-
* Validate project name for npm package naming conventions
|
|
11195
|
-
*/
|
|
11196
|
-
function validateProjectName(name) {
|
|
11197
|
-
if (!name) return "Project name is required";
|
|
11198
|
-
if (!/^[a-z0-9-_@/.]+$/i.test(name)) return "Project name can only contain letters, numbers, hyphens, underscores, @, /, and .";
|
|
11199
|
-
const flag = name.match(/--(\w[\w-]*)$/)?.[1];
|
|
11200
|
-
if (flag && INIT_FLAGS.includes(flag)) return `"${name}" ends in the --${flag} flag. Did you mean \`${name.slice(0, -(flag.length + 2))} --${flag}\`?`;
|
|
11201
|
-
if ([
|
|
11202
|
-
"node_modules",
|
|
11203
|
-
".git",
|
|
11204
|
-
"package.json",
|
|
11205
|
-
"src"
|
|
11206
|
-
].includes(name.toLowerCase())) return `"${name}" is a reserved name`;
|
|
11207
|
-
return true;
|
|
11208
|
-
}
|
|
11209
|
-
/**
|
|
11210
|
-
* Check if a directory already exists at the target path
|
|
11211
|
-
*/
|
|
11212
|
-
function checkDirectoryExists(name, cwd = process.cwd()) {
|
|
11213
|
-
const targetPath = (0, node_path.join)(cwd, name);
|
|
11214
|
-
if ((0, node_fs.existsSync)(targetPath)) return `Directory "${name}" already exists`;
|
|
11215
|
-
return true;
|
|
11216
|
-
}
|
|
11217
|
-
/**
|
|
11218
|
-
* Get the install command for a package manager
|
|
11219
|
-
*/
|
|
11220
|
-
function getInstallCommand(pkgManager) {
|
|
11221
|
-
switch (pkgManager) {
|
|
11222
|
-
case "pnpm": return "pnpm install";
|
|
11223
|
-
case "yarn": return "yarn";
|
|
11224
|
-
case "bun": return "bun install";
|
|
11225
|
-
default: return "npm install";
|
|
11226
|
-
}
|
|
11227
|
-
}
|
|
11228
|
-
/**
|
|
11229
|
-
* Get the dev command for a package manager
|
|
11230
|
-
*/
|
|
11231
|
-
function getRunCommand(pkgManager, script) {
|
|
11232
|
-
switch (pkgManager) {
|
|
11233
|
-
case "pnpm": return `pnpm ${script}`;
|
|
11234
|
-
case "yarn": return `yarn ${script}`;
|
|
11235
|
-
case "bun": return `bun run ${script}`;
|
|
11236
|
-
default: return `npm run ${script}`;
|
|
11237
|
-
}
|
|
11238
|
-
}
|
|
11239
|
-
/**
|
|
11240
|
-
* Run a binary the project installed, through its package manager — so it is
|
|
11241
|
-
* the project's own version, not whatever \`npx\` would fetch.
|
|
11242
|
-
*/
|
|
11243
|
-
function getExecCommand(pkgManager, command) {
|
|
11244
|
-
switch (pkgManager) {
|
|
11245
|
-
case "pnpm": return `pnpm exec ${command}`;
|
|
11246
|
-
case "yarn": return `yarn ${command}`;
|
|
11247
|
-
case "bun": return `bunx ${command}`;
|
|
11248
|
-
default: return `npx --no-install ${command}`;
|
|
11249
|
-
}
|
|
11250
|
-
}
|
|
11251
|
-
const lockfileByPm = {
|
|
11252
|
-
pnpm: "pnpm-lock.yaml",
|
|
11253
|
-
yarn: "yarn.lock",
|
|
11254
|
-
npm: "package-lock.json",
|
|
11255
|
-
bun: "bun.lockb"
|
|
11256
|
-
};
|
|
11257
|
-
/**
|
|
11258
|
-
* Find the workspace/project root by walking up from cwd.
|
|
11259
|
-
* Checks for PM-specific workspace config, package.json#workspaces,
|
|
11260
|
-
* and lockfiles.
|
|
11261
|
-
*/
|
|
11262
|
-
function findWorkspaceRoot(cwd, pm) {
|
|
11263
|
-
let dir = cwd;
|
|
11264
|
-
const root = (0, node_path.parse)(dir).root;
|
|
11265
|
-
const lockfile = lockfileByPm[pm];
|
|
11266
|
-
while (dir !== root) {
|
|
11267
|
-
if (pm === "pnpm" && (0, node_fs.existsSync)((0, node_path.join)(dir, "pnpm-workspace.yaml"))) return dir;
|
|
11268
|
-
const pkgJsonPath = (0, node_path.join)(dir, "package.json");
|
|
11269
|
-
if ((0, node_fs.existsSync)(pkgJsonPath)) try {
|
|
11270
|
-
if (JSON.parse((0, node_fs.readFileSync)(pkgJsonPath, "utf-8")).workspaces) return dir;
|
|
11271
|
-
} catch {}
|
|
11272
|
-
if ((0, node_fs.existsSync)((0, node_path.join)(dir, lockfile))) return dir;
|
|
11273
|
-
dir = (0, node_path.dirname)(dir);
|
|
11274
|
-
}
|
|
11275
|
-
return cwd;
|
|
11276
|
-
}
|
|
11277
|
-
/**
|
|
11278
|
-
* Get workspace package glob patterns from pnpm-workspace.yaml
|
|
11279
|
-
* or package.json#workspaces.
|
|
11280
|
-
*/
|
|
11281
|
-
function getWorkspaceGlobs(root) {
|
|
11282
|
-
const pnpmWorkspacePath = (0, node_path.join)(root, "pnpm-workspace.yaml");
|
|
11283
|
-
if ((0, node_fs.existsSync)(pnpmWorkspacePath)) {
|
|
11284
|
-
const content = (0, node_fs.readFileSync)(pnpmWorkspacePath, "utf-8");
|
|
11285
|
-
return (0, yaml.parse)(content)?.packages ?? [];
|
|
11286
|
-
}
|
|
11287
|
-
const rootPkgJsonPath = (0, node_path.join)(root, "package.json");
|
|
11288
|
-
if ((0, node_fs.existsSync)(rootPkgJsonPath)) {
|
|
11289
|
-
const pkg = JSON.parse((0, node_fs.readFileSync)(rootPkgJsonPath, "utf-8"));
|
|
11290
|
-
if (Array.isArray(pkg.workspaces)) return pkg.workspaces;
|
|
11291
|
-
if (pkg.workspaces?.packages) return pkg.workspaces.packages;
|
|
11292
|
-
}
|
|
11293
|
-
return [];
|
|
11294
|
-
}
|
|
11295
|
-
/**
|
|
11296
|
-
* Find all package.json files across a workspace.
|
|
11297
|
-
* Returns the root package.json plus all workspace member package.json paths.
|
|
11298
|
-
*/
|
|
11299
|
-
function findWorkspacePackages(cwd, pm) {
|
|
11300
|
-
const workspaceRoot = findWorkspaceRoot(cwd, pm);
|
|
11301
|
-
const results = [];
|
|
11302
|
-
const rootPkgJson = (0, node_path.join)(workspaceRoot, "package.json");
|
|
11303
|
-
if ((0, node_fs.existsSync)(rootPkgJson)) results.push(rootPkgJson);
|
|
11304
|
-
const globs = getWorkspaceGlobs(workspaceRoot);
|
|
11305
|
-
for (const glob of globs) {
|
|
11306
|
-
const pattern = `${glob}/package.json`;
|
|
11307
|
-
const matches = fast_glob.default.sync(pattern, {
|
|
11308
|
-
cwd: workspaceRoot,
|
|
11309
|
-
absolute: true,
|
|
11310
|
-
ignore: ["**/node_modules/**"]
|
|
11311
|
-
});
|
|
11312
|
-
results.push(...matches);
|
|
11313
|
-
}
|
|
11314
|
-
return [...new Set(results)];
|
|
11315
|
-
}
|
|
11316
|
-
//#endregion
|
|
11317
9763
|
//#region src/init/index.ts
|
|
11318
9764
|
/** `' staging, prod '` → `['staging', 'prod']`. */
|
|
11319
9765
|
const stageList = (value) => value.split(",").map((name) => name.trim()).filter(Boolean);
|
|
@@ -11324,7 +9770,7 @@ const AWS_REGION = /^[a-z]{2}(-gov)?-[a-z]+-\d$/;
|
|
|
11324
9770
|
*/
|
|
11325
9771
|
async function initCommand(projectName, options = {}) {
|
|
11326
9772
|
const cwd = process.cwd();
|
|
11327
|
-
const detectedPkgManager = detectPackageManager(cwd);
|
|
9773
|
+
const detectedPkgManager = require_utils.detectPackageManager(cwd);
|
|
11328
9774
|
if (options.deploy && !deployTargetChoices.some((choice) => choice.value === options.deploy)) throw new UnknownDeployTarget(options.deploy, deployTargetChoices.map((c) => c.value));
|
|
11329
9775
|
if (options.region && !AWS_REGION.test(options.region)) throw new NotAnAwsRegion(options.region);
|
|
11330
9776
|
if (options.stages || options.localStage || options.protectedStage) {
|
|
@@ -11351,9 +9797,9 @@ async function initCommand(projectName, options = {}) {
|
|
|
11351
9797
|
message: "Project name:",
|
|
11352
9798
|
initial: "my-app",
|
|
11353
9799
|
validate: (value) => {
|
|
11354
|
-
const nameValid = validateProjectName(value);
|
|
9800
|
+
const nameValid = require_utils.validateProjectName(value);
|
|
11355
9801
|
if (nameValid !== true) return nameValid;
|
|
11356
|
-
const dirValid = checkDirectoryExists(value, cwd);
|
|
9802
|
+
const dirValid = require_utils.checkDirectoryExists(value, cwd);
|
|
11357
9803
|
if (dirValid !== true) return dirValid;
|
|
11358
9804
|
return true;
|
|
11359
9805
|
}
|
|
@@ -11461,12 +9907,12 @@ async function initCommand(projectName, options = {}) {
|
|
|
11461
9907
|
}
|
|
11462
9908
|
if (projectName || options.name) {
|
|
11463
9909
|
const nameToValidate = projectName || options.name;
|
|
11464
|
-
const nameValid = validateProjectName(nameToValidate);
|
|
9910
|
+
const nameValid = require_utils.validateProjectName(nameToValidate);
|
|
11465
9911
|
if (nameValid !== true) {
|
|
11466
9912
|
console.error(nameValid);
|
|
11467
9913
|
process.exit(1);
|
|
11468
9914
|
}
|
|
11469
|
-
const dirValid = checkDirectoryExists(nameToValidate, cwd);
|
|
9915
|
+
const dirValid = require_utils.checkDirectoryExists(nameToValidate, cwd);
|
|
11470
9916
|
if (dirValid !== true) {
|
|
11471
9917
|
console.error(dirValid);
|
|
11472
9918
|
process.exit(1);
|
|
@@ -11572,7 +10018,7 @@ async function initCommand(projectName, options = {}) {
|
|
|
11572
10018
|
if (!options.skipInstall) {
|
|
11573
10019
|
console.log("\n📦 Installing dependencies...\n");
|
|
11574
10020
|
try {
|
|
11575
|
-
(0, node_child_process.execSync)(getInstallCommand(pkgManager), {
|
|
10021
|
+
(0, node_child_process.execSync)(require_utils.getInstallCommand(pkgManager), {
|
|
11576
10022
|
cwd: targetDir,
|
|
11577
10023
|
stdio: "inherit"
|
|
11578
10024
|
});
|
|
@@ -11580,12 +10026,12 @@ async function initCommand(projectName, options = {}) {
|
|
|
11580
10026
|
console.error("Failed to install dependencies");
|
|
11581
10027
|
}
|
|
11582
10028
|
try {
|
|
11583
|
-
(0, node_child_process.execSync)(getExecCommand(pkgManager, "biome check --write ."), {
|
|
10029
|
+
(0, node_child_process.execSync)(require_utils.getExecCommand(pkgManager, "biome check --write ."), {
|
|
11584
10030
|
cwd: targetDir,
|
|
11585
10031
|
stdio: "inherit"
|
|
11586
10032
|
});
|
|
11587
10033
|
} catch {
|
|
11588
|
-
console.warn(`\n⚠️ Formatting did not finish cleanly. Run ${getRunCommand(pkgManager, "fmt")} to see why.`);
|
|
10034
|
+
console.warn(`\n⚠️ Formatting did not finish cleanly. Run ${require_utils.getRunCommand(pkgManager, "fmt")} to see why.`);
|
|
11589
10035
|
}
|
|
11590
10036
|
}
|
|
11591
10037
|
console.log("\n📦 Initializing git repository...\n");
|
|
@@ -11616,7 +10062,7 @@ async function initCommand(projectName, options = {}) {
|
|
|
11616
10062
|
* Print success message with next steps
|
|
11617
10063
|
*/
|
|
11618
10064
|
function printNextSteps(projectName, options, pkgManager) {
|
|
11619
|
-
const devCommand = getRunCommand(pkgManager, "dev");
|
|
10065
|
+
const devCommand = require_utils.getRunCommand(pkgManager, "dev");
|
|
11620
10066
|
const cdCommand = `cd ${projectName}`;
|
|
11621
10067
|
console.log(`\n${"─".repeat(50)}`);
|
|
11622
10068
|
console.log("\n✅ Project created successfully!\n");
|
|
@@ -11665,12 +10111,12 @@ function printNextSteps(projectName, options, pkgManager) {
|
|
|
11665
10111
|
console.log("");
|
|
11666
10112
|
if (options.deployTarget === "dokploy") {
|
|
11667
10113
|
console.log("🚀 Deployment:");
|
|
11668
|
-
for (const stage of deployed) console.log(` ${getRunCommand(pkgManager, `deploy:${stage}`)}`);
|
|
10114
|
+
for (const stage of deployed) console.log(` ${require_utils.getRunCommand(pkgManager, `deploy:${stage}`)}`);
|
|
11669
10115
|
console.log("");
|
|
11670
10116
|
}
|
|
11671
10117
|
if (options.deployTarget === "sst") {
|
|
11672
10118
|
console.log("🚀 Deployment (AWS, through SST):");
|
|
11673
|
-
for (const stage of deployed) console.log(` ${getRunCommand(pkgManager, `deploy:${stage}`)} # gkm build,
|
|
10119
|
+
for (const stage of deployed) console.log(` ${require_utils.getRunCommand(pkgManager, `deploy:${stage}`)} # gkm deploy: build, sst deploy, health checks`);
|
|
11674
10120
|
console.log(` Uses your AWS credentials, in ${options.region}.`);
|
|
11675
10121
|
if (options.constructs.mail) console.log(" Set MAIL_FROM to a sender verified in SES first.");
|
|
11676
10122
|
console.log("");
|
|
@@ -12029,7 +10475,7 @@ async function setupCommand(options = {}) {
|
|
|
12029
10475
|
logger$1.error("❌ Failed to resolve secrets. Exiting.");
|
|
12030
10476
|
process.exit(1);
|
|
12031
10477
|
}
|
|
12032
|
-
const containers = await
|
|
10478
|
+
const containers = await require_state.derivedContainers(workspace, stage);
|
|
12033
10479
|
if (isMultiApp && containers.includes("postgres")) {
|
|
12034
10480
|
await writeDockerEnvFromSecrets(secrets, workspace.root);
|
|
12035
10481
|
logger$1.log("📄 Generated docker/.env with database passwords");
|
|
@@ -12048,7 +10494,7 @@ async function setupCommand(options = {}) {
|
|
|
12048
10494
|
* free and quiet.
|
|
12049
10495
|
*/
|
|
12050
10496
|
async function reconcileLocal(workspace, stage, options) {
|
|
12051
|
-
const result = await
|
|
10497
|
+
const result = await require_state.reconcileWorkspace(workspace, { stage });
|
|
12052
10498
|
if (result.plan.containers.length === 0) {
|
|
12053
10499
|
logger$1.log("🐳 No containers declared");
|
|
12054
10500
|
return;
|
|
@@ -12058,7 +10504,7 @@ async function reconcileLocal(workspace, stage, options) {
|
|
|
12058
10504
|
return;
|
|
12059
10505
|
}
|
|
12060
10506
|
logger$1.log("🐳 Services");
|
|
12061
|
-
for (const line of
|
|
10507
|
+
for (const line of require_state.describeServices(result.services)) logger$1.log(line);
|
|
12062
10508
|
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://"));
|
|
12063
10509
|
if (url) await ensureTrusted(workspace.root, url, { ...options.yes ? { assumeYes: true } : {} });
|
|
12064
10510
|
}
|
|
@@ -12079,7 +10525,7 @@ async function resolveSecrets(stage, workspace, options) {
|
|
|
12079
10525
|
const secrets = await store.read(stage);
|
|
12080
10526
|
if (secrets) {
|
|
12081
10527
|
logger$1.log("🔐 Using existing secrets");
|
|
12082
|
-
const reconciled = reconcileSecrets(secrets, workspace, await
|
|
10528
|
+
const reconciled = reconcileSecrets(secrets, workspace, await require_state.derivedContainers(workspace, stage));
|
|
12083
10529
|
if (reconciled) await store.write(stage, reconciled);
|
|
12084
10530
|
return reconciled ?? secrets;
|
|
12085
10531
|
}
|
|
@@ -12201,7 +10647,7 @@ async function ensureStageSecrets(stage, cwd = process.cwd()) {
|
|
|
12201
10647
|
const { workspace } = await require_config.loadWorkspaceConfig(cwd);
|
|
12202
10648
|
const store = await require_store.secretsStoreFor(workspace, stage);
|
|
12203
10649
|
if (await store.read(stage)) return false;
|
|
12204
|
-
const secrets = createFreshWorkspaceSecrets(stage, workspace, await
|
|
10650
|
+
const secrets = createFreshWorkspaceSecrets(stage, workspace, await require_state.derivedContainers(workspace, stage));
|
|
12205
10651
|
await store.write(stage, secrets);
|
|
12206
10652
|
return true;
|
|
12207
10653
|
}
|
|
@@ -12209,7 +10655,7 @@ async function ensureStageSecrets(stage, cwd = process.cwd()) {
|
|
|
12209
10655
|
* Generate fresh secrets for the workspace.
|
|
12210
10656
|
*/
|
|
12211
10657
|
async function generateFreshSecrets(stage, workspace) {
|
|
12212
|
-
const secrets = createFreshWorkspaceSecrets(stage, workspace, await
|
|
10658
|
+
const secrets = createFreshWorkspaceSecrets(stage, workspace, await require_state.derivedContainers(workspace, stage));
|
|
12213
10659
|
const store = await require_store.secretsStoreFor(workspace, stage);
|
|
12214
10660
|
await store.write(stage, secrets);
|
|
12215
10661
|
logger$1.log(` Secrets written to the "${stage}" store (${store.name})`);
|
|
@@ -12250,7 +10696,7 @@ function printSummary(workspace, stage) {
|
|
|
12250
10696
|
* A no-op when nothing is recorded — no run yet, or one already torn down —
|
|
12251
10697
|
* so running it twice, or before a setup, is harmless.
|
|
12252
10698
|
*/
|
|
12253
|
-
async function dropTestDatabases(cwd, root, sql =
|
|
10699
|
+
async function dropTestDatabases(cwd, root, sql = require_state.pgClient) {
|
|
12254
10700
|
const readyPath = (0, node_path.join)(cwd, require_ready.TEST_READY_FILE);
|
|
12255
10701
|
const ready = await readReady(readyPath);
|
|
12256
10702
|
const recorded = ready?.databases;
|
|
@@ -12258,9 +10704,9 @@ async function dropTestDatabases(cwd, root, sql = require_sniffer.pgClient) {
|
|
|
12258
10704
|
const client = sql(recorded.port);
|
|
12259
10705
|
for (const name of recorded.names) {
|
|
12260
10706
|
if (!name.endsWith(`_test`)) throw new NotATestDatabase(name);
|
|
12261
|
-
await client.query(void 0, `DROP DATABASE IF EXISTS ${
|
|
10707
|
+
await client.query(void 0, `DROP DATABASE IF EXISTS ${require_state.quoteIdentifier(name)} WITH (FORCE)`);
|
|
12262
10708
|
}
|
|
12263
|
-
await
|
|
10709
|
+
await require_state.forgetState(root, require_workspace.TEST_STAGE);
|
|
12264
10710
|
const { databases: _dropped, ...rest } = ready;
|
|
12265
10711
|
await (0, node_fs_promises.writeFile)(readyPath, JSON.stringify(rest, null, 2));
|
|
12266
10712
|
return recorded.names;
|
|
@@ -12300,10 +10746,10 @@ async function testCommand(options = {}) {
|
|
|
12300
10746
|
const { ensureStageSecrets } = await Promise.resolve().then(() => setup_exports);
|
|
12301
10747
|
if (await ensureStageSecrets(stage, cwd)) console.log(` 🔐 Generated fresh ${stage} secrets (auto-setup)`);
|
|
12302
10748
|
}
|
|
12303
|
-
const defaultEnv =
|
|
10749
|
+
const defaultEnv = require_state.loadEnvFiles(".env");
|
|
12304
10750
|
if (defaultEnv.loaded.length > 0) console.log(` 📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
12305
10751
|
if (!options.prepare) await teardown(cwd);
|
|
12306
|
-
const result = await
|
|
10752
|
+
const result = await require_state.prepareEntryCredentials({
|
|
12307
10753
|
stage,
|
|
12308
10754
|
startDocker: !options.prepare,
|
|
12309
10755
|
secretsFileName: "test-secrets.json",
|
|
@@ -12324,13 +10770,13 @@ async function testCommand(options = {}) {
|
|
|
12324
10770
|
await (0, node_fs_promises.writeFile)(result.secretsJsonPath, JSON.stringify(finalCredentials, null, 2));
|
|
12325
10771
|
const gkmDir = (0, node_path.join)(cwd, ".gkm");
|
|
12326
10772
|
const preloadPath = (0, node_path.join)(gkmDir, "test-credentials-preload.ts");
|
|
12327
|
-
await
|
|
10773
|
+
await require_state.createCredentialsPreload(preloadPath, result.secretsJsonPath);
|
|
12328
10774
|
const workspace = await require_config.loadWorkspaceConfig(cwd).then((loaded) => loaded.workspace).catch(() => void 0);
|
|
12329
10775
|
const manifestPath = workspace ? await require_ready.writeTestHarness({
|
|
12330
10776
|
root: workspace.root,
|
|
12331
10777
|
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))],
|
|
12332
|
-
patterns:
|
|
12333
|
-
cacheBackend:
|
|
10778
|
+
patterns: require_state.constructGlobs(workspace),
|
|
10779
|
+
cacheBackend: require_state.backendsOf(workspace).cache,
|
|
12334
10780
|
stage: require_workspace.TEST_STAGE,
|
|
12335
10781
|
env: finalCredentials,
|
|
12336
10782
|
factories: (0, node_path.join)(workspace.root, workspace.test.factories ?? "test/factories")
|
|
@@ -12393,8 +10839,8 @@ async function teardown(cwd) {
|
|
|
12393
10839
|
* the plan provisions, on the port the shared container answers.
|
|
12394
10840
|
*/
|
|
12395
10841
|
function testDatabases(reconciled) {
|
|
12396
|
-
const port = reconciled?.ports[
|
|
12397
|
-
const names = reconciled ?
|
|
10842
|
+
const port = reconciled?.ports[require_state.primaryPortKey("postgres")];
|
|
10843
|
+
const names = reconciled ? require_state.postgresDatabaseNames(reconciled.plan) : [];
|
|
12398
10844
|
return port !== void 0 && names.length > 0 ? { databases: {
|
|
12399
10845
|
port,
|
|
12400
10846
|
names
|
|
@@ -12405,11 +10851,11 @@ function testDatabases(reconciled) {
|
|
|
12405
10851
|
* starts without the reference data the app needs.
|
|
12406
10852
|
*/
|
|
12407
10853
|
async function migrateTestStage(workspace, env) {
|
|
12408
|
-
const { migrateAndSeed } = await Promise.resolve().then(() => require("./migrate-
|
|
12409
|
-
const { discover } = await Promise.resolve().then(() => require("./discover-
|
|
10854
|
+
const { migrateAndSeed } = await Promise.resolve().then(() => require("./migrate-q4pPAXtO.cjs"));
|
|
10855
|
+
const { discover } = await Promise.resolve().then(() => require("./discover-DHdECueV.cjs")).then((n) => n.discover_exports);
|
|
12410
10856
|
const sources = {};
|
|
12411
10857
|
const manifest = await discover({
|
|
12412
|
-
patterns:
|
|
10858
|
+
patterns: require_state.constructGlobs(workspace),
|
|
12413
10859
|
cwd: workspace.root,
|
|
12414
10860
|
sources
|
|
12415
10861
|
});
|
|
@@ -12564,11 +11010,11 @@ function withVersion(range, version) {
|
|
|
12564
11010
|
}
|
|
12565
11011
|
async function upgradeCommand(options = {}) {
|
|
12566
11012
|
const cwd = process.cwd();
|
|
12567
|
-
const pm = detectPackageManager(cwd);
|
|
12568
|
-
const root = findWorkspaceRoot(cwd, pm);
|
|
11013
|
+
const pm = require_utils.detectPackageManager(cwd);
|
|
11014
|
+
const root = require_utils.findWorkspaceRoot(cwd, pm);
|
|
12569
11015
|
logger.log("\n📦 Scanning workspace for @geekmidas packages...\n");
|
|
12570
11016
|
logger.log(` Package manager: ${pm}`);
|
|
12571
|
-
const packageJsons = findWorkspacePackages(cwd, pm);
|
|
11017
|
+
const packageJsons = require_utils.findWorkspacePackages(cwd, pm);
|
|
12572
11018
|
const occurrences = [...packageOccurrences(packageJsons), ...catalogOccurrences(root)];
|
|
12573
11019
|
logger.log(` Found ${packageJsons.length} package(s) in workspace\n`);
|
|
12574
11020
|
const ours = occurrences.filter((o) => o.name.startsWith(SCOPE) && !isIndirect(o.range));
|
|
@@ -12679,7 +11125,7 @@ function applyChanges(changes) {
|
|
|
12679
11125
|
}
|
|
12680
11126
|
}
|
|
12681
11127
|
function install(pm, cwd) {
|
|
12682
|
-
const command = getInstallCommand(pm);
|
|
11128
|
+
const command = require_utils.getInstallCommand(pm);
|
|
12683
11129
|
logger.log(`\n Running: ${command}\n`);
|
|
12684
11130
|
try {
|
|
12685
11131
|
(0, node_child_process.execSync)(command, {
|
|
@@ -12836,7 +11282,7 @@ program.command("migrate").description("Apply each database construct's migratio
|
|
|
12836
11282
|
try {
|
|
12837
11283
|
const globalOptions = program.opts();
|
|
12838
11284
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
12839
|
-
const { migrateCommand } = await Promise.resolve().then(() => require("./migrate-
|
|
11285
|
+
const { migrateCommand } = await Promise.resolve().then(() => require("./migrate-q4pPAXtO.cjs"));
|
|
12840
11286
|
await migrateCommand({
|
|
12841
11287
|
...options,
|
|
12842
11288
|
...construct ? { construct } : {}
|
|
@@ -12850,7 +11296,7 @@ program.command("seed").description("Migrate, then run each database construct's
|
|
|
12850
11296
|
try {
|
|
12851
11297
|
const globalOptions = program.opts();
|
|
12852
11298
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
12853
|
-
const { seedCommand } = await Promise.resolve().then(() => require("./migrate-
|
|
11299
|
+
const { seedCommand } = await Promise.resolve().then(() => require("./migrate-q4pPAXtO.cjs"));
|
|
12854
11300
|
await seedCommand({
|
|
12855
11301
|
...options,
|
|
12856
11302
|
...construct ? { construct } : {}
|
|
@@ -12864,7 +11310,7 @@ program.command("migration").description("Write a construct's next migration: an
|
|
|
12864
11310
|
try {
|
|
12865
11311
|
const globalOptions = program.opts();
|
|
12866
11312
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
12867
|
-
const { migrationCommand } = await Promise.resolve().then(() => require("./migrate-
|
|
11313
|
+
const { migrationCommand } = await Promise.resolve().then(() => require("./migrate-q4pPAXtO.cjs"));
|
|
12868
11314
|
await migrationCommand({
|
|
12869
11315
|
construct,
|
|
12870
11316
|
...name ? { name } : {}
|
|
@@ -12903,7 +11349,7 @@ program.command("docker").description("Generate Docker deployment files").option
|
|
|
12903
11349
|
try {
|
|
12904
11350
|
const globalOptions = program.opts();
|
|
12905
11351
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
12906
|
-
await
|
|
11352
|
+
await require_state.dockerCommand(options);
|
|
12907
11353
|
} catch (error) {
|
|
12908
11354
|
console.error(formatError(error));
|
|
12909
11355
|
process.exit(1);
|
|
@@ -12928,7 +11374,7 @@ program.command("prepack").description("Generate Docker files for production dep
|
|
|
12928
11374
|
production: true,
|
|
12929
11375
|
skipBundle: options.skipBundle
|
|
12930
11376
|
});
|
|
12931
|
-
await
|
|
11377
|
+
await require_state.dockerCommand({
|
|
12932
11378
|
build: options.build,
|
|
12933
11379
|
push: options.push,
|
|
12934
11380
|
tag: options.tag,
|
|
@@ -12972,7 +11418,7 @@ program.command("encryption:rotate").description("Add a new current key to an En
|
|
|
12972
11418
|
try {
|
|
12973
11419
|
const globalOptions = program.opts();
|
|
12974
11420
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
12975
|
-
const { encryptionRotateCommand } = await Promise.resolve().then(() => require("./encryption-
|
|
11421
|
+
const { encryptionRotateCommand } = await Promise.resolve().then(() => require("./encryption-B1wJwgae.cjs"));
|
|
12976
11422
|
await encryptionRotateCommand(construct, options);
|
|
12977
11423
|
} catch (error) {
|
|
12978
11424
|
console.error(formatError(error));
|
|
@@ -12983,7 +11429,7 @@ program.command("encryption:retire").description("Remove an old key from an Encr
|
|
|
12983
11429
|
try {
|
|
12984
11430
|
const globalOptions = program.opts();
|
|
12985
11431
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
12986
|
-
const { encryptionRetireCommand } = await Promise.resolve().then(() => require("./encryption-
|
|
11432
|
+
const { encryptionRetireCommand } = await Promise.resolve().then(() => require("./encryption-B1wJwgae.cjs"));
|
|
12987
11433
|
await encryptionRetireCommand(construct, key, options);
|
|
12988
11434
|
} catch (error) {
|
|
12989
11435
|
console.error(formatError(error));
|
|
@@ -13026,7 +11472,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
|
|
|
13026
11472
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
13027
11473
|
const { loadWorkspaceConfig } = await Promise.resolve().then(() => require("./config.cjs"));
|
|
13028
11474
|
const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-CuCQBa5-.cjs"));
|
|
13029
|
-
const { secretsStoreFor } = await Promise.resolve().then(() => require("./store-
|
|
11475
|
+
const { secretsStoreFor } = await Promise.resolve().then(() => require("./store-CJF1Tqkq.cjs")).then((n) => n.store_exports);
|
|
13030
11476
|
const { workspace } = await loadWorkspaceConfig();
|
|
13031
11477
|
const stage = options.stage ?? workspace.stages.local;
|
|
13032
11478
|
const store = await secretsStoreFor(workspace, stage);
|
|
@@ -13035,7 +11481,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
|
|
|
13035
11481
|
console.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
13036
11482
|
process.exit(1);
|
|
13037
11483
|
}
|
|
13038
|
-
const { derivedContainers } = await Promise.resolve().then(() => require("./
|
|
11484
|
+
const { derivedContainers } = await Promise.resolve().then(() => require("./state-DF26e_lo.cjs")).then((n) => n.workspace_exports);
|
|
13039
11485
|
const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
|
|
13040
11486
|
if (!result) {
|
|
13041
11487
|
console.log(`\n✓ Secrets for stage "${stage}" are up-to-date`);
|
|
@@ -13049,8 +11495,8 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
|
|
|
13049
11495
|
process.exit(1);
|
|
13050
11496
|
}
|
|
13051
11497
|
});
|
|
13052
|
-
program.command("deploy").description("Deploy a stage through its target").option("--target <name>", "Deploy target: dokploy, or one named in deploy.targets (default: deploy.default)").option("--provider <provider>", "[DEPRECATED] Use --target instead").requiredOption("--stage <stage>", "Deployment stage (e.g., production, staging)").option("--tag <tag>", "Image tag (default: stage-timestamp)").option("--skip-push", "Skip pushing image to registry").option("--skip-build", "Skip build step (use existing build)").option("--json", "Write events as JSON lines instead of progress; never prompts").option("--dry-run", "Show what would be created or reused; change, build and push nothing").action(async (options) => {
|
|
13053
|
-
const { deployCli } = await Promise.resolve().then(() => require("./cli-
|
|
11498
|
+
program.command("deploy").description("Deploy a stage through its target").option("--target <name>", "Deploy target: dokploy, compose, sst, or one named in deploy.targets (default: deploy.default)").option("--provider <provider>", "[DEPRECATED] Use --target instead").requiredOption("--stage <stage>", "Deployment stage (e.g., production, staging)").option("--tag <tag>", "Image tag (default: stage-timestamp; compose: the commit). Compose pulls a given tag").option("--skip-push", "Skip pushing image to registry").option("--skip-build", "Skip build step (use existing build)").option("--json", "Write events as JSON lines instead of progress; never prompts").option("--dry-run", "Show what would be created or reused; change, build and push nothing").action(async (options) => {
|
|
11499
|
+
const { deployCli } = await Promise.resolve().then(() => require("./cli-BfH1deAT.cjs"));
|
|
13054
11500
|
const globalOptions = program.opts();
|
|
13055
11501
|
const code = await deployCli({
|
|
13056
11502
|
cwd: (0, node_path.resolve)(globalOptions.cwd ?? process.cwd()),
|
|
@@ -13083,7 +11529,7 @@ program.command("deploy:github").description("Let GitHub Actions deploy a stage:
|
|
|
13083
11529
|
try {
|
|
13084
11530
|
const globalOptions = program.opts();
|
|
13085
11531
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
13086
|
-
const { deployGithubCommand } = await Promise.resolve().then(() => require("./github-
|
|
11532
|
+
const { deployGithubCommand } = await Promise.resolve().then(() => require("./github-CYcmTdFs.cjs"));
|
|
13087
11533
|
await deployGithubCommand(options);
|
|
13088
11534
|
} catch (error) {
|
|
13089
11535
|
console.error(formatError(error));
|
|
@@ -13218,23 +11664,8 @@ program.command("upgrade").description("Upgrade @geekmidas/cli (with --all, ever
|
|
|
13218
11664
|
});
|
|
13219
11665
|
program.parse();
|
|
13220
11666
|
//#endregion
|
|
13221
|
-
exports.DuplicateMigration = DuplicateMigration;
|
|
13222
|
-
exports.KyselyNotFound = KyselyNotFound;
|
|
13223
|
-
exports.MigrationFailed = MigrationFailed;
|
|
13224
|
-
exports.MigrationHasNoUp = MigrationHasNoUp;
|
|
13225
|
-
exports.MigrationsOutsideFolder = MigrationsOutsideFolder;
|
|
13226
|
-
exports.NoOwnerCredential = NoOwnerCredential;
|
|
13227
|
-
exports.NoSuchMigrationTarget = NoSuchMigrationTarget;
|
|
13228
|
-
exports.SeedFailed = SeedFailed;
|
|
13229
|
-
exports.SeedHasNoSeed = SeedHasNoSeed;
|
|
13230
|
-
exports.UnknownDatabaseFolder = UnknownDatabaseFolder;
|
|
13231
|
-
exports.UnknownMigrationFolder = UnknownMigrationFolder;
|
|
13232
11667
|
exports.formatError = formatError;
|
|
13233
11668
|
exports.generateFullstackCustomSecrets = generateFullstackCustomSecrets;
|
|
13234
|
-
exports.migrateAndSeed = migrateAndSeed;
|
|
13235
|
-
exports.migrateDatabases = migrateDatabases;
|
|
13236
|
-
exports.pendingMigrations = pendingMigrations;
|
|
13237
|
-
exports.seedDatabases = seedDatabases;
|
|
13238
11669
|
exports.slug = slug;
|
|
13239
11670
|
exports.stamp = stamp;
|
|
13240
11671
|
|