@geekmidas/cli 10.0.0-alpha.64 → 10.0.0-alpha.66
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 +15 -1
- package/dist/{HostingerProvider-Bx2lHHvu.cjs → HostingerProvider-BznqRC2S.cjs} +5 -5
- package/dist/HostingerProvider-BznqRC2S.cjs.map +1 -0
- package/dist/{HostingerProvider-Cd4aS5Kj.mjs → HostingerProvider-CY_VpVud.mjs} +4 -4
- package/dist/HostingerProvider-CY_VpVud.mjs.map +1 -0
- package/dist/{LegacyStateStore-C2abV49N.cjs → LegacyStateStore-BFcTT2QL.cjs} +2 -2
- package/dist/{LegacyStateStore-C2abV49N.cjs.map → LegacyStateStore-BFcTT2QL.cjs.map} +1 -1
- package/dist/{LegacyStateStore-DIgkGaUP.mjs → LegacyStateStore-CFheFo62.mjs} +2 -2
- package/dist/{LegacyStateStore-DIgkGaUP.mjs.map → LegacyStateStore-CFheFo62.mjs.map} +1 -1
- package/dist/{LocalStateStore-DiJqKyPy.cjs → LocalStateStore-BVUexSvu.cjs} +2 -2
- package/dist/{LocalStateStore-DiJqKyPy.cjs.map → LocalStateStore-BVUexSvu.cjs.map} +1 -1
- package/dist/{LocalStateStore-Cy1ZBW7U.mjs → LocalStateStore-Bm3x9oOg.mjs} +2 -2
- package/dist/{LocalStateStore-Cy1ZBW7U.mjs.map → LocalStateStore-Bm3x9oOg.mjs.map} +1 -1
- package/dist/{OpenApiTsGenerator-BWuDRHe9.cjs → OpenApiTsGenerator-B2zrdyP4.cjs} +2 -2
- package/dist/{OpenApiTsGenerator-BWuDRHe9.cjs.map → OpenApiTsGenerator-B2zrdyP4.cjs.map} +1 -1
- package/dist/{OpenApiTsGenerator-C6v-IeHt.mjs → OpenApiTsGenerator-BmUAcR7d.mjs} +2 -2
- package/dist/{OpenApiTsGenerator-C6v-IeHt.mjs.map → OpenApiTsGenerator-BmUAcR7d.mjs.map} +1 -1
- package/dist/{Route53Provider-DVILBsvV.cjs → Route53Provider-Bllus2NR.cjs} +2 -2
- package/dist/Route53Provider-Bllus2NR.cjs.map +1 -0
- package/dist/{Route53Provider-Cvj40A8I.mjs → Route53Provider-QXxjkZO1.mjs} +2 -2
- package/dist/Route53Provider-QXxjkZO1.mjs.map +1 -0
- package/dist/{S3StateStore-D3x6ivBL.mjs → S3StateStore-BFHuctWM.mjs} +2 -2
- package/dist/{S3StateStore-D3x6ivBL.mjs.map → S3StateStore-BFHuctWM.mjs.map} +1 -1
- package/dist/{S3StateStore-Ds0V1Lgm.cjs → S3StateStore-CoXBhILd.cjs} +2 -2
- package/dist/{S3StateStore-Ds0V1Lgm.cjs.map → S3StateStore-CoXBhILd.cjs.map} +1 -1
- package/dist/{SSMStateStore-CLipvx47.cjs → SSMStateStore-BqCA9TP2.cjs} +2 -2
- package/dist/{SSMStateStore-CLipvx47.cjs.map → SSMStateStore-BqCA9TP2.cjs.map} +1 -1
- package/dist/{SSMStateStore-DfF174xQ.mjs → SSMStateStore-C1kvHScY.mjs} +2 -2
- package/dist/{SSMStateStore-DfF174xQ.mjs.map → SSMStateStore-C1kvHScY.mjs.map} +1 -1
- package/dist/{backup-provisioner-DIVCbfee.mjs → backup-provisioner-C8DnCmxK.mjs} +2 -2
- package/dist/backup-provisioner-C8DnCmxK.mjs.map +1 -0
- package/dist/{backup-provisioner-BmhIO_Yr.cjs → backup-provisioner-oDEsPj02.cjs} +2 -2
- package/dist/backup-provisioner-oDEsPj02.cjs.map +1 -0
- package/dist/{bundler-DYgm9xsd.mjs → bundler-Dvaiud0h.mjs} +4 -4
- package/dist/{bundler-DYgm9xsd.mjs.map → bundler-Dvaiud0h.mjs.map} +1 -1
- package/dist/{bundler-C8uJf324.cjs → bundler-orRqUJNZ.cjs} +4 -4
- package/dist/{bundler-C8uJf324.cjs.map → bundler-orRqUJNZ.cjs.map} +1 -1
- package/dist/{cli-Bogz-zEj.mjs → cli-CfHm0AO7.mjs} +7 -6
- package/dist/cli-CfHm0AO7.mjs.map +1 -0
- package/dist/{cli-D3jofJ6j.cjs → cli-D1JkkOwg.cjs} +8 -7
- package/dist/cli-D1JkkOwg.cjs.map +1 -0
- package/dist/compose-CNQL8BnO.mjs +1398 -0
- package/dist/compose-CNQL8BnO.mjs.map +1 -0
- package/dist/compose-jrLy7KGO.cjs +1427 -0
- package/dist/compose-jrLy7KGO.cjs.map +1 -0
- package/dist/{config-Be-4_I6H.d.cts → config-4XFxX1al.d.cts} +3 -3
- package/dist/{config-Be-4_I6H.d.cts.map → config-4XFxX1al.d.cts.map} +1 -1
- package/dist/{config-DqNRiQOp.d.mts → config-UtKzhpFb.d.mts} +3 -3
- package/dist/{config-DqNRiQOp.d.mts.map → config-UtKzhpFb.d.mts.map} +1 -1
- package/dist/config.cjs +3 -3
- package/dist/config.d.cts +3 -3
- package/dist/config.d.mts +3 -3
- package/dist/config.mjs +3 -3
- package/dist/{credentials-B70vmLcm.cjs → credentials-Bzbg23n8.cjs} +176 -3
- package/dist/credentials-Bzbg23n8.cjs.map +1 -0
- package/dist/{credentials-s0wMeZBU.mjs → credentials-Dq1MnWQf.mjs} +128 -3
- package/dist/credentials-Dq1MnWQf.mjs.map +1 -0
- package/dist/databases-C2p5LGAp.cjs +419 -0
- package/dist/databases-C2p5LGAp.cjs.map +1 -0
- package/dist/databases-hUHTuwhx.mjs +346 -0
- package/dist/databases-hUHTuwhx.mjs.map +1 -0
- package/dist/{define-CYs0PEfZ.mjs → define-DR8xGYfL.mjs} +5 -1
- package/dist/define-DR8xGYfL.mjs.map +1 -0
- package/dist/{define-zUsP72ym.cjs → define-DzRz8FgH.cjs} +5 -1
- package/dist/define-DzRz8FgH.cjs.map +1 -0
- package/dist/deploy/public.cjs +35 -26
- package/dist/deploy/public.cjs.map +1 -1
- package/dist/deploy/public.d.cts +140 -20
- package/dist/deploy/public.d.cts.map +1 -1
- package/dist/deploy/public.d.mts +140 -20
- package/dist/deploy/public.d.mts.map +1 -1
- package/dist/deploy/public.mjs +13 -13
- package/dist/{terminal-Bdv--jhY.cjs → deploy-B7aJi_Ah.cjs} +146 -116
- package/dist/deploy-B7aJi_Ah.cjs.map +1 -0
- package/dist/{terminal-D7Ntz6WE.mjs → deploy-DWn7YfIc.mjs} +137 -89
- package/dist/deploy-DWn7YfIc.mjs.map +1 -0
- package/dist/{discover-LMrqsWSU.mjs → discover-BFoJWKGD.mjs} +4 -2
- package/dist/discover-BFoJWKGD.mjs.map +1 -0
- package/dist/{discover-DHdECueV.cjs → discover-OEfZ9OkA.cjs} +4 -2
- package/dist/discover-OEfZ9OkA.cjs.map +1 -0
- package/dist/dokploy-B4LcYG1E.cjs +38 -0
- package/dist/dokploy-B4LcYG1E.cjs.map +1 -0
- package/dist/dokploy-DalwLML5.mjs +38 -0
- package/dist/dokploy-DalwLML5.mjs.map +1 -0
- package/dist/{encryption-C9X98u2F.cjs → encryption-3KlGS1sl.cjs} +6 -8
- package/dist/{encryption-C9X98u2F.cjs.map → encryption-3KlGS1sl.cjs.map} +1 -1
- package/dist/{encryption-CKHjMpCr.mjs → encryption-CPfENwWO.mjs} +3 -5
- package/dist/{encryption-CKHjMpCr.mjs.map → encryption-CPfENwWO.mjs.map} +1 -1
- package/dist/{deploy-ZRXIPp1c.mjs → engine-CunvOG3s.mjs} +2116 -1855
- package/dist/engine-CunvOG3s.mjs.map +1 -0
- package/dist/{deploy-55rJkdKz.cjs → engine-DR6KurWX.cjs} +2172 -1869
- package/dist/engine-DR6KurWX.cjs.map +1 -0
- package/dist/{github-Dd3tMa1u.cjs → github-BJ5gh9bw.cjs} +7 -7
- package/dist/{github-Dd3tMa1u.cjs.map → github-BJ5gh9bw.cjs.map} +1 -1
- package/dist/{github-D-byNI7T.mjs → github-MXA9v7Ge.mjs} +3 -3
- package/dist/{github-D-byNI7T.mjs.map → github-MXA9v7Ge.mjs.map} +1 -1
- package/dist/{index-CfE9Y885.d.cts → index-CyApaHVe.d.cts} +16 -3
- package/dist/index-CyApaHVe.d.cts.map +1 -0
- package/dist/{index-CJ2qsSpp.d.mts → index-DD1FfUHh.d.mts} +16 -3
- package/dist/index-DD1FfUHh.d.mts.map +1 -0
- package/dist/index.cjs +465 -1691
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +405 -1619
- package/dist/index.mjs.map +1 -1
- package/dist/{local-C7wnrcYK.cjs → local-BdH4XN0E.cjs} +1 -178
- package/dist/local-BdH4XN0E.cjs.map +1 -0
- package/dist/{local-Cd8ZYQG3.mjs → local-CWR4Y_kW.mjs} +3 -126
- package/dist/local-CWR4Y_kW.mjs.map +1 -0
- package/dist/{migrate-B4gngTLe.mjs → migrate-BJBcm4sG.mjs} +7 -6
- package/dist/{migrate-B4gngTLe.mjs.map → migrate-BJBcm4sG.mjs.map} +1 -1
- package/dist/{migrate-tx9-P9uz.cjs → migrate-CF4J4GQS.cjs} +20 -19
- package/dist/{migrate-tx9-P9uz.cjs.map → migrate-CF4J4GQS.cjs.map} +1 -1
- package/dist/openapi.cjs +1 -1
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/openapi.mjs +1 -1
- package/dist/provider-B3Sv_a74.cjs +173 -0
- package/dist/provider-B3Sv_a74.cjs.map +1 -0
- package/dist/provider-C9_-45d5.cjs +56 -0
- package/dist/provider-C9_-45d5.cjs.map +1 -0
- package/dist/provider-D7rQzzzf.mjs +138 -0
- package/dist/provider-D7rQzzzf.mjs.map +1 -0
- package/dist/provider-q4lsYzYG.mjs +45 -0
- package/dist/provider-q4lsYzYG.mjs.map +1 -0
- package/dist/{ready-bQfu9GES.mjs → ready-2s3-CndS.mjs} +3 -3
- package/dist/{ready-bQfu9GES.mjs.map → ready-2s3-CndS.mjs.map} +1 -1
- package/dist/{ready-brEhh68f.cjs → ready-B6uHr4sm.cjs} +3 -3
- package/dist/{ready-brEhh68f.cjs.map → ready-B6uHr4sm.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-CVmch0wI.cjs +315 -0
- package/dist/resolve-CVmch0wI.cjs.map +1 -0
- package/dist/resolve-Db0tNlrW.mjs +262 -0
- package/dist/resolve-Db0tNlrW.mjs.map +1 -0
- package/dist/rollback-DvAo4OKS.mjs +133 -0
- package/dist/rollback-DvAo4OKS.mjs.map +1 -0
- package/dist/rollback-DxJK84hy.cjs +162 -0
- package/dist/rollback-DxJK84hy.cjs.map +1 -0
- package/dist/{runtime-CJEYK4Gz.d.mts → runtime-BRqFOvPM.d.cts} +2 -2
- package/dist/{runtime-CJEYK4Gz.d.mts.map → runtime-BRqFOvPM.d.cts.map} +1 -1
- package/dist/{runtime-Cjq1jlVX.d.cts → runtime-CEk5n4VH.d.mts} +2 -2
- package/dist/{runtime-Cjq1jlVX.d.cts.map → runtime-CEk5n4VH.d.mts.map} +1 -1
- package/dist/sandbox/discover-worker.cjs +1 -1
- package/dist/sandbox/discover-worker.mjs +1 -1
- package/dist/sandbox/migrate-worker.cjs +61 -0
- package/dist/sandbox/migrate-worker.cjs.map +1 -0
- package/dist/sandbox/migrate-worker.d.cts +1 -0
- package/dist/sandbox/migrate-worker.d.mts +1 -0
- package/dist/sandbox/migrate-worker.mjs +62 -0
- package/dist/sandbox/migrate-worker.mjs.map +1 -0
- package/dist/{schemas-D2LCF689.mjs → schemas-Cfhtpdo0.mjs} +10 -2
- package/dist/{schemas-Ckm_aBnF.cjs.map → schemas-Cfhtpdo0.mjs.map} +1 -1
- package/dist/{schemas-Ckm_aBnF.cjs → schemas-RUlKHu27.cjs} +15 -1
- package/dist/{schemas-D2LCF689.mjs.map → schemas-RUlKHu27.cjs.map} +1 -1
- package/dist/sniffer-5xmrkh2a.cjs +1742 -0
- package/dist/sniffer-5xmrkh2a.cjs.map +1 -0
- package/dist/sniffer-BAQhLEsI.mjs +1617 -0
- package/dist/sniffer-BAQhLEsI.mjs.map +1 -0
- package/dist/{sst-Cper040I.mjs → sst-B2HbrDR3.mjs} +4 -4
- package/dist/{sst-Cper040I.mjs.map → sst-B2HbrDR3.mjs.map} +1 -1
- package/dist/{sst-BdALKEas.cjs → sst-DcfsVH6x.cjs} +4 -4
- package/dist/{sst-BdALKEas.cjs.map → sst-DcfsVH6x.cjs.map} +1 -1
- package/dist/{sniffer-ByhbEW5A.cjs → state-Y6ATakMX.cjs} +1769 -2759
- package/dist/state-Y6ATakMX.cjs.map +1 -0
- package/dist/{sniffer-49KPbDgq.mjs → state-uuYqnRVO.mjs} +1763 -2764
- package/dist/state-uuYqnRVO.mjs.map +1 -0
- package/dist/target/public.cjs +6 -6
- package/dist/target/public.d.cts +2 -2
- package/dist/target/public.d.mts +2 -2
- package/dist/target/public.mjs +3 -3
- package/dist/terminal-CHy_MdjG.cjs +84 -0
- package/dist/terminal-CHy_MdjG.cjs.map +1 -0
- package/dist/terminal-DUhcZ1ND.mjs +84 -0
- package/dist/terminal-DUhcZ1ND.mjs.map +1 -0
- package/dist/{types-DkrDOMxw.d.cts → types-CSw2w69a.d.mts} +110 -20
- package/dist/types-CSw2w69a.d.mts.map +1 -0
- package/dist/{types-B_bEQvb9.d.mts → types-D5WIxRDP.d.cts} +111 -19
- package/dist/types-D5WIxRDP.d.cts.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-Bwit4q9O.mjs → workspace-Be6uBhlF.mjs} +36 -6
- package/dist/workspace-Be6uBhlF.mjs.map +1 -0
- package/dist/{workspace-BwgWJK7h.cjs → workspace-ON-bqiJz.cjs} +47 -5
- package/dist/workspace-ON-bqiJz.cjs.map +1 -0
- package/package.json +13 -13
- package/dist/HostingerProvider-Bx2lHHvu.cjs.map +0 -1
- package/dist/HostingerProvider-Cd4aS5Kj.mjs.map +0 -1
- package/dist/Route53Provider-Cvj40A8I.mjs.map +0 -1
- package/dist/Route53Provider-DVILBsvV.cjs.map +0 -1
- package/dist/backup-provisioner-BmhIO_Yr.cjs.map +0 -1
- package/dist/backup-provisioner-DIVCbfee.mjs.map +0 -1
- package/dist/cli-Bogz-zEj.mjs.map +0 -1
- package/dist/cli-D3jofJ6j.cjs.map +0 -1
- package/dist/credentials-B70vmLcm.cjs.map +0 -1
- package/dist/credentials-s0wMeZBU.mjs.map +0 -1
- package/dist/define-CYs0PEfZ.mjs.map +0 -1
- package/dist/define-zUsP72ym.cjs.map +0 -1
- package/dist/deploy-55rJkdKz.cjs.map +0 -1
- package/dist/deploy-Dc2LBJaK.mjs +0 -136
- package/dist/deploy-Dc2LBJaK.mjs.map +0 -1
- package/dist/deploy-ZRXIPp1c.mjs.map +0 -1
- package/dist/deploy-yUcR2OlW.cjs +0 -141
- package/dist/deploy-yUcR2OlW.cjs.map +0 -1
- package/dist/discover-DHdECueV.cjs.map +0 -1
- package/dist/discover-LMrqsWSU.mjs.map +0 -1
- package/dist/dokploy-CepdXuMl.mjs +0 -32
- package/dist/dokploy-CepdXuMl.mjs.map +0 -1
- package/dist/dokploy-D1d37rvM.cjs +0 -32
- package/dist/dokploy-D1d37rvM.cjs.map +0 -1
- package/dist/index-CJ2qsSpp.d.mts.map +0 -1
- package/dist/index-CfE9Y885.d.cts.map +0 -1
- package/dist/local-C7wnrcYK.cjs.map +0 -1
- package/dist/local-Cd8ZYQG3.mjs.map +0 -1
- package/dist/resolve-BXsNQjMR.cjs +0 -220
- package/dist/resolve-BXsNQjMR.cjs.map +0 -1
- package/dist/resolve-DZ2IJ0qY.mjs +0 -173
- package/dist/resolve-DZ2IJ0qY.mjs.map +0 -1
- package/dist/runtime-BGNDrHzh.cjs +0 -163
- package/dist/runtime-BGNDrHzh.cjs.map +0 -1
- package/dist/runtime-Cmn-FeYO.mjs +0 -134
- package/dist/runtime-Cmn-FeYO.mjs.map +0 -1
- package/dist/sniffer-49KPbDgq.mjs.map +0 -1
- package/dist/sniffer-ByhbEW5A.cjs.map +0 -1
- package/dist/storage-BzaUxmqe.mjs +0 -124
- package/dist/storage-BzaUxmqe.mjs.map +0 -1
- package/dist/storage-CSGoC3bK.cjs +0 -152
- package/dist/storage-CSGoC3bK.cjs.map +0 -1
- package/dist/store-BaaonhkJ.mjs +0 -415
- package/dist/store-BaaonhkJ.mjs.map +0 -1
- package/dist/store-D_2ByRtW.cjs +0 -510
- package/dist/store-D_2ByRtW.cjs.map +0 -1
- package/dist/terminal-Bdv--jhY.cjs.map +0 -1
- package/dist/terminal-D7Ntz6WE.mjs.map +0 -1
- package/dist/types-B_bEQvb9.d.mts.map +0 -1
- package/dist/types-DkrDOMxw.d.cts.map +0 -1
- package/dist/workspace-BwgWJK7h.cjs.map +0 -1
- package/dist/workspace-Bwit4q9O.mjs.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -1,24 +1,28 @@
|
|
|
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
|
|
5
|
-
const
|
|
6
|
-
const require_discover = require("./discover-
|
|
4
|
+
const require_local = require("./local-BdH4XN0E.cjs");
|
|
5
|
+
const require_credentials = require("./credentials-Bzbg23n8.cjs");
|
|
6
|
+
const require_discover = require("./discover-OEfZ9OkA.cjs");
|
|
7
7
|
const require_derive = require("./derive-DAbo49sm.cjs");
|
|
8
|
-
const require_workspace = require("./workspace-
|
|
8
|
+
const require_workspace = require("./workspace-ON-bqiJz.cjs");
|
|
9
9
|
const require_config = require("./config.cjs");
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
10
|
+
const require_state = require("./state-Y6ATakMX.cjs");
|
|
11
|
+
const require_OpenApiTsGenerator = require("./OpenApiTsGenerator-B2zrdyP4.cjs");
|
|
12
|
+
const require_sniffer = require("./sniffer-5xmrkh2a.cjs");
|
|
13
13
|
const require_openapi = require("./openapi.cjs");
|
|
14
|
-
const require_fakes = require("./fakes-zW-jNAbQ.cjs");
|
|
15
14
|
const require_appPorts = require("./appPorts-Cu79hJF6.cjs");
|
|
16
|
-
const require_ready = require("./ready-
|
|
15
|
+
const require_ready = require("./ready-B6uHr4sm.cjs");
|
|
17
16
|
const require_emit = require("./emit-Dmc1nvdS.cjs");
|
|
18
|
-
const
|
|
17
|
+
const require_deploy = require("./deploy-B7aJi_Ah.cjs");
|
|
18
|
+
const require_compose = require("./compose-jrLy7KGO.cjs");
|
|
19
|
+
const require_LocalStateStore = require("./LocalStateStore-BVUexSvu.cjs");
|
|
19
20
|
const require_utils = require("./utils-D2Nk1Nv2.cjs");
|
|
20
21
|
let node_path = require("node:path");
|
|
21
22
|
let commander = require("commander");
|
|
23
|
+
let node_process = require("node:process");
|
|
24
|
+
let node_readline_promises = require("node:readline/promises");
|
|
25
|
+
node_readline_promises = require_rolldown_runtime.__toESM(node_readline_promises, 1);
|
|
22
26
|
let node_fs = require("node:fs");
|
|
23
27
|
let node_fs_promises = require("node:fs/promises");
|
|
24
28
|
let node_os = require("node:os");
|
|
@@ -33,8 +37,6 @@ let chokidar = require("chokidar");
|
|
|
33
37
|
chokidar = require_rolldown_runtime.__toESM(chokidar, 1);
|
|
34
38
|
let node_net = require("node:net");
|
|
35
39
|
let node_crypto = require("node:crypto");
|
|
36
|
-
let pg = require("pg");
|
|
37
|
-
pg = require_rolldown_runtime.__toESM(pg, 1);
|
|
38
40
|
let yaml = require("yaml");
|
|
39
41
|
let prompts = require("prompts");
|
|
40
42
|
prompts = require_rolldown_runtime.__toESM(prompts, 1);
|
|
@@ -50,7 +52,192 @@ let _geekmidas_constructs_functions = require("@geekmidas/constructs/functions")
|
|
|
50
52
|
let _geekmidas_constructs_queue = require("@geekmidas/constructs/queue");
|
|
51
53
|
let _geekmidas_constructs_topic = require("@geekmidas/constructs/topic");
|
|
52
54
|
//#region package.json
|
|
53
|
-
var version = "10.0.0-alpha.
|
|
55
|
+
var version = "10.0.0-alpha.66";
|
|
56
|
+
//#endregion
|
|
57
|
+
//#region src/prompt.ts
|
|
58
|
+
/**
|
|
59
|
+
* Asking the person at the terminal.
|
|
60
|
+
*
|
|
61
|
+
* CLI layer only: a command's action may prompt, nothing it calls may. A
|
|
62
|
+
* library call (`deploy()`) that needs something it was not given raises a
|
|
63
|
+
* named error instead, and the command decides whether to ask.
|
|
64
|
+
*/
|
|
65
|
+
/** A prompt was needed with no terminal to ask at. */
|
|
66
|
+
var PromptNeedsTerminal = class extends Error {
|
|
67
|
+
question;
|
|
68
|
+
instead;
|
|
69
|
+
constructor(question, instead) {
|
|
70
|
+
super(`Interactive input required. ${instead}`);
|
|
71
|
+
this.question = question;
|
|
72
|
+
this.instead = instead;
|
|
73
|
+
this.name = "PromptNeedsTerminal";
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
/** Whether there is a person at a terminal to ask. */
|
|
77
|
+
function canPrompt() {
|
|
78
|
+
return process.stdin.isTTY === true;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Ask `message` and resolve with the line typed — without echoing it when
|
|
82
|
+
* `hidden`. Ctrl+C at a hidden prompt exits, as it would anywhere else in a
|
|
83
|
+
* terminal program: raw mode has taken the signal away from the shell.
|
|
84
|
+
*/
|
|
85
|
+
async function prompt(message, options) {
|
|
86
|
+
if (!canPrompt()) throw new PromptNeedsTerminal(message, options.instead);
|
|
87
|
+
if (options.hidden) {
|
|
88
|
+
process.stdout.write(message);
|
|
89
|
+
return new Promise((resolve, reject) => {
|
|
90
|
+
let value = "";
|
|
91
|
+
const cleanup = () => {
|
|
92
|
+
process.stdin.setRawMode(false);
|
|
93
|
+
process.stdin.pause();
|
|
94
|
+
process.stdin.removeListener("data", onData);
|
|
95
|
+
process.stdin.removeListener("error", onError);
|
|
96
|
+
};
|
|
97
|
+
const onError = (err) => {
|
|
98
|
+
cleanup();
|
|
99
|
+
reject(err);
|
|
100
|
+
};
|
|
101
|
+
const onData = (char) => {
|
|
102
|
+
const c = char.toString();
|
|
103
|
+
if (c === "\n" || c === "\r") {
|
|
104
|
+
cleanup();
|
|
105
|
+
process.stdout.write("\n");
|
|
106
|
+
resolve(value);
|
|
107
|
+
} else if (c === "") {
|
|
108
|
+
cleanup();
|
|
109
|
+
process.stdout.write("\n");
|
|
110
|
+
process.exit(1);
|
|
111
|
+
} else if (c === "" || c === "\b") {
|
|
112
|
+
if (value.length > 0) value = value.slice(0, -1);
|
|
113
|
+
} else value += c;
|
|
114
|
+
};
|
|
115
|
+
process.stdin.setRawMode(true);
|
|
116
|
+
process.stdin.resume();
|
|
117
|
+
process.stdin.on("data", onData);
|
|
118
|
+
process.stdin.on("error", onError);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
const rl = node_readline_promises.createInterface({
|
|
122
|
+
input: node_process.stdin,
|
|
123
|
+
output: node_process.stdout
|
|
124
|
+
});
|
|
125
|
+
try {
|
|
126
|
+
return await rl.question(message);
|
|
127
|
+
} finally {
|
|
128
|
+
rl.close();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
//#endregion
|
|
132
|
+
//#region src/auth/index.ts
|
|
133
|
+
const logger$8 = console;
|
|
134
|
+
/**
|
|
135
|
+
* Validate Dokploy token by making a test API call
|
|
136
|
+
*/
|
|
137
|
+
async function validateDokployToken(endpoint, token) {
|
|
138
|
+
const { DokployApi } = await Promise.resolve().then(() => require("./sniffer-5xmrkh2a.cjs")).then((n) => n.dokploy_api_exports);
|
|
139
|
+
return new DokployApi({
|
|
140
|
+
baseUrl: endpoint,
|
|
141
|
+
token
|
|
142
|
+
}).validateToken();
|
|
143
|
+
}
|
|
144
|
+
/** What answers a login prompt without a terminal. */
|
|
145
|
+
const LOGIN_INSTEAD = "Please provide --token option.";
|
|
146
|
+
/**
|
|
147
|
+
* Login to a service
|
|
148
|
+
*/
|
|
149
|
+
async function loginCommand(options) {
|
|
150
|
+
const { provider, token: providedToken, endpoint: providedEndpoint } = options;
|
|
151
|
+
if (provider === "dokploy") {
|
|
152
|
+
logger$8.log("\n🔐 Logging in to Dokploy...\n");
|
|
153
|
+
let endpoint = providedEndpoint;
|
|
154
|
+
if (!endpoint) endpoint = await prompt("Dokploy URL (e.g., https://dokploy.example.com): ", { instead: LOGIN_INSTEAD });
|
|
155
|
+
endpoint = endpoint.replace(/\/$/, "");
|
|
156
|
+
try {
|
|
157
|
+
new URL(endpoint);
|
|
158
|
+
} catch {
|
|
159
|
+
logger$8.error("Invalid URL format");
|
|
160
|
+
process.exit(1);
|
|
161
|
+
}
|
|
162
|
+
let token = providedToken;
|
|
163
|
+
if (!token) {
|
|
164
|
+
logger$8.log(`\nGenerate a token at: ${endpoint}/settings/profile\n`);
|
|
165
|
+
token = await prompt("API Token: ", {
|
|
166
|
+
hidden: true,
|
|
167
|
+
instead: LOGIN_INSTEAD
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
if (!token) {
|
|
171
|
+
logger$8.error("Token is required");
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
logger$8.log("\nValidating credentials...");
|
|
175
|
+
if (!await validateDokployToken(endpoint, token)) {
|
|
176
|
+
logger$8.error("\n✗ Invalid credentials. Please check your token and try again.");
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
await require_credentials.storeDokployCredentials(token, endpoint);
|
|
180
|
+
logger$8.log("\n✓ Successfully logged in to Dokploy!");
|
|
181
|
+
logger$8.log(` Endpoint: ${endpoint}`);
|
|
182
|
+
logger$8.log(` Credentials stored in: ${require_credentials.getCredentialsPath()}`);
|
|
183
|
+
logger$8.log("\nYou can now use deploy commands without setting DOKPLOY_API_TOKEN.");
|
|
184
|
+
}
|
|
185
|
+
if (provider === "hostinger") {
|
|
186
|
+
logger$8.log("\n🔐 Logging in to Hostinger...\n");
|
|
187
|
+
let token = providedToken;
|
|
188
|
+
if (!token) {
|
|
189
|
+
logger$8.log("\nGenerate a token at: https://hpanel.hostinger.com/profile/api\n");
|
|
190
|
+
token = await prompt("API Token: ", {
|
|
191
|
+
hidden: true,
|
|
192
|
+
instead: LOGIN_INSTEAD
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
if (!token) {
|
|
196
|
+
logger$8.error("Token is required");
|
|
197
|
+
process.exit(1);
|
|
198
|
+
}
|
|
199
|
+
await require_credentials.storeHostingerToken(token);
|
|
200
|
+
logger$8.log("\n✓ Hostinger token stored.");
|
|
201
|
+
logger$8.log(` Credentials stored in: ${require_credentials.getCredentialsPath()}`);
|
|
202
|
+
logger$8.log(" Not validated here: every DNS endpoint is scoped to a domain, so the\n first deploy that touches DNS is where a bad token reports itself.");
|
|
203
|
+
logger$8.log("\nYou can now use it as a DNS provider without setting HOSTINGER_API_TOKEN.");
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Logout from a service
|
|
208
|
+
*/
|
|
209
|
+
async function logoutCommand(options) {
|
|
210
|
+
const { provider = "dokploy" } = options;
|
|
211
|
+
if (provider === "all") {
|
|
212
|
+
if (await require_credentials.removeDokployCredentials()) logger$8.log("\n✓ Logged out from all services");
|
|
213
|
+
else logger$8.log("\nNo stored credentials found");
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (provider === "dokploy") {
|
|
217
|
+
if (await require_credentials.removeDokployCredentials()) logger$8.log("\n✓ Logged out from Dokploy");
|
|
218
|
+
else logger$8.log("\nNo Dokploy credentials found");
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Show current login status
|
|
223
|
+
*/
|
|
224
|
+
async function whoamiCommand() {
|
|
225
|
+
logger$8.log("\n📋 Current credentials:\n");
|
|
226
|
+
const dokploy = await require_credentials.getDokployCredentials();
|
|
227
|
+
if (dokploy) {
|
|
228
|
+
logger$8.log(" Dokploy:");
|
|
229
|
+
logger$8.log(` Endpoint: ${dokploy.endpoint}`);
|
|
230
|
+
logger$8.log(` Token: ${maskToken(dokploy.token)}`);
|
|
231
|
+
} else logger$8.log(" Dokploy: Not logged in");
|
|
232
|
+
logger$8.log(`\n Credentials file: ${require_credentials.getCredentialsPath()}`);
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Mask a token for display
|
|
236
|
+
*/
|
|
237
|
+
function maskToken(token) {
|
|
238
|
+
if (token.length <= 8) return "****";
|
|
239
|
+
return `${token.slice(0, 4)}...${token.slice(-4)}`;
|
|
240
|
+
}
|
|
54
241
|
//#endregion
|
|
55
242
|
//#region src/trust/index.ts
|
|
56
243
|
/**
|
|
@@ -67,7 +254,7 @@ var version = "10.0.0-alpha.64";
|
|
|
67
254
|
* worse trade than one that tells you what to type. Caddy's own `caddy trust`
|
|
68
255
|
* draws the line in the same place.
|
|
69
256
|
*/
|
|
70
|
-
const logger$
|
|
257
|
+
const logger$7 = console;
|
|
71
258
|
const run = (0, node_util.promisify)(node_child_process.execFile);
|
|
72
259
|
/**
|
|
73
260
|
* How each platform is told about a new authority.
|
|
@@ -116,18 +303,18 @@ function installation(platform, certificate) {
|
|
|
116
303
|
*/
|
|
117
304
|
async function trustCommand(options = {}) {
|
|
118
305
|
const { workspace } = await require_config.loadWorkspaceConfig();
|
|
119
|
-
const certificate = (0, node_path.join)(workspace.root,
|
|
306
|
+
const certificate = (0, node_path.join)(workspace.root, require_state.LOCAL_CA_PATH);
|
|
120
307
|
if (!(0, node_fs.existsSync)(certificate)) throw new NoLocalAuthority(certificate);
|
|
121
308
|
const { describe, asks, steps } = installation(process.platform, certificate);
|
|
122
|
-
logger$
|
|
123
|
-
logger$
|
|
309
|
+
logger$7.log(`\n🔐 Trusting the local authority in ${describe}`);
|
|
310
|
+
logger$7.log(` ${certificate}\n`);
|
|
124
311
|
if (options.dryRun) {
|
|
125
|
-
for (const [command, args] of steps) logger$
|
|
312
|
+
for (const [command, args] of steps) logger$7.log(` ${command} ${args.join(" ")}`);
|
|
126
313
|
return;
|
|
127
314
|
}
|
|
128
|
-
logger$
|
|
315
|
+
logger$7.log(` ${asks}\n`);
|
|
129
316
|
for (const [command, args] of steps) await run(command, args, { stdio: "inherit" });
|
|
130
|
-
logger$
|
|
317
|
+
logger$7.log("\n✅ Local https addresses are trusted. Restart your browser.\n");
|
|
131
318
|
}
|
|
132
319
|
/**
|
|
133
320
|
* Trust the authority if this machine lacks it and its owner agrees.
|
|
@@ -144,7 +331,7 @@ async function ensureTrusted(root, url, options = {}) {
|
|
|
144
331
|
if (await isTrusted(url)) return;
|
|
145
332
|
if (!options.assumeYes) {
|
|
146
333
|
if (!process.stdin.isTTY) {
|
|
147
|
-
logger$
|
|
334
|
+
logger$7.log(" Local https addresses are not trusted here. Run \"gkm trust\", or \"gkm setup --yes\".");
|
|
148
335
|
return;
|
|
149
336
|
}
|
|
150
337
|
if (await declined(root)) return;
|
|
@@ -156,7 +343,7 @@ async function ensureTrusted(root, url, options = {}) {
|
|
|
156
343
|
});
|
|
157
344
|
if (!install) {
|
|
158
345
|
await recordDeclined(root);
|
|
159
|
-
logger$
|
|
346
|
+
logger$7.log(" Skipped. Run \"gkm trust\" later.");
|
|
160
347
|
return;
|
|
161
348
|
}
|
|
162
349
|
}
|
|
@@ -1300,7 +1487,7 @@ async function serveFakes(fakes, ports) {
|
|
|
1300
1487
|
const served = [];
|
|
1301
1488
|
for (const [id, local] of Object.entries(fakes)) {
|
|
1302
1489
|
if (local.image) continue;
|
|
1303
|
-
const port = ports[
|
|
1490
|
+
const port = ports[require_state.fakeKey(id)];
|
|
1304
1491
|
if (port === void 0) continue;
|
|
1305
1492
|
const { handler } = (await import((0, node_url.pathToFileURL)(local.file).href)).default;
|
|
1306
1493
|
const server = serve({
|
|
@@ -1321,7 +1508,7 @@ function closeFakes(served) {
|
|
|
1321
1508
|
}
|
|
1322
1509
|
//#endregion
|
|
1323
1510
|
//#region src/exec/index.ts
|
|
1324
|
-
const logger$
|
|
1511
|
+
const logger$6 = console;
|
|
1325
1512
|
/** `gkm exec` was given nothing to run. */
|
|
1326
1513
|
var NoCommandSpecified = class extends Error {
|
|
1327
1514
|
constructor() {
|
|
@@ -1343,18 +1530,18 @@ var NoCommandSpecified = class extends Error {
|
|
|
1343
1530
|
async function execCommand(commandArgs, options = {}) {
|
|
1344
1531
|
const cwd = options.cwd ?? process.cwd();
|
|
1345
1532
|
if (!commandArgs[0]) throw new NoCommandSpecified();
|
|
1346
|
-
const defaultEnv =
|
|
1347
|
-
if (defaultEnv.loaded.length > 0) logger$
|
|
1348
|
-
const { credentials, secretsJsonPath, appName } = await
|
|
1349
|
-
if (appName) logger$
|
|
1533
|
+
const defaultEnv = require_state.loadEnvFiles(".env");
|
|
1534
|
+
if (defaultEnv.loaded.length > 0) logger$6.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
1535
|
+
const { credentials, secretsJsonPath, appName } = await require_state.prepareEntryCredentials({ cwd });
|
|
1536
|
+
if (appName) logger$6.log(`📦 App: ${appName}`);
|
|
1350
1537
|
const secretCount = Object.keys(credentials).filter((k) => k !== "PORT").length;
|
|
1351
|
-
if (secretCount > 0) logger$
|
|
1538
|
+
if (secretCount > 0) logger$6.log(`🔐 Loaded ${secretCount} secret(s)`);
|
|
1352
1539
|
const preloadDir = (0, node_path.join)(cwd, ".gkm");
|
|
1353
1540
|
await (0, node_fs_promises.mkdir)(preloadDir, { recursive: true });
|
|
1354
1541
|
const preloadPath = (0, node_path.join)(preloadDir, "credentials-preload.mjs");
|
|
1355
|
-
await
|
|
1542
|
+
await require_state.createCredentialsPreload(preloadPath, secretsJsonPath);
|
|
1356
1543
|
const [cmd, ...args] = commandArgs;
|
|
1357
|
-
logger$
|
|
1544
|
+
logger$6.log(`🚀 Running: ${[cmd, ...args].join(" ")}`);
|
|
1358
1545
|
const nodeOptions = [(process.env.NODE_OPTIONS ?? "").replace(/--import[= ]\S+/g, "").trim(), `--import=${preloadPath}`].filter(Boolean).join(" ");
|
|
1359
1546
|
const child = (0, node_child_process.spawn)(cmd, args, {
|
|
1360
1547
|
cwd,
|
|
@@ -1368,7 +1555,7 @@ async function execCommand(commandArgs, options = {}) {
|
|
|
1368
1555
|
const exitCode = await new Promise((resolve) => {
|
|
1369
1556
|
child.on("close", (code) => resolve(code ?? 0));
|
|
1370
1557
|
child.on("error", (error) => {
|
|
1371
|
-
logger$
|
|
1558
|
+
logger$6.error(`Failed to run command: ${error.message}`);
|
|
1372
1559
|
resolve(1);
|
|
1373
1560
|
});
|
|
1374
1561
|
});
|
|
@@ -1376,7 +1563,7 @@ async function execCommand(commandArgs, options = {}) {
|
|
|
1376
1563
|
}
|
|
1377
1564
|
//#endregion
|
|
1378
1565
|
//#region src/dev/index.ts
|
|
1379
|
-
const logger$
|
|
1566
|
+
const logger$5 = console;
|
|
1380
1567
|
/**
|
|
1381
1568
|
* Normalize telescope configuration
|
|
1382
1569
|
* @internal Exported for testing
|
|
@@ -1445,14 +1632,14 @@ const SUBSCRIBERS_ENV = "GKM_SUBSCRIBERS";
|
|
|
1445
1632
|
async function devCommand(options) {
|
|
1446
1633
|
if (options.subscribers === false) process.env[SUBSCRIBERS_ENV] = "off";
|
|
1447
1634
|
if (options.entry) return entryDevCommand(options);
|
|
1448
|
-
const defaultEnv =
|
|
1449
|
-
if (defaultEnv.loaded.length > 0) logger$
|
|
1635
|
+
const defaultEnv = require_state.loadEnvFiles(".env");
|
|
1636
|
+
if (defaultEnv.loaded.length > 0) logger$5.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
1450
1637
|
const appConfig = await require_config.loadAppConfig().catch((error) => {
|
|
1451
1638
|
if (error instanceof require_config.NotInAnApp || error instanceof require_config.NotABackendApp) return;
|
|
1452
1639
|
throw error;
|
|
1453
1640
|
});
|
|
1454
1641
|
if (!appConfig) {
|
|
1455
|
-
logger$
|
|
1642
|
+
logger$5.log("🔎 Reading gkm.config.ts and the constructs");
|
|
1456
1643
|
const { workspace: everything } = await require_config.loadWorkspaceConfig();
|
|
1457
1644
|
return workspaceDevCommand(everything, options);
|
|
1458
1645
|
}
|
|
@@ -1461,16 +1648,16 @@ async function devCommand(options) {
|
|
|
1461
1648
|
const secretsRoot = appConfig.workspaceRoot;
|
|
1462
1649
|
const workspaceAppName = appConfig.appName;
|
|
1463
1650
|
const assigned = await require_appPorts.assignAppPorts(appConfig.workspace, [workspaceAppName], {
|
|
1464
|
-
free:
|
|
1651
|
+
free: require_state.isPortAvailable,
|
|
1465
1652
|
holder: require_appPorts.holderOf
|
|
1466
1653
|
});
|
|
1467
1654
|
if (assigned.running.length > 0) throw new WorkspacePortsInUse(assigned.running);
|
|
1468
|
-
for (const { app, from, to, holder } of assigned.moved) logger$
|
|
1655
|
+
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}`);
|
|
1469
1656
|
const workspace = require_appPorts.withAppPorts(appConfig.workspace, assigned.ports);
|
|
1470
1657
|
const workspaceAppPort = assigned.ports[workspaceAppName] ?? appConfig.app.port;
|
|
1471
1658
|
process.env[require_appPorts.APP_TAG_ENV] = require_appPorts.appTag(appConfig.workspaceRoot, workspaceAppName);
|
|
1472
1659
|
if (appConfig.app.entry) {
|
|
1473
|
-
logger$
|
|
1660
|
+
logger$5.log(`📄 Using entry point: ${appConfig.app.entry}`);
|
|
1474
1661
|
return entryDevCommand({
|
|
1475
1662
|
...options,
|
|
1476
1663
|
entry: appConfig.app.entry,
|
|
@@ -1479,9 +1666,9 @@ async function devCommand(options) {
|
|
|
1479
1666
|
});
|
|
1480
1667
|
}
|
|
1481
1668
|
if (config.env) {
|
|
1482
|
-
const { loaded, missing } =
|
|
1483
|
-
if (loaded.length > 0) logger$
|
|
1484
|
-
if (missing.length > 0) logger$
|
|
1669
|
+
const { loaded, missing } = require_state.loadEnvFiles(config.env, appRoot);
|
|
1670
|
+
if (loaded.length > 0) logger$5.log(`📦 Loaded env: ${loaded.join(", ")}`);
|
|
1671
|
+
if (missing.length > 0) logger$5.warn(`⚠️ Missing env files: ${missing.join(", ")}`);
|
|
1485
1672
|
}
|
|
1486
1673
|
const telescope = normalizeTelescopeConfig(config.telescope);
|
|
1487
1674
|
const hooks = normalizeHooksConfig(config.hooks, appRoot);
|
|
@@ -1492,7 +1679,7 @@ async function devCommand(options) {
|
|
|
1492
1679
|
appRoot,
|
|
1493
1680
|
target: "server",
|
|
1494
1681
|
enableOpenApi,
|
|
1495
|
-
cacheBackend:
|
|
1682
|
+
cacheBackend: require_state.cacheBackendFor(require_state.providerOf(workspace ?? config)),
|
|
1496
1683
|
telescope,
|
|
1497
1684
|
databaseApi: true,
|
|
1498
1685
|
hooks,
|
|
@@ -1500,22 +1687,22 @@ async function devCommand(options) {
|
|
|
1500
1687
|
bustCache,
|
|
1501
1688
|
serveEmpty: true
|
|
1502
1689
|
}));
|
|
1503
|
-
logger$
|
|
1690
|
+
logger$5.log(`🔨 Building ${workspaceAppName ?? "the app"}`);
|
|
1504
1691
|
const initial = await build();
|
|
1505
1692
|
const runtime = config.runtime ?? "node";
|
|
1506
1693
|
let secretsJsonPath;
|
|
1507
1694
|
let publicUrl;
|
|
1508
1695
|
let manifest;
|
|
1509
|
-
const appSecrets = await
|
|
1696
|
+
const appSecrets = await require_state.loadSecretsForApp(workspace ? await require_state.secretsStoreFor(workspace, config.stages.local) : new require_state.FileSecretsStore(secretsRoot), config.stages.local, workspaceAppName);
|
|
1510
1697
|
if (workspace) {
|
|
1511
|
-
const reconciled = await
|
|
1698
|
+
const reconciled = await require_state.reconcileWorkspace(workspace, {
|
|
1512
1699
|
stage: workspace.stages.local,
|
|
1513
1700
|
...options.fake ? { fake: true } : {},
|
|
1514
|
-
progress: (message) => logger$
|
|
1701
|
+
progress: (message) => logger$5.log(message)
|
|
1515
1702
|
});
|
|
1516
1703
|
if (reconciled.services.length > 0) {
|
|
1517
|
-
logger$
|
|
1518
|
-
for (const line of
|
|
1704
|
+
logger$5.log("🐳 Services");
|
|
1705
|
+
for (const line of require_state.describeServices(reconciled.services)) logger$5.log(line);
|
|
1519
1706
|
}
|
|
1520
1707
|
Object.assign(appSecrets, reconciled.env);
|
|
1521
1708
|
const own = reconciled.plan.resources.find((r) => (r.kind === "rest-api" || r.kind === "site") && require_derive.appKey(r.id) === workspaceAppName);
|
|
@@ -1582,11 +1769,11 @@ async function devCommand(options) {
|
|
|
1582
1769
|
cwd: appRoot
|
|
1583
1770
|
});
|
|
1584
1771
|
watcher.on("error", (error) => {
|
|
1585
|
-
logger$
|
|
1772
|
+
logger$5.error("❌ Watcher error:", error);
|
|
1586
1773
|
});
|
|
1587
1774
|
let rebuildTimeout = null;
|
|
1588
1775
|
watcher.on("change", async (path) => {
|
|
1589
|
-
logger$
|
|
1776
|
+
logger$5.log(`🔄 ${path} changed — rebuilding`);
|
|
1590
1777
|
if (rebuildTimeout) clearTimeout(rebuildTimeout);
|
|
1591
1778
|
rebuildTimeout = setTimeout(async () => {
|
|
1592
1779
|
try {
|
|
@@ -1595,8 +1782,8 @@ async function devCommand(options) {
|
|
|
1595
1782
|
await devServer.restart();
|
|
1596
1783
|
} catch (error) {
|
|
1597
1784
|
const err = error;
|
|
1598
|
-
logger$
|
|
1599
|
-
if (err.stack) logger$
|
|
1785
|
+
logger$5.error(`❌ Rebuild failed: ${err.message}`);
|
|
1786
|
+
if (err.stack) logger$5.error(err.stack);
|
|
1600
1787
|
}
|
|
1601
1788
|
}, 300);
|
|
1602
1789
|
});
|
|
@@ -1604,13 +1791,13 @@ async function devCommand(options) {
|
|
|
1604
1791
|
const shutdown = () => {
|
|
1605
1792
|
if (isShuttingDown) return;
|
|
1606
1793
|
isShuttingDown = true;
|
|
1607
|
-
logger$
|
|
1794
|
+
logger$5.log("\n🛑 Shutting down...");
|
|
1608
1795
|
Promise.all([
|
|
1609
1796
|
watcher.close(),
|
|
1610
1797
|
devServer.stop(),
|
|
1611
1798
|
discovery.leave()
|
|
1612
1799
|
]).catch((err) => {
|
|
1613
|
-
logger$
|
|
1800
|
+
logger$5.error("Error during shutdown:", err);
|
|
1614
1801
|
}).finally(() => {
|
|
1615
1802
|
process.exit(0);
|
|
1616
1803
|
});
|
|
@@ -1794,8 +1981,8 @@ async function validateFrontendApps(workspace) {
|
|
|
1794
1981
|
async function loadDevSecrets(workspace) {
|
|
1795
1982
|
if (!workspace.secrets.enabled) return {};
|
|
1796
1983
|
const stage = workspace.stages.local;
|
|
1797
|
-
const secrets = await (await
|
|
1798
|
-
if (secrets) return
|
|
1984
|
+
const secrets = await (await require_state.secretsStoreFor(workspace, stage)).read(stage);
|
|
1985
|
+
if (secrets) return require_state.toEmbeddableSecrets(secrets);
|
|
1799
1986
|
return {};
|
|
1800
1987
|
}
|
|
1801
1988
|
/**
|
|
@@ -1810,17 +1997,17 @@ async function loadDevSecrets(workspace) {
|
|
|
1810
1997
|
async function workspaceDevCommand(configured, options) {
|
|
1811
1998
|
const scoped = options.app ? [options.app] : options.filter ? [] : Object.keys(configured.apps);
|
|
1812
1999
|
const assigned = await require_appPorts.assignAppPorts(configured, scoped, {
|
|
1813
|
-
free:
|
|
2000
|
+
free: require_state.isPortAvailable,
|
|
1814
2001
|
holder: require_appPorts.holderOf
|
|
1815
2002
|
});
|
|
1816
2003
|
if (assigned.running.length > 0) throw new WorkspacePortsInUse(assigned.running);
|
|
1817
|
-
for (const { app, from, to, holder } of assigned.moved) logger$
|
|
2004
|
+
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}`);
|
|
1818
2005
|
const workspace = require_appPorts.withAppPorts(configured, assigned.ports);
|
|
1819
2006
|
const appCount = Object.keys(workspace.apps).length;
|
|
1820
2007
|
const frontendApps = Object.entries(workspace.apps).filter(([_, app]) => app.type === "web");
|
|
1821
2008
|
const conflicts = checkPortConflicts(workspace);
|
|
1822
2009
|
if (conflicts.length > 0) {
|
|
1823
|
-
for (const conflict of conflicts) logger$
|
|
2010
|
+
for (const conflict of conflicts) logger$5.error(`❌ Port conflict: Apps "${conflict.app1}" and "${conflict.app2}" both use port ${conflict.port}`);
|
|
1824
2011
|
throw new Error("Port conflicts detected. Please assign unique ports to each app.");
|
|
1825
2012
|
}
|
|
1826
2013
|
if (frontendApps.length > 0) {
|
|
@@ -1829,35 +2016,35 @@ async function workspaceDevCommand(configured, options) {
|
|
|
1829
2016
|
for (const result of validationResults) {
|
|
1830
2017
|
if (!result.valid) {
|
|
1831
2018
|
hasErrors = true;
|
|
1832
|
-
logger$
|
|
1833
|
-
for (const error of result.errors) logger$
|
|
2019
|
+
logger$5.error(`\n❌ Frontend app "${result.appName}" validation failed:`);
|
|
2020
|
+
for (const error of result.errors) logger$5.error(` • ${error}`);
|
|
1834
2021
|
}
|
|
1835
|
-
for (const warning of result.warnings) logger$
|
|
2022
|
+
for (const warning of result.warnings) logger$5.warn(` ⚠️ ${result.appName}: ${warning}`);
|
|
1836
2023
|
}
|
|
1837
2024
|
if (hasErrors) throw new Error("Frontend app validation failed. Fix the issues above and try again.");
|
|
1838
2025
|
}
|
|
1839
2026
|
const rawSecrets = await loadDevSecrets(workspace);
|
|
1840
|
-
const reconciled = await
|
|
2027
|
+
const reconciled = await require_state.reconcileWorkspace(workspace, {
|
|
1841
2028
|
stage: workspace.stages.local,
|
|
1842
2029
|
...options.fake ? { fake: true } : {},
|
|
1843
|
-
progress: (message) => logger$
|
|
2030
|
+
progress: (message) => logger$5.log(message)
|
|
1844
2031
|
});
|
|
1845
2032
|
if (reconciled.services.length > 0) {
|
|
1846
|
-
logger$
|
|
1847
|
-
for (const line of
|
|
2033
|
+
logger$5.log("🐳 Services");
|
|
2034
|
+
for (const line of require_state.describeServices(reconciled.services)) logger$5.log(line);
|
|
1848
2035
|
}
|
|
1849
2036
|
const secretsEnv = {
|
|
1850
2037
|
...rawSecrets,
|
|
1851
2038
|
...reconciled.env
|
|
1852
2039
|
};
|
|
1853
2040
|
const fakes = options.fake ? await serveFakes(reconciled.fakes, reconciled.ports) : [];
|
|
1854
|
-
for (const { id, port } of fakes) logger$
|
|
1855
|
-
logger$
|
|
2041
|
+
for (const { id, port } of fakes) logger$5.log(`🎭 ${id} fake: http://localhost:${port}`);
|
|
2042
|
+
logger$5.log(options.migrate || options.seed ? "🗄️ Migrating the databases" : "🗄️ Checking for pending migrations");
|
|
1856
2043
|
if (options.migrate || options.seed) {
|
|
1857
|
-
const { prepareDevDatabases } = await Promise.resolve().then(() => require("./migrate-
|
|
2044
|
+
const { prepareDevDatabases } = await Promise.resolve().then(() => require("./migrate-CF4J4GQS.cjs"));
|
|
1858
2045
|
await prepareDevDatabases(workspace, reconciled.env, { seed: !!options.seed });
|
|
1859
|
-
} else await Promise.resolve().then(() => require("./migrate-
|
|
1860
|
-
logger$
|
|
2046
|
+
} else await Promise.resolve().then(() => require("./migrate-CF4J4GQS.cjs")).then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
|
|
2047
|
+
logger$5.log(`⚠️ Could not check for pending migrations: ${error instanceof Error ? error.message : String(error)}`);
|
|
1861
2048
|
});
|
|
1862
2049
|
const appUrls = {};
|
|
1863
2050
|
for (const resource of reconciled.plan.resources) {
|
|
@@ -1878,11 +2065,11 @@ async function workspaceDevCommand(configured, options) {
|
|
|
1878
2065
|
else {
|
|
1879
2066
|
const { filters, unpackaged } = turboFilters(workspace);
|
|
1880
2067
|
turboFilter = filters.flatMap((name) => ["--filter", name]);
|
|
1881
|
-
if (unpackaged.length > 0) logger$
|
|
2068
|
+
if (unpackaged.length > 0) logger$5.warn(`⚠️ No package.json with a name for: ${unpackaged.join(", ")} — turbo cannot run ${unpackaged.length === 1 ? "it" : "them"}.`);
|
|
1882
2069
|
}
|
|
1883
2070
|
const buildOrder = require_workspace.getAppBuildOrder(workspace);
|
|
1884
2071
|
const width = Math.max(...buildOrder.map((name) => name.length));
|
|
1885
|
-
logger$
|
|
2072
|
+
logger$5.log(`\n${workspace.name}: ${appCount} app(s)`);
|
|
1886
2073
|
for (const appName of buildOrder) {
|
|
1887
2074
|
const app = workspace.apps[appName];
|
|
1888
2075
|
if (!app) continue;
|
|
@@ -1890,14 +2077,14 @@ async function workspaceDevCommand(configured, options) {
|
|
|
1890
2077
|
const own = reconciled.plan.resources.find((r) => r.kind === "mobile-app" && require_derive.appKey(r.id) === appName);
|
|
1891
2078
|
const scheme = own ? reconciled.env[own.envKey] : void 0;
|
|
1892
2079
|
const device = Object.entries(reconciled.env).find(([key]) => key.endsWith("_DEVICE_URL"))?.[1];
|
|
1893
|
-
logger$
|
|
2080
|
+
logger$5.log(` ${appName.padEnd(width)} ${scheme ? `${scheme}://` : "mobile"}${device ? ` — devices reach ${new URL(device).hostname}` : ""}`);
|
|
1894
2081
|
continue;
|
|
1895
2082
|
}
|
|
1896
2083
|
const local = `http://localhost:${app.port}`;
|
|
1897
2084
|
const url = Object.entries(appUrls).find(([id]) => require_derive.appKey(id) === appName)?.[1];
|
|
1898
|
-
logger$
|
|
2085
|
+
logger$5.log(` ${appName.padEnd(width)} ${url ? `${url} -> ${local}` : local}`);
|
|
1899
2086
|
}
|
|
1900
|
-
logger$
|
|
2087
|
+
logger$5.log("");
|
|
1901
2088
|
const configFiles = [
|
|
1902
2089
|
"gkm.config.ts",
|
|
1903
2090
|
"gkm.config.js",
|
|
@@ -1942,10 +2129,10 @@ async function workspaceDevCommand(configured, options) {
|
|
|
1942
2129
|
...secretsEnv,
|
|
1943
2130
|
NODE_ENV: "development",
|
|
1944
2131
|
...configPath ? { GKM_CONFIG_PATH: configPath } : {},
|
|
1945
|
-
...options.fake ? { [
|
|
2132
|
+
...options.fake ? { [require_state.FAKE_ENV]: "1" } : {},
|
|
1946
2133
|
[DISCOVERY_QUIET_ENV]: "1"
|
|
1947
2134
|
};
|
|
1948
|
-
logger$
|
|
2135
|
+
logger$5.log(`🚀 Starting ${appCount} app(s) — each builds before it answers, and says so when it is ready`);
|
|
1949
2136
|
const turboProcess = (0, node_child_process.spawn)("pnpm", [
|
|
1950
2137
|
"turbo",
|
|
1951
2138
|
"run",
|
|
@@ -1961,7 +2148,7 @@ async function workspaceDevCommand(configured, options) {
|
|
|
1961
2148
|
const shutdown = () => {
|
|
1962
2149
|
if (isShuttingDown) return;
|
|
1963
2150
|
isShuttingDown = true;
|
|
1964
|
-
logger$
|
|
2151
|
+
logger$5.log("\n🛑 Shutting down workspace...");
|
|
1965
2152
|
closeFakes(fakes);
|
|
1966
2153
|
discovery.leave();
|
|
1967
2154
|
const pid = turboProcess.pid;
|
|
@@ -1987,7 +2174,7 @@ async function workspaceDevCommand(configured, options) {
|
|
|
1987
2174
|
process.on("SIGTERM", shutdown);
|
|
1988
2175
|
return new Promise((resolve, reject) => {
|
|
1989
2176
|
turboProcess.on("error", (error) => {
|
|
1990
|
-
logger$
|
|
2177
|
+
logger$5.error("❌ Turbo error:", error);
|
|
1991
2178
|
reject(error);
|
|
1992
2179
|
});
|
|
1993
2180
|
turboProcess.on("exit", (code) => {
|
|
@@ -2005,23 +2192,23 @@ async function entryDevCommand(options) {
|
|
|
2005
2192
|
if (!entry) throw new Error("--entry requires a file path");
|
|
2006
2193
|
const entryPath = (0, node_path.resolve)(process.cwd(), entry);
|
|
2007
2194
|
if (!(0, node_fs.existsSync)(entryPath)) throw new Error(`Entry file not found: ${entryPath}`);
|
|
2008
|
-
const defaultEnv =
|
|
2009
|
-
if (defaultEnv.loaded.length > 0) logger$
|
|
2010
|
-
const { credentials, resolvedPort, secretsJsonPath, appName } = await
|
|
2011
|
-
if (appName) logger$
|
|
2012
|
-
logger$
|
|
2013
|
-
if (Object.keys(credentials).length > 1) logger$
|
|
2195
|
+
const defaultEnv = require_state.loadEnvFiles(".env");
|
|
2196
|
+
if (defaultEnv.loaded.length > 0) logger$5.log(`📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
2197
|
+
const { credentials, resolvedPort, secretsJsonPath, appName } = await require_state.prepareEntryCredentials({ explicitPort: options.portExplicit ? options.port : void 0 });
|
|
2198
|
+
if (appName) logger$5.log(`📦 App: ${appName} (port ${resolvedPort})`);
|
|
2199
|
+
logger$5.log(`🚀 Starting entry file: ${entry} on port ${resolvedPort}`);
|
|
2200
|
+
if (Object.keys(credentials).length > 1) logger$5.log(`🔐 Loaded ${Object.keys(credentials).length - 1} secret(s) + PORT`);
|
|
2014
2201
|
const wrapperDir = (0, node_path.join)(process.cwd(), ".gkm");
|
|
2015
2202
|
await (0, node_fs_promises.mkdir)(wrapperDir, { recursive: true });
|
|
2016
2203
|
const wrapperPath = (0, node_path.join)(wrapperDir, "entry-wrapper.ts");
|
|
2017
|
-
await
|
|
2204
|
+
await require_state.createEntryWrapper(wrapperPath, entryPath, secretsJsonPath);
|
|
2018
2205
|
const runner = new EntryRunner(wrapperPath, entryPath, watch, resolvedPort);
|
|
2019
2206
|
await runner.start();
|
|
2020
2207
|
let isShuttingDown = false;
|
|
2021
2208
|
const shutdown = () => {
|
|
2022
2209
|
if (isShuttingDown) return;
|
|
2023
2210
|
isShuttingDown = true;
|
|
2024
|
-
logger$
|
|
2211
|
+
logger$5.log("\n🛑 Shutting down...");
|
|
2025
2212
|
runner.stop();
|
|
2026
2213
|
process.exit(0);
|
|
2027
2214
|
};
|
|
@@ -2057,14 +2244,14 @@ var EntryRunner = class {
|
|
|
2057
2244
|
});
|
|
2058
2245
|
let restartTimeout = null;
|
|
2059
2246
|
this.watcher.on("change", (path) => {
|
|
2060
|
-
logger$
|
|
2247
|
+
logger$5.log(`📝 File changed: ${path}`);
|
|
2061
2248
|
if (restartTimeout) clearTimeout(restartTimeout);
|
|
2062
2249
|
restartTimeout = setTimeout(async () => {
|
|
2063
|
-
logger$
|
|
2250
|
+
logger$5.log("🔄 Restarting...");
|
|
2064
2251
|
await this.restart();
|
|
2065
2252
|
}, 300);
|
|
2066
2253
|
});
|
|
2067
|
-
logger$
|
|
2254
|
+
logger$5.log(`👀 Watching for changes in: ${watchDir}`);
|
|
2068
2255
|
}
|
|
2069
2256
|
}
|
|
2070
2257
|
async runProcess() {
|
|
@@ -2080,17 +2267,17 @@ var EntryRunner = class {
|
|
|
2080
2267
|
});
|
|
2081
2268
|
this.isRunning = true;
|
|
2082
2269
|
this.childProcess.on("error", (error) => {
|
|
2083
|
-
logger$
|
|
2270
|
+
logger$5.error("❌ Process error:", error);
|
|
2084
2271
|
});
|
|
2085
2272
|
this.childProcess.on("exit", (code) => {
|
|
2086
|
-
if (code !== null && code !== 0 && code !== 143) logger$
|
|
2273
|
+
if (code !== null && code !== 0 && code !== 143) logger$5.error(`❌ Process exited with code ${code}`);
|
|
2087
2274
|
this.isRunning = false;
|
|
2088
2275
|
});
|
|
2089
2276
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
2090
2277
|
if (this.isRunning) {
|
|
2091
|
-
logger$
|
|
2092
|
-
logger$
|
|
2093
|
-
logger$
|
|
2278
|
+
logger$5.log("");
|
|
2279
|
+
logger$5.log(` \x1b[32m✓ Ready\x1b[0m at \x1b[36mhttp://localhost:${this.port}\x1b[0m`);
|
|
2280
|
+
logger$5.log("");
|
|
2094
2281
|
}
|
|
2095
2282
|
}
|
|
2096
2283
|
async restart() {
|
|
@@ -2209,7 +2396,7 @@ start({
|
|
|
2209
2396
|
* lets a console read what this machine is running.
|
|
2210
2397
|
*/
|
|
2211
2398
|
function announce(discovery) {
|
|
2212
|
-
logger$
|
|
2399
|
+
logger$5.log(`🧭 Discovery: ${discovery.connectUrl}`);
|
|
2213
2400
|
}
|
|
2214
2401
|
/** Run `fn` with `console.log` muted — warnings and errors still print. */
|
|
2215
2402
|
async function quietly(fn) {
|
|
@@ -2323,11 +2510,11 @@ var DevServer = class {
|
|
|
2323
2510
|
this.startTime = Date.now();
|
|
2324
2511
|
if (this.isRunning) await this.stop();
|
|
2325
2512
|
if (this.portExplicit) {
|
|
2326
|
-
if (!await
|
|
2513
|
+
if (!await require_state.isPortAvailable(this.requestedPort)) throw new DevPortInUse(this.requestedPort, listenerOn(this.requestedPort));
|
|
2327
2514
|
this.actualPort = this.requestedPort;
|
|
2328
2515
|
} else {
|
|
2329
|
-
this.actualPort = await
|
|
2330
|
-
if (this.actualPort !== this.requestedPort) logger$
|
|
2516
|
+
this.actualPort = await require_state.findAvailablePort(this.requestedPort);
|
|
2517
|
+
if (this.actualPort !== this.requestedPort) logger$5.log(`ℹ️ Port ${this.requestedPort} was in use, using port ${this.actualPort} instead`);
|
|
2331
2518
|
}
|
|
2332
2519
|
const serverEntryPath = (0, node_path.join)(this.appRoot, ".gkm", "server", "server.ts");
|
|
2333
2520
|
await this.createServerEntry();
|
|
@@ -2347,11 +2534,11 @@ var DevServer = class {
|
|
|
2347
2534
|
});
|
|
2348
2535
|
this.isRunning = true;
|
|
2349
2536
|
this.serverProcess.on("error", (error) => {
|
|
2350
|
-
logger$
|
|
2537
|
+
logger$5.error("❌ Server error:", error);
|
|
2351
2538
|
});
|
|
2352
2539
|
const child = this.serverProcess;
|
|
2353
2540
|
child.on("exit", (code, signal) => {
|
|
2354
|
-
if (code !== null && code !== 0 && signal !== "SIGTERM") logger$
|
|
2541
|
+
if (code !== null && code !== 0 && signal !== "SIGTERM") logger$5.error(`❌ Server exited with code ${code}`);
|
|
2355
2542
|
this.isRunning = false;
|
|
2356
2543
|
if (this.serverProcess === child) this.onStatus?.("stopped");
|
|
2357
2544
|
});
|
|
@@ -2360,13 +2547,13 @@ var DevServer = class {
|
|
|
2360
2547
|
const local = `http://localhost:${this.actualPort}`;
|
|
2361
2548
|
const address = this.publicUrl ? `${this.publicUrl} -> ${local}` : local;
|
|
2362
2549
|
const seconds = ((Date.now() - this.startTime) / 1e3).toFixed(1);
|
|
2363
|
-
logger$
|
|
2550
|
+
logger$5.log(`\x1b[32m✓\x1b[0m ${this.label} ready in ${seconds}s ${address}`);
|
|
2364
2551
|
const tools = this.selfServing ? [] : [
|
|
2365
2552
|
...this.enableOpenApi ? ["docs /__docs"] : [],
|
|
2366
2553
|
...this.telescope ? [`telescope ${this.telescope.path}`] : [],
|
|
2367
2554
|
...this.databaseApi ? [`db ${this.databaseApi}`] : []
|
|
2368
2555
|
];
|
|
2369
|
-
if (tools.length > 0) logger$
|
|
2556
|
+
if (tools.length > 0) logger$5.log(` ${tools.join(" · ")}`);
|
|
2370
2557
|
this.onStatus?.("ready");
|
|
2371
2558
|
}
|
|
2372
2559
|
}
|
|
@@ -2397,7 +2584,7 @@ var DevServer = class {
|
|
|
2397
2584
|
await this.stop();
|
|
2398
2585
|
let attempts = 0;
|
|
2399
2586
|
while (attempts < 30) {
|
|
2400
|
-
if (await
|
|
2587
|
+
if (await require_state.isPortAvailable(portToReuse)) break;
|
|
2401
2588
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
2402
2589
|
attempts++;
|
|
2403
2590
|
}
|
|
@@ -3062,7 +3249,7 @@ ${shutdown}`);
|
|
|
3062
3249
|
}
|
|
3063
3250
|
//#endregion
|
|
3064
3251
|
//#region src/build/index.ts
|
|
3065
|
-
const logger$
|
|
3252
|
+
const logger$4 = console;
|
|
3066
3253
|
/**
|
|
3067
3254
|
* Where `gkm build --stage` leaves the key its bundle's secrets were encrypted
|
|
3068
3255
|
* with, beside the bundle in `.gkm/server/` and readable by its owner alone.
|
|
@@ -3110,12 +3297,12 @@ async function buildCommand(options) {
|
|
|
3110
3297
|
const isAtWorkspaceRoot = cwd === workspaceRoot;
|
|
3111
3298
|
const someAppIsElsewhere = Object.values(loadedConfig.workspace.apps).some((app) => (0, node_path.resolve)(workspaceRoot, app.path) !== workspaceRoot);
|
|
3112
3299
|
if (isAtWorkspaceRoot && someAppIsElsewhere) {
|
|
3113
|
-
logger$
|
|
3300
|
+
logger$4.log("📦 Detected workspace configuration");
|
|
3114
3301
|
return workspaceBuildCommand(loadedConfig.workspace, options);
|
|
3115
3302
|
}
|
|
3116
3303
|
}
|
|
3117
3304
|
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;
|
|
3118
|
-
const target = options.provider ??
|
|
3305
|
+
const target = options.provider ?? require_state.providerOf(loadedConfig.workspace);
|
|
3119
3306
|
const workspaceRoot = loadedConfig.workspace.root;
|
|
3120
3307
|
const appRoot = process.cwd();
|
|
3121
3308
|
const output = await buildOneApp({
|
|
@@ -3147,22 +3334,22 @@ async function declaredIn(config, cwd) {
|
|
|
3147
3334
|
*/
|
|
3148
3335
|
function backendsOf(workspace) {
|
|
3149
3336
|
return {
|
|
3150
|
-
cache:
|
|
3337
|
+
cache: require_state.cacheBackendFor(require_state.providerOf(workspace)),
|
|
3151
3338
|
email: "ses"
|
|
3152
3339
|
};
|
|
3153
3340
|
}
|
|
3154
3341
|
/** One app's build, from its config and the command's options. */
|
|
3155
3342
|
async function buildOneApp(input) {
|
|
3156
3343
|
const { config, options, workspace, appRoot, target } = input;
|
|
3157
|
-
const cacheBackend =
|
|
3344
|
+
const cacheBackend = require_state.cacheBackendFor(require_state.providerOf(workspace));
|
|
3158
3345
|
const production = normalizeProductionConfig(options.production ?? false);
|
|
3159
|
-
if (production) logger$
|
|
3160
|
-
logger$
|
|
3161
|
-
logger$
|
|
3346
|
+
if (production) logger$4.log(`🏭 Building for PRODUCTION`);
|
|
3347
|
+
logger$4.log(`Building for ${target}`);
|
|
3348
|
+
logger$4.log(`Loading constructs from: ${formatRoutes(config.constructs)}`);
|
|
3162
3349
|
const telescope = production ? void 0 : normalizeTelescopeConfig(config.telescope);
|
|
3163
|
-
if (telescope) logger$
|
|
3350
|
+
if (telescope) logger$4.log(`🔭 Telescope enabled at ${telescope.path}`);
|
|
3164
3351
|
const hooks = normalizeHooksConfig(config.hooks, appRoot);
|
|
3165
|
-
if (hooks) logger$
|
|
3352
|
+
if (hooks) logger$4.log(`🪝 Server hooks enabled`);
|
|
3166
3353
|
return buildApp({
|
|
3167
3354
|
config,
|
|
3168
3355
|
workspaceRoot: workspace.root,
|
|
@@ -3204,7 +3391,7 @@ async function writeManifest(input) {
|
|
|
3204
3391
|
});
|
|
3205
3392
|
const path = (0, node_path.join)(manifestDir, `${target}.ts`);
|
|
3206
3393
|
await (0, node_fs_promises.writeFile)(path, require_emit.manifestModule(folded, backends));
|
|
3207
|
-
logger$
|
|
3394
|
+
logger$4.log(`Manifest: ${(0, node_path.relative)(process.cwd(), path)}`);
|
|
3208
3395
|
}
|
|
3209
3396
|
/**
|
|
3210
3397
|
* Turn one app's constructs into its generated entry.
|
|
@@ -3232,11 +3419,11 @@ async function buildApp(input) {
|
|
|
3232
3419
|
});
|
|
3233
3420
|
if (selfServing) {
|
|
3234
3421
|
await writeSurfaceEntry((0, node_path.join)(appRoot, ".gkm", "server"), selfServing.source);
|
|
3235
|
-
logger$
|
|
3422
|
+
logger$4.log(`Generated a server for ${selfServing.id} from its own declaration`);
|
|
3236
3423
|
if (production?.bundle && !input.skipBundle) {
|
|
3237
3424
|
const outputDir = (0, node_path.join)(appRoot, ".gkm", "server");
|
|
3238
3425
|
const entryPoint = await writeSurfaceServer(outputDir, production);
|
|
3239
|
-
const { bundleServer } = await Promise.resolve().then(() => require("./bundler-
|
|
3426
|
+
const { bundleServer } = await Promise.resolve().then(() => require("./bundler-orRqUJNZ.cjs"));
|
|
3240
3427
|
await bundleServer({
|
|
3241
3428
|
entryPoint,
|
|
3242
3429
|
outputDir: (0, node_path.join)(outputDir, "dist"),
|
|
@@ -3245,7 +3432,7 @@ async function buildApp(input) {
|
|
|
3245
3432
|
external: production.external,
|
|
3246
3433
|
...input.stage ? { stage: input.stage } : {}
|
|
3247
3434
|
});
|
|
3248
|
-
logger$
|
|
3435
|
+
logger$4.log(`✅ Bundle complete: .gkm/server/dist/server.mjs`);
|
|
3249
3436
|
}
|
|
3250
3437
|
return { selfServing: selfServing.id };
|
|
3251
3438
|
}
|
|
@@ -3291,14 +3478,14 @@ async function buildApp(input) {
|
|
|
3291
3478
|
topicGenerator.load(code, appRoot, bustCache)
|
|
3292
3479
|
]);
|
|
3293
3480
|
const allEndpoints = servedBy(loadedEndpoints, derived.surface);
|
|
3294
|
-
logger$
|
|
3295
|
-
logger$
|
|
3296
|
-
logger$
|
|
3297
|
-
logger$
|
|
3298
|
-
logger$
|
|
3299
|
-
logger$
|
|
3481
|
+
logger$4.log(`Found ${allEndpoints.length} endpoints`);
|
|
3482
|
+
logger$4.log(`Found ${allFunctions.length} functions`);
|
|
3483
|
+
logger$4.log(`Found ${allCrons.length} crons`);
|
|
3484
|
+
logger$4.log(`Found ${allSubscribers.length} subscribers`);
|
|
3485
|
+
logger$4.log(`Found ${allQueues.length} queues`);
|
|
3486
|
+
logger$4.log(`Found ${allTopics.length} topics`);
|
|
3300
3487
|
if (!input.serveEmpty && allEndpoints.length === 0 && allFunctions.length === 0 && allCrons.length === 0 && allSubscribers.length === 0 && allQueues.length === 0 && allTopics.length === 0) {
|
|
3301
|
-
logger$
|
|
3488
|
+
logger$4.log("No endpoints, functions, crons, subscribers, queues, or topics found to process");
|
|
3302
3489
|
return {};
|
|
3303
3490
|
}
|
|
3304
3491
|
if (production?.subscribers === "include" && derived.surface) {
|
|
@@ -3311,7 +3498,7 @@ async function buildApp(input) {
|
|
|
3311
3498
|
[allQueues.length, "queue consumer"],
|
|
3312
3499
|
[allSubscribers.length, "subscriber"]
|
|
3313
3500
|
].filter(([n]) => n > 0).map(([n, what]) => `${n} ${what}${n === 1 ? "" : "s"}`);
|
|
3314
|
-
if (left.length > 0) logger$
|
|
3501
|
+
if (left.length > 0) logger$4.log(`Serving ${derived.surface.id} only: leaving out ${left.join(", ")} — they run in a Worker's image`);
|
|
3315
3502
|
}
|
|
3316
3503
|
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);
|
|
3317
3504
|
await require_openapi.generateOpenApi(allEndpoints.map(({ construct }) => construct), {
|
|
@@ -3334,7 +3521,7 @@ async function buildForTarget(target, context, appRoot, workspaceRoot, endpointG
|
|
|
3334
3521
|
force: true
|
|
3335
3522
|
});
|
|
3336
3523
|
await (0, node_fs_promises.mkdir)(outputDir, { recursive: true });
|
|
3337
|
-
logger$
|
|
3524
|
+
logger$4.log(`\nGenerating handlers for ${target}`);
|
|
3338
3525
|
const options = {
|
|
3339
3526
|
target,
|
|
3340
3527
|
root: workspaceRoot
|
|
@@ -3350,7 +3537,7 @@ async function buildForTarget(target, context, appRoot, workspaceRoot, endpointG
|
|
|
3350
3537
|
queueGenerator.build(context, queues, outputDir, options),
|
|
3351
3538
|
topicGenerator.build(context, topics, outputDir, options)
|
|
3352
3539
|
]);
|
|
3353
|
-
logger$
|
|
3540
|
+
logger$4.log(`Generated ${routes.length} routes, ${functionInfos.length} functions, ${cronInfos.length} crons, ${subscriberInfos.length} subscribers, ${queueInfos.length} queues, ${topicInfos.length} topics for ${target}`);
|
|
3354
3541
|
const fields = {
|
|
3355
3542
|
...context.surface ? { surface: context.surface.id } : {},
|
|
3356
3543
|
functions: functionInfos,
|
|
@@ -3373,8 +3560,8 @@ async function buildForTarget(target, context, appRoot, workspaceRoot, endpointG
|
|
|
3373
3560
|
};
|
|
3374
3561
|
let masterKey;
|
|
3375
3562
|
if (context.production?.bundle && !skipBundle) {
|
|
3376
|
-
logger$
|
|
3377
|
-
const { bundleServer } = await Promise.resolve().then(() => require("./bundler-
|
|
3563
|
+
logger$4.log(`\n📦 Bundling production server...`);
|
|
3564
|
+
const { bundleServer } = await Promise.resolve().then(() => require("./bundler-orRqUJNZ.cjs"));
|
|
3378
3565
|
const allConstructs = [
|
|
3379
3566
|
...endpoints.map((e) => e.construct),
|
|
3380
3567
|
...functions.map((f) => f.construct),
|
|
@@ -3391,13 +3578,13 @@ async function buildForTarget(target, context, appRoot, workspaceRoot, endpointG
|
|
|
3391
3578
|
stage,
|
|
3392
3579
|
constructs: allConstructs
|
|
3393
3580
|
})).masterKey;
|
|
3394
|
-
logger$
|
|
3581
|
+
logger$4.log(`✅ Bundle complete: .gkm/server/dist/server.mjs`);
|
|
3395
3582
|
if (masterKey) {
|
|
3396
3583
|
const keyPath = (0, node_path.join)(outputDir, MASTER_KEY_FILE);
|
|
3397
3584
|
await (0, node_fs_promises.writeFile)(keyPath, `${masterKey}\n`, { mode: 384 });
|
|
3398
3585
|
await (0, node_fs_promises.chmod)(keyPath, 384);
|
|
3399
|
-
logger$
|
|
3400
|
-
logger$
|
|
3586
|
+
logger$4.log(`\n🔐 Secrets encrypted for deployment`);
|
|
3587
|
+
logger$4.log(` Run with GKM_MASTER_KEY set to the key in ${(0, node_path.relative)(appRoot, keyPath)} (fingerprint ${require_sniffer.keyFingerprint(masterKey)})`);
|
|
3401
3588
|
}
|
|
3402
3589
|
}
|
|
3403
3590
|
return {
|
|
@@ -3498,20 +3685,20 @@ async function workspaceBuildCommand(workspace, options) {
|
|
|
3498
3685
|
const apps = Object.entries(workspace.apps);
|
|
3499
3686
|
const backendApps = apps.filter(([, app]) => app.type === "backend");
|
|
3500
3687
|
const frontendApps = apps.filter(([, app]) => app.type === "web");
|
|
3501
|
-
logger$
|
|
3502
|
-
logger$
|
|
3503
|
-
logger$
|
|
3504
|
-
if (options.production) logger$
|
|
3688
|
+
logger$4.log(`\n🏗️ Building workspace: ${workspace.name}`);
|
|
3689
|
+
logger$4.log(` Backend apps: ${backendApps.map(([name]) => name).join(", ") || "none"}`);
|
|
3690
|
+
logger$4.log(` Frontend apps: ${frontendApps.map(([name]) => name).join(", ") || "none"}`);
|
|
3691
|
+
if (options.production) logger$4.log(` 🏭 Production mode enabled`);
|
|
3505
3692
|
const buildOrder = require_workspace.getAppBuildOrder(workspace);
|
|
3506
|
-
logger$
|
|
3693
|
+
logger$4.log(` Build order: ${buildOrder.join(" → ")}`);
|
|
3507
3694
|
try {
|
|
3508
|
-
const target = options.provider ??
|
|
3695
|
+
const target = options.provider ?? require_state.providerOf(workspace);
|
|
3509
3696
|
const builds = [];
|
|
3510
3697
|
for (const appName of buildOrder) {
|
|
3511
3698
|
const app = workspace.apps[appName];
|
|
3512
3699
|
const config = require_workspace.getAppGkmConfig(workspace, appName);
|
|
3513
3700
|
if (!app || app.type !== "backend" || !config) continue;
|
|
3514
|
-
logger$
|
|
3701
|
+
logger$4.log(`\n⚙️ ${appName}`);
|
|
3515
3702
|
const output = await buildOneApp({
|
|
3516
3703
|
config,
|
|
3517
3704
|
options,
|
|
@@ -3536,8 +3723,8 @@ async function workspaceBuildCommand(workspace, options) {
|
|
|
3536
3723
|
const pm = detectPackageManager();
|
|
3537
3724
|
const [turboBin, turboArgs] = getTurboArgs(pm, filters, { only: true });
|
|
3538
3725
|
if (filters.length > 0) {
|
|
3539
|
-
logger$
|
|
3540
|
-
logger$
|
|
3726
|
+
logger$4.log(`\n📦 Using ${pm} with Turbo for the other apps...\n`);
|
|
3727
|
+
logger$4.log(`Running: ${[turboBin, ...turboArgs].join(" ")}`);
|
|
3541
3728
|
const sandbox = options.sandbox ?? new require_local.LocalSandbox({
|
|
3542
3729
|
root: workspace.root,
|
|
3543
3730
|
passEnv: ["TURBO_TOKEN"]
|
|
@@ -3562,15 +3749,15 @@ async function workspaceBuildCommand(workspace, options) {
|
|
|
3562
3749
|
outputPath
|
|
3563
3750
|
});
|
|
3564
3751
|
}
|
|
3565
|
-
logger$
|
|
3566
|
-
logger$
|
|
3752
|
+
logger$4.log(`\n✅ Workspace build complete!`);
|
|
3753
|
+
logger$4.log(`\n📋 Build Summary:`);
|
|
3567
3754
|
for (const result of results) {
|
|
3568
3755
|
const icon = result.type === "backend" ? "⚙️" : result.type === "mobile" ? "📱" : "🌐";
|
|
3569
|
-
logger$
|
|
3756
|
+
logger$4.log(` ${icon} ${result.appName}: ${result.outputPath || "built"}`);
|
|
3570
3757
|
}
|
|
3571
3758
|
} catch (error) {
|
|
3572
3759
|
const errorMessage = error instanceof Error ? error.message : "Build failed";
|
|
3573
|
-
logger$
|
|
3760
|
+
logger$4.log(`\n❌ Build failed: ${errorMessage}`);
|
|
3574
3761
|
for (const [appName, app] of apps) results.push({
|
|
3575
3762
|
appName,
|
|
3576
3763
|
type: app.type,
|
|
@@ -3600,1169 +3787,17 @@ function formatRoutes(routes) {
|
|
|
3600
3787
|
return Array.isArray(routes) ? routes.join(", ") : routes;
|
|
3601
3788
|
}
|
|
3602
3789
|
//#endregion
|
|
3603
|
-
//#region src/migrate/provider.ts
|
|
3604
|
-
/**
|
|
3605
|
-
* One construct's migrations and seeds, read from its folders.
|
|
3606
|
-
*
|
|
3607
|
-
* Two kinds of file, told apart by extension rather than by configuration: a
|
|
3608
|
-
* script exports `up` (and may export `down`) — or `seed(db, { stage })`, in
|
|
3609
|
-
* `seeds/` — and a
|
|
3610
|
-
* `.sql` file is run whole. The second is what makes a folder independent of
|
|
3611
|
-
* the client the app uses — Better Auth's schema is SQL whatever the
|
|
3612
|
-
* application queries with.
|
|
3613
|
-
*/
|
|
3614
|
-
const SCRIPTS = /* @__PURE__ */ new Set([
|
|
3615
|
-
".ts",
|
|
3616
|
-
".mts",
|
|
3617
|
-
".js",
|
|
3618
|
-
".mjs"
|
|
3619
|
-
]);
|
|
3620
|
-
/** Two files in one folder that would be one migration. */
|
|
3621
|
-
var DuplicateMigration = class extends Error {
|
|
3622
|
-
folder;
|
|
3623
|
-
migration;
|
|
3624
|
-
constructor(folder, migration) {
|
|
3625
|
-
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.`);
|
|
3626
|
-
this.folder = folder;
|
|
3627
|
-
this.migration = migration;
|
|
3628
|
-
this.name = "DuplicateMigration";
|
|
3629
|
-
}
|
|
3630
|
-
};
|
|
3631
|
-
/** A script in a migrations folder that exports no `up`. */
|
|
3632
|
-
var MigrationHasNoUp = class extends Error {
|
|
3633
|
-
file;
|
|
3634
|
-
constructor(file) {
|
|
3635
|
-
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.`);
|
|
3636
|
-
this.file = file;
|
|
3637
|
-
this.name = "MigrationHasNoUp";
|
|
3638
|
-
}
|
|
3639
|
-
};
|
|
3640
|
-
/** A script in a seeds folder that exports no `seed`. */
|
|
3641
|
-
var SeedHasNoSeed = class extends Error {
|
|
3642
|
-
file;
|
|
3643
|
-
constructor(file) {
|
|
3644
|
-
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.`);
|
|
3645
|
-
this.file = file;
|
|
3646
|
-
this.name = "SeedHasNoSeed";
|
|
3647
|
-
}
|
|
3648
|
-
};
|
|
3649
|
-
/**
|
|
3650
|
-
* The migration files in a folder, by name, without reading them.
|
|
3651
|
-
*
|
|
3652
|
-
* Also what `gkm dev` compares against a construct's history to report what
|
|
3653
|
-
* is pending, which is why it is separate from loading them.
|
|
3654
|
-
*/
|
|
3655
|
-
async function migrationFiles(folder) {
|
|
3656
|
-
const entries = await (0, node_fs_promises.readdir)(folder, { withFileTypes: true }).catch((error) => {
|
|
3657
|
-
if (error.code === "ENOENT") return [];
|
|
3658
|
-
throw error;
|
|
3659
|
-
});
|
|
3660
|
-
const files = /* @__PURE__ */ new Map();
|
|
3661
|
-
for (const entry of entries) {
|
|
3662
|
-
if (!entry.isFile() || !isMigration(entry.name)) continue;
|
|
3663
|
-
const name = entry.name.slice(0, -(0, node_path.extname)(entry.name).length);
|
|
3664
|
-
if (files.has(name)) throw new DuplicateMigration(folder, name);
|
|
3665
|
-
files.set(name, (0, node_path.join)(folder, entry.name));
|
|
3666
|
-
}
|
|
3667
|
-
return files;
|
|
3668
|
-
}
|
|
3669
|
-
/** A Kysely provider over one construct's folder. */
|
|
3670
|
-
function folderProvider(folder, raw) {
|
|
3671
|
-
return { async getMigrations() {
|
|
3672
|
-
const migrations = {};
|
|
3673
|
-
for (const [name, file] of await migrationFiles(folder)) migrations[name] = (0, node_path.extname)(file) === ".sql" ? await sqlMigration(file, raw) : await scriptMigration(file);
|
|
3674
|
-
return migrations;
|
|
3675
|
-
} };
|
|
3676
|
-
}
|
|
3677
|
-
/**
|
|
3678
|
-
* The seeds in a folder, loaded, in name order.
|
|
3679
|
-
*
|
|
3680
|
-
* Every one runs on every pass — there is no history to consult — so the order
|
|
3681
|
-
* is the only thing between two seeds where one reads what another writes.
|
|
3682
|
-
* Name them to sort that way.
|
|
3683
|
-
*/
|
|
3684
|
-
async function loadSeeds(folder, raw) {
|
|
3685
|
-
const files = [...await migrationFiles(folder)].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
3686
|
-
const seeds = [];
|
|
3687
|
-
for (const [name, file] of files) seeds.push({
|
|
3688
|
-
name,
|
|
3689
|
-
run: (0, node_path.extname)(file) === ".sql" ? (await sqlMigration(file, raw)).up : await scriptSeed(file)
|
|
3690
|
-
});
|
|
3691
|
-
return seeds;
|
|
3692
|
-
}
|
|
3693
|
-
/**
|
|
3694
|
-
* A seed script, imported as it is on disk now.
|
|
3695
|
-
*
|
|
3696
|
-
* Keyed by its modification time: a seed is run again after it is edited, and
|
|
3697
|
-
* a process that has imported it once would otherwise run the old one.
|
|
3698
|
-
*/
|
|
3699
|
-
async function scriptSeed(file) {
|
|
3700
|
-
const url = (0, node_url.pathToFileURL)(file);
|
|
3701
|
-
url.searchParams.set("mtime", String((await (0, node_fs_promises.stat)(file)).mtimeMs));
|
|
3702
|
-
const module = await import(url.href);
|
|
3703
|
-
if (typeof module.seed !== "function") throw new SeedHasNoSeed(file);
|
|
3704
|
-
return module.seed;
|
|
3705
|
-
}
|
|
3706
|
-
function isMigration(file) {
|
|
3707
|
-
if (file.endsWith(".d.ts")) return false;
|
|
3708
|
-
if (/\.(spec|test)\.[cm]?[jt]s$/.test(file)) return false;
|
|
3709
|
-
const extension = (0, node_path.extname)(file);
|
|
3710
|
-
return extension === ".sql" || SCRIPTS.has(extension);
|
|
3711
|
-
}
|
|
3712
|
-
async function scriptMigration(file) {
|
|
3713
|
-
const module = await import((0, node_url.pathToFileURL)(file).href);
|
|
3714
|
-
if (typeof module.up !== "function") throw new MigrationHasNoUp(file);
|
|
3715
|
-
return {
|
|
3716
|
-
up: module.up,
|
|
3717
|
-
...typeof module.down === "function" ? { down: module.down } : {}
|
|
3718
|
-
};
|
|
3719
|
-
}
|
|
3720
|
-
/**
|
|
3721
|
-
* A `.sql` file, run whole.
|
|
3722
|
-
*
|
|
3723
|
-
* One query rather than split on `;`: with no parameters, Postgres takes
|
|
3724
|
-
* several statements in one message, and splitting by hand breaks on the first
|
|
3725
|
-
* `;` inside a string or a function body. There is no `down` — a generated
|
|
3726
|
-
* schema is rolled back by a migration that says how, not by guessing.
|
|
3727
|
-
*/
|
|
3728
|
-
async function sqlMigration(file, raw) {
|
|
3729
|
-
const statements = await (0, node_fs_promises.readFile)(file, "utf-8");
|
|
3730
|
-
return { up: async (db) => {
|
|
3731
|
-
await raw.raw(statements).execute(db);
|
|
3732
|
-
} };
|
|
3733
|
-
}
|
|
3734
|
-
//#endregion
|
|
3735
|
-
//#region src/migrate/databases.ts
|
|
3736
|
-
/**
|
|
3737
|
-
* Bring each construct's schema up to its migrations folder.
|
|
3738
|
-
*
|
|
3739
|
-
* Which constructs, in what order, from which folder, is `migrationTargets`'
|
|
3740
|
-
* answer and nobody else's. What is decided here is only how one target is
|
|
3741
|
-
* run: as its owner, in its own schema, with a history of its own.
|
|
3742
|
-
*/
|
|
3743
|
-
/** A folder under `db/` that names no construct. */
|
|
3744
|
-
var UnknownMigrationFolder = class extends Error {
|
|
3745
|
-
folder;
|
|
3746
|
-
known;
|
|
3747
|
-
constructor(folder, known) {
|
|
3748
|
-
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.`);
|
|
3749
|
-
this.folder = folder;
|
|
3750
|
-
this.known = known;
|
|
3751
|
-
this.name = "UnknownMigrationFolder";
|
|
3752
|
-
}
|
|
3753
|
-
};
|
|
3754
|
-
/** A construct was asked for by a name that takes no migrations. */
|
|
3755
|
-
var NoSuchMigrationTarget = class extends Error {
|
|
3756
|
-
construct;
|
|
3757
|
-
known;
|
|
3758
|
-
constructor(construct, known) {
|
|
3759
|
-
super(`'${construct}' is not a database construct. ` + (known.length > 0 ? `The ones with migrations: ${known.join(", ")}.` : "This project declares no database."));
|
|
3760
|
-
this.construct = construct;
|
|
3761
|
-
this.known = known;
|
|
3762
|
-
this.name = "NoSuchMigrationTarget";
|
|
3763
|
-
}
|
|
3764
|
-
};
|
|
3765
|
-
/**
|
|
3766
|
-
* A migration left directly in a construct's folder, where nothing reads it.
|
|
3767
|
-
*
|
|
3768
|
-
* Migrations moved into `migrations/` so seeds could sit beside them. A file
|
|
3769
|
-
* still at the old level would otherwise never run, and nothing would say so.
|
|
3770
|
-
*/
|
|
3771
|
-
var MigrationsOutsideFolder = class extends Error {
|
|
3772
|
-
folder;
|
|
3773
|
-
files;
|
|
3774
|
-
constructor(folder, files) {
|
|
3775
|
-
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.`);
|
|
3776
|
-
this.folder = folder;
|
|
3777
|
-
this.files = files;
|
|
3778
|
-
this.name = "MigrationsOutsideFolder";
|
|
3779
|
-
}
|
|
3780
|
-
};
|
|
3781
|
-
/** A folder inside a construct's folder that is neither `migrations` nor `seeds`. */
|
|
3782
|
-
var UnknownDatabaseFolder = class extends Error {
|
|
3783
|
-
folder;
|
|
3784
|
-
constructor(folder) {
|
|
3785
|
-
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.`);
|
|
3786
|
-
this.folder = folder;
|
|
3787
|
-
this.name = "UnknownDatabaseFolder";
|
|
3788
|
-
}
|
|
3789
|
-
};
|
|
3790
|
-
/** A seed failed; its own writes are rolled back, the ones before it stay. */
|
|
3791
|
-
var SeedFailed = class extends Error {
|
|
3792
|
-
construct;
|
|
3793
|
-
seed;
|
|
3794
|
-
cause;
|
|
3795
|
-
constructor(construct, seed, cause) {
|
|
3796
|
-
super(`${construct}: seed '${seed}' failed — ${cause instanceof Error ? cause.message : String(cause)}. Its writes were rolled back; fix it and run it again.`);
|
|
3797
|
-
this.construct = construct;
|
|
3798
|
-
this.seed = seed;
|
|
3799
|
-
this.cause = cause;
|
|
3800
|
-
this.name = "SeedFailed";
|
|
3801
|
-
}
|
|
3802
|
-
};
|
|
3803
|
-
/** A construct with roles has no owner URL to migrate with. */
|
|
3804
|
-
var NoOwnerCredential = class extends Error {
|
|
3805
|
-
construct;
|
|
3806
|
-
key;
|
|
3807
|
-
constructor(construct, key) {
|
|
3808
|
-
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?`);
|
|
3809
|
-
this.construct = construct;
|
|
3810
|
-
this.key = key;
|
|
3811
|
-
this.name = "NoOwnerCredential";
|
|
3812
|
-
}
|
|
3813
|
-
};
|
|
3814
|
-
/** A migration failed; the ones before it in the run stay applied. */
|
|
3815
|
-
var MigrationFailed = class extends Error {
|
|
3816
|
-
construct;
|
|
3817
|
-
migration;
|
|
3818
|
-
cause;
|
|
3819
|
-
constructor(construct, migration, cause) {
|
|
3820
|
-
super(`${construct}: migration ${migration ? `'${migration}' ` : ""}failed — ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
3821
|
-
this.construct = construct;
|
|
3822
|
-
this.migration = migration;
|
|
3823
|
-
this.cause = cause;
|
|
3824
|
-
this.name = "MigrationFailed";
|
|
3825
|
-
}
|
|
3826
|
-
};
|
|
3827
|
-
/** The project's Kysely could not be found from where a database is declared. */
|
|
3828
|
-
var KyselyNotFound = class extends Error {
|
|
3829
|
-
from;
|
|
3830
|
-
constructor(from) {
|
|
3831
|
-
super(`Could not resolve 'kysely' from ${from}. Migrations run with the project's own Kysely — install it where the database is declared.`);
|
|
3832
|
-
this.from = from;
|
|
3833
|
-
this.name = "KyselyNotFound";
|
|
3834
|
-
}
|
|
3835
|
-
};
|
|
3836
|
-
/** Apply every pending migration, construct by construct, parents first. */
|
|
3837
|
-
async function migrateDatabases(options) {
|
|
3838
|
-
const runs = [];
|
|
3839
|
-
for (const target of await targetsFor(options)) {
|
|
3840
|
-
if ((await migrationFiles((0, node_path.join)(options.root, target.migrations))).size === 0) {
|
|
3841
|
-
runs.push({
|
|
3842
|
-
target,
|
|
3843
|
-
applied: []
|
|
3844
|
-
});
|
|
3845
|
-
continue;
|
|
3846
|
-
}
|
|
3847
|
-
const applied = await withMigrator(options, target, async (migrator) => {
|
|
3848
|
-
const { error, results = [] } = await migrator.migrateToLatest();
|
|
3849
|
-
if (error) {
|
|
3850
|
-
const failed = results.find((result) => result.status === "Error");
|
|
3851
|
-
throw new MigrationFailed(target.id, failed?.migrationName, error);
|
|
3852
|
-
}
|
|
3853
|
-
return results.filter((result) => result.status === "Success").map((result) => result.migrationName);
|
|
3854
|
-
});
|
|
3855
|
-
runs.push({
|
|
3856
|
-
target,
|
|
3857
|
-
applied
|
|
3858
|
-
});
|
|
3859
|
-
}
|
|
3860
|
-
return runs;
|
|
3861
|
-
}
|
|
3862
|
-
/**
|
|
3863
|
-
* Run every construct's seeds, parents first — each one, every time, on every
|
|
3864
|
-
* stage, production included. Each is handed the stage, so a seed decides
|
|
3865
|
-
* what belongs where.
|
|
3866
|
-
*
|
|
3867
|
-
* Seeds are reference data kept up to date by being run again, so there is no
|
|
3868
|
-
* history: a seed is an upsert, and a changed one is applied by the next run.
|
|
3869
|
-
* Each runs in its own transaction, as the construct's owner, so a failure
|
|
3870
|
-
* leaves nothing half-written. Assumes the schema is migrated —
|
|
3871
|
-
* {@link migrateAndSeed} is what makes that true.
|
|
3872
|
-
*/
|
|
3873
|
-
async function seedDatabases(options) {
|
|
3874
|
-
const runs = [];
|
|
3875
|
-
for (const target of await targetsFor(options)) {
|
|
3876
|
-
const folder = (0, node_path.join)(options.root, target.seeds);
|
|
3877
|
-
if ((await migrationFiles(folder)).size === 0) {
|
|
3878
|
-
runs.push({
|
|
3879
|
-
target,
|
|
3880
|
-
seeded: []
|
|
3881
|
-
});
|
|
3882
|
-
continue;
|
|
3883
|
-
}
|
|
3884
|
-
const seeded = await withOwner(options, target, async (db, kysely) => {
|
|
3885
|
-
const names = [];
|
|
3886
|
-
for (const seed of await loadSeeds(folder, kysely.sql)) {
|
|
3887
|
-
try {
|
|
3888
|
-
await db.transaction().execute((trx) => seed.run(trx, { stage: options.stage }));
|
|
3889
|
-
} catch (error) {
|
|
3890
|
-
throw new SeedFailed(target.id, seed.name, error);
|
|
3891
|
-
}
|
|
3892
|
-
names.push(seed.name);
|
|
3893
|
-
}
|
|
3894
|
-
return names;
|
|
3895
|
-
});
|
|
3896
|
-
runs.push({
|
|
3897
|
-
target,
|
|
3898
|
-
seeded
|
|
3899
|
-
});
|
|
3900
|
-
}
|
|
3901
|
-
return runs;
|
|
3902
|
-
}
|
|
3903
|
-
/**
|
|
3904
|
-
* Migrate, then seed: a seed only ever runs against the schema it was written
|
|
3905
|
-
* for.
|
|
3906
|
-
*
|
|
3907
|
-
* With `only`, the constructs it lives in are migrated too — a tenant's seed
|
|
3908
|
-
* cannot run before the database that owns its schema exists — and only it is
|
|
3909
|
-
* seeded.
|
|
3910
|
-
*/
|
|
3911
|
-
async function migrateAndSeed(options) {
|
|
3912
|
-
const migrations = [];
|
|
3913
|
-
for (const target of await withParents(options)) migrations.push(...await migrateDatabases({
|
|
3914
|
-
...options,
|
|
3915
|
-
only: target.id
|
|
3916
|
-
}));
|
|
3917
|
-
return {
|
|
3918
|
-
migrations,
|
|
3919
|
-
seeds: await seedDatabases(options)
|
|
3920
|
-
};
|
|
3921
|
-
}
|
|
3922
|
-
/** The targets `only` names, after the ones it lives in; all without it. */
|
|
3923
|
-
async function withParents(options) {
|
|
3924
|
-
const chosen = await targetsFor(options);
|
|
3925
|
-
if (!options.only) return chosen;
|
|
3926
|
-
const all = (0, _geekmidas_manifest.migrationTargets)(options.manifest);
|
|
3927
|
-
const ids = /* @__PURE__ */ new Set();
|
|
3928
|
-
for (const target of chosen) {
|
|
3929
|
-
let current = target;
|
|
3930
|
-
while (current) {
|
|
3931
|
-
ids.add(current.id);
|
|
3932
|
-
const parent = current.of;
|
|
3933
|
-
current = parent ? all.find((t) => t.id === parent) : void 0;
|
|
3934
|
-
}
|
|
3935
|
-
}
|
|
3936
|
-
return all.filter((target) => ids.has(target.id));
|
|
3937
|
-
}
|
|
3938
|
-
/**
|
|
3939
|
-
* What each construct's folder has that its history does not — without
|
|
3940
|
-
* applying any of it. What `gkm dev` reports.
|
|
3941
|
-
*/
|
|
3942
|
-
async function pendingMigrations(options) {
|
|
3943
|
-
const found = [];
|
|
3944
|
-
for (const target of await targetsFor(options)) {
|
|
3945
|
-
if ((await migrationFiles((0, node_path.join)(options.root, target.migrations))).size === 0) continue;
|
|
3946
|
-
const pending = await withMigrator(options, target, async (migrator) => (await migrator.getMigrations()).filter((migration) => migration.executedAt === void 0).map((migration) => migration.name));
|
|
3947
|
-
if (pending.length > 0) found.push({
|
|
3948
|
-
target,
|
|
3949
|
-
pending
|
|
3950
|
-
});
|
|
3951
|
-
}
|
|
3952
|
-
return found;
|
|
3953
|
-
}
|
|
3954
|
-
/**
|
|
3955
|
-
* The targets this run covers, with every folder under `db/` accounted for.
|
|
3956
|
-
*
|
|
3957
|
-
* A folder that names no construct is an error rather than something to skip:
|
|
3958
|
-
* a typo in its name is otherwise a migration that silently never runs.
|
|
3959
|
-
*/
|
|
3960
|
-
async function targetsFor(options) {
|
|
3961
|
-
const targets = (0, _geekmidas_manifest.migrationTargets)(options.manifest);
|
|
3962
|
-
const folders = targets.map((target) => target.folder);
|
|
3963
|
-
for (const folder of await foldersIn((0, node_path.join)(options.root, _geekmidas_manifest.MIGRATIONS_ROOT))) {
|
|
3964
|
-
const relative = `${_geekmidas_manifest.MIGRATIONS_ROOT}/${folder}`;
|
|
3965
|
-
if (!folders.includes(relative)) throw new UnknownMigrationFolder(relative, folders);
|
|
3966
|
-
await checkLayout(options.root, relative);
|
|
3967
|
-
}
|
|
3968
|
-
if (!options.only) return targets;
|
|
3969
|
-
const id = (0, _geekmidas_manifest.canonicalId)(options.only);
|
|
3970
|
-
const chosen = targets.filter((target) => target.id === id);
|
|
3971
|
-
if (chosen.length === 0) throw new NoSuchMigrationTarget(options.only, targets.map((target) => target.id));
|
|
3972
|
-
return chosen;
|
|
3973
|
-
}
|
|
3974
|
-
/**
|
|
3975
|
-
* A construct's folder holds `migrations/` and `seeds/`, and nothing else —
|
|
3976
|
-
* anything beside them is a file or folder nothing would ever run.
|
|
3977
|
-
*/
|
|
3978
|
-
async function checkLayout(root, folder) {
|
|
3979
|
-
const entries = await (0, node_fs_promises.readdir)((0, node_path.join)(root, folder), { withFileTypes: true });
|
|
3980
|
-
const loose = entries.filter((entry) => entry.isFile() && !entry.name.startsWith(".")).map((entry) => entry.name);
|
|
3981
|
-
if (loose.length > 0) throw new MigrationsOutsideFolder(folder, loose);
|
|
3982
|
-
for (const entry of entries) if (entry.isDirectory() && !_geekmidas_manifest.DATABASE_FOLDERS.includes(entry.name)) throw new UnknownDatabaseFolder(`${folder}/${entry.name}`);
|
|
3983
|
-
}
|
|
3984
|
-
async function foldersIn(dir) {
|
|
3985
|
-
return (await (0, node_fs_promises.readdir)(dir, { withFileTypes: true }).catch((error) => {
|
|
3986
|
-
if (error.code === "ENOENT") return [];
|
|
3987
|
-
throw error;
|
|
3988
|
-
})).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
3989
|
-
}
|
|
3990
|
-
/**
|
|
3991
|
-
* A migrator for one target, connected as its owner.
|
|
3992
|
-
*
|
|
3993
|
-
* No `migrationTableSchema`: the owner role's `search_path` is pinned to the
|
|
3994
|
-
* construct's schema, so the history lands there — `app.kysely_migration` for
|
|
3995
|
-
* the database, `auth_database.kysely_migration` for its tenant — and two
|
|
3996
|
-
* constructs in one Postgres never share one.
|
|
3997
|
-
*
|
|
3998
|
-
* Unordered migrations are allowed. Files are timestamped, so a branch merged
|
|
3999
|
-
* after another's migration ran carries an older one; refusing it would make
|
|
4000
|
-
* every such merge a manual repair.
|
|
4001
|
-
*/
|
|
4002
|
-
async function withMigrator(options, target, run) {
|
|
4003
|
-
return withOwner(options, target, (db, { Migrator, sql }) => run(new Migrator({
|
|
4004
|
-
db,
|
|
4005
|
-
provider: folderProvider((0, node_path.join)(options.root, target.migrations), sql),
|
|
4006
|
-
allowUnorderedMigrations: true
|
|
4007
|
-
})));
|
|
4008
|
-
}
|
|
4009
|
-
/**
|
|
4010
|
-
* One target's database, connected as its owner and closed afterwards — what
|
|
4011
|
-
* migrations and seeds both run as.
|
|
4012
|
-
*/
|
|
4013
|
-
async function withOwner(options, target, run) {
|
|
4014
|
-
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"));
|
|
4015
|
-
const db = new kysely.Kysely({ dialect: new kysely.PostgresDialect({ pool: new pg.default.Pool(poolConfig(ownerUrl(options, target))) }) });
|
|
4016
|
-
try {
|
|
4017
|
-
return await run(db, kysely);
|
|
4018
|
-
} finally {
|
|
4019
|
-
await db.destroy();
|
|
4020
|
-
}
|
|
4021
|
-
}
|
|
4022
|
-
/**
|
|
4023
|
-
* The owner's URL, with no fallback to the runtime one.
|
|
4024
|
-
*
|
|
4025
|
-
* The runtime role can create nothing, so falling back would only move the
|
|
4026
|
-
* failure to the first `CREATE TABLE` and make it read like a permissions
|
|
4027
|
-
* bug. The one exception is a database declared `roles: false`, where there is
|
|
4028
|
-
* no owner — both URLs are the master credential by design.
|
|
4029
|
-
*/
|
|
4030
|
-
function ownerUrl(options, target) {
|
|
4031
|
-
const key = (0, _geekmidas_manifest.provideKey)(target.id, "ownerUrl");
|
|
4032
|
-
const owner = options.env[key];
|
|
4033
|
-
if (owner) return owner;
|
|
4034
|
-
const database = options.manifest[target.of ?? target.id];
|
|
4035
|
-
if (database?.kind === "database" && database.roles === false) {
|
|
4036
|
-
const url = options.env[(0, _geekmidas_manifest.provideKey)(target.id, "url")];
|
|
4037
|
-
if (url) return url;
|
|
4038
|
-
}
|
|
4039
|
-
throw new NoOwnerCredential(target.id, key);
|
|
4040
|
-
}
|
|
4041
|
-
/**
|
|
4042
|
-
* Pool settings for a URL, with `?search_path=` as the startup option Postgres
|
|
4043
|
-
* reads. Only a `roles: false` URL carries it — a role pins its own.
|
|
4044
|
-
*/
|
|
4045
|
-
function poolConfig(url) {
|
|
4046
|
-
const parsed = new URL(url);
|
|
4047
|
-
const searchPath = parsed.searchParams.get("search_path");
|
|
4048
|
-
if (!searchPath) return { connectionString: url };
|
|
4049
|
-
parsed.searchParams.delete("search_path");
|
|
4050
|
-
return {
|
|
4051
|
-
connectionString: parsed.toString(),
|
|
4052
|
-
options: `-c search_path=${searchPath}`
|
|
4053
|
-
};
|
|
4054
|
-
}
|
|
4055
|
-
/** Kysely as the project resolves it from `from`. */
|
|
4056
|
-
async function kyselyFrom(from) {
|
|
4057
|
-
const require$2 = (0, node_module.createRequire)(from);
|
|
4058
|
-
try {
|
|
4059
|
-
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)]);
|
|
4060
|
-
return {
|
|
4061
|
-
Kysely: main.Kysely,
|
|
4062
|
-
PostgresDialect: main.PostgresDialect,
|
|
4063
|
-
sql: main.sql,
|
|
4064
|
-
Migrator: migration.Migrator
|
|
4065
|
-
};
|
|
4066
|
-
} catch {
|
|
4067
|
-
throw new KyselyNotFound(from);
|
|
4068
|
-
}
|
|
4069
|
-
}
|
|
4070
|
-
//#endregion
|
|
4071
|
-
//#region src/compose/docker.ts
|
|
4072
|
-
/**
|
|
4073
|
-
* The Docker operations `gkm compose` needs, behind an interface.
|
|
4074
|
-
*
|
|
4075
|
-
* Small on purpose, like reconcile's: the rules about *when* each runs — the
|
|
4076
|
-
* registry is asked before anything is pulled, nothing is started for a tag
|
|
4077
|
-
* that is missing — are asserted against a fake, and this file is only the
|
|
4078
|
-
* wiring to the real CLI.
|
|
4079
|
-
*
|
|
4080
|
-
* Every call is an argument array. Image refs, tags and paths come from the
|
|
4081
|
-
* workspace, and none of them reaches a shell.
|
|
4082
|
-
*/
|
|
4083
|
-
/** How long a stack has to pass its health checks. */
|
|
4084
|
-
const WAIT_TIMEOUT_S = 180;
|
|
4085
|
-
/** `docker compose port` printed nothing a port can be read from. */
|
|
4086
|
-
var ServicePortUnknown = class extends Error {
|
|
4087
|
-
service;
|
|
4088
|
-
inside;
|
|
4089
|
-
output;
|
|
4090
|
-
constructor(service, inside, output) {
|
|
4091
|
-
super(`'${service}' publishes no host port for ${inside} (docker said: ${output.trim() || "nothing"}). Is it running? \`docker compose ps\` shows the stack.`);
|
|
4092
|
-
this.service = service;
|
|
4093
|
-
this.inside = inside;
|
|
4094
|
-
this.output = output;
|
|
4095
|
-
this.name = "ServicePortUnknown";
|
|
4096
|
-
}
|
|
4097
|
-
};
|
|
4098
|
-
function compose(stack, args) {
|
|
4099
|
-
return [
|
|
4100
|
-
"compose",
|
|
4101
|
-
"-p",
|
|
4102
|
-
stack.project,
|
|
4103
|
-
"-f",
|
|
4104
|
-
stack.file,
|
|
4105
|
-
...args
|
|
4106
|
-
];
|
|
4107
|
-
}
|
|
4108
|
-
/** Run a program and keep everything it said, whatever it exits with. */
|
|
4109
|
-
function capture(command, args, timeoutMs = 6e4) {
|
|
4110
|
-
return new Promise((resolve, reject) => {
|
|
4111
|
-
const child = (0, node_child_process.spawn)(command, [...args], {
|
|
4112
|
-
stdio: [
|
|
4113
|
-
"ignore",
|
|
4114
|
-
"pipe",
|
|
4115
|
-
"pipe"
|
|
4116
|
-
],
|
|
4117
|
-
shell: false
|
|
4118
|
-
});
|
|
4119
|
-
let stdout = "";
|
|
4120
|
-
let stderr = "";
|
|
4121
|
-
child.stdout.on("data", (chunk) => {
|
|
4122
|
-
stdout += chunk.toString();
|
|
4123
|
-
});
|
|
4124
|
-
child.stderr.on("data", (chunk) => {
|
|
4125
|
-
stderr += chunk.toString();
|
|
4126
|
-
});
|
|
4127
|
-
const timer = setTimeout(() => child.kill("SIGKILL"), timeoutMs);
|
|
4128
|
-
timer.unref?.();
|
|
4129
|
-
child.on("error", (error) => {
|
|
4130
|
-
clearTimeout(timer);
|
|
4131
|
-
reject(error);
|
|
4132
|
-
});
|
|
4133
|
-
child.on("close", (code) => {
|
|
4134
|
-
clearTimeout(timer);
|
|
4135
|
-
resolve({
|
|
4136
|
-
code,
|
|
4137
|
-
stdout,
|
|
4138
|
-
stderr
|
|
4139
|
-
});
|
|
4140
|
-
});
|
|
4141
|
-
});
|
|
4142
|
-
}
|
|
4143
|
-
/**
|
|
4144
|
-
* Whether a failed `docker manifest inspect` means the tag is not there, as
|
|
4145
|
-
* opposed to the registry not answering.
|
|
4146
|
-
*
|
|
4147
|
-
* Read off the message because that is all the CLI gives. Anything not
|
|
4148
|
-
* recognisably "no such tag" is treated as the registry being unreachable:
|
|
4149
|
-
* a registry that refused credentials must not be reported as a tag that was
|
|
4150
|
-
* never pushed, or the fix offered would be the wrong one.
|
|
4151
|
-
*/
|
|
4152
|
-
function isMissingManifest(output) {
|
|
4153
|
-
return /no such manifest|manifest unknown|manifest for .* not found|not found: manifest/i.test(output);
|
|
4154
|
-
}
|
|
4155
|
-
const dockerCompose = {
|
|
4156
|
-
async lookup(ref) {
|
|
4157
|
-
const { code, stdout, stderr } = await capture("docker", [
|
|
4158
|
-
"manifest",
|
|
4159
|
-
"inspect",
|
|
4160
|
-
ref
|
|
4161
|
-
]);
|
|
4162
|
-
if (code === 0) return {
|
|
4163
|
-
ref,
|
|
4164
|
-
status: "found"
|
|
4165
|
-
};
|
|
4166
|
-
const output = `${stderr}\n${stdout}`.trim();
|
|
4167
|
-
return isMissingManifest(output) ? {
|
|
4168
|
-
ref,
|
|
4169
|
-
status: "missing"
|
|
4170
|
-
} : {
|
|
4171
|
-
ref,
|
|
4172
|
-
status: "unreachable",
|
|
4173
|
-
detail: output
|
|
4174
|
-
};
|
|
4175
|
-
},
|
|
4176
|
-
async build(stack, services) {
|
|
4177
|
-
await require_local.run("docker", compose(stack, ["build", ...services]), {
|
|
4178
|
-
cwd: stack.cwd,
|
|
4179
|
-
env: {
|
|
4180
|
-
...process.env,
|
|
4181
|
-
DOCKER_BUILDKIT: "1"
|
|
4182
|
-
}
|
|
4183
|
-
});
|
|
4184
|
-
},
|
|
4185
|
-
async pull(stack, services) {
|
|
4186
|
-
await require_local.run("docker", compose(stack, ["pull", ...services]), { cwd: stack.cwd });
|
|
4187
|
-
},
|
|
4188
|
-
async up(stack, services) {
|
|
4189
|
-
await require_local.run("docker", compose(stack, [
|
|
4190
|
-
"up",
|
|
4191
|
-
"-d",
|
|
4192
|
-
"--wait",
|
|
4193
|
-
`--wait-timeout=${WAIT_TIMEOUT_S}`,
|
|
4194
|
-
...services ? [] : ["--remove-orphans"],
|
|
4195
|
-
...services ?? []
|
|
4196
|
-
]), { cwd: stack.cwd });
|
|
4197
|
-
},
|
|
4198
|
-
async down(stack) {
|
|
4199
|
-
await require_local.run("docker", compose(stack, ["down"]), { cwd: stack.cwd });
|
|
4200
|
-
},
|
|
4201
|
-
async port(stack, service, inside) {
|
|
4202
|
-
const output = await require_local.runOutput("docker", compose(stack, [
|
|
4203
|
-
"port",
|
|
4204
|
-
service,
|
|
4205
|
-
String(inside)
|
|
4206
|
-
]), { cwd: stack.cwd });
|
|
4207
|
-
const port = Number(output.trim().split(":").pop());
|
|
4208
|
-
if (!Number.isInteger(port) || port <= 0) throw new ServicePortUnknown(service, inside, output);
|
|
4209
|
-
return port;
|
|
4210
|
-
},
|
|
4211
|
-
async copyOut(stack, service, from, to) {
|
|
4212
|
-
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(to), { recursive: true });
|
|
4213
|
-
await require_local.run("docker", compose(stack, [
|
|
4214
|
-
"cp",
|
|
4215
|
-
`${service}:${from}`,
|
|
4216
|
-
to
|
|
4217
|
-
]), { cwd: stack.cwd });
|
|
4218
|
-
},
|
|
4219
|
-
async digest(ref) {
|
|
4220
|
-
const { code, stdout } = await capture("docker", [
|
|
4221
|
-
"image",
|
|
4222
|
-
"inspect",
|
|
4223
|
-
"--format={{json .RepoDigests}} {{.Id}}",
|
|
4224
|
-
ref
|
|
4225
|
-
]);
|
|
4226
|
-
if (code !== 0) return void 0;
|
|
4227
|
-
const [json = "[]", id] = stdout.trim().split(" ");
|
|
4228
|
-
const repository = ref.split("@")[0].replace(/:[\w][\w.-]*$/, "");
|
|
4229
|
-
try {
|
|
4230
|
-
const digests = JSON.parse(json);
|
|
4231
|
-
const match = Array.isArray(digests) ? digests.find((entry) => typeof entry === "string" && entry.startsWith(`${repository}@`)) : void 0;
|
|
4232
|
-
if (match) return match.slice(repository.length + 1);
|
|
4233
|
-
} catch {}
|
|
4234
|
-
return id || void 0;
|
|
4235
|
-
}
|
|
4236
|
-
};
|
|
4237
|
-
//#endregion
|
|
4238
|
-
//#region src/compose/images.ts
|
|
4239
|
-
/**
|
|
4240
|
-
* The tag a site's image carries for a release.
|
|
4241
|
-
*
|
|
4242
|
-
* A site's public URLs are inlined when it is built, so one bundle serves one
|
|
4243
|
-
* stage: `v1.4.0-production` and `v1.4.0-staging` are two images of the same
|
|
4244
|
-
* commit. A backend reads its URLs at runtime, so one image serves every stage
|
|
4245
|
-
* and its tag is the release's alone.
|
|
4246
|
-
*/
|
|
4247
|
-
function siteTag(tag, stage) {
|
|
4248
|
-
return `${tag}-${stage}`;
|
|
4249
|
-
}
|
|
4250
|
-
/** Images a tag names that the registry does not have. */
|
|
4251
|
-
var ImageTagNotFound = class extends Error {
|
|
4252
|
-
tag;
|
|
4253
|
-
refs;
|
|
4254
|
-
constructor(tag, refs) {
|
|
4255
|
-
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.`);
|
|
4256
|
-
this.tag = tag;
|
|
4257
|
-
this.refs = refs;
|
|
4258
|
-
this.name = "ImageTagNotFound";
|
|
4259
|
-
}
|
|
4260
|
-
};
|
|
4261
|
-
/** The registry did not answer, or did not let this machine in. */
|
|
4262
|
-
var RegistryUnreachable = class extends Error {
|
|
4263
|
-
ref;
|
|
4264
|
-
detail;
|
|
4265
|
-
constructor(ref, detail) {
|
|
4266
|
-
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>\`).`);
|
|
4267
|
-
this.ref = ref;
|
|
4268
|
-
this.detail = detail;
|
|
4269
|
-
this.name = "RegistryUnreachable";
|
|
4270
|
-
}
|
|
4271
|
-
};
|
|
4272
|
-
/**
|
|
4273
|
-
* Assert every image exists, before anything else happens.
|
|
4274
|
-
*
|
|
4275
|
-
* Asked all at once and reported all at once: a release missing two images is
|
|
4276
|
-
* one problem with two names, and finding the second only after fixing the
|
|
4277
|
-
* first is a slower way to learn the same thing.
|
|
4278
|
-
*
|
|
4279
|
-
* @throws {RegistryUnreachable} when the registry cannot say
|
|
4280
|
-
* @throws {ImageTagNotFound} listing every missing image
|
|
4281
|
-
*/
|
|
4282
|
-
async function assertImagesExist(docker, tag, images) {
|
|
4283
|
-
const lookups = await Promise.all(images.map((image) => docker.lookup(image.ref)));
|
|
4284
|
-
const unreachable = lookups.find((lookup) => lookup.status === "unreachable");
|
|
4285
|
-
if (unreachable?.status === "unreachable") throw new RegistryUnreachable(unreachable.ref, unreachable.detail);
|
|
4286
|
-
const missing = lookups.filter((lookup) => lookup.status === "missing").map((lookup) => lookup.ref);
|
|
4287
|
-
if (missing.length > 0) throw new ImageTagNotFound(tag, missing);
|
|
4288
|
-
}
|
|
4289
|
-
//#endregion
|
|
4290
|
-
//#region src/compose/caddyfile.ts
|
|
4291
|
-
/** The Caddyfile for a stack's edge. */
|
|
4292
|
-
function edgeCaddyfile(sites, options) {
|
|
4293
|
-
const global = options.local ? `{
|
|
4294
|
-
# No ACME and no public DNS: the CA is Caddy's own and the names resolve
|
|
4295
|
-
# to this machine.
|
|
4296
|
-
local_certs
|
|
4297
|
-
auto_https disable_redirects
|
|
4298
|
-
}` : options.email ? `{
|
|
4299
|
-
email ${options.email}
|
|
4300
|
-
}` : "";
|
|
4301
|
-
const blocks = sites.map(({ host, upstream }) => `https://${host} {${options.local ? `
|
|
4302
|
-
tls internal
|
|
4303
|
-
` : ""}
|
|
4304
|
-
# Host and X-Forwarded-* are passed through as Caddy does by default, so
|
|
4305
|
-
# an app builds redirects, cookies and links on the address a caller used.
|
|
4306
|
-
reverse_proxy ${upstream} {
|
|
4307
|
-
# Never buffer: a streamed response — server-sent events, a Next.js
|
|
4308
|
-
# RSC payload — reaches the client as each chunk is written.
|
|
4309
|
-
flush_interval -1
|
|
4310
|
-
}
|
|
4311
|
-
}`);
|
|
4312
|
-
return `${HEADER}${global ? `${global}\n\n` : ""}${blocks.join("\n\n")}\n`;
|
|
4313
|
-
}
|
|
4314
|
-
const HEADER = `# Generated by gkm compose from the construct manifest — do not edit.
|
|
4315
|
-
#
|
|
4316
|
-
# One host per API and site, each in front of its own container.
|
|
4317
|
-
`;
|
|
4318
|
-
//#endregion
|
|
4319
|
-
//#region src/compose/stack.ts
|
|
4320
|
-
/**
|
|
4321
|
-
* The stack a workspace runs as for one stage — pure.
|
|
4322
|
-
*
|
|
4323
|
-
* Everything `gkm compose` writes is decided here, as data: the compose file,
|
|
4324
|
-
* the Caddyfile, one env file per backend, and the image each app runs. The
|
|
4325
|
-
* command around it reads the workspace, asks the registry, writes the files
|
|
4326
|
-
* and starts Docker; none of those decide anything, so all of it can be
|
|
4327
|
-
* asserted without a daemon.
|
|
4328
|
-
*
|
|
4329
|
-
* The values come from the same derivations every other target uses. The
|
|
4330
|
-
* plan is reconcile's `planFor`, the keys an app reads are `appEnvKeys`, and
|
|
4331
|
-
* every URL is `envFor`'s — asked twice, once with each app at its public
|
|
4332
|
-
* address and once on the compose network, because a browser and a sibling
|
|
4333
|
-
* service reach the same API by different names.
|
|
4334
|
-
*/
|
|
4335
|
-
/** Where a stack's files are written, relative to the workspace root. */
|
|
4336
|
-
function stackDir(stage) {
|
|
4337
|
-
return `.gkm/compose/${stage}`;
|
|
4338
|
-
}
|
|
4339
|
-
/**
|
|
4340
|
-
* The compose project a stage's stack runs as.
|
|
4341
|
-
*
|
|
4342
|
-
* The deploy identity's scope, so two workspaces with one name in different
|
|
4343
|
-
* namespaces never share containers or volumes — and the stage, so a
|
|
4344
|
-
* machine can run two stages side by side.
|
|
4345
|
-
*/
|
|
4346
|
-
function composeProject(identity) {
|
|
4347
|
-
return `${identity.scope}-${identity.stage}`;
|
|
4348
|
-
}
|
|
4349
|
-
/** The ports the edge is published on, and the variables that move them. */
|
|
4350
|
-
const EDGE_PORT_ENV = {
|
|
4351
|
-
https: "GKM_COMPOSE_HTTPS_PORT",
|
|
4352
|
-
http: "GKM_COMPOSE_HTTP_PORT"
|
|
4353
|
-
};
|
|
4354
|
-
/**
|
|
4355
|
-
* Containers this target does not run.
|
|
4356
|
-
*
|
|
4357
|
-
* Object storage is the stage's own — an externally hosted bucket whose URL
|
|
4358
|
-
* the stage's secrets hold — and so is a deployed stage's mail. The AWS
|
|
4359
|
-
* emulator never applies: a stack is a server target, whose events are
|
|
4360
|
-
* pg-boss.
|
|
4361
|
-
*/
|
|
4362
|
-
const NOT_RUN = {
|
|
4363
|
-
minio: true,
|
|
4364
|
-
localstack: true,
|
|
4365
|
-
caddy: true
|
|
4366
|
-
};
|
|
4367
|
-
/** Kinds whose `<ID>_URL` a stage must be given, because nothing here runs them. */
|
|
4368
|
-
const EXTERNAL_KINDS = {
|
|
4369
|
-
objects: true,
|
|
4370
|
-
"file-server": true
|
|
4371
|
-
};
|
|
4372
|
-
/** The S3 client's credentials, read beside a bucket's URL when it has them. */
|
|
4373
|
-
const STORAGE_KEYS = [
|
|
4374
|
-
"AWS_ACCESS_KEY_ID",
|
|
4375
|
-
"AWS_SECRET_ACCESS_KEY",
|
|
4376
|
-
"AWS_REGION"
|
|
4377
|
-
];
|
|
4378
|
-
/** A bucket the stack needs and the stage was not given. */
|
|
4379
|
-
var BucketNotConfigured = class extends Error {
|
|
4380
|
-
app;
|
|
4381
|
-
id;
|
|
4382
|
-
key;
|
|
4383
|
-
stage;
|
|
4384
|
-
constructor(app, id, key, stage) {
|
|
4385
|
-
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}`);
|
|
4386
|
-
this.app = app;
|
|
4387
|
-
this.id = id;
|
|
4388
|
-
this.key = key;
|
|
4389
|
-
this.stage = stage;
|
|
4390
|
-
this.name = "BucketNotConfigured";
|
|
4391
|
-
}
|
|
4392
|
-
};
|
|
4393
|
-
/** A value only the stage's secrets can hold, and they do not. */
|
|
4394
|
-
var StageSecretMissing = class extends Error {
|
|
4395
|
-
app;
|
|
4396
|
-
key;
|
|
4397
|
-
stage;
|
|
4398
|
-
constructor(app, key, stage) {
|
|
4399
|
-
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}`);
|
|
4400
|
-
this.app = app;
|
|
4401
|
-
this.key = key;
|
|
4402
|
-
this.stage = stage;
|
|
4403
|
-
this.name = "StageSecretMissing";
|
|
4404
|
-
}
|
|
4405
|
-
};
|
|
4406
|
-
/** A deployed stage's secrets with no seed to derive its passwords from. */
|
|
4407
|
-
var StageSeedMissing = class extends Error {
|
|
4408
|
-
stage;
|
|
4409
|
-
constructor(stage) {
|
|
4410
|
-
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.`);
|
|
4411
|
-
this.stage = stage;
|
|
4412
|
-
this.name = "StageSeedMissing";
|
|
4413
|
-
}
|
|
4414
|
-
};
|
|
4415
|
-
/** A workspace with no API and no site to run. */
|
|
4416
|
-
var NothingToCompose = class extends Error {
|
|
4417
|
-
root;
|
|
4418
|
-
constructor(root) {
|
|
4419
|
-
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.`);
|
|
4420
|
-
this.root = root;
|
|
4421
|
-
this.name = "NothingToCompose";
|
|
4422
|
-
}
|
|
4423
|
-
};
|
|
4424
|
-
/** A value that cannot be written to an env file a line at a time. */
|
|
4425
|
-
var EnvValueMultiline = class extends Error {
|
|
4426
|
-
app;
|
|
4427
|
-
key;
|
|
4428
|
-
constructor(app, key) {
|
|
4429
|
-
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.`);
|
|
4430
|
-
this.app = app;
|
|
4431
|
-
this.key = key;
|
|
4432
|
-
this.name = "EnvValueMultiline";
|
|
4433
|
-
}
|
|
4434
|
-
};
|
|
4435
|
-
/** The services a stack's apps wait on and the edge waits on in turn. */
|
|
4436
|
-
const HEALTHY = { condition: "service_healthy" };
|
|
4437
|
-
/**
|
|
4438
|
-
* An app's health check: its own server, asked on 127.0.0.1 — not
|
|
4439
|
-
* `localhost`, which resolves to `::1` first in an Alpine container while a
|
|
4440
|
-
* server may listen on IPv4 alone.
|
|
4441
|
-
*/
|
|
4442
|
-
function probe(port, path) {
|
|
4443
|
-
return {
|
|
4444
|
-
test: [
|
|
4445
|
-
"CMD",
|
|
4446
|
-
"wget",
|
|
4447
|
-
"-q",
|
|
4448
|
-
"-O",
|
|
4449
|
-
"/dev/null",
|
|
4450
|
-
`http://127.0.0.1:${port}${path}`
|
|
4451
|
-
],
|
|
4452
|
-
interval: "5s",
|
|
4453
|
-
timeout: "3s",
|
|
4454
|
-
retries: 20,
|
|
4455
|
-
start_period: "10s"
|
|
4456
|
-
};
|
|
4457
|
-
}
|
|
4458
|
-
/** The stack for one stage. */
|
|
4459
|
-
function composeStack(input) {
|
|
4460
|
-
const { workspace, manifest, stage, identity } = input;
|
|
4461
|
-
const local = stage === workspace.stages.local;
|
|
4462
|
-
const project = composeProject(identity);
|
|
4463
|
-
const custom = input.secrets?.custom ?? {};
|
|
4464
|
-
const https = input.ports?.https ?? 443;
|
|
4465
|
-
const http = input.ports?.http ?? 80;
|
|
4466
|
-
const plan = require_sniffer.planFor(manifest, stage, (0, _geekmidas_manifest.provisionOrder)(manifest), {
|
|
4467
|
-
localStage: workspace.stages.local,
|
|
4468
|
-
events: require_fakes.DEFAULT_EVENTS.server,
|
|
4469
|
-
cache: require_fakes.DEFAULT_CACHE.server,
|
|
4470
|
-
edge: false
|
|
4471
|
-
});
|
|
4472
|
-
const byId = new Map(plan.resources.map((r) => [r.id, r]));
|
|
4473
|
-
const seed = local ? void 0 : input.secrets?.seed;
|
|
4474
|
-
if (!local && !seed) throw new StageSeedMissing(stage);
|
|
4475
|
-
const credential = {
|
|
4476
|
-
master: local ? "geekmidas" : require_sniffer.localRolePassword(workspace.name, plan, "master", seed),
|
|
4477
|
-
...seed ? { seed } : {}
|
|
4478
|
-
};
|
|
4479
|
-
const infra = plan.containers.filter((c) => !NOT_RUN[c]).filter((c) => local || c !== "mailpit").sort();
|
|
4480
|
-
const apps = stackApps(workspace, manifest, plan, {
|
|
4481
|
-
local,
|
|
4482
|
-
stage,
|
|
4483
|
-
https,
|
|
4484
|
-
identity,
|
|
4485
|
-
images: input.images
|
|
4486
|
-
});
|
|
4487
|
-
if (apps.length === 0) throw new NothingToCompose(workspace.root);
|
|
4488
|
-
const derivation = {
|
|
4489
|
-
project: workspace.name,
|
|
4490
|
-
master: credential.master,
|
|
4491
|
-
...credential.seed ? { seed: credential.seed } : {}
|
|
4492
|
-
};
|
|
4493
|
-
const outside = require_sniffer.networkEnv(plan, {
|
|
4494
|
-
...derivation,
|
|
4495
|
-
addresses: Object.fromEntries(apps.map((app) => [app.id, app.url]))
|
|
4496
|
-
});
|
|
4497
|
-
const inside = require_sniffer.networkEnv(plan, {
|
|
4498
|
-
...derivation,
|
|
4499
|
-
addresses: Object.fromEntries(apps.map((app) => [app.id, `http://${app.name}:${app.port}`]))
|
|
4500
|
-
});
|
|
4501
|
-
const owners = /* @__PURE__ */ new Map();
|
|
4502
|
-
for (const resource of plan.resources) {
|
|
4503
|
-
owners.set(resource.envKey, resource);
|
|
4504
|
-
if (resource.kind === "email") {
|
|
4505
|
-
owners.set((0, _geekmidas_manifest.provideKey)(resource.id, "from"), resource);
|
|
4506
|
-
owners.set((0, _geekmidas_manifest.provideKey)(resource.id, "inboxUrl"), resource);
|
|
4507
|
-
}
|
|
4508
|
-
if (resource.kind === "external-api") owners.set((0, _geekmidas_manifest.provideKey)(resource.id, "credentials"), resource);
|
|
4509
|
-
}
|
|
4510
|
-
const surfaceUrls = new Map(plan.resources.filter((r) => r.kind === "rest-api" || r.kind === "site").map((r) => [r.envKey, r.id]));
|
|
4511
|
-
const appById = new Map(apps.map((app) => [app.id, app]));
|
|
4512
|
-
/**
|
|
4513
|
-
* What a key resolves to for one app, from the stage's secrets where only
|
|
4514
|
-
* they can hold it, and the derivations otherwise.
|
|
4515
|
-
*/
|
|
4516
|
-
const valueFor = (app, key, owner) => {
|
|
4517
|
-
if (owner && EXTERNAL_KINDS[owner.kind] && key === owner.envKey) {
|
|
4518
|
-
const value = custom[key];
|
|
4519
|
-
if (value === void 0) throw new BucketNotConfigured(app.name, owner.id, key, stage);
|
|
4520
|
-
return value;
|
|
4521
|
-
}
|
|
4522
|
-
if (owner && suppliedBy(owner, key, local)) {
|
|
4523
|
-
const value = custom[key];
|
|
4524
|
-
if (value === void 0) throw new StageSecretMissing(app.name, key, stage);
|
|
4525
|
-
return value;
|
|
4526
|
-
}
|
|
4527
|
-
if (custom[key] !== void 0) return custom[key];
|
|
4528
|
-
const surface = surfaceUrls.get(key);
|
|
4529
|
-
if (app.kind === "rest-api" && surface && surface !== app.id) return inside[key];
|
|
4530
|
-
return outside[key];
|
|
4531
|
-
};
|
|
4532
|
-
for (const app of apps) {
|
|
4533
|
-
if (app.kind === "site") {
|
|
4534
|
-
const site = manifest[app.id];
|
|
4535
|
-
if (site?.kind !== "site") continue;
|
|
4536
|
-
const args = {};
|
|
4537
|
-
for (const [key, source] of Object.entries((0, _geekmidas_manifest.publicEnvFor)(site, manifest))) {
|
|
4538
|
-
const value = valueFor(app, source, owners.get(source));
|
|
4539
|
-
if (value !== void 0) args[key] = value;
|
|
4540
|
-
}
|
|
4541
|
-
if (app.build && Object.keys(args).length > 0) app.build.args = args;
|
|
4542
|
-
continue;
|
|
4543
|
-
}
|
|
4544
|
-
const keys = require_sniffer.appEnvKeys(manifest, app.name, input.runnables) ?? /* @__PURE__ */ new Set();
|
|
4545
|
-
const values = {};
|
|
4546
|
-
for (const key of [...keys].sort()) {
|
|
4547
|
-
const owner = owners.get(key) ?? storageOwner(key, plan);
|
|
4548
|
-
if (!local && owner?.kind === "email" && key.endsWith("_INBOX_URL")) continue;
|
|
4549
|
-
if (STORAGE_KEYS.includes(key)) {
|
|
4550
|
-
if (custom[key] !== void 0) values[key] = custom[key];
|
|
4551
|
-
continue;
|
|
4552
|
-
}
|
|
4553
|
-
const value = valueFor(app, key, owner);
|
|
4554
|
-
if (value !== void 0) values[key] = value;
|
|
4555
|
-
}
|
|
4556
|
-
const trusted = (0, _geekmidas_manifest.provideKey)(app.id, "trustedOrigins");
|
|
4557
|
-
if (keys.has(trusted)) {
|
|
4558
|
-
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}`);
|
|
4559
|
-
const origins = [...(values[trusted] ?? "").split(",").filter(Boolean), ...internal];
|
|
4560
|
-
values[trusted] = [...new Set(origins)].join(",");
|
|
4561
|
-
}
|
|
4562
|
-
app.env = {
|
|
4563
|
-
...values,
|
|
4564
|
-
NODE_ENV: "production",
|
|
4565
|
-
PORT: String(app.port),
|
|
4566
|
-
STAGE: stage
|
|
4567
|
-
};
|
|
4568
|
-
for (const [key, value] of Object.entries(app.env)) if (/[\r\n]/.test(value)) throw new EnvValueMultiline(app.name, key);
|
|
4569
|
-
}
|
|
4570
|
-
const compose = stackFile({
|
|
4571
|
-
project,
|
|
4572
|
-
plan,
|
|
4573
|
-
infra,
|
|
4574
|
-
apps,
|
|
4575
|
-
master: credential.master,
|
|
4576
|
-
https,
|
|
4577
|
-
http
|
|
4578
|
-
});
|
|
4579
|
-
const caddyfile = edgeCaddyfile(apps.map((app) => ({
|
|
4580
|
-
host: app.host,
|
|
4581
|
-
upstream: `${app.name}:${app.port}`
|
|
4582
|
-
})), { local });
|
|
4583
|
-
const dockerfiles = {};
|
|
4584
|
-
for (const app of apps) {
|
|
4585
|
-
if (!app.build) continue;
|
|
4586
|
-
dockerfiles[app.build.dockerfile] = app.kind === "site" ? require_sniffer.siteDockerfile(app.name, workspace.apps[app.name], {
|
|
4587
|
-
turboPackage: input.packages?.[app.name] ?? app.name,
|
|
4588
|
-
packageManager: input.packageManager ?? "pnpm",
|
|
4589
|
-
manifest
|
|
4590
|
-
}) : require_sniffer.generateSlimDockerfile({
|
|
4591
|
-
imageName: app.name,
|
|
4592
|
-
baseImage: "node:22-alpine",
|
|
4593
|
-
port: app.port,
|
|
4594
|
-
healthCheckPath: "/health",
|
|
4595
|
-
prebuilt: true,
|
|
4596
|
-
packageManager: input.packageManager ?? "pnpm",
|
|
4597
|
-
bundle: `${app.path}/.gkm/server/dist/server.mjs`
|
|
4598
|
-
});
|
|
4599
|
-
}
|
|
4600
|
-
return {
|
|
4601
|
-
project,
|
|
4602
|
-
stage,
|
|
4603
|
-
local,
|
|
4604
|
-
apps,
|
|
4605
|
-
infra,
|
|
4606
|
-
compose,
|
|
4607
|
-
caddyfile,
|
|
4608
|
-
dockerfiles,
|
|
4609
|
-
plan,
|
|
4610
|
-
credential
|
|
4611
|
-
};
|
|
4612
|
-
/** Whether only the stage's secrets can supply `key` to `owner`. */
|
|
4613
|
-
function suppliedBy(owner, key, isLocal) {
|
|
4614
|
-
if (owner.kind === "credential") return true;
|
|
4615
|
-
if (owner.kind === "external-api") return key === (0, _geekmidas_manifest.provideKey)(owner.id, "credentials");
|
|
4616
|
-
if (isLocal) return false;
|
|
4617
|
-
if (owner.kind === "secret" || owner.kind === "encryption") return true;
|
|
4618
|
-
return owner.kind === "email" && !key.endsWith("_INBOX_URL");
|
|
4619
|
-
}
|
|
4620
|
-
}
|
|
4621
|
-
/** Which bucket an S3 credential key belongs to — the first one declared. */
|
|
4622
|
-
function storageOwner(key, plan) {
|
|
4623
|
-
if (!STORAGE_KEYS.includes(key)) return void 0;
|
|
4624
|
-
return plan.resources.find((r) => r.kind === "objects");
|
|
4625
|
-
}
|
|
4626
|
-
/** The apps a stack runs: every API and every site, at its stage address. */
|
|
4627
|
-
function stackApps(workspace, manifest, plan, options) {
|
|
4628
|
-
const apps = [];
|
|
4629
|
-
const resources = new Map(plan.resources.map((r) => [r.id, r]));
|
|
4630
|
-
for (const [id, declaration] of Object.entries(manifest)) {
|
|
4631
|
-
if (declaration.kind !== "rest-api" && declaration.kind !== "site") continue;
|
|
4632
|
-
const name = require_derive.appKey(id);
|
|
4633
|
-
const app = workspace.apps[name];
|
|
4634
|
-
const resource = resources.get(id);
|
|
4635
|
-
if (!app || !resource || !app.port) continue;
|
|
4636
|
-
const host = hostOf(name, app, workspace, resource, options);
|
|
4637
|
-
const tag = declaration.kind === "site" ? siteTag(options.images.tag, options.stage) : options.images.tag;
|
|
4638
|
-
const ref = require_sniffer.validateImageRef(require_store.imageRef(options.identity, name, options.images.registry, tag));
|
|
4639
|
-
apps.push({
|
|
4640
|
-
app: name,
|
|
4641
|
-
name,
|
|
4642
|
-
id,
|
|
4643
|
-
kind: declaration.kind,
|
|
4644
|
-
path: app.path,
|
|
4645
|
-
port: app.port,
|
|
4646
|
-
host,
|
|
4647
|
-
url: `https://${host}${options.local && options.https !== 443 ? `:${options.https}` : ""}`,
|
|
4648
|
-
ref,
|
|
4649
|
-
tag,
|
|
4650
|
-
...options.images.mode === "build" ? { build: { dockerfile: `${stackDir(options.stage)}/Dockerfile.${name}` } } : {}
|
|
4651
|
-
});
|
|
4652
|
-
}
|
|
4653
|
-
return apps.sort((a, b) => a.name.localeCompare(b.name));
|
|
4654
|
-
}
|
|
4655
|
-
/**
|
|
4656
|
-
* The host an app answers on for a stage: the `gkm dev` edge's
|
|
4657
|
-
* `*.localhost` names for the local stage, and the stage's domain — by the
|
|
4658
|
-
* same rule a deploy follows — for any other.
|
|
4659
|
-
*/
|
|
4660
|
-
function hostOf(name, app, workspace, resource, options) {
|
|
4661
|
-
if (options.local) return require_sniffer.hostFor(resource, workspace.name);
|
|
4662
|
-
return require_sniffer.resolveHost(name, app, options.stage, workspace.deploy?.domains, require_sniffer.isMainFrontendApp(name, app, workspace.apps));
|
|
4663
|
-
}
|
|
4664
|
-
/** The compose document: the infrastructure, the apps, and the edge. */
|
|
4665
|
-
function stackFile(options) {
|
|
4666
|
-
const { project, plan, infra, apps } = options;
|
|
4667
|
-
const derived = require_sniffer.composeFor({
|
|
4668
|
-
...plan,
|
|
4669
|
-
containers: [...infra]
|
|
4670
|
-
}, {
|
|
4671
|
-
project,
|
|
4672
|
-
ports: Object.fromEntries(require_sniffer.portKeys(infra, plan.fakes).map((key) => [key, 0]))
|
|
4673
|
-
});
|
|
4674
|
-
const services = {};
|
|
4675
|
-
const volumes = {
|
|
4676
|
-
...derived.volumes,
|
|
4677
|
-
"caddy-data": {},
|
|
4678
|
-
"caddy-config": {}
|
|
4679
|
-
};
|
|
4680
|
-
for (const name of infra) {
|
|
4681
|
-
const { ports: _ports, depends_on, profiles: _profiles, ...service } = derived.services[name];
|
|
4682
|
-
services[name] = {
|
|
4683
|
-
...service,
|
|
4684
|
-
...depends_on?.length ? { depends_on: Object.fromEntries(depends_on.map((dep) => [dep, HEALTHY])) } : {}
|
|
4685
|
-
};
|
|
4686
|
-
}
|
|
4687
|
-
const postgres = services.postgres;
|
|
4688
|
-
if (postgres) {
|
|
4689
|
-
postgres.environment = {
|
|
4690
|
-
...postgres.environment,
|
|
4691
|
-
POSTGRES_PASSWORD: options.master
|
|
4692
|
-
};
|
|
4693
|
-
postgres.ports = ["127.0.0.1::5432"];
|
|
4694
|
-
}
|
|
4695
|
-
for (const app of apps) services[app.name] = {
|
|
4696
|
-
image: app.ref,
|
|
4697
|
-
...app.build ? { build: {
|
|
4698
|
-
context: "../../..",
|
|
4699
|
-
dockerfile: app.build.dockerfile,
|
|
4700
|
-
...app.build.args ? { args: app.build.args } : {}
|
|
4701
|
-
} } : {},
|
|
4702
|
-
restart: "unless-stopped",
|
|
4703
|
-
...app.kind === "rest-api" ? {
|
|
4704
|
-
env_file: [{
|
|
4705
|
-
path: `./${app.name}.env`,
|
|
4706
|
-
format: "raw"
|
|
4707
|
-
}],
|
|
4708
|
-
...infra.length ? { depends_on: Object.fromEntries(infra.map((name) => [name, HEALTHY])) } : {}
|
|
4709
|
-
} : {},
|
|
4710
|
-
healthcheck: probe(app.port, app.kind === "site" ? "/" : "/health")
|
|
4711
|
-
};
|
|
4712
|
-
services.caddy = {
|
|
4713
|
-
image: require_sniffer.DEFAULT_IMAGES.caddy,
|
|
4714
|
-
restart: "unless-stopped",
|
|
4715
|
-
ports: [`${options.https}:443`, `${options.http}:80`],
|
|
4716
|
-
volumes: [
|
|
4717
|
-
"./Caddyfile:/etc/caddy/Caddyfile:ro",
|
|
4718
|
-
"caddy-data:/data",
|
|
4719
|
-
"caddy-config:/config"
|
|
4720
|
-
],
|
|
4721
|
-
depends_on: Object.fromEntries(apps.map((app) => [app.name, HEALTHY])),
|
|
4722
|
-
healthcheck: {
|
|
4723
|
-
test: [
|
|
4724
|
-
"CMD",
|
|
4725
|
-
"caddy",
|
|
4726
|
-
"version"
|
|
4727
|
-
],
|
|
4728
|
-
interval: "10s",
|
|
4729
|
-
timeout: "5s",
|
|
4730
|
-
retries: 5
|
|
4731
|
-
}
|
|
4732
|
-
};
|
|
4733
|
-
return {
|
|
4734
|
-
name: project,
|
|
4735
|
-
services,
|
|
4736
|
-
volumes
|
|
4737
|
-
};
|
|
4738
|
-
}
|
|
4739
|
-
/** Render an app's env file: one `KEY=value` per line, read raw by compose. */
|
|
4740
|
-
function envFile(env) {
|
|
4741
|
-
return `${Object.entries(env).sort(([a], [b]) => a.localeCompare(b)).map(([key, value]) => `${key}=${value}`).join("\n")}\n`;
|
|
4742
|
-
}
|
|
4743
|
-
//#endregion
|
|
4744
3790
|
//#region src/compose/index.ts
|
|
4745
3791
|
/**
|
|
4746
3792
|
* `gkm compose` — a workspace's APIs and sites for one stage, as one Docker
|
|
4747
3793
|
* Compose stack behind Caddy.
|
|
4748
3794
|
*
|
|
4749
|
-
*
|
|
4750
|
-
*
|
|
4751
|
-
*
|
|
4752
|
-
*
|
|
4753
|
-
*
|
|
4754
|
-
* - The databases exist before the apps do. Postgres comes up alone, its
|
|
4755
|
-
* databases, roles and grants are created and its migrations applied from
|
|
4756
|
-
* this machine, and only then do the apps start — so an app never boots
|
|
4757
|
-
* against a schema that is not there yet.
|
|
4758
|
-
* - What a stage runs is recorded. Each app's tag and the digest it resolved
|
|
4759
|
-
* to are written to the stage's state, so "what is this stage running" has
|
|
4760
|
-
* an exact answer.
|
|
4761
|
-
*
|
|
4762
|
-
* Docker, the registry, the build and the clock are injected, so the rules are
|
|
4763
|
-
* asserted without a daemon; the defaults are the real ones.
|
|
3795
|
+
* A convenience over `gkm deploy --target compose`: the stage defaults to the
|
|
3796
|
+
* local one, `--build`/`--pull` choose the images whatever `--tag` says, and
|
|
3797
|
+
* `--down` stops the stack. Everything else — the order, the image check, the
|
|
3798
|
+
* state, the health checks — is the compose target's, run through the same
|
|
3799
|
+
* `deploy()` as any other target, so there is one way a stack comes up.
|
|
4764
3800
|
*/
|
|
4765
|
-
const logger$4 = console;
|
|
4766
3801
|
/** `--build` and `--pull` together ask for two different things. */
|
|
4767
3802
|
var ComposeModeConflict = class extends Error {
|
|
4768
3803
|
constructor() {
|
|
@@ -4770,319 +3805,51 @@ var ComposeModeConflict = class extends Error {
|
|
|
4770
3805
|
this.name = "ComposeModeConflict";
|
|
4771
3806
|
}
|
|
4772
3807
|
};
|
|
4773
|
-
/** A build with no commit to tag its images with. */
|
|
4774
|
-
var NoGitRevision = class extends Error {
|
|
4775
|
-
root;
|
|
4776
|
-
constructor(root) {
|
|
4777
|
-
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.`);
|
|
4778
|
-
this.root = root;
|
|
4779
|
-
this.name = "NoGitRevision";
|
|
4780
|
-
}
|
|
4781
|
-
};
|
|
4782
|
-
/** A port variable that does not hold a port. */
|
|
4783
|
-
var EdgePortInvalid = class extends Error {
|
|
4784
|
-
variable;
|
|
4785
|
-
value;
|
|
4786
|
-
constructor(variable, value) {
|
|
4787
|
-
super(`${variable}=${value} is not a port. Set it to a number between 1 and 65535, or unset it for the default.`);
|
|
4788
|
-
this.variable = variable;
|
|
4789
|
-
this.value = value;
|
|
4790
|
-
this.name = "EdgePortInvalid";
|
|
4791
|
-
}
|
|
4792
|
-
};
|
|
4793
|
-
/** The commit HEAD is at, short — and `-dirty` when the tree has changes. */
|
|
4794
|
-
async function gitRevision(root) {
|
|
4795
|
-
let sha;
|
|
4796
|
-
try {
|
|
4797
|
-
sha = (await require_local.runOutput("git", [
|
|
4798
|
-
"rev-parse",
|
|
4799
|
-
"--short",
|
|
4800
|
-
"HEAD"
|
|
4801
|
-
], { cwd: root })).trim();
|
|
4802
|
-
} catch {
|
|
4803
|
-
throw new NoGitRevision(root);
|
|
4804
|
-
}
|
|
4805
|
-
if (!sha) throw new NoGitRevision(root);
|
|
4806
|
-
return (await require_local.runOutput("git", ["status", "--porcelain"], { cwd: root }).catch(() => "")).trim() ? `${sha}-dirty` : sha;
|
|
4807
|
-
}
|
|
4808
|
-
/**
|
|
4809
|
-
* Bundle a backend with the gkm that is running — the same build a deploy
|
|
4810
|
-
* makes, so the image holds one file and no dependencies.
|
|
4811
|
-
*/
|
|
4812
|
-
async function bundleWithThisCli(appRoot) {
|
|
4813
|
-
const entry = process.argv[1];
|
|
4814
|
-
if (!entry) return;
|
|
4815
|
-
await require_local.run(process.execPath, [
|
|
4816
|
-
...process.execArgv,
|
|
4817
|
-
entry,
|
|
4818
|
-
"build",
|
|
4819
|
-
"--provider",
|
|
4820
|
-
"server",
|
|
4821
|
-
"--production"
|
|
4822
|
-
], { cwd: appRoot });
|
|
4823
|
-
}
|
|
4824
|
-
const defaultDeps = {
|
|
4825
|
-
docker: dockerCompose,
|
|
4826
|
-
bundle: bundleWithThisCli,
|
|
4827
|
-
revision: gitRevision
|
|
4828
|
-
};
|
|
4829
|
-
/** The edge's ports, from the environment — 443 and 80 when unset. */
|
|
4830
|
-
function edgePorts(env = process.env) {
|
|
4831
|
-
const read = (variable, fallback) => {
|
|
4832
|
-
const value = env[variable];
|
|
4833
|
-
if (value === void 0 || value === "") return fallback;
|
|
4834
|
-
const port = Number(value);
|
|
4835
|
-
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new EdgePortInvalid(variable, value);
|
|
4836
|
-
return port;
|
|
4837
|
-
};
|
|
4838
|
-
return {
|
|
4839
|
-
https: read(EDGE_PORT_ENV.https, 443),
|
|
4840
|
-
http: read(EDGE_PORT_ENV.http, 80)
|
|
4841
|
-
};
|
|
4842
|
-
}
|
|
4843
3808
|
/** `gkm compose`. */
|
|
4844
3809
|
async function composeCommand(options = {}, deps = {}) {
|
|
4845
|
-
const { docker, bundle, revision } = {
|
|
4846
|
-
...defaultDeps,
|
|
4847
|
-
...deps
|
|
4848
|
-
};
|
|
4849
|
-
const env = deps.env ?? process.env;
|
|
4850
3810
|
if (options.build && options.pull) throw new ComposeModeConflict();
|
|
4851
|
-
const
|
|
4852
|
-
const
|
|
3811
|
+
const cwd = (0, node_path.resolve)(options.cwd ?? process.cwd());
|
|
3812
|
+
const { workspace } = await require_config.loadWorkspaceConfig(cwd);
|
|
4853
3813
|
const stage = options.stage ?? workspace.stages.local;
|
|
4854
|
-
if (!(stage === workspace.stages.local)) require_workspace.assertDeployedStage(workspace.stages, stage);
|
|
4855
|
-
const identity = require_store.deployIdentity(workspace, stage);
|
|
4856
|
-
const dir = (0, node_path.join)(root, stackDir(stage));
|
|
4857
|
-
const file = (0, node_path.join)(dir, "docker-compose.yml");
|
|
4858
|
-
const ref = {
|
|
4859
|
-
project: composeProject(identity),
|
|
4860
|
-
file,
|
|
4861
|
-
cwd: root
|
|
4862
|
-
};
|
|
4863
3814
|
if (options.down) {
|
|
4864
|
-
|
|
4865
|
-
|
|
3815
|
+
const ref = {
|
|
3816
|
+
project: require_compose.composeProject(require_state.deployIdentity(workspace, stage)),
|
|
3817
|
+
file: (0, node_path.join)(workspace.root, require_compose.stackDir(stage), "docker-compose.yml"),
|
|
3818
|
+
cwd: workspace.root
|
|
3819
|
+
};
|
|
3820
|
+
await (deps.docker ?? require_compose.dockerCompose).down(ref);
|
|
3821
|
+
console.log(`🛑 Stopped ${ref.project}. Its volumes are kept.`);
|
|
4866
3822
|
return;
|
|
4867
3823
|
}
|
|
4868
|
-
|
|
4869
|
-
const
|
|
4870
|
-
const
|
|
4871
|
-
|
|
4872
|
-
cwd
|
|
4873
|
-
sources,
|
|
4874
|
-
runnables
|
|
4875
|
-
});
|
|
4876
|
-
const mode = options.build || !options.pull && !options.tag ? "build" : "pull";
|
|
4877
|
-
const tag = options.tag ?? (mode === "pull" ? "latest" : await revision(root));
|
|
4878
|
-
const { secrets, keep } = await stageSecrets(workspace, manifest, stage);
|
|
4879
|
-
const packageManager = require_sniffer.detectPackageManager(root);
|
|
4880
|
-
const stack = composeStack({
|
|
4881
|
-
workspace,
|
|
4882
|
-
manifest,
|
|
4883
|
-
runnables,
|
|
3824
|
+
let finished;
|
|
3825
|
+
const mode = options.build ? "build" : options.pull ? "pull" : void 0;
|
|
3826
|
+
const tag = options.tag ?? (options.pull ? "latest" : void 0);
|
|
3827
|
+
const result = await require_deploy.deploy({
|
|
3828
|
+
cwd,
|
|
4884
3829
|
stage,
|
|
4885
|
-
|
|
4886
|
-
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
secrets,
|
|
4892
|
-
ports: edgePorts(env),
|
|
4893
|
-
packageManager,
|
|
4894
|
-
packages: Object.fromEntries(Object.entries(workspace.apps).map(([name, app]) => [name, require_sniffer.appPackageName(root, name, app.path)]))
|
|
4895
|
-
});
|
|
4896
|
-
if (mode === "pull" && !options.dryRun) await assertImagesExist(docker, tag, stack.apps);
|
|
4897
|
-
if (options.dryRun) keep.skip();
|
|
4898
|
-
else await keep.write();
|
|
4899
|
-
const files = await writeStack(root, dir, stack);
|
|
4900
|
-
printPlan(stack, mode, files);
|
|
4901
|
-
if (options.dryRun) return {
|
|
4902
|
-
stack,
|
|
4903
|
-
files
|
|
4904
|
-
};
|
|
4905
|
-
const store = deps.state ? await deps.state(workspace) : await require_sniffer.createStateStore({
|
|
4906
|
-
config: workspace.state,
|
|
4907
|
-
workspaceRoot: root,
|
|
4908
|
-
workspaceName: workspace.name
|
|
4909
|
-
});
|
|
4910
|
-
const lock = await store.lock(stage, { operation: "compose" });
|
|
4911
|
-
try {
|
|
4912
|
-
const apps = stack.apps.map((app) => app.name);
|
|
4913
|
-
if (mode === "build") {
|
|
4914
|
-
for (const app of stack.apps.filter((a) => a.kind === "rest-api")) {
|
|
4915
|
-
logger$4.log(`\n📦 Bundling ${app.name}…`);
|
|
4916
|
-
await bundle((0, node_path.join)(root, app.path));
|
|
3830
|
+
target: "compose",
|
|
3831
|
+
targets: { compose: require_compose.composeTarget({
|
|
3832
|
+
...deps,
|
|
3833
|
+
...mode ? { mode } : {},
|
|
3834
|
+
report: (done) => {
|
|
3835
|
+
finished = done;
|
|
4917
3836
|
}
|
|
4918
|
-
|
|
4919
|
-
|
|
4920
|
-
}
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
logger$4.log(`\n🗄️ Starting ${stack.infra.join(", ")}…`);
|
|
4926
|
-
await docker.up(ref, stack.infra);
|
|
3837
|
+
}) },
|
|
3838
|
+
...tag ? { tag } : {},
|
|
3839
|
+
...options.dryRun ? { dryRun: true } : {},
|
|
3840
|
+
logger: {
|
|
3841
|
+
info: (message) => console.log(message),
|
|
3842
|
+
warn: (message) => console.warn(message),
|
|
3843
|
+
error: (message) => console.error(message)
|
|
4927
3844
|
}
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
ref,
|
|
4931
|
-
sql: deps.sql ?? require_sniffer.pgClient,
|
|
4932
|
-
migrate: deps.migrate ?? migrateDatabases
|
|
4933
|
-
});
|
|
4934
|
-
logger$4.log("\n🚀 Starting the stack…");
|
|
4935
|
-
await docker.up(ref);
|
|
4936
|
-
if (stack.local) await docker.copyOut(ref, "caddy", "/data/caddy/pki/authorities/local/root.crt", (0, node_path.join)(dir, "caddy-root.crt")).catch(() => {});
|
|
4937
|
-
const images = await recordImages(store, stack, docker, mode);
|
|
4938
|
-
logger$4.log(`\n✅ ${ref.project} is running:`);
|
|
4939
|
-
for (const app of stack.apps) logger$4.log(` ${app.name.padEnd(12)} ${app.url}`);
|
|
4940
|
-
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")}`);
|
|
4941
|
-
return {
|
|
4942
|
-
stack,
|
|
4943
|
-
files,
|
|
4944
|
-
images
|
|
4945
|
-
};
|
|
4946
|
-
} finally {
|
|
4947
|
-
await lock.release();
|
|
4948
|
-
}
|
|
4949
|
-
}
|
|
4950
|
-
/**
|
|
4951
|
-
* The stage's secrets — and, for a deployed stage, everything it generates
|
|
4952
|
-
* once: its seed, and each declared secret and keyring. Written back before
|
|
4953
|
-
* anything is derived from them, so the next run derives the same passwords.
|
|
4954
|
-
*/
|
|
4955
|
-
async function stageSecrets(workspace, manifest, stage) {
|
|
4956
|
-
const store = await require_store.secretsStoreFor(workspace, stage);
|
|
4957
|
-
const stored = await store.read(stage);
|
|
4958
|
-
const nothing = {
|
|
4959
|
-
write: async () => {},
|
|
4960
|
-
skip: () => {}
|
|
4961
|
-
};
|
|
4962
|
-
if (stage === workspace.stages.local) return {
|
|
4963
|
-
secrets: stored,
|
|
4964
|
-
keep: nothing
|
|
4965
|
-
};
|
|
4966
|
-
const { secrets, generated } = require_sniffer.withGeneratedSecrets(stored ?? require_storage.initStageSecrets(stage), manifest);
|
|
4967
|
-
if (generated.length === 0) return {
|
|
4968
|
-
secrets,
|
|
4969
|
-
keep: nothing
|
|
4970
|
-
};
|
|
3845
|
+
}).result;
|
|
3846
|
+
if (!finished) return void 0;
|
|
4971
3847
|
return {
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
logger$4.log(`🔑 Generated for "${stage}" (${store.name}): ${generated.join(", ")}`);
|
|
4977
|
-
},
|
|
4978
|
-
skip() {
|
|
4979
|
-
logger$4.log(`🔑 "${stage}" has no ${generated.join(", ")} yet; this dry run used values it did not keep.`);
|
|
4980
|
-
}
|
|
4981
|
-
}
|
|
4982
|
-
};
|
|
4983
|
-
}
|
|
4984
|
-
/** Write the stack's files: owner-only, since the env files hold secrets. */
|
|
4985
|
-
async function writeStack(root, dir, stack) {
|
|
4986
|
-
await (0, node_fs_promises.mkdir)(dir, {
|
|
4987
|
-
recursive: true,
|
|
4988
|
-
mode: 448
|
|
4989
|
-
});
|
|
4990
|
-
await (0, node_fs_promises.chmod)(dir, 448);
|
|
4991
|
-
const files = [];
|
|
4992
|
-
const write = async (path, content, mode = 420) => {
|
|
4993
|
-
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(path), { recursive: true });
|
|
4994
|
-
await (0, node_fs_promises.writeFile)(path, content, { mode });
|
|
4995
|
-
await (0, node_fs_promises.chmod)(path, mode);
|
|
4996
|
-
files.push(path);
|
|
3848
|
+
stack: finished.stack,
|
|
3849
|
+
files: finished.files,
|
|
3850
|
+
...options.dryRun ? {} : { images: finished.images },
|
|
3851
|
+
deploy: result
|
|
4997
3852
|
};
|
|
4998
|
-
await write((0, node_path.join)(dir, "docker-compose.yml"), composeYaml(stack));
|
|
4999
|
-
await write((0, node_path.join)(dir, "Caddyfile"), stack.caddyfile);
|
|
5000
|
-
for (const app of stack.apps) if (app.env) await write((0, node_path.join)(dir, `${app.name}.env`), envFile(app.env), 384);
|
|
5001
|
-
for (const [path, content] of Object.entries(stack.dockerfiles)) await write((0, node_path.join)(root, path), content);
|
|
5002
|
-
await ignoreStacks(root);
|
|
5003
|
-
return files;
|
|
5004
|
-
}
|
|
5005
|
-
/** Make sure the build context leaves `.gkm/compose` out. */
|
|
5006
|
-
async function ignoreStacks(root) {
|
|
5007
|
-
const path = (0, node_path.join)(root, ".dockerignore");
|
|
5008
|
-
if (!(0, node_fs.existsSync)(path)) {
|
|
5009
|
-
await (0, node_fs_promises.writeFile)(path, require_sniffer.generateDockerignore());
|
|
5010
|
-
return;
|
|
5011
|
-
}
|
|
5012
|
-
const current = await (0, node_fs_promises.readFile)(path, "utf-8");
|
|
5013
|
-
if (/^\/?\.gkm\/compose\/?$|^\/?\.gkm\/?$/m.test(current)) return;
|
|
5014
|
-
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`);
|
|
5015
|
-
}
|
|
5016
|
-
function composeYaml(stack) {
|
|
5017
|
-
return `# Generated by gkm compose from the construct manifest — do not edit.
|
|
5018
|
-
# The ${stack.stage} stage's APIs and sites behind one Caddy. Each backend
|
|
5019
|
-
# reads exactly the keys in its own env file beside this one.
|
|
5020
|
-
#
|
|
5021
|
-
# gkm compose${stack.local ? "" : ` --stage ${stack.stage}`} start or update it
|
|
5022
|
-
# gkm compose${stack.local ? "" : ` --stage ${stack.stage}`} --down stop it
|
|
5023
|
-
${(0, yaml.stringify)(stack.compose, {
|
|
5024
|
-
lineWidth: 0,
|
|
5025
|
-
aliasDuplicateObjects: false
|
|
5026
|
-
})}`;
|
|
5027
|
-
}
|
|
5028
|
-
/** Create what the plan names in the stack's Postgres, then migrate it. */
|
|
5029
|
-
async function prepareDatabases(stack, workspace, manifest, sources, deps) {
|
|
5030
|
-
const port = await deps.docker.port(deps.ref, "postgres", 5432);
|
|
5031
|
-
const statements = require_sniffer.postgresStatements(stack.plan, workspace.name, stack.credential.seed);
|
|
5032
|
-
if (statements.length > 0) {
|
|
5033
|
-
logger$4.log("🗄️ Creating databases, roles and grants…");
|
|
5034
|
-
await require_sniffer.applyPostgres(deps.sql(port, stack.credential.master), statements);
|
|
5035
|
-
}
|
|
5036
|
-
const env = require_sniffer.envFor(stack.plan, {
|
|
5037
|
-
ports: { [require_sniffer.primaryPortKey("postgres")]: port },
|
|
5038
|
-
project: workspace.name,
|
|
5039
|
-
master: stack.credential.master,
|
|
5040
|
-
...stack.credential.seed ? { seed: stack.credential.seed } : {}
|
|
5041
|
-
});
|
|
5042
|
-
const runs = await deps.migrate({
|
|
5043
|
-
root: workspace.root,
|
|
5044
|
-
manifest,
|
|
5045
|
-
sources,
|
|
5046
|
-
env
|
|
5047
|
-
});
|
|
5048
|
-
for (const { target, applied } of runs) if (applied.length > 0) logger$4.log(`🗄️ ${target.migrations}: applied ${applied.length}`);
|
|
5049
|
-
}
|
|
5050
|
-
/**
|
|
5051
|
-
* Record each app's image in the stage's state: the tag it ran and the digest
|
|
5052
|
-
* the tag resolved to — or, for an image built here and never pushed, its
|
|
5053
|
-
* content id.
|
|
5054
|
-
*/
|
|
5055
|
-
async function recordImages(store, stack, docker, mode) {
|
|
5056
|
-
if (!await store.read(stack.stage)) await store.write(stack.stage, require_sniffer.createEmptyState(stack.stage, "", ""), { expectedVersion: null });
|
|
5057
|
-
const images = {};
|
|
5058
|
-
for (const app of stack.apps) {
|
|
5059
|
-
const digest = await docker.digest(app.ref);
|
|
5060
|
-
images[app.name] = {
|
|
5061
|
-
ref: app.ref,
|
|
5062
|
-
tag: app.tag,
|
|
5063
|
-
...digest ? { digest } : {}
|
|
5064
|
-
};
|
|
5065
|
-
await store.putResource(stack.stage, {
|
|
5066
|
-
key: `compose:${app.name}`,
|
|
5067
|
-
type: "compose-image",
|
|
5068
|
-
status: "ready",
|
|
5069
|
-
...digest ? { id: digest } : {},
|
|
5070
|
-
data: {
|
|
5071
|
-
project: stack.project,
|
|
5072
|
-
ref: app.ref,
|
|
5073
|
-
tag: app.tag,
|
|
5074
|
-
source: mode,
|
|
5075
|
-
...digest ? { digest } : {}
|
|
5076
|
-
}
|
|
5077
|
-
});
|
|
5078
|
-
}
|
|
5079
|
-
return images;
|
|
5080
|
-
}
|
|
5081
|
-
function printPlan(stack, mode, files) {
|
|
5082
|
-
logger$4.log(`\n🧱 ${stack.project} — stage ${stack.stage}`);
|
|
5083
|
-
for (const app of stack.apps) logger$4.log(` ${app.name.padEnd(12)} ${app.url.padEnd(40)} ${mode === "build" ? "build" : "pull"} ${app.ref}`);
|
|
5084
|
-
if (stack.infra.length > 0) logger$4.log(` infrastructure: ${stack.infra.join(", ")}`);
|
|
5085
|
-
logger$4.log(`\n📝 Wrote ${files.length} file(s) under ${stackDir(stack.stage)}/`);
|
|
5086
3853
|
}
|
|
5087
3854
|
/**
|
|
5088
3855
|
* Format a fatal error for display.
|
|
@@ -5108,7 +3875,7 @@ const logger$3 = console;
|
|
|
5108
3875
|
* Get the Dokploy API token from stored credentials or environment
|
|
5109
3876
|
*/
|
|
5110
3877
|
async function getApiToken() {
|
|
5111
|
-
const token = await
|
|
3878
|
+
const token = await require_credentials.getDokployToken();
|
|
5112
3879
|
if (!token) throw new Error("Dokploy credentials not found.\nRun \"gkm login --service dokploy\" to authenticate, or set DOKPLOY_API_TOKEN.");
|
|
5113
3880
|
return token;
|
|
5114
3881
|
}
|
|
@@ -5117,7 +3884,7 @@ async function getApiToken() {
|
|
|
5117
3884
|
*/
|
|
5118
3885
|
async function getEndpoint(providedEndpoint) {
|
|
5119
3886
|
if (providedEndpoint) return providedEndpoint;
|
|
5120
|
-
const stored = await
|
|
3887
|
+
const stored = await require_credentials.getDokployCredentials();
|
|
5121
3888
|
if (stored) return stored.endpoint;
|
|
5122
3889
|
throw new Error("Dokploy endpoint not specified.\nEither run \"gkm login --service dokploy\" first, or provide --endpoint.");
|
|
5123
3890
|
}
|
|
@@ -5220,7 +3987,7 @@ async function deployListCommand(options) {
|
|
|
5220
3987
|
logger$3.log(" Run \"gkm registry:setup\" to configure a registry");
|
|
5221
3988
|
return;
|
|
5222
3989
|
}
|
|
5223
|
-
const storedRegistryId = await
|
|
3990
|
+
const storedRegistryId = await require_credentials.getDokployRegistryId();
|
|
5224
3991
|
for (const registry of registries) {
|
|
5225
3992
|
const marker = registry.registryId === storedRegistryId ? " (default)" : "";
|
|
5226
3993
|
logger$3.log(`\n ${registry.registryName}${marker} (${registry.registryId})`);
|
|
@@ -6489,25 +5256,25 @@ function loadPackageJson() {
|
|
|
6489
5256
|
* Run: pnpm --filter @geekmidas/cli sync-versions
|
|
6490
5257
|
*/
|
|
6491
5258
|
const GEEKMIDAS_VERSIONS = {
|
|
6492
|
-
"@geekmidas/audit": "~10.0.0-alpha.
|
|
6493
|
-
"@geekmidas/auth": "~10.0.0-alpha.
|
|
6494
|
-
"@geekmidas/cache": "~10.0.0-alpha.
|
|
6495
|
-
"@geekmidas/client": "~10.0.0-alpha.
|
|
6496
|
-
"@geekmidas/cloud": "~10.0.0-alpha.
|
|
6497
|
-
"@geekmidas/constructs": "~10.0.0-alpha.
|
|
6498
|
-
"@geekmidas/db": "~10.0.0-alpha.
|
|
6499
|
-
"@geekmidas/emailkit": "~10.0.0-alpha.
|
|
6500
|
-
"@geekmidas/envkit": "~10.0.0-alpha.
|
|
6501
|
-
"@geekmidas/errors": "~10.0.0-alpha.
|
|
6502
|
-
"@geekmidas/events": "~10.0.0-alpha.
|
|
6503
|
-
"@geekmidas/logger": "~10.0.0-alpha.
|
|
6504
|
-
"@geekmidas/manifest": "~10.0.0-alpha.
|
|
6505
|
-
"@geekmidas/rate-limit": "~10.0.0-alpha.
|
|
6506
|
-
"@geekmidas/schema": "~10.0.0-alpha.
|
|
6507
|
-
"@geekmidas/services": "~10.0.0-alpha.
|
|
6508
|
-
"@geekmidas/storage": "~10.0.0-alpha.
|
|
6509
|
-
"@geekmidas/telescope": "~10.0.0-alpha.
|
|
6510
|
-
"@geekmidas/testkit": "~10.0.0-alpha.
|
|
5259
|
+
"@geekmidas/audit": "~10.0.0-alpha.66",
|
|
5260
|
+
"@geekmidas/auth": "~10.0.0-alpha.66",
|
|
5261
|
+
"@geekmidas/cache": "~10.0.0-alpha.66",
|
|
5262
|
+
"@geekmidas/client": "~10.0.0-alpha.66",
|
|
5263
|
+
"@geekmidas/cloud": "~10.0.0-alpha.66",
|
|
5264
|
+
"@geekmidas/constructs": "~10.0.0-alpha.66",
|
|
5265
|
+
"@geekmidas/db": "~10.0.0-alpha.66",
|
|
5266
|
+
"@geekmidas/emailkit": "~10.0.0-alpha.66",
|
|
5267
|
+
"@geekmidas/envkit": "~10.0.0-alpha.66",
|
|
5268
|
+
"@geekmidas/errors": "~10.0.0-alpha.66",
|
|
5269
|
+
"@geekmidas/events": "~10.0.0-alpha.66",
|
|
5270
|
+
"@geekmidas/logger": "~10.0.0-alpha.66",
|
|
5271
|
+
"@geekmidas/manifest": "~10.0.0-alpha.66",
|
|
5272
|
+
"@geekmidas/rate-limit": "~10.0.0-alpha.66",
|
|
5273
|
+
"@geekmidas/schema": "~10.0.0-alpha.66",
|
|
5274
|
+
"@geekmidas/services": "~10.0.0-alpha.66",
|
|
5275
|
+
"@geekmidas/storage": "~10.0.0-alpha.66",
|
|
5276
|
+
"@geekmidas/telescope": "~10.0.0-alpha.66",
|
|
5277
|
+
"@geekmidas/testkit": "~10.0.0-alpha.66",
|
|
6511
5278
|
"@geekmidas/cli": `~${loadPackageJson().version}`
|
|
6512
5279
|
};
|
|
6513
5280
|
//#endregion
|
|
@@ -7090,7 +5857,7 @@ function deploy(options) {
|
|
|
7090
5857
|
role-to-assume: \${{ vars.AWS_ROLE_ARN }}
|
|
7091
5858
|
aws-region: ${options.region}
|
|
7092
5859
|
` : "";
|
|
7093
|
-
const keyDir = `~/.gkm/keys/${
|
|
5860
|
+
const keyDir = `~/.gkm/keys/${require_state.projectKey({ name: options.name })}`;
|
|
7094
5861
|
const stageSecrets = sst ? credentials : `
|
|
7095
5862
|
# The stage's secrets are kept in the encrypted .gkm/secrets/<stage>.json,
|
|
7096
5863
|
# and each environment holds its key as GKM_SECRETS_KEY. .gkm/ is
|
|
@@ -11427,11 +10194,11 @@ async function initCommand(projectName, options = {}) {
|
|
|
11427
10194
|
}
|
|
11428
10195
|
devSecrets.custom = customSecrets;
|
|
11429
10196
|
const keys = {
|
|
11430
|
-
key:
|
|
10197
|
+
key: require_state.projectKey({ name }),
|
|
11431
10198
|
legacy: [(0, node_path.basename)(targetDir)]
|
|
11432
10199
|
};
|
|
11433
|
-
await new
|
|
11434
|
-
const keyPath =
|
|
10200
|
+
await new require_state.FileSecretsStore(targetDir, keys).write(local, devSecrets);
|
|
10201
|
+
const keyPath = require_state.getKeyPath(local, keys);
|
|
11435
10202
|
console.log(` Secrets: .gkm/secrets/${local}.json (encrypted)`);
|
|
11436
10203
|
console.log(` Key: ${keyPath}\n`);
|
|
11437
10204
|
if (!options.skipInstall) {
|
|
@@ -11666,9 +10433,9 @@ const logger$2 = console;
|
|
|
11666
10433
|
*/
|
|
11667
10434
|
async function storeFor(stage) {
|
|
11668
10435
|
try {
|
|
11669
|
-
return await
|
|
10436
|
+
return await require_state.secretsStoreFor(await require_config.loadWorkspaceSettings(), stage);
|
|
11670
10437
|
} catch (error) {
|
|
11671
|
-
if (error instanceof require_config.ConfigNotFound) return new
|
|
10438
|
+
if (error instanceof require_config.ConfigNotFound) return new require_state.FileSecretsStore(process.cwd());
|
|
11672
10439
|
throw error;
|
|
11673
10440
|
}
|
|
11674
10441
|
}
|
|
@@ -11738,7 +10505,7 @@ async function secretsSetCommand(key, value, options) {
|
|
|
11738
10505
|
logger$2.error(`Secrets not found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
11739
10506
|
process.exit(1);
|
|
11740
10507
|
}
|
|
11741
|
-
await store.write(stage,
|
|
10508
|
+
await store.write(stage, require_state.withCustomSecret(secrets, key, secretValue));
|
|
11742
10509
|
logger$2.log(`\n✓ Secret "${key}" set for stage "${stage}" (${store.name})`);
|
|
11743
10510
|
}
|
|
11744
10511
|
/**
|
|
@@ -11760,7 +10527,7 @@ async function secretsShowCommand(options) {
|
|
|
11760
10527
|
logger$2.log(` host: ${creds.host}`);
|
|
11761
10528
|
logger$2.log(` port: ${creds.port}`);
|
|
11762
10529
|
logger$2.log(` username: ${creds.username}`);
|
|
11763
|
-
logger$2.log(` password: ${reveal ? creds.password :
|
|
10530
|
+
logger$2.log(` password: ${reveal ? creds.password : require_state.maskPassword(creds.password)}`);
|
|
11764
10531
|
if (creds.database) logger$2.log(` database: ${creds.database}`);
|
|
11765
10532
|
if (creds.bucket) logger$2.log(` bucket: ${creds.bucket}`);
|
|
11766
10533
|
}
|
|
@@ -11770,7 +10537,7 @@ async function secretsShowCommand(options) {
|
|
|
11770
10537
|
if (secrets.urls.STORAGE_ENDPOINT) logger$2.log(` STORAGE_ENDPOINT: ${secrets.urls.STORAGE_ENDPOINT}`);
|
|
11771
10538
|
if (Object.keys(secrets.custom).length > 0) {
|
|
11772
10539
|
logger$2.log("\nCustom Secrets:");
|
|
11773
|
-
for (const [key, value] of Object.entries(secrets.custom)) logger$2.log(` ${key}: ${reveal ? value :
|
|
10540
|
+
for (const [key, value] of Object.entries(secrets.custom)) logger$2.log(` ${key}: ${reveal ? value : require_state.maskPassword(value)}`);
|
|
11774
10541
|
}
|
|
11775
10542
|
if (!reveal) logger$2.log("\nUse --reveal to show actual values");
|
|
11776
10543
|
}
|
|
@@ -11850,7 +10617,7 @@ async function secretsImportCommand(file, options) {
|
|
|
11850
10617
|
function maskUrl(url) {
|
|
11851
10618
|
try {
|
|
11852
10619
|
const parsed = new URL(url);
|
|
11853
|
-
if (parsed.password) parsed.password =
|
|
10620
|
+
if (parsed.password) parsed.password = require_state.maskPassword(parsed.password);
|
|
11854
10621
|
return parsed.toString();
|
|
11855
10622
|
} catch {
|
|
11856
10623
|
return url;
|
|
@@ -11894,7 +10661,7 @@ async function setupCommand(options = {}) {
|
|
|
11894
10661
|
logger$1.error("❌ Failed to resolve secrets. Exiting.");
|
|
11895
10662
|
process.exit(1);
|
|
11896
10663
|
}
|
|
11897
|
-
const containers = await
|
|
10664
|
+
const containers = await require_state.derivedContainers(workspace, stage);
|
|
11898
10665
|
if (isMultiApp && containers.includes("postgres")) {
|
|
11899
10666
|
await writeDockerEnvFromSecrets(secrets, workspace.root);
|
|
11900
10667
|
logger$1.log("📄 Generated docker/.env with database passwords");
|
|
@@ -11913,7 +10680,7 @@ async function setupCommand(options = {}) {
|
|
|
11913
10680
|
* free and quiet.
|
|
11914
10681
|
*/
|
|
11915
10682
|
async function reconcileLocal(workspace, stage, options) {
|
|
11916
|
-
const result = await
|
|
10683
|
+
const result = await require_state.reconcileWorkspace(workspace, { stage });
|
|
11917
10684
|
if (result.plan.containers.length === 0) {
|
|
11918
10685
|
logger$1.log("🐳 No containers declared");
|
|
11919
10686
|
return;
|
|
@@ -11923,7 +10690,7 @@ async function reconcileLocal(workspace, stage, options) {
|
|
|
11923
10690
|
return;
|
|
11924
10691
|
}
|
|
11925
10692
|
logger$1.log("🐳 Services");
|
|
11926
|
-
for (const line of
|
|
10693
|
+
for (const line of require_state.describeServices(result.services)) logger$1.log(line);
|
|
11927
10694
|
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://"));
|
|
11928
10695
|
if (url) await ensureTrusted(workspace.root, url, { ...options.yes ? { assumeYes: true } : {} });
|
|
11929
10696
|
}
|
|
@@ -11940,11 +10707,11 @@ async function resolveSecrets(stage, workspace, options) {
|
|
|
11940
10707
|
logger$1.log("🔐 Generating fresh secrets (--force)...");
|
|
11941
10708
|
return generateFreshSecrets(stage, workspace);
|
|
11942
10709
|
}
|
|
11943
|
-
const store = await
|
|
10710
|
+
const store = await require_state.secretsStoreFor(workspace, stage);
|
|
11944
10711
|
const secrets = await store.read(stage);
|
|
11945
10712
|
if (secrets) {
|
|
11946
10713
|
logger$1.log("🔐 Using existing secrets");
|
|
11947
|
-
const reconciled = reconcileSecrets(secrets, workspace, await
|
|
10714
|
+
const reconciled = reconcileSecrets(secrets, workspace, await require_state.derivedContainers(workspace, stage));
|
|
11948
10715
|
if (reconciled) await store.write(stage, reconciled);
|
|
11949
10716
|
return reconciled ?? secrets;
|
|
11950
10717
|
}
|
|
@@ -12064,9 +10831,9 @@ function createFreshWorkspaceSecrets(stage, workspace, containers = []) {
|
|
|
12064
10831
|
*/
|
|
12065
10832
|
async function ensureStageSecrets(stage, cwd = process.cwd()) {
|
|
12066
10833
|
const { workspace } = await require_config.loadWorkspaceConfig(cwd);
|
|
12067
|
-
const store = await
|
|
10834
|
+
const store = await require_state.secretsStoreFor(workspace, stage);
|
|
12068
10835
|
if (await store.read(stage)) return false;
|
|
12069
|
-
const secrets = createFreshWorkspaceSecrets(stage, workspace, await
|
|
10836
|
+
const secrets = createFreshWorkspaceSecrets(stage, workspace, await require_state.derivedContainers(workspace, stage));
|
|
12070
10837
|
await store.write(stage, secrets);
|
|
12071
10838
|
return true;
|
|
12072
10839
|
}
|
|
@@ -12074,8 +10841,8 @@ async function ensureStageSecrets(stage, cwd = process.cwd()) {
|
|
|
12074
10841
|
* Generate fresh secrets for the workspace.
|
|
12075
10842
|
*/
|
|
12076
10843
|
async function generateFreshSecrets(stage, workspace) {
|
|
12077
|
-
const secrets = createFreshWorkspaceSecrets(stage, workspace, await
|
|
12078
|
-
const store = await
|
|
10844
|
+
const secrets = createFreshWorkspaceSecrets(stage, workspace, await require_state.derivedContainers(workspace, stage));
|
|
10845
|
+
const store = await require_state.secretsStoreFor(workspace, stage);
|
|
12079
10846
|
await store.write(stage, secrets);
|
|
12080
10847
|
logger$1.log(` Secrets written to the "${stage}" store (${store.name})`);
|
|
12081
10848
|
return secrets;
|
|
@@ -12115,7 +10882,7 @@ function printSummary(workspace, stage) {
|
|
|
12115
10882
|
* A no-op when nothing is recorded — no run yet, or one already torn down —
|
|
12116
10883
|
* so running it twice, or before a setup, is harmless.
|
|
12117
10884
|
*/
|
|
12118
|
-
async function dropTestDatabases(cwd, root, sql =
|
|
10885
|
+
async function dropTestDatabases(cwd, root, sql = require_state.pgClient) {
|
|
12119
10886
|
const readyPath = (0, node_path.join)(cwd, require_ready.TEST_READY_FILE);
|
|
12120
10887
|
const ready = await readReady(readyPath);
|
|
12121
10888
|
const recorded = ready?.databases;
|
|
@@ -12123,9 +10890,9 @@ async function dropTestDatabases(cwd, root, sql = require_sniffer.pgClient) {
|
|
|
12123
10890
|
const client = sql(recorded.port);
|
|
12124
10891
|
for (const name of recorded.names) {
|
|
12125
10892
|
if (!name.endsWith(`_test`)) throw new NotATestDatabase(name);
|
|
12126
|
-
await client.query(void 0, `DROP DATABASE IF EXISTS ${
|
|
10893
|
+
await client.query(void 0, `DROP DATABASE IF EXISTS ${require_state.quoteIdentifier(name)} WITH (FORCE)`);
|
|
12127
10894
|
}
|
|
12128
|
-
await
|
|
10895
|
+
await require_state.forgetState(root, require_workspace.TEST_STAGE);
|
|
12129
10896
|
const { databases: _dropped, ...rest } = ready;
|
|
12130
10897
|
await (0, node_fs_promises.writeFile)(readyPath, JSON.stringify(rest, null, 2));
|
|
12131
10898
|
return recorded.names;
|
|
@@ -12165,10 +10932,10 @@ async function testCommand(options = {}) {
|
|
|
12165
10932
|
const { ensureStageSecrets } = await Promise.resolve().then(() => setup_exports);
|
|
12166
10933
|
if (await ensureStageSecrets(stage, cwd)) console.log(` 🔐 Generated fresh ${stage} secrets (auto-setup)`);
|
|
12167
10934
|
}
|
|
12168
|
-
const defaultEnv =
|
|
10935
|
+
const defaultEnv = require_state.loadEnvFiles(".env");
|
|
12169
10936
|
if (defaultEnv.loaded.length > 0) console.log(` 📦 Loaded env: ${defaultEnv.loaded.join(", ")}`);
|
|
12170
10937
|
if (!options.prepare) await teardown(cwd);
|
|
12171
|
-
const result = await
|
|
10938
|
+
const result = await require_state.prepareEntryCredentials({
|
|
12172
10939
|
stage,
|
|
12173
10940
|
startDocker: !options.prepare,
|
|
12174
10941
|
secretsFileName: "test-secrets.json",
|
|
@@ -12189,13 +10956,13 @@ async function testCommand(options = {}) {
|
|
|
12189
10956
|
await (0, node_fs_promises.writeFile)(result.secretsJsonPath, JSON.stringify(finalCredentials, null, 2));
|
|
12190
10957
|
const gkmDir = (0, node_path.join)(cwd, ".gkm");
|
|
12191
10958
|
const preloadPath = (0, node_path.join)(gkmDir, "test-credentials-preload.ts");
|
|
12192
|
-
await
|
|
10959
|
+
await require_state.createCredentialsPreload(preloadPath, result.secretsJsonPath);
|
|
12193
10960
|
const workspace = await require_config.loadWorkspaceConfig(cwd).then((loaded) => loaded.workspace).catch(() => void 0);
|
|
12194
10961
|
const manifestPath = workspace ? await require_ready.writeTestHarness({
|
|
12195
10962
|
root: workspace.root,
|
|
12196
10963
|
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))],
|
|
12197
|
-
patterns:
|
|
12198
|
-
cacheBackend:
|
|
10964
|
+
patterns: require_state.constructGlobs(workspace),
|
|
10965
|
+
cacheBackend: require_state.backendsOf(workspace).cache,
|
|
12199
10966
|
stage: require_workspace.TEST_STAGE,
|
|
12200
10967
|
env: finalCredentials,
|
|
12201
10968
|
factories: (0, node_path.join)(workspace.root, workspace.test.factories ?? "test/factories")
|
|
@@ -12258,8 +11025,8 @@ async function teardown(cwd) {
|
|
|
12258
11025
|
* the plan provisions, on the port the shared container answers.
|
|
12259
11026
|
*/
|
|
12260
11027
|
function testDatabases(reconciled) {
|
|
12261
|
-
const port = reconciled?.ports[
|
|
12262
|
-
const names = reconciled ?
|
|
11028
|
+
const port = reconciled?.ports[require_state.primaryPortKey("postgres")];
|
|
11029
|
+
const names = reconciled ? require_state.postgresDatabaseNames(reconciled.plan) : [];
|
|
12263
11030
|
return port !== void 0 && names.length > 0 ? { databases: {
|
|
12264
11031
|
port,
|
|
12265
11032
|
names
|
|
@@ -12270,11 +11037,11 @@ function testDatabases(reconciled) {
|
|
|
12270
11037
|
* starts without the reference data the app needs.
|
|
12271
11038
|
*/
|
|
12272
11039
|
async function migrateTestStage(workspace, env) {
|
|
12273
|
-
const { migrateAndSeed } = await Promise.resolve().then(() => require("./migrate-
|
|
12274
|
-
const { discover } = await Promise.resolve().then(() => require("./discover-
|
|
11040
|
+
const { migrateAndSeed } = await Promise.resolve().then(() => require("./migrate-CF4J4GQS.cjs"));
|
|
11041
|
+
const { discover } = await Promise.resolve().then(() => require("./discover-OEfZ9OkA.cjs")).then((n) => n.discover_exports);
|
|
12275
11042
|
const sources = {};
|
|
12276
11043
|
const manifest = await discover({
|
|
12277
|
-
patterns:
|
|
11044
|
+
patterns: require_state.constructGlobs(workspace),
|
|
12278
11045
|
cwd: workspace.root,
|
|
12279
11046
|
sources
|
|
12280
11047
|
});
|
|
@@ -12701,7 +11468,7 @@ program.command("migrate").description("Apply each database construct's migratio
|
|
|
12701
11468
|
try {
|
|
12702
11469
|
const globalOptions = program.opts();
|
|
12703
11470
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
12704
|
-
const { migrateCommand } = await Promise.resolve().then(() => require("./migrate-
|
|
11471
|
+
const { migrateCommand } = await Promise.resolve().then(() => require("./migrate-CF4J4GQS.cjs"));
|
|
12705
11472
|
await migrateCommand({
|
|
12706
11473
|
...options,
|
|
12707
11474
|
...construct ? { construct } : {}
|
|
@@ -12715,7 +11482,7 @@ program.command("seed").description("Migrate, then run each database construct's
|
|
|
12715
11482
|
try {
|
|
12716
11483
|
const globalOptions = program.opts();
|
|
12717
11484
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
12718
|
-
const { seedCommand } = await Promise.resolve().then(() => require("./migrate-
|
|
11485
|
+
const { seedCommand } = await Promise.resolve().then(() => require("./migrate-CF4J4GQS.cjs"));
|
|
12719
11486
|
await seedCommand({
|
|
12720
11487
|
...options,
|
|
12721
11488
|
...construct ? { construct } : {}
|
|
@@ -12729,7 +11496,7 @@ program.command("migration").description("Write a construct's next migration: an
|
|
|
12729
11496
|
try {
|
|
12730
11497
|
const globalOptions = program.opts();
|
|
12731
11498
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
12732
|
-
const { migrationCommand } = await Promise.resolve().then(() => require("./migrate-
|
|
11499
|
+
const { migrationCommand } = await Promise.resolve().then(() => require("./migrate-CF4J4GQS.cjs"));
|
|
12733
11500
|
await migrationCommand({
|
|
12734
11501
|
construct,
|
|
12735
11502
|
...name ? { name } : {}
|
|
@@ -12768,7 +11535,7 @@ program.command("docker").description("Generate Docker deployment files").option
|
|
|
12768
11535
|
try {
|
|
12769
11536
|
const globalOptions = program.opts();
|
|
12770
11537
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
12771
|
-
await
|
|
11538
|
+
await require_state.dockerCommand(options);
|
|
12772
11539
|
} catch (error) {
|
|
12773
11540
|
console.error(formatError(error));
|
|
12774
11541
|
process.exit(1);
|
|
@@ -12793,7 +11560,7 @@ program.command("prepack").description("Generate Docker files for production dep
|
|
|
12793
11560
|
production: true,
|
|
12794
11561
|
skipBundle: options.skipBundle
|
|
12795
11562
|
});
|
|
12796
|
-
await
|
|
11563
|
+
await require_state.dockerCommand({
|
|
12797
11564
|
build: options.build,
|
|
12798
11565
|
push: options.push,
|
|
12799
11566
|
tag: options.tag,
|
|
@@ -12837,7 +11604,7 @@ program.command("encryption:rotate").description("Add a new current key to an En
|
|
|
12837
11604
|
try {
|
|
12838
11605
|
const globalOptions = program.opts();
|
|
12839
11606
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
12840
|
-
const { encryptionRotateCommand } = await Promise.resolve().then(() => require("./encryption-
|
|
11607
|
+
const { encryptionRotateCommand } = await Promise.resolve().then(() => require("./encryption-3KlGS1sl.cjs"));
|
|
12841
11608
|
await encryptionRotateCommand(construct, options);
|
|
12842
11609
|
} catch (error) {
|
|
12843
11610
|
console.error(formatError(error));
|
|
@@ -12848,7 +11615,7 @@ program.command("encryption:retire").description("Remove an old key from an Encr
|
|
|
12848
11615
|
try {
|
|
12849
11616
|
const globalOptions = program.opts();
|
|
12850
11617
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
12851
|
-
const { encryptionRetireCommand } = await Promise.resolve().then(() => require("./encryption-
|
|
11618
|
+
const { encryptionRetireCommand } = await Promise.resolve().then(() => require("./encryption-3KlGS1sl.cjs"));
|
|
12852
11619
|
await encryptionRetireCommand(construct, key, options);
|
|
12853
11620
|
} catch (error) {
|
|
12854
11621
|
console.error(formatError(error));
|
|
@@ -12891,7 +11658,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
|
|
|
12891
11658
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
12892
11659
|
const { loadWorkspaceConfig } = await Promise.resolve().then(() => require("./config.cjs"));
|
|
12893
11660
|
const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-CuCQBa5-.cjs"));
|
|
12894
|
-
const { secretsStoreFor } = await Promise.resolve().then(() => require("./
|
|
11661
|
+
const { secretsStoreFor } = await Promise.resolve().then(() => require("./state-Y6ATakMX.cjs")).then((n) => n.store_exports);
|
|
12895
11662
|
const { workspace } = await loadWorkspaceConfig();
|
|
12896
11663
|
const stage = options.stage ?? workspace.stages.local;
|
|
12897
11664
|
const store = await secretsStoreFor(workspace, stage);
|
|
@@ -12900,7 +11667,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
|
|
|
12900
11667
|
console.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
12901
11668
|
process.exit(1);
|
|
12902
11669
|
}
|
|
12903
|
-
const { derivedContainers } = await Promise.resolve().then(() => require("./
|
|
11670
|
+
const { derivedContainers } = await Promise.resolve().then(() => require("./state-Y6ATakMX.cjs")).then((n) => n.workspace_exports);
|
|
12904
11671
|
const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
|
|
12905
11672
|
if (!result) {
|
|
12906
11673
|
console.log(`\n✓ Secrets for stage "${stage}" are up-to-date`);
|
|
@@ -12914,8 +11681,8 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
|
|
|
12914
11681
|
process.exit(1);
|
|
12915
11682
|
}
|
|
12916
11683
|
});
|
|
12917
|
-
program.command("deploy").description("Deploy a stage through its target").option("--target <name>", "Deploy target: dokploy, 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)").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) => {
|
|
12918
|
-
const { deployCli } = await Promise.resolve().then(() => require("./cli-
|
|
11684
|
+
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").option("--atomic", "If the release fails, roll back every app it released, not only the failed ones").action(async (options) => {
|
|
11685
|
+
const { deployCli } = await Promise.resolve().then(() => require("./cli-D1JkkOwg.cjs"));
|
|
12919
11686
|
const globalOptions = program.opts();
|
|
12920
11687
|
const code = await deployCli({
|
|
12921
11688
|
cwd: (0, node_path.resolve)(globalOptions.cwd ?? process.cwd()),
|
|
@@ -12924,10 +11691,29 @@ program.command("deploy").description("Deploy a stage through its target").optio
|
|
|
12924
11691
|
stage: options.stage,
|
|
12925
11692
|
...options.tag ? { tag: options.tag } : {},
|
|
12926
11693
|
...options.json ? { json: true } : {},
|
|
12927
|
-
...options.dryRun ? { dryRun: true } : {}
|
|
11694
|
+
...options.dryRun ? { dryRun: true } : {},
|
|
11695
|
+
...options.atomic ? { atomic: true } : {}
|
|
12928
11696
|
});
|
|
12929
11697
|
if (code !== 0) process.exit(code);
|
|
12930
11698
|
});
|
|
11699
|
+
program.command("deploy:rollback").description("Put a stage's app back on its previous release (Dokploy): one app with --app, every app with --atomic").requiredOption("--stage <stage>", "A deployed stage").option("--app <app>", "The app to roll back").option("--atomic", "Roll back every app that has an earlier release").action(async (options) => {
|
|
11700
|
+
try {
|
|
11701
|
+
const { rollbackStage } = await Promise.resolve().then(() => require("./rollback-DxJK84hy.cjs")).then((n) => n.rollback_exports);
|
|
11702
|
+
const { terminalCredentials } = await Promise.resolve().then(() => require("./terminal-CHy_MdjG.cjs"));
|
|
11703
|
+
const globalOptions = program.opts();
|
|
11704
|
+
await rollbackStage({
|
|
11705
|
+
cwd: (0, node_path.resolve)(globalOptions.cwd ?? process.cwd()),
|
|
11706
|
+
stage: options.stage,
|
|
11707
|
+
...options.app ? { app: options.app } : {},
|
|
11708
|
+
...options.atomic ? { atomic: true } : {},
|
|
11709
|
+
credentials: terminalCredentials(),
|
|
11710
|
+
log: (line) => console.log(line)
|
|
11711
|
+
});
|
|
11712
|
+
} catch (error) {
|
|
11713
|
+
console.error(formatError(error));
|
|
11714
|
+
process.exit(1);
|
|
11715
|
+
}
|
|
11716
|
+
});
|
|
12931
11717
|
program.command("deploy:init").description("Initialize Dokploy deployment (create project and application)").option("--endpoint <url>", "Dokploy server URL (uses stored credentials if logged in)").requiredOption("--project <name>", "Project name (creates if not exists)").requiredOption("--app <name>", "Application name").option("--project-id <id>", "Use existing project ID instead of creating").option("--registry-id <id>", "Configure registry for the application").action(async (options) => {
|
|
12932
11718
|
try {
|
|
12933
11719
|
const globalOptions = program.opts();
|
|
@@ -12948,7 +11734,7 @@ program.command("deploy:github").description("Let GitHub Actions deploy a stage:
|
|
|
12948
11734
|
try {
|
|
12949
11735
|
const globalOptions = program.opts();
|
|
12950
11736
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
12951
|
-
const { deployGithubCommand } = await Promise.resolve().then(() => require("./github-
|
|
11737
|
+
const { deployGithubCommand } = await Promise.resolve().then(() => require("./github-BJ5gh9bw.cjs"));
|
|
12952
11738
|
await deployGithubCommand(options);
|
|
12953
11739
|
} catch (error) {
|
|
12954
11740
|
console.error(formatError(error));
|
|
@@ -12991,7 +11777,7 @@ program.command("login").description("Authenticate with a deployment or DNS prov
|
|
|
12991
11777
|
console.error(`Unknown provider: ${provider}. Supported: dokploy, hostinger`);
|
|
12992
11778
|
process.exit(1);
|
|
12993
11779
|
}
|
|
12994
|
-
await
|
|
11780
|
+
await loginCommand({
|
|
12995
11781
|
provider,
|
|
12996
11782
|
token: options.token,
|
|
12997
11783
|
endpoint: options.endpoint
|
|
@@ -13005,7 +11791,7 @@ program.command("logout").description("Remove stored credentials").option("--pro
|
|
|
13005
11791
|
try {
|
|
13006
11792
|
const globalOptions = program.opts();
|
|
13007
11793
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
13008
|
-
await
|
|
11794
|
+
await logoutCommand({ provider: options.service ?? options.provider });
|
|
13009
11795
|
} catch (error) {
|
|
13010
11796
|
console.error(formatError(error));
|
|
13011
11797
|
process.exit(1);
|
|
@@ -13015,7 +11801,7 @@ program.command("whoami").description("Show current authentication status").acti
|
|
|
13015
11801
|
try {
|
|
13016
11802
|
const globalOptions = program.opts();
|
|
13017
11803
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
13018
|
-
await
|
|
11804
|
+
await whoamiCommand();
|
|
13019
11805
|
} catch (error) {
|
|
13020
11806
|
console.error(formatError(error));
|
|
13021
11807
|
process.exit(1);
|
|
@@ -13083,24 +11869,12 @@ program.command("upgrade").description("Upgrade @geekmidas/cli (with --all, ever
|
|
|
13083
11869
|
});
|
|
13084
11870
|
program.parse();
|
|
13085
11871
|
//#endregion
|
|
13086
|
-
exports.
|
|
13087
|
-
exports.KyselyNotFound = KyselyNotFound;
|
|
13088
|
-
exports.MigrationFailed = MigrationFailed;
|
|
13089
|
-
exports.MigrationHasNoUp = MigrationHasNoUp;
|
|
13090
|
-
exports.MigrationsOutsideFolder = MigrationsOutsideFolder;
|
|
13091
|
-
exports.NoOwnerCredential = NoOwnerCredential;
|
|
13092
|
-
exports.NoSuchMigrationTarget = NoSuchMigrationTarget;
|
|
13093
|
-
exports.SeedFailed = SeedFailed;
|
|
13094
|
-
exports.SeedHasNoSeed = SeedHasNoSeed;
|
|
13095
|
-
exports.UnknownDatabaseFolder = UnknownDatabaseFolder;
|
|
13096
|
-
exports.UnknownMigrationFolder = UnknownMigrationFolder;
|
|
11872
|
+
exports.canPrompt = canPrompt;
|
|
13097
11873
|
exports.formatError = formatError;
|
|
13098
11874
|
exports.generateFullstackCustomSecrets = generateFullstackCustomSecrets;
|
|
13099
|
-
exports.
|
|
13100
|
-
exports.migrateDatabases = migrateDatabases;
|
|
13101
|
-
exports.pendingMigrations = pendingMigrations;
|
|
13102
|
-
exports.seedDatabases = seedDatabases;
|
|
11875
|
+
exports.prompt = prompt;
|
|
13103
11876
|
exports.slug = slug;
|
|
13104
11877
|
exports.stamp = stamp;
|
|
11878
|
+
exports.validateDokployToken = validateDokployToken;
|
|
13105
11879
|
|
|
13106
11880
|
//# sourceMappingURL=index.cjs.map
|