@geekmidas/cli 10.0.0-alpha.63 → 10.0.0-alpha.64
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{HostingerProvider-mJ7YruxH.cjs → HostingerProvider-Bx2lHHvu.cjs} +3 -3
- package/dist/{HostingerProvider-mJ7YruxH.cjs.map → HostingerProvider-Bx2lHHvu.cjs.map} +1 -1
- package/dist/{HostingerProvider-DBBwpDjn.mjs → HostingerProvider-Cd4aS5Kj.mjs} +2 -2
- package/dist/{HostingerProvider-DBBwpDjn.mjs.map → HostingerProvider-Cd4aS5Kj.mjs.map} +1 -1
- package/dist/{LegacyStateStore-CP7SZwTP.cjs → LegacyStateStore-C2abV49N.cjs} +2 -2
- package/dist/{LegacyStateStore-CP7SZwTP.cjs.map → LegacyStateStore-C2abV49N.cjs.map} +1 -1
- package/dist/{LegacyStateStore-Q3GmKOdw.mjs → LegacyStateStore-DIgkGaUP.mjs} +2 -2
- package/dist/{LegacyStateStore-Q3GmKOdw.mjs.map → LegacyStateStore-DIgkGaUP.mjs.map} +1 -1
- package/dist/{LocalStateStore-BGIXcrvX.mjs → LocalStateStore-Cy1ZBW7U.mjs} +2 -2
- package/dist/{LocalStateStore-BGIXcrvX.mjs.map → LocalStateStore-Cy1ZBW7U.mjs.map} +1 -1
- package/dist/{LocalStateStore-sS8mvLeq.cjs → LocalStateStore-DiJqKyPy.cjs} +2 -2
- package/dist/{LocalStateStore-sS8mvLeq.cjs.map → LocalStateStore-DiJqKyPy.cjs.map} +1 -1
- package/dist/{OpenApiTsGenerator-CjHWFRDd.cjs → OpenApiTsGenerator-BWuDRHe9.cjs} +2 -2
- package/dist/{OpenApiTsGenerator-CjHWFRDd.cjs.map → OpenApiTsGenerator-BWuDRHe9.cjs.map} +1 -1
- package/dist/{OpenApiTsGenerator-CnAwhGxi.mjs → OpenApiTsGenerator-C6v-IeHt.mjs} +2 -2
- package/dist/{OpenApiTsGenerator-CnAwhGxi.mjs.map → OpenApiTsGenerator-C6v-IeHt.mjs.map} +1 -1
- package/dist/{S3StateStore-D7l_lWJ3.mjs → S3StateStore-D3x6ivBL.mjs} +2 -2
- package/dist/{S3StateStore-D7l_lWJ3.mjs.map → S3StateStore-D3x6ivBL.mjs.map} +1 -1
- package/dist/{S3StateStore-6jqITH2F.cjs → S3StateStore-Ds0V1Lgm.cjs} +2 -2
- package/dist/{S3StateStore-6jqITH2F.cjs.map → S3StateStore-Ds0V1Lgm.cjs.map} +1 -1
- package/dist/{SSMStateStore-B__F7IJa.cjs → SSMStateStore-CLipvx47.cjs} +2 -2
- package/dist/{SSMStateStore-B__F7IJa.cjs.map → SSMStateStore-CLipvx47.cjs.map} +1 -1
- package/dist/{SSMStateStore-CAQDVpoc.mjs → SSMStateStore-DfF174xQ.mjs} +2 -2
- package/dist/{SSMStateStore-CAQDVpoc.mjs.map → SSMStateStore-DfF174xQ.mjs.map} +1 -1
- package/dist/{bundler-DLfOn0ls.cjs → bundler-C8uJf324.cjs} +3 -3
- package/dist/{bundler-DLfOn0ls.cjs.map → bundler-C8uJf324.cjs.map} +1 -1
- package/dist/{bundler-DyWI41Iy.mjs → bundler-DYgm9xsd.mjs} +3 -3
- package/dist/{bundler-DyWI41Iy.mjs.map → bundler-DYgm9xsd.mjs.map} +1 -1
- package/dist/{cli-PkVF28LM.mjs → cli-Bogz-zEj.mjs} +5 -4
- package/dist/{cli-PkVF28LM.mjs.map → cli-Bogz-zEj.mjs.map} +1 -1
- package/dist/{cli-D-ZM-X2s.cjs → cli-D3jofJ6j.cjs} +7 -6
- package/dist/{cli-D-ZM-X2s.cjs.map → cli-D3jofJ6j.cjs.map} +1 -1
- package/dist/{config-CemHVcIr.d.cts → config-Be-4_I6H.d.cts} +3 -3
- package/dist/{config-C2BUosLm.d.mts.map → config-Be-4_I6H.d.cts.map} +1 -1
- package/dist/{config-C2BUosLm.d.mts → config-DqNRiQOp.d.mts} +3 -3
- package/dist/{config-CemHVcIr.d.cts.map → config-DqNRiQOp.d.mts.map} +1 -1
- package/dist/config.cjs +416 -17
- package/dist/config.cjs.map +1 -0
- package/dist/config.d.cts +3 -3
- package/dist/config.d.mts +3 -3
- package/dist/config.mjs +401 -2
- package/dist/config.mjs.map +1 -0
- package/dist/credentials-B70vmLcm.cjs +125 -0
- package/dist/credentials-B70vmLcm.cjs.map +1 -0
- package/dist/credentials-s0wMeZBU.mjs +108 -0
- package/dist/credentials-s0wMeZBU.mjs.map +1 -0
- package/dist/{define-CHBIEc5j.mjs → define-CYs0PEfZ.mjs} +2 -3
- package/dist/define-CYs0PEfZ.mjs.map +1 -0
- package/dist/{define-Cv_QvDrg.cjs → define-zUsP72ym.cjs} +2 -3
- package/dist/define-zUsP72ym.cjs.map +1 -0
- package/dist/deploy/public.cjs +32 -26
- package/dist/deploy/public.cjs.map +1 -1
- package/dist/deploy/public.d.cts +40 -4
- package/dist/deploy/public.d.cts.map +1 -1
- package/dist/deploy/public.d.mts +40 -4
- package/dist/deploy/public.d.mts.map +1 -1
- package/dist/deploy/public.mjs +15 -12
- package/dist/deploy/public.mjs.map +1 -1
- package/dist/{deploy-ABBI0lf1.cjs → deploy-55rJkdKz.cjs} +17 -14
- package/dist/{deploy-ABBI0lf1.cjs.map → deploy-55rJkdKz.cjs.map} +1 -1
- package/dist/{deploy-CGW-QY9D.mjs → deploy-Dc2LBJaK.mjs} +8 -7
- package/dist/{deploy-CGW-QY9D.mjs.map → deploy-Dc2LBJaK.mjs.map} +1 -1
- package/dist/{deploy-CG8bfFij.mjs → deploy-ZRXIPp1c.mjs} +11 -8
- package/dist/{deploy-CG8bfFij.mjs.map → deploy-ZRXIPp1c.mjs.map} +1 -1
- package/dist/{deploy-BiMrP9i7.cjs → deploy-yUcR2OlW.cjs} +12 -11
- package/dist/{deploy-BiMrP9i7.cjs.map → deploy-yUcR2OlW.cjs.map} +1 -1
- package/dist/derive-B4vDpzgP.mjs +150 -0
- package/dist/derive-B4vDpzgP.mjs.map +1 -0
- package/dist/derive-DAbo49sm.cjs +161 -0
- package/dist/derive-DAbo49sm.cjs.map +1 -0
- package/dist/{discover-C_6COHEv.cjs → discover-DHdECueV.cjs} +6 -157
- package/dist/discover-DHdECueV.cjs.map +1 -0
- package/dist/{discover-BaT6bpnA.mjs → discover-LMrqsWSU.mjs} +8 -117
- package/dist/discover-LMrqsWSU.mjs.map +1 -0
- package/dist/{dokploy-CwdmlyQL.mjs → dokploy-CepdXuMl.mjs} +3 -3
- package/dist/{dokploy-CwdmlyQL.mjs.map → dokploy-CepdXuMl.mjs.map} +1 -1
- package/dist/{dokploy-QJxMhxdf.cjs → dokploy-D1d37rvM.cjs} +3 -3
- package/dist/{dokploy-QJxMhxdf.cjs.map → dokploy-D1d37rvM.cjs.map} +1 -1
- package/dist/{encryption-DXw3jj0c.cjs → encryption-C9X98u2F.cjs} +5 -5
- package/dist/{encryption-DXw3jj0c.cjs.map → encryption-C9X98u2F.cjs.map} +1 -1
- package/dist/{encryption-CbQG_ayW.mjs → encryption-CKHjMpCr.mjs} +5 -5
- package/dist/{encryption-CbQG_ayW.mjs.map → encryption-CKHjMpCr.mjs.map} +1 -1
- package/dist/{github-ciwbIwVa.mjs → github-D-byNI7T.mjs} +4 -4
- package/dist/{github-ciwbIwVa.mjs.map → github-D-byNI7T.mjs.map} +1 -1
- package/dist/{github-BH_mETav.cjs → github-Dd3tMa1u.cjs} +4 -4
- package/dist/{github-BH_mETav.cjs.map → github-Dd3tMa1u.cjs.map} +1 -1
- package/dist/{index-DrKzpHl-.d.mts → index-CJ2qsSpp.d.mts} +2 -2
- package/dist/{index-BtXDZQuF.d.cts.map → index-CJ2qsSpp.d.mts.map} +1 -1
- package/dist/{index-BtXDZQuF.d.cts → index-CfE9Y885.d.cts} +2 -2
- package/dist/{index-DrKzpHl-.d.mts.map → index-CfE9Y885.d.cts.map} +1 -1
- package/dist/index.cjs +100 -235
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +71 -206
- package/dist/index.mjs.map +1 -1
- package/dist/local-C7wnrcYK.cjs +586 -0
- package/dist/local-C7wnrcYK.cjs.map +1 -0
- package/dist/local-Cd8ZYQG3.mjs +473 -0
- package/dist/local-Cd8ZYQG3.mjs.map +1 -0
- package/dist/{migrate-ImBKTBcW.mjs → migrate-B4gngTLe.mjs} +6 -6
- package/dist/{migrate-ImBKTBcW.mjs.map → migrate-B4gngTLe.mjs.map} +1 -1
- package/dist/{migrate-6oa9y_f2.cjs → migrate-tx9-P9uz.cjs} +6 -6
- package/dist/{migrate-6oa9y_f2.cjs.map → migrate-tx9-P9uz.cjs.map} +1 -1
- package/dist/openapi.cjs +2 -2
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/openapi.mjs +2 -2
- package/dist/{ready-Bpdnn2ON.mjs → ready-bQfu9GES.mjs} +3 -3
- package/dist/{ready-Bpdnn2ON.mjs.map → ready-bQfu9GES.mjs.map} +1 -1
- package/dist/{ready-BiG0rt4h.cjs → ready-brEhh68f.cjs} +3 -3
- package/dist/{ready-BiG0rt4h.cjs.map → ready-brEhh68f.cjs.map} +1 -1
- package/dist/reconcile/public.cjs +1 -1
- package/dist/reconcile/public.d.cts +1 -1
- package/dist/reconcile/public.d.mts +1 -1
- package/dist/reconcile/public.mjs +1 -1
- package/dist/{resolve-xytBTtG4.cjs → resolve-BXsNQjMR.cjs} +9 -6
- package/dist/resolve-BXsNQjMR.cjs.map +1 -0
- package/dist/{resolve-B1T0hUaR.mjs → resolve-DZ2IJ0qY.mjs} +9 -6
- package/dist/resolve-DZ2IJ0qY.mjs.map +1 -0
- package/dist/{runtime-CMwtq0AL.cjs → runtime-BGNDrHzh.cjs} +2 -2
- package/dist/{runtime-CMwtq0AL.cjs.map → runtime-BGNDrHzh.cjs.map} +1 -1
- package/dist/{runtime-Dn_n-29S.d.cts → runtime-CJEYK4Gz.d.mts} +2 -2
- package/dist/{runtime-CXPlkZzs.d.mts.map → runtime-CJEYK4Gz.d.mts.map} +1 -1
- package/dist/{runtime-CXPlkZzs.d.mts → runtime-Cjq1jlVX.d.cts} +2 -2
- package/dist/{runtime-Dn_n-29S.d.cts.map → runtime-Cjq1jlVX.d.cts.map} +1 -1
- package/dist/{runtime-DbakJS8u.mjs → runtime-Cmn-FeYO.mjs} +2 -2
- package/dist/{runtime-DbakJS8u.mjs.map → runtime-Cmn-FeYO.mjs.map} +1 -1
- package/dist/sandbox/discover-worker.cjs +1 -1
- package/dist/sandbox/discover-worker.mjs +1 -1
- package/dist/sandbox-CL--ZDui.cjs +164 -0
- package/dist/sandbox-CL--ZDui.cjs.map +1 -0
- package/dist/sandbox-Dm247NGx.mjs +117 -0
- package/dist/sandbox-Dm247NGx.mjs.map +1 -0
- package/dist/{sniffer-aO_-s5vq.mjs → sniffer-49KPbDgq.mjs} +25 -470
- package/dist/sniffer-49KPbDgq.mjs.map +1 -0
- package/dist/{sniffer-D_SYc_pO.cjs → sniffer-ByhbEW5A.cjs} +38 -567
- package/dist/sniffer-ByhbEW5A.cjs.map +1 -0
- package/dist/sst-BdALKEas.cjs +453 -0
- package/dist/sst-BdALKEas.cjs.map +1 -0
- package/dist/sst-Cper040I.mjs +430 -0
- package/dist/sst-Cper040I.mjs.map +1 -0
- package/dist/{store-BS8A7NcA.mjs → store-BaaonhkJ.mjs} +2 -2
- package/dist/{store-BS8A7NcA.mjs.map → store-BaaonhkJ.mjs.map} +1 -1
- package/dist/{store-KvNQwYaB.cjs → store-D_2ByRtW.cjs} +4 -4
- package/dist/{store-KvNQwYaB.cjs.map → store-D_2ByRtW.cjs.map} +1 -1
- package/dist/target/public.cjs +3 -3
- package/dist/target/public.d.cts +3 -3
- package/dist/target/public.d.mts +3 -3
- package/dist/target/public.mjs +3 -3
- package/dist/{terminal-D0f1u0LG.cjs → terminal-Bdv--jhY.cjs} +16 -111
- package/dist/terminal-Bdv--jhY.cjs.map +1 -0
- package/dist/{terminal-gMxAo9Dj.mjs → terminal-D7Ntz6WE.mjs} +13 -90
- package/dist/terminal-D7Ntz6WE.mjs.map +1 -0
- package/dist/{types-DIA7Li-J.d.cts → types-B_bEQvb9.d.mts} +42 -7
- package/dist/types-B_bEQvb9.d.mts.map +1 -0
- package/dist/{types-CfLKGfC_.d.mts → types-DkrDOMxw.d.cts} +43 -6
- package/dist/types-DkrDOMxw.d.cts.map +1 -0
- package/dist/utils-D2Nk1Nv2.cjs +205 -0
- package/dist/utils-D2Nk1Nv2.cjs.map +1 -0
- package/dist/utils-D_fxUqvU.mjs +156 -0
- package/dist/utils-D_fxUqvU.mjs.map +1 -0
- package/dist/vitest.cjs +1 -1
- package/dist/vitest.mjs +1 -1
- package/dist/workspace/index.cjs +1 -1
- package/dist/workspace/index.d.cts +2 -2
- package/dist/workspace/index.d.mts +2 -2
- package/dist/workspace/index.mjs +1 -1
- package/dist/{workspace-Kl4NG9IH.cjs → workspace-BwgWJK7h.cjs} +5 -5
- package/dist/workspace-BwgWJK7h.cjs.map +1 -0
- package/dist/{workspace-Dh7rr7l4.mjs → workspace-Bwit4q9O.mjs} +5 -5
- package/dist/workspace-Bwit4q9O.mjs.map +1 -0
- package/package.json +13 -13
- package/dist/config-BEJYKd4s.cjs +0 -648
- package/dist/config-BEJYKd4s.cjs.map +0 -1
- package/dist/config-LKKCVCnC.mjs +0 -547
- package/dist/config-LKKCVCnC.mjs.map +0 -1
- package/dist/define-CHBIEc5j.mjs.map +0 -1
- package/dist/define-Cv_QvDrg.cjs.map +0 -1
- package/dist/discover-BaT6bpnA.mjs.map +0 -1
- package/dist/discover-C_6COHEv.cjs.map +0 -1
- package/dist/home-DOzBaWaL.cjs +0 -33
- package/dist/home-DOzBaWaL.cjs.map +0 -1
- package/dist/home-GI7pJl1M.mjs +0 -22
- package/dist/home-GI7pJl1M.mjs.map +0 -1
- package/dist/resolve-B1T0hUaR.mjs.map +0 -1
- package/dist/resolve-xytBTtG4.cjs.map +0 -1
- package/dist/sniffer-D_SYc_pO.cjs.map +0 -1
- package/dist/sniffer-aO_-s5vq.mjs.map +0 -1
- package/dist/terminal-D0f1u0LG.cjs.map +0 -1
- package/dist/terminal-gMxAo9Dj.mjs.map +0 -1
- package/dist/types-CfLKGfC_.d.mts.map +0 -1
- package/dist/types-DIA7Li-J.d.cts.map +0 -1
- package/dist/workspace-Dh7rr7l4.mjs.map +0 -1
- package/dist/workspace-Kl4NG9IH.cjs.map +0 -1
package/dist/config.mjs
CHANGED
|
@@ -1,3 +1,402 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
import {
|
|
1
|
+
import { a as discriminatedUnion, c as object, l as record, n as array, o as literal, p as unknown, u as string } from "./schemas-D2LCF689.mjs";
|
|
2
|
+
import { c as runWorker, d as output, r as discover } from "./discover-LMrqsWSU.mjs";
|
|
3
|
+
import { i as activeSandbox } from "./sandbox-Dm247NGx.mjs";
|
|
4
|
+
import { n as derivedApps } from "./derive-B4vDpzgP.mjs";
|
|
5
|
+
import { h as isWorkspaceConfig, i as defineWorkspace, l as processConfig, o as getAppGkmConfig, t as allConstructGlobs } from "./workspace-Bwit4q9O.mjs";
|
|
6
|
+
import { dirname, join, parse, resolve, sep } from "node:path";
|
|
7
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
8
|
+
//#region src/config.ts
|
|
9
|
+
/**
|
|
10
|
+
* Define GKM configuration with full TypeScript support.
|
|
11
|
+
* This is an identity function that provides type safety and autocomplete.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* // gkm.config.ts
|
|
16
|
+
* import { defineConfig } from '@geekmidas/cli/config';
|
|
17
|
+
*
|
|
18
|
+
* export default defineConfig({
|
|
19
|
+
* routes: './src/endpoints/**\/*.ts',
|
|
20
|
+
* envParser: './src/config/env',
|
|
21
|
+
* logger: './src/config/logger',
|
|
22
|
+
* telescope: true,
|
|
23
|
+
* });
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
function defineConfig(config) {
|
|
27
|
+
return config;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Parse a module config string into path and import pattern.
|
|
31
|
+
*
|
|
32
|
+
* @param configString - Config string in format "./path/to/module" or "./path/to/module#exportName"
|
|
33
|
+
* @param defaultAlias - The default alias name to use if no export name specified
|
|
34
|
+
* @returns Object with path and import pattern
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* parseModuleConfig('./src/config/env', 'envParser')
|
|
38
|
+
* // { path: './src/config/env', importPattern: 'envParser' }
|
|
39
|
+
*
|
|
40
|
+
* parseModuleConfig('./src/config/env#envParser', 'envParser')
|
|
41
|
+
* // { path: './src/config/env', importPattern: '{ envParser }' }
|
|
42
|
+
*
|
|
43
|
+
* parseModuleConfig('./src/config/env#myEnv', 'envParser')
|
|
44
|
+
* // { path: './src/config/env', importPattern: '{ myEnv as envParser }' }
|
|
45
|
+
*/
|
|
46
|
+
function parseModuleConfig(configString, defaultAlias) {
|
|
47
|
+
const parts = configString.split("#");
|
|
48
|
+
const path = parts[0] ?? configString;
|
|
49
|
+
const exportName = parts[1];
|
|
50
|
+
return {
|
|
51
|
+
path,
|
|
52
|
+
importPattern: !exportName ? defaultAlias : exportName === defaultAlias ? `{ ${defaultAlias} }` : `{ ${exportName} as ${defaultAlias} }`
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Find and return the path to the config file.
|
|
57
|
+
*
|
|
58
|
+
* Resolution order:
|
|
59
|
+
* 1. GKM_CONFIG_PATH env var (set by workspace dev command)
|
|
60
|
+
* 2. Walk up directory tree from cwd
|
|
61
|
+
*/
|
|
62
|
+
function findConfigPath(cwd) {
|
|
63
|
+
const files = [
|
|
64
|
+
"gkm.config.json",
|
|
65
|
+
"gkm.config.ts",
|
|
66
|
+
"gkm.config.js"
|
|
67
|
+
];
|
|
68
|
+
const envConfigPath = process.env.GKM_CONFIG_PATH;
|
|
69
|
+
if (envConfigPath && existsSync(envConfigPath)) return {
|
|
70
|
+
configPath: envConfigPath,
|
|
71
|
+
workspaceRoot: dirname(envConfigPath)
|
|
72
|
+
};
|
|
73
|
+
let currentDir = cwd;
|
|
74
|
+
const { root } = parse(currentDir);
|
|
75
|
+
while (currentDir !== root) {
|
|
76
|
+
for (const file of files) {
|
|
77
|
+
const configPath = join(currentDir, file);
|
|
78
|
+
if (existsSync(configPath)) return {
|
|
79
|
+
configPath,
|
|
80
|
+
workspaceRoot: currentDir
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
currentDir = dirname(currentDir);
|
|
84
|
+
}
|
|
85
|
+
throw new ConfigNotFound(cwd);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The directory holding the gkm config that `cwd` belongs to — the project a
|
|
89
|
+
* sandbox for it is confined to.
|
|
90
|
+
*
|
|
91
|
+
* @throws {ConfigNotFound} when neither `cwd` nor any parent has one.
|
|
92
|
+
*/
|
|
93
|
+
function findWorkspaceRoot(cwd) {
|
|
94
|
+
return findConfigPath(cwd).workspaceRoot;
|
|
95
|
+
}
|
|
96
|
+
/** No gkm config in a directory or any of its parents. */
|
|
97
|
+
var ConfigNotFound = class extends Error {
|
|
98
|
+
cwd;
|
|
99
|
+
constructor(cwd) {
|
|
100
|
+
super("Configuration file not found. Please create gkm.config.json, gkm.config.ts, or gkm.config.js in the project root.");
|
|
101
|
+
this.cwd = cwd;
|
|
102
|
+
this.name = "ConfigNotFound";
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
/**
|
|
106
|
+
* How long loading a config, or discovering its constructs, may take in a
|
|
107
|
+
* sandbox: room for tsx to compile a large project cold, and not a deploy
|
|
108
|
+
* held open by a config that never finishes importing.
|
|
109
|
+
*/
|
|
110
|
+
const CONFIG_LOAD_TIMEOUT_MS = 6e4;
|
|
111
|
+
/** The config failed to import, or threw while it ran. */
|
|
112
|
+
var ConfigLoadFailed = class extends Error {
|
|
113
|
+
configPath;
|
|
114
|
+
detail;
|
|
115
|
+
constructor(configPath, detail) {
|
|
116
|
+
super(`Failed to load config: ${detail}`);
|
|
117
|
+
this.configPath = configPath;
|
|
118
|
+
this.detail = detail;
|
|
119
|
+
this.name = "ConfigLoadFailed";
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* The config holds something that is not data — a custom state store, an
|
|
124
|
+
* inline deploy target, a function — and the sandbox it was loaded in hands
|
|
125
|
+
* the deploy nothing but data.
|
|
126
|
+
*/
|
|
127
|
+
var ConfigObjectNotSerializable = class extends Error {
|
|
128
|
+
configPath;
|
|
129
|
+
paths;
|
|
130
|
+
constructor(configPath, paths) {
|
|
131
|
+
super(`${configPath} holds values that are not plain data at: ${paths.join(", ")}. It was loaded in an isolating sandbox, which hands the deploy the config as JSON, so a live object — a custom store, an inline target, a function — cannot reach it. Configure these with plain values instead, or deploy this project with a sandbox that is not isolating (the default) if the host trusts it.`);
|
|
132
|
+
this.configPath = configPath;
|
|
133
|
+
this.paths = paths;
|
|
134
|
+
this.name = "ConfigObjectNotSerializable";
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
/** What the config worker answers. Checked: the worker ran project code. */
|
|
138
|
+
const ConfigAnswer = discriminatedUnion("reason", [
|
|
139
|
+
object({
|
|
140
|
+
reason: literal("loaded"),
|
|
141
|
+
config: record(string(), unknown())
|
|
142
|
+
}),
|
|
143
|
+
object({
|
|
144
|
+
reason: literal("live"),
|
|
145
|
+
paths: array(string())
|
|
146
|
+
}),
|
|
147
|
+
object({
|
|
148
|
+
reason: literal("failed"),
|
|
149
|
+
error: object({
|
|
150
|
+
name: string(),
|
|
151
|
+
message: string()
|
|
152
|
+
})
|
|
153
|
+
})
|
|
154
|
+
]);
|
|
155
|
+
/** Import the config in this process — live objects and all. */
|
|
156
|
+
async function importConfig(configPath) {
|
|
157
|
+
try {
|
|
158
|
+
return (await import(configPath)).default;
|
|
159
|
+
} catch (error) {
|
|
160
|
+
throw new ConfigLoadFailed(configPath, error.message);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* The config, loaded in `sandbox` and handed back as JSON.
|
|
165
|
+
*
|
|
166
|
+
* A config holding live objects cannot come back that way. An isolating
|
|
167
|
+
* sandbox refuses it with {@link ConfigObjectNotSerializable}; any other
|
|
168
|
+
* imports it here as well, as before, since the host already runs this
|
|
169
|
+
* project's code as its own.
|
|
170
|
+
*/
|
|
171
|
+
async function loadConfigInSandbox(sandbox, configPath, workspaceRoot, timeoutMs = CONFIG_LOAD_TIMEOUT_MS) {
|
|
172
|
+
const { value } = await runWorker(sandbox, "config load", {
|
|
173
|
+
name: "config-worker",
|
|
174
|
+
args: [configPath],
|
|
175
|
+
cwd: workspaceRoot,
|
|
176
|
+
timeoutMs,
|
|
177
|
+
schema: ConfigAnswer
|
|
178
|
+
});
|
|
179
|
+
switch (value.reason) {
|
|
180
|
+
case "loaded": return value.config;
|
|
181
|
+
case "failed": throw new ConfigLoadFailed(configPath, value.error.message);
|
|
182
|
+
case "live":
|
|
183
|
+
if (sandbox.isolating) throw new ConfigObjectNotSerializable(configPath, value.paths);
|
|
184
|
+
return importConfig(configPath);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Load raw configuration from file.
|
|
189
|
+
*/
|
|
190
|
+
async function loadRawConfig(cwd, options = {}) {
|
|
191
|
+
const { configPath, workspaceRoot } = findConfigPath(cwd);
|
|
192
|
+
const sandbox = options.sandbox ?? activeSandbox();
|
|
193
|
+
return {
|
|
194
|
+
config: sandbox ? await loadConfigInSandbox(sandbox, configPath, workspaceRoot, options.timeoutMs) : await importConfig(configPath),
|
|
195
|
+
workspaceRoot
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Load configuration file (single-app format).
|
|
200
|
+
* For backwards compatibility with existing code.
|
|
201
|
+
*
|
|
202
|
+
* @deprecated Use loadWorkspaceConfig for new code
|
|
203
|
+
*/
|
|
204
|
+
async function loadConfig(cwd = process.cwd()) {
|
|
205
|
+
const { config } = await loadRawConfig(cwd);
|
|
206
|
+
if (isWorkspaceConfig(config)) throw new Error("Workspace configuration detected. Use loadWorkspaceConfig() instead.");
|
|
207
|
+
return config;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Load configuration file and process it as a workspace.
|
|
211
|
+
* Works with both single-app and workspace configurations.
|
|
212
|
+
*
|
|
213
|
+
* Single-app configs are automatically wrapped as a workspace with one app.
|
|
214
|
+
*
|
|
215
|
+
* @example
|
|
216
|
+
* ```ts
|
|
217
|
+
* const { type, workspace } = await loadWorkspaceConfig();
|
|
218
|
+
*
|
|
219
|
+
* if (type === 'workspace') {
|
|
220
|
+
* console.log('Multi-app workspace:', workspace.apps);
|
|
221
|
+
* } else {
|
|
222
|
+
* console.log('Single app wrapped as workspace');
|
|
223
|
+
* }
|
|
224
|
+
* ```
|
|
225
|
+
*/
|
|
226
|
+
/**
|
|
227
|
+
* The workspace a directory belongs to, as its config states it — without
|
|
228
|
+
* discovering its constructs.
|
|
229
|
+
*
|
|
230
|
+
* For callers that need a fact the config holds (where a stage's secrets are
|
|
231
|
+
* stored) and nothing the graph adds. `loadWorkspaceConfig` imports every
|
|
232
|
+
* construct module to derive the apps, which is a cost a secrets read should
|
|
233
|
+
* not pay.
|
|
234
|
+
*/
|
|
235
|
+
async function loadWorkspaceSettings(cwd = process.cwd(), options = {}) {
|
|
236
|
+
const { config, workspaceRoot } = await loadRawConfig(cwd, options);
|
|
237
|
+
return processConfig(config, workspaceRoot).workspace;
|
|
238
|
+
}
|
|
239
|
+
async function loadWorkspaceConfig(cwd = process.cwd(), options = {}) {
|
|
240
|
+
const { config, workspaceRoot } = await loadRawConfig(cwd, options);
|
|
241
|
+
return withDerivedApps(processConfig(config, workspaceRoot), options.sandbox);
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* The loaded config, with its apps read off the graph.
|
|
245
|
+
*
|
|
246
|
+
* Here rather than at each call site because every command needs the same
|
|
247
|
+
* answer, and the ones that forgot to ask were exactly the bugs: a site that
|
|
248
|
+
* never deployed, a surface that could not be its own process. `gkm dev`,
|
|
249
|
+
* `gkm build`, `gkm docker` and `gkm deploy` now all see the same apps without
|
|
250
|
+
* any of them knowing a derivation happened.
|
|
251
|
+
*
|
|
252
|
+
* Discovery imports the construct modules — declarations only, not endpoints —
|
|
253
|
+
* which is the same import the reconciler and the build already do. A workspace
|
|
254
|
+
* with no `constructs` glob discovers nothing and keeps whatever its config
|
|
255
|
+
* said, so nothing that worked before changes.
|
|
256
|
+
*
|
|
257
|
+
* A construct file that throws is reported and skipped rather than taking the
|
|
258
|
+
* command down: `gkm dev` failing to start because a half-written declaration
|
|
259
|
+
* cannot be imported is a worse failure than starting with the app it does not
|
|
260
|
+
* describe yet.
|
|
261
|
+
*/
|
|
262
|
+
async function withDerivedApps(loaded, sandbox) {
|
|
263
|
+
const globs = allConstructGlobs(loaded.workspace);
|
|
264
|
+
if (globs.length === 0) throw new WorkspaceDeclaresNoConstructs(loaded.workspace.root);
|
|
265
|
+
try {
|
|
266
|
+
const manifest = await discover({
|
|
267
|
+
patterns: globs,
|
|
268
|
+
cwd: loaded.workspace.root,
|
|
269
|
+
...sandbox ? { sandbox } : {}
|
|
270
|
+
});
|
|
271
|
+
return {
|
|
272
|
+
...loaded,
|
|
273
|
+
manifest,
|
|
274
|
+
workspace: {
|
|
275
|
+
...loaded.workspace,
|
|
276
|
+
apps: derivedApps(manifest, loaded.workspace)
|
|
277
|
+
}
|
|
278
|
+
};
|
|
279
|
+
} catch (error) {
|
|
280
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
281
|
+
output.warn(`⚠️ Could not read constructs, so apps come from config alone: ${message}`);
|
|
282
|
+
return loaded;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* A workspace with no `constructs` glob.
|
|
287
|
+
*
|
|
288
|
+
* In v10 every address, container and app comes from what the workspace
|
|
289
|
+
* declares — there is no second path that reads a hand-written compose file or
|
|
290
|
+
* an `apps` block's ports. A config that declares nothing has nothing to run.
|
|
291
|
+
*/
|
|
292
|
+
var WorkspaceDeclaresNoConstructs = class extends Error {
|
|
293
|
+
root;
|
|
294
|
+
constructor(root) {
|
|
295
|
+
super(`The workspace at ${root} declares no constructs. Every workspace names where its constructs live — \`constructs: ['./constructs/**/*.ts', './apps/*/endpoints/**/*.ts']\` in gkm.config.ts — and its apps, containers and URLs are derived from them.`);
|
|
296
|
+
this.root = root;
|
|
297
|
+
this.name = "WorkspaceDeclaresNoConstructs";
|
|
298
|
+
}
|
|
299
|
+
};
|
|
300
|
+
/**
|
|
301
|
+
* The app a directory is part of: the one whose `path` contains it.
|
|
302
|
+
*
|
|
303
|
+
* An app's path is the one its construct declares — `new RestApi('Api',
|
|
304
|
+
* { path: 'apps/api' })` — read off the workspace the constructs were derived
|
|
305
|
+
* into, never off the filesystem's package names. The deepest match wins, so
|
|
306
|
+
* an app inside another app's folder is its own; the workspace root belongs to
|
|
307
|
+
* no app unless one lives there.
|
|
308
|
+
*/
|
|
309
|
+
function resolveWorkspaceApp(loadedConfig, cwd) {
|
|
310
|
+
const here = real(cwd);
|
|
311
|
+
let found;
|
|
312
|
+
for (const [key, app] of Object.entries(loadedConfig.workspace.apps)) {
|
|
313
|
+
const dir = real(join(loadedConfig.workspace.root, app.path));
|
|
314
|
+
if ((here === dir || here.startsWith(`${dir}${sep}`)) && (!found || dir.length > found.dir.length)) found = {
|
|
315
|
+
key,
|
|
316
|
+
app,
|
|
317
|
+
dir
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
return found && {
|
|
321
|
+
key: found.key,
|
|
322
|
+
app: found.app
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
/** A path with its symlinks resolved, where it exists — `/tmp` is `/private/tmp`. */
|
|
326
|
+
function real(path) {
|
|
327
|
+
const absolute = resolve(path);
|
|
328
|
+
return existsSync(absolute) ? realpathSync(absolute) : absolute;
|
|
329
|
+
}
|
|
330
|
+
/** A directory that no app in the workspace lives in. */
|
|
331
|
+
var NotInAnApp = class extends Error {
|
|
332
|
+
cwd;
|
|
333
|
+
apps;
|
|
334
|
+
constructor(cwd, apps) {
|
|
335
|
+
super(`${cwd} is not inside any app's path. Apps: ` + Object.entries(apps).map(([key, path]) => `${key} (${path})`).join(", ") + ".");
|
|
336
|
+
this.cwd = cwd;
|
|
337
|
+
this.apps = apps;
|
|
338
|
+
this.name = "NotInAnApp";
|
|
339
|
+
}
|
|
340
|
+
};
|
|
341
|
+
/** Every app's path, by key — what `NotInAnApp` lists. */
|
|
342
|
+
function appPaths(loadedConfig) {
|
|
343
|
+
return Object.fromEntries(Object.entries(loadedConfig.workspace.apps).map(([key, app]) => [key, app.path]));
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* The backend app a directory is part of, with its gkm config — found by the
|
|
347
|
+
* `path` its construct declares.
|
|
348
|
+
*
|
|
349
|
+
* @example
|
|
350
|
+
* ```ts
|
|
351
|
+
* // From apps/api, where a construct declares `path: 'apps/api'`
|
|
352
|
+
* const { app, workspace, workspaceRoot } = await loadAppConfig();
|
|
353
|
+
* ```
|
|
354
|
+
*
|
|
355
|
+
* @throws {NotInAnApp} when no app's path contains the directory.
|
|
356
|
+
* @throws {NotABackendApp} when the app it is in has no gkm config to run.
|
|
357
|
+
*/
|
|
358
|
+
async function loadAppConfig(cwd = process.cwd()) {
|
|
359
|
+
const { appName, app, workspace, workspaceRoot } = await loadWorkspaceAppInfo(cwd);
|
|
360
|
+
const gkmConfig = getAppGkmConfig(workspace, appName);
|
|
361
|
+
if (!gkmConfig) throw new NotABackendApp(appName);
|
|
362
|
+
return {
|
|
363
|
+
appName,
|
|
364
|
+
app,
|
|
365
|
+
gkmConfig,
|
|
366
|
+
workspace,
|
|
367
|
+
workspaceRoot,
|
|
368
|
+
appRoot: join(workspaceRoot, app.path)
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
/** The app a directory is in has no gkm config — a site, or an entry app. */
|
|
372
|
+
var NotABackendApp = class extends Error {
|
|
373
|
+
appName;
|
|
374
|
+
constructor(appName) {
|
|
375
|
+
super(`App "${appName}" is not a backend app and cannot be run with gkm dev.`);
|
|
376
|
+
this.appName = appName;
|
|
377
|
+
this.name = "NotABackendApp";
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
/**
|
|
381
|
+
* The app a directory is part of, frontend or backend — found by the `path`
|
|
382
|
+
* its construct declares. Unlike `loadAppConfig`, the app need not have a
|
|
383
|
+
* gkm config, which is what `gkm exec` and `gkm test` from a site need.
|
|
384
|
+
*
|
|
385
|
+
* @throws {NotInAnApp} when no app's path contains the directory.
|
|
386
|
+
*/
|
|
387
|
+
async function loadWorkspaceAppInfo(cwd = process.cwd()) {
|
|
388
|
+
const { config, workspaceRoot } = await loadRawConfig(cwd);
|
|
389
|
+
const loadedConfig = await withDerivedApps(processConfig(config, workspaceRoot));
|
|
390
|
+
const resolved = resolveWorkspaceApp(loadedConfig, cwd);
|
|
391
|
+
if (!resolved) throw new NotInAnApp(cwd, appPaths(loadedConfig));
|
|
392
|
+
return {
|
|
393
|
+
appName: resolved.key,
|
|
394
|
+
app: resolved.app,
|
|
395
|
+
workspace: loadedConfig.workspace,
|
|
396
|
+
workspaceRoot
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
//#endregion
|
|
3
400
|
export { CONFIG_LOAD_TIMEOUT_MS, ConfigLoadFailed, ConfigNotFound, ConfigObjectNotSerializable, NotABackendApp, NotInAnApp, WorkspaceDeclaresNoConstructs, defineConfig, defineWorkspace, findWorkspaceRoot, loadAppConfig, loadConfig, loadWorkspaceAppInfo, loadWorkspaceConfig, loadWorkspaceSettings, parseModuleConfig };
|
|
401
|
+
|
|
402
|
+
//# sourceMappingURL=config.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.mjs","names":["z.discriminatedUnion","z.object","z.literal","z.record","z.string","z.unknown","z.array"],"sources":["../src/config.ts"],"sourcesContent":["import { existsSync, realpathSync } from 'node:fs';\nimport { dirname, join, parse, resolve, sep } from 'node:path';\nimport { z } from 'zod';\nimport { output } from './output.js';\nimport { discover } from './reconcile/discover.js';\nimport { activeSandbox, type Sandbox } from './sandbox/sandbox.js';\nimport { runWorker } from './sandbox/worker.js';\nimport type { GkmConfig } from './types.js';\nimport { derivedApps } from './workspace/derive.js';\nimport {\n\tallConstructGlobs,\n\tgetAppGkmConfig,\n\tisWorkspaceConfig,\n\ttype LoadedConfig,\n\ttype NormalizedAppConfig,\n\ttype NormalizedWorkspace,\n\tprocessConfig,\n\ttype WorkspaceConfig,\n} from './workspace/index.js';\n\n// What `gkm dev`'s discovery endpoint answers, for the tools that read it.\nexport type {\n\tAppStatus,\n\tDataApiKind,\n\tDiscoveredApp,\n\tDiscoveredDataApi,\n\tDiscoveredWorkspace,\n\tDiscoveryEvent,\n\tDiscoveryResponse,\n} from './dev/discovery.js';\nexport type { GkmConfig } from './types.js';\nexport type { LoadedConfig, WorkspaceConfig } from './workspace/index.js';\nexport { defineWorkspace } from './workspace/index.js';\nexport type { DevConfig } from './workspace/types.js';\n/**\n * Define GKM configuration with full TypeScript support.\n * This is an identity function that provides type safety and autocomplete.\n *\n * @example\n * ```ts\n * // gkm.config.ts\n * import { defineConfig } from '@geekmidas/cli/config';\n *\n * export default defineConfig({\n * routes: './src/endpoints/**\\/*.ts',\n * envParser: './src/config/env',\n * logger: './src/config/logger',\n * telescope: true,\n * });\n * ```\n */\nexport function defineConfig(config: GkmConfig): GkmConfig {\n\treturn config;\n}\n\nexport interface ParsedModuleConfig {\n\tpath: string;\n\timportPattern: string;\n}\n\n/**\n * Parse a module config string into path and import pattern.\n *\n * @param configString - Config string in format \"./path/to/module\" or \"./path/to/module#exportName\"\n * @param defaultAlias - The default alias name to use if no export name specified\n * @returns Object with path and import pattern\n *\n * @example\n * parseModuleConfig('./src/config/env', 'envParser')\n * // { path: './src/config/env', importPattern: 'envParser' }\n *\n * parseModuleConfig('./src/config/env#envParser', 'envParser')\n * // { path: './src/config/env', importPattern: '{ envParser }' }\n *\n * parseModuleConfig('./src/config/env#myEnv', 'envParser')\n * // { path: './src/config/env', importPattern: '{ myEnv as envParser }' }\n */\nexport function parseModuleConfig(\n\tconfigString: string,\n\tdefaultAlias: string,\n): ParsedModuleConfig {\n\tconst parts = configString.split('#');\n\tconst path = parts[0] ?? configString;\n\tconst exportName = parts[1];\n\tconst importPattern = !exportName\n\t\t? defaultAlias\n\t\t: exportName === defaultAlias\n\t\t\t? `{ ${defaultAlias} }`\n\t\t\t: `{ ${exportName} as ${defaultAlias} }`;\n\n\treturn { path, importPattern };\n}\n\nexport interface ConfigDiscoveryResult {\n\tconfigPath: string;\n\tworkspaceRoot: string;\n}\n\n/**\n * Find and return the path to the config file.\n *\n * Resolution order:\n * 1. GKM_CONFIG_PATH env var (set by workspace dev command)\n * 2. Walk up directory tree from cwd\n */\nfunction findConfigPath(cwd: string): ConfigDiscoveryResult {\n\tconst files = ['gkm.config.json', 'gkm.config.ts', 'gkm.config.js'];\n\n\t// Check GKM_CONFIG_PATH env var first (set by workspace dev command)\n\tconst envConfigPath = process.env.GKM_CONFIG_PATH;\n\tif (envConfigPath && existsSync(envConfigPath)) {\n\t\treturn {\n\t\t\tconfigPath: envConfigPath,\n\t\t\tworkspaceRoot: dirname(envConfigPath),\n\t\t};\n\t}\n\n\t// Walk up directory tree to find config\n\tlet currentDir = cwd;\n\tconst { root } = parse(currentDir);\n\n\twhile (currentDir !== root) {\n\t\tfor (const file of files) {\n\t\t\tconst configPath = join(currentDir, file);\n\t\t\tif (existsSync(configPath)) {\n\t\t\t\treturn {\n\t\t\t\t\tconfigPath,\n\t\t\t\t\tworkspaceRoot: currentDir,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t\tcurrentDir = dirname(currentDir);\n\t}\n\n\tthrow new ConfigNotFound(cwd);\n}\n\n/**\n * The directory holding the gkm config that `cwd` belongs to — the project a\n * sandbox for it is confined to.\n *\n * @throws {ConfigNotFound} when neither `cwd` nor any parent has one.\n */\nexport function findWorkspaceRoot(cwd: string): string {\n\treturn findConfigPath(cwd).workspaceRoot;\n}\n\n/** No gkm config in a directory or any of its parents. */\nexport class ConfigNotFound extends Error {\n\tconstructor(readonly cwd: string) {\n\t\tsuper(\n\t\t\t'Configuration file not found. Please create gkm.config.json, gkm.config.ts, or gkm.config.js in the project root.',\n\t\t);\n\t\tthis.name = 'ConfigNotFound';\n\t}\n}\n\ninterface RawConfigResult {\n\tconfig: GkmConfig | WorkspaceConfig;\n\tworkspaceRoot: string;\n}\n\nexport interface LoadConfigOptions {\n\t/**\n\t * Load the config — and discover the constructs it names — in this\n\t * sandbox rather than in this process. The config then arrives as data;\n\t * see {@link Sandbox.isolating} for what happens to one holding live\n\t * objects. Defaults to the run's own (`withSandbox`); with neither, the\n\t * config is imported here, as `gkm` always has.\n\t */\n\tsandbox?: Sandbox;\n\t/**\n\t * How long the sandboxed config load may run. Defaults to\n\t * {@link CONFIG_LOAD_TIMEOUT_MS}.\n\t */\n\ttimeoutMs?: number;\n}\n\n/**\n * How long loading a config, or discovering its constructs, may take in a\n * sandbox: room for tsx to compile a large project cold, and not a deploy\n * held open by a config that never finishes importing.\n */\nexport const CONFIG_LOAD_TIMEOUT_MS = 60_000;\n\n/** The config failed to import, or threw while it ran. */\nexport class ConfigLoadFailed extends Error {\n\tconstructor(\n\t\treadonly configPath: string,\n\t\treadonly detail: string,\n\t) {\n\t\tsuper(`Failed to load config: ${detail}`);\n\t\tthis.name = 'ConfigLoadFailed';\n\t}\n}\n\n/**\n * The config holds something that is not data — a custom state store, an\n * inline deploy target, a function — and the sandbox it was loaded in hands\n * the deploy nothing but data.\n */\nexport class ConfigObjectNotSerializable extends Error {\n\tconstructor(\n\t\treadonly configPath: string,\n\t\treadonly paths: readonly string[],\n\t) {\n\t\tsuper(\n\t\t\t`${configPath} holds values that are not plain data at: ` +\n\t\t\t\t`${paths.join(', ')}. It was loaded in an isolating sandbox, which ` +\n\t\t\t\t'hands the deploy the config as JSON, so a live object — a custom ' +\n\t\t\t\t'store, an inline target, a function — cannot reach it. Configure ' +\n\t\t\t\t'these with plain values instead, or deploy this project with a ' +\n\t\t\t\t'sandbox that is not isolating (the default) if the host trusts it.',\n\t\t);\n\t\tthis.name = 'ConfigObjectNotSerializable';\n\t}\n}\n\n/** What the config worker answers. Checked: the worker ran project code. */\nconst ConfigAnswer = z.discriminatedUnion('reason', [\n\tz.object({\n\t\treason: z.literal('loaded'),\n\t\tconfig: z.record(z.string(), z.unknown()),\n\t}),\n\tz.object({ reason: z.literal('live'), paths: z.array(z.string()) }),\n\tz.object({\n\t\treason: z.literal('failed'),\n\t\terror: z.object({ name: z.string(), message: z.string() }),\n\t}),\n]);\n\n/** Import the config in this process — live objects and all. */\nasync function importConfig(\n\tconfigPath: string,\n): Promise<GkmConfig | WorkspaceConfig> {\n\ttry {\n\t\tconst config = await import(configPath);\n\t\treturn config.default;\n\t} catch (error) {\n\t\tthrow new ConfigLoadFailed(configPath, (error as Error).message);\n\t}\n}\n\n/**\n * The config, loaded in `sandbox` and handed back as JSON.\n *\n * A config holding live objects cannot come back that way. An isolating\n * sandbox refuses it with {@link ConfigObjectNotSerializable}; any other\n * imports it here as well, as before, since the host already runs this\n * project's code as its own.\n */\nasync function loadConfigInSandbox(\n\tsandbox: Sandbox,\n\tconfigPath: string,\n\tworkspaceRoot: string,\n\ttimeoutMs = CONFIG_LOAD_TIMEOUT_MS,\n): Promise<GkmConfig | WorkspaceConfig> {\n\tconst { value } = await runWorker(sandbox, 'config load', {\n\t\tname: 'config-worker',\n\t\targs: [configPath],\n\t\tcwd: workspaceRoot,\n\t\ttimeoutMs,\n\t\tschema: ConfigAnswer,\n\t});\n\n\tswitch (value.reason) {\n\t\tcase 'loaded':\n\t\t\t// Its shape is `processConfig`'s to check, against the workspace\n\t\t\t// schema, exactly as for a config imported here.\n\t\t\treturn value.config as unknown as GkmConfig | WorkspaceConfig;\n\t\tcase 'failed':\n\t\t\tthrow new ConfigLoadFailed(configPath, value.error.message);\n\t\tcase 'live':\n\t\t\tif (sandbox.isolating) {\n\t\t\t\tthrow new ConfigObjectNotSerializable(configPath, value.paths);\n\t\t\t}\n\t\t\treturn importConfig(configPath);\n\t}\n}\n\n/**\n * Load raw configuration from file.\n */\nasync function loadRawConfig(\n\tcwd: string,\n\toptions: LoadConfigOptions = {},\n): Promise<RawConfigResult> {\n\tconst { configPath, workspaceRoot } = findConfigPath(cwd);\n\t// The run's own sandbox when none is given: a deploy reloads the config\n\t// deep in the engine (generating Dockerfiles), and that load is the\n\t// project's code as much as the first.\n\tconst sandbox = options.sandbox ?? activeSandbox();\n\tconst config = sandbox\n\t\t? await loadConfigInSandbox(\n\t\t\t\tsandbox,\n\t\t\t\tconfigPath,\n\t\t\t\tworkspaceRoot,\n\t\t\t\toptions.timeoutMs,\n\t\t\t)\n\t\t: await importConfig(configPath);\n\n\treturn { config, workspaceRoot };\n}\n\n/**\n * Load configuration file (single-app format).\n * For backwards compatibility with existing code.\n *\n * @deprecated Use loadWorkspaceConfig for new code\n */\nexport async function loadConfig(\n\tcwd: string = process.cwd(),\n): Promise<GkmConfig> {\n\tconst { config } = await loadRawConfig(cwd);\n\n\t// If it's a workspace config, throw an error\n\tif (isWorkspaceConfig(config)) {\n\t\tthrow new Error(\n\t\t\t'Workspace configuration detected. Use loadWorkspaceConfig() instead.',\n\t\t);\n\t}\n\n\treturn config;\n}\n\n/**\n * Load configuration file and process it as a workspace.\n * Works with both single-app and workspace configurations.\n *\n * Single-app configs are automatically wrapped as a workspace with one app.\n *\n * @example\n * ```ts\n * const { type, workspace } = await loadWorkspaceConfig();\n *\n * if (type === 'workspace') {\n * console.log('Multi-app workspace:', workspace.apps);\n * } else {\n * console.log('Single app wrapped as workspace');\n * }\n * ```\n */\n/**\n * The workspace a directory belongs to, as its config states it — without\n * discovering its constructs.\n *\n * For callers that need a fact the config holds (where a stage's secrets are\n * stored) and nothing the graph adds. `loadWorkspaceConfig` imports every\n * construct module to derive the apps, which is a cost a secrets read should\n * not pay.\n */\nexport async function loadWorkspaceSettings(\n\tcwd: string = process.cwd(),\n\toptions: LoadConfigOptions = {},\n): Promise<LoadedConfig['workspace']> {\n\tconst { config, workspaceRoot } = await loadRawConfig(cwd, options);\n\treturn processConfig(config, workspaceRoot).workspace;\n}\n\nexport async function loadWorkspaceConfig(\n\tcwd: string = process.cwd(),\n\toptions: LoadConfigOptions = {},\n): Promise<LoadedConfig> {\n\tconst { config, workspaceRoot } = await loadRawConfig(cwd, options);\n\treturn withDerivedApps(processConfig(config, workspaceRoot), options.sandbox);\n}\n\n/**\n * The loaded config, with its apps read off the graph.\n *\n * Here rather than at each call site because every command needs the same\n * answer, and the ones that forgot to ask were exactly the bugs: a site that\n * never deployed, a surface that could not be its own process. `gkm dev`,\n * `gkm build`, `gkm docker` and `gkm deploy` now all see the same apps without\n * any of them knowing a derivation happened.\n *\n * Discovery imports the construct modules — declarations only, not endpoints —\n * which is the same import the reconciler and the build already do. A workspace\n * with no `constructs` glob discovers nothing and keeps whatever its config\n * said, so nothing that worked before changes.\n *\n * A construct file that throws is reported and skipped rather than taking the\n * command down: `gkm dev` failing to start because a half-written declaration\n * cannot be imported is a worse failure than starting with the app it does not\n * describe yet.\n */\nasync function withDerivedApps(\n\tloaded: LoadedConfig,\n\tsandbox?: Sandbox,\n): Promise<LoadedConfig> {\n\tconst globs = allConstructGlobs(loaded.workspace);\n\tif (globs.length === 0) {\n\t\tthrow new WorkspaceDeclaresNoConstructs(loaded.workspace.root);\n\t}\n\n\ttry {\n\t\tconst manifest = await discover({\n\t\t\tpatterns: globs,\n\t\t\tcwd: loaded.workspace.root,\n\t\t\t...(sandbox ? { sandbox } : {}),\n\t\t});\n\n\t\treturn {\n\t\t\t...loaded,\n\t\t\tmanifest,\n\t\t\tworkspace: {\n\t\t\t\t...loaded.workspace,\n\t\t\t\tapps: derivedApps(manifest, loaded.workspace),\n\t\t\t},\n\t\t};\n\t} catch (error) {\n\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\toutput.warn(\n\t\t\t`⚠️ Could not read constructs, so apps come from config alone: ${message}`,\n\t\t);\n\t\treturn loaded;\n\t}\n}\n\n/**\n * A workspace with no `constructs` glob.\n *\n * In v10 every address, container and app comes from what the workspace\n * declares — there is no second path that reads a hand-written compose file or\n * an `apps` block's ports. A config that declares nothing has nothing to run.\n */\nexport class WorkspaceDeclaresNoConstructs extends Error {\n\tconstructor(readonly root: string) {\n\t\tsuper(\n\t\t\t`The workspace at ${root} declares no constructs. Every workspace names ` +\n\t\t\t\t\"where its constructs live — `constructs: ['./constructs/**/*.ts', \" +\n\t\t\t\t\"'./apps/*/endpoints/**/*.ts']` in gkm.config.ts — and its apps, \" +\n\t\t\t\t'containers and URLs are derived from them.',\n\t\t);\n\t\tthis.name = 'WorkspaceDeclaresNoConstructs';\n\t}\n}\n\nexport interface AppConfigResult {\n\tappName: string;\n\tapp: NormalizedAppConfig;\n\tgkmConfig: GkmConfig;\n\tworkspace: NormalizedWorkspace;\n\tworkspaceRoot: string;\n\tappRoot: string;\n}\n\n/**\n * The app a directory is part of: the one whose `path` contains it.\n *\n * An app's path is the one its construct declares — `new RestApi('Api',\n * { path: 'apps/api' })` — read off the workspace the constructs were derived\n * into, never off the filesystem's package names. The deepest match wins, so\n * an app inside another app's folder is its own; the workspace root belongs to\n * no app unless one lives there.\n */\nfunction resolveWorkspaceApp(\n\tloadedConfig: LoadedConfig,\n\tcwd: string,\n): { key: string; app: NormalizedAppConfig } | undefined {\n\tconst here = real(cwd);\n\tlet found: { key: string; app: NormalizedAppConfig; dir: string } | undefined;\n\n\tfor (const [key, app] of Object.entries(loadedConfig.workspace.apps)) {\n\t\tconst dir = real(join(loadedConfig.workspace.root, app.path));\n\t\tconst inside = here === dir || here.startsWith(`${dir}${sep}`);\n\t\tif (inside && (!found || dir.length > found.dir.length)) {\n\t\t\tfound = { key, app, dir };\n\t\t}\n\t}\n\n\treturn found && { key: found.key, app: found.app };\n}\n\n/** A path with its symlinks resolved, where it exists — `/tmp` is `/private/tmp`. */\nfunction real(path: string): string {\n\tconst absolute = resolve(path);\n\treturn existsSync(absolute) ? realpathSync(absolute) : absolute;\n}\n\n/** A directory that no app in the workspace lives in. */\nexport class NotInAnApp extends Error {\n\tconstructor(\n\t\treadonly cwd: string,\n\t\treadonly apps: Readonly<Record<string, string>>,\n\t) {\n\t\tsuper(\n\t\t\t`${cwd} is not inside any app's path. Apps: ` +\n\t\t\t\tObject.entries(apps)\n\t\t\t\t\t.map(([key, path]) => `${key} (${path})`)\n\t\t\t\t\t.join(', ') +\n\t\t\t\t'.',\n\t\t);\n\t\tthis.name = 'NotInAnApp';\n\t}\n}\n\n/** Every app's path, by key — what `NotInAnApp` lists. */\nfunction appPaths(loadedConfig: LoadedConfig): Record<string, string> {\n\treturn Object.fromEntries(\n\t\tObject.entries(loadedConfig.workspace.apps).map(([key, app]) => [\n\t\t\tkey,\n\t\t\tapp.path,\n\t\t]),\n\t);\n}\n\n/**\n * The backend app a directory is part of, with its gkm config — found by the\n * `path` its construct declares.\n *\n * @example\n * ```ts\n * // From apps/api, where a construct declares `path: 'apps/api'`\n * const { app, workspace, workspaceRoot } = await loadAppConfig();\n * ```\n *\n * @throws {NotInAnApp} when no app's path contains the directory.\n * @throws {NotABackendApp} when the app it is in has no gkm config to run.\n */\nexport async function loadAppConfig(\n\tcwd: string = process.cwd(),\n): Promise<AppConfigResult> {\n\tconst { appName, app, workspace, workspaceRoot } =\n\t\tawait loadWorkspaceAppInfo(cwd);\n\n\tconst gkmConfig = getAppGkmConfig(workspace, appName);\n\tif (!gkmConfig) throw new NotABackendApp(appName);\n\n\treturn {\n\t\tappName,\n\t\tapp,\n\t\tgkmConfig,\n\t\tworkspace,\n\t\tworkspaceRoot,\n\t\tappRoot: join(workspaceRoot, app.path),\n\t};\n}\n\n/** The app a directory is in has no gkm config — a site, or an entry app. */\nexport class NotABackendApp extends Error {\n\tconstructor(readonly appName: string) {\n\t\tsuper(\n\t\t\t`App \"${appName}\" is not a backend app and cannot be run with gkm dev.`,\n\t\t);\n\t\tthis.name = 'NotABackendApp';\n\t}\n}\n\nexport interface WorkspaceAppInfo {\n\t/** The app's key in the workspace. */\n\tappName: string;\n\tapp: NormalizedAppConfig;\n\tworkspace: NormalizedWorkspace;\n\tworkspaceRoot: string;\n}\n\n/**\n * The app a directory is part of, frontend or backend — found by the `path`\n * its construct declares. Unlike `loadAppConfig`, the app need not have a\n * gkm config, which is what `gkm exec` and `gkm test` from a site need.\n *\n * @throws {NotInAnApp} when no app's path contains the directory.\n */\nexport async function loadWorkspaceAppInfo(\n\tcwd: string = process.cwd(),\n): Promise<WorkspaceAppInfo> {\n\tconst { config, workspaceRoot } = await loadRawConfig(cwd);\n\tconst loadedConfig = await withDerivedApps(\n\t\tprocessConfig(config, workspaceRoot),\n\t);\n\n\tconst resolved = resolveWorkspaceApp(loadedConfig, cwd);\n\tif (!resolved) throw new NotInAnApp(cwd, appPaths(loadedConfig));\n\n\treturn {\n\t\tappName: resolved.key,\n\t\tapp: resolved.app,\n\t\tworkspace: loadedConfig.workspace,\n\t\tworkspaceRoot,\n\t};\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
const require_local = require("./local-C7wnrcYK.cjs");
|
|
2
|
+
//#region src/deploy/credentials.ts
|
|
3
|
+
/**
|
|
4
|
+
* How a deploy gets the credentials it acts with.
|
|
5
|
+
*
|
|
6
|
+
* `deploy()` never prompts and never reads a terminal: it asks a
|
|
7
|
+
* `CredentialProvider` and, when the provider has nothing, stops with
|
|
8
|
+
* `MissingCredential` naming what it needed and how to supply it. Whether to
|
|
9
|
+
* ask a person, read a vault or fail is the caller's decision — `gkm deploy`
|
|
10
|
+
* prompts at a terminal and stores what was typed; a CI job sets the
|
|
11
|
+
* environment; a host passes its own provider.
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* How to supply each kind the CLI knows, for the error that says it is
|
|
15
|
+
* missing. A target's own kinds are its to explain.
|
|
16
|
+
*/
|
|
17
|
+
const HOW_TO_PROVIDE = {
|
|
18
|
+
dokploy: "Set DOKPLOY_API_TOKEN (and DOKPLOY_ENDPOINT, or deploy.dokploy.endpoint in gkm.config.ts), run `gkm login --provider dokploy`, or pass them through the deploy's CredentialProvider.",
|
|
19
|
+
registry: "Add the registry in Dokploy (Settings → Docker Registry) and set deploy.dokploy.registryId, set DOCKER_REGISTRY_USERNAME and DOCKER_REGISTRY_PASSWORD, or run `gkm deploy` at a terminal to be asked for them.",
|
|
20
|
+
aws: "Set AWS_PROFILE to the stage account's profile, or AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY (and AWS_SESSION_TOKEN) — in CI, the keys aws-actions/configure-aws-credentials exports — or pass them through the deploy's CredentialProvider."
|
|
21
|
+
};
|
|
22
|
+
/** A deploy needed a credential, and its provider had none. */
|
|
23
|
+
var MissingCredential = class extends Error {
|
|
24
|
+
kind;
|
|
25
|
+
target;
|
|
26
|
+
howToProvide;
|
|
27
|
+
constructor(kind, target, howToProvide = HOW_TO_PROVIDE[kind] ?? "Pass it through the deploy's CredentialProvider.") {
|
|
28
|
+
const what = kind === "dokploy" ? `No Dokploy credentials${target ? ` for ${target}` : ""}.` : kind === "registry" ? `Dokploy has no registry for ${target ?? "the configured registry"}, and there are no credentials to create one with.` : kind === "aws" ? `No AWS credentials${target ? ` for the "${target}" stage` : ""}.` : `No "${String(kind)}" credentials${target ? ` for ${target}` : ""}.`;
|
|
29
|
+
super(`${what} ${howToProvide}`);
|
|
30
|
+
this.kind = kind;
|
|
31
|
+
this.target = target;
|
|
32
|
+
this.howToProvide = howToProvide;
|
|
33
|
+
this.name = "MissingCredential";
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
/** Whatever was asked for, from the first provider that has it. */
|
|
37
|
+
function chainCredentials(...providers) {
|
|
38
|
+
return { async get(request, options) {
|
|
39
|
+
for (const provider of providers) {
|
|
40
|
+
const found = await provider.get(request, options);
|
|
41
|
+
if (found) return found;
|
|
42
|
+
}
|
|
43
|
+
} };
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Credentials from the environment, then from what `gkm login` stored. What
|
|
47
|
+
* `deploy()` uses when it is handed no provider.
|
|
48
|
+
*
|
|
49
|
+
* - Dokploy: `DOKPLOY_API_TOKEN` / `DOKPLOY_ENDPOINT`, each falling back to
|
|
50
|
+
* the stored login, and the endpoint finally to `deploy.dokploy.endpoint`.
|
|
51
|
+
* Each half is read on its own: a token is a secret and belongs in the
|
|
52
|
+
* environment, an endpoint is configuration and belongs in the config.
|
|
53
|
+
* - Registry: `DOCKER_REGISTRY_USERNAME` / `DOCKER_REGISTRY_PASSWORD`.
|
|
54
|
+
* - AWS: `AWS_PROFILE` when it is set, and then nothing else; otherwise
|
|
55
|
+
* `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` / `AWS_SESSION_TOKEN`. The
|
|
56
|
+
* profile winning is `secretsStoreFor`'s rule: a profile is a choice of
|
|
57
|
+
* account someone made, exported keys may be left over from another.
|
|
58
|
+
* Read here, by name, and handed only to the command that deploys — the
|
|
59
|
+
* sandbox every other step runs in passes no `AWS_*` on.
|
|
60
|
+
*/
|
|
61
|
+
function storedCredentials(options = {}) {
|
|
62
|
+
const env = options.env ?? process.env;
|
|
63
|
+
return { async get(asked) {
|
|
64
|
+
const request = asked;
|
|
65
|
+
switch (request.kind) {
|
|
66
|
+
case "dokploy": {
|
|
67
|
+
const stored = (await require_local.readCredentials(options.home ? { home: options.home } : void 0)).dokploy;
|
|
68
|
+
const token = env.DOKPLOY_API_TOKEN ?? stored?.token;
|
|
69
|
+
const endpoint = env.DOKPLOY_ENDPOINT ?? stored?.endpoint ?? request.endpoint;
|
|
70
|
+
if (!token || !endpoint) return void 0;
|
|
71
|
+
return {
|
|
72
|
+
endpoint: endpoint.replace(/\/$/, ""),
|
|
73
|
+
token
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
case "registry": {
|
|
77
|
+
const username = env.DOCKER_REGISTRY_USERNAME;
|
|
78
|
+
const password = env.DOCKER_REGISTRY_PASSWORD;
|
|
79
|
+
if (!username || !password) return void 0;
|
|
80
|
+
return {
|
|
81
|
+
username,
|
|
82
|
+
password
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
case "aws": {
|
|
86
|
+
const region = env.AWS_REGION ?? env.AWS_DEFAULT_REGION;
|
|
87
|
+
const where = region ? { region } : {};
|
|
88
|
+
if (env.AWS_PROFILE) return {
|
|
89
|
+
profile: env.AWS_PROFILE,
|
|
90
|
+
...where
|
|
91
|
+
};
|
|
92
|
+
const accessKeyId = env.AWS_ACCESS_KEY_ID;
|
|
93
|
+
const secretAccessKey = env.AWS_SECRET_ACCESS_KEY;
|
|
94
|
+
if (!accessKeyId || !secretAccessKey) return void 0;
|
|
95
|
+
return {
|
|
96
|
+
accessKeyId,
|
|
97
|
+
secretAccessKey,
|
|
98
|
+
...env.AWS_SESSION_TOKEN ? { sessionToken: env.AWS_SESSION_TOKEN } : {},
|
|
99
|
+
...where
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
} };
|
|
104
|
+
}
|
|
105
|
+
//#endregion
|
|
106
|
+
Object.defineProperty(exports, "MissingCredential", {
|
|
107
|
+
enumerable: true,
|
|
108
|
+
get: function() {
|
|
109
|
+
return MissingCredential;
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
Object.defineProperty(exports, "chainCredentials", {
|
|
113
|
+
enumerable: true,
|
|
114
|
+
get: function() {
|
|
115
|
+
return chainCredentials;
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
Object.defineProperty(exports, "storedCredentials", {
|
|
119
|
+
enumerable: true,
|
|
120
|
+
get: function() {
|
|
121
|
+
return storedCredentials;
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
//# sourceMappingURL=credentials-B70vmLcm.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials-B70vmLcm.cjs","names":["readCredentials"],"sources":["../src/deploy/credentials.ts"],"sourcesContent":["/**\n * How a deploy gets the credentials it acts with.\n *\n * `deploy()` never prompts and never reads a terminal: it asks a\n * `CredentialProvider` and, when the provider has nothing, stops with\n * `MissingCredential` naming what it needed and how to supply it. Whether to\n * ask a person, read a vault or fail is the caller's decision — `gkm deploy`\n * prompts at a terminal and stores what was typed; a CI job sets the\n * environment; a host passes its own provider.\n */\n\nimport { readCredentials } from '../auth/credentials';\n\n/**\n * What each kind of credential is asked for with, and what it is.\n *\n * A target that needs a kind the CLI does not know declares it by augmenting\n * this interface, and lists it in its `credentials`:\n *\n * ```ts\n * declare module '@geekmidas/cli/target' {\n * interface CredentialKinds {\n * fly: { request: { org: string }; value: { token: string } };\n * }\n * }\n * ```\n */\nexport interface CredentialKinds {\n\t/** The Dokploy API a stage deploys through. */\n\tdokploy: {\n\t\t/** `deploy.dokploy.endpoint`, when the config names one. */\n\t\trequest: { endpoint?: string };\n\t\tvalue: { endpoint: string; token: string };\n\t};\n\t/**\n\t * A container registry login, for Dokploy to pull with. Asked for only when\n\t * Dokploy has no registry for `deploy.dokploy.registry` and one has to be\n\t * created.\n\t */\n\tregistry: {\n\t\t/** `deploy.dokploy.registry`. */\n\t\trequest: { url: string };\n\t\tvalue: { username: string; password: string };\n\t};\n\t/**\n\t * The AWS account a stage deploys to, for the SST target's `sst deploy`.\n\t * Handed to that one command's environment and to nothing else.\n\t */\n\taws: {\n\t\t/** The stage, for a provider that keeps one account per stage. */\n\t\trequest: { stage: string };\n\t\tvalue: AwsCredential;\n\t};\n}\n\n/**\n * Who to act as on AWS: a named profile, or keys.\n *\n * Never both. Given both, every AWS SDK takes the keys and ignores the\n * profile, so `--profile prod` with staging's keys still exported would deploy\n * production's stack into staging's account — the reason `secretsStoreFor`\n * resolves a named profile on its own, and why this cannot express the mix.\n */\nexport type AwsCredential =\n\t| {\n\t\t\t/** A profile in `~/.aws/config` — keys, SSO, an assumed role. */\n\t\t\tprofile: string;\n\t\t\tregion?: string;\n\t }\n\t| {\n\t\t\taccessKeyId: string;\n\t\t\tsecretAccessKey: string;\n\t\t\t/** Present for temporary keys: an assumed role, GitHub's OIDC. */\n\t\t\tsessionToken?: string;\n\t\t\tregion?: string;\n\t };\n\nexport type CredentialKind = keyof CredentialKinds;\n\n/** What a deploy asks a provider for. */\nexport type CredentialRequest<K extends CredentialKind = CredentialKind> = {\n\t[P in K]: { kind: P } & CredentialKinds[P]['request'];\n}[K];\n\n/** What a provider answers with. */\nexport type Credential<K extends CredentialKind> = CredentialKinds[K]['value'];\n\nexport interface CredentialProvider {\n\t/**\n\t * The credential `request` asks for, or `undefined` when this provider has\n\t * none — the deploy then raises {@link MissingCredential}.\n\t */\n\tget<K extends CredentialKind>(\n\t\trequest: CredentialRequest<K>,\n\t\toptions?: { signal?: AbortSignal },\n\t): Promise<Credential<K> | undefined>;\n}\n\n/**\n * How to supply each kind the CLI knows, for the error that says it is\n * missing. A target's own kinds are its to explain.\n */\nconst HOW_TO_PROVIDE: Partial<Record<CredentialKind, string>> = {\n\tdokploy:\n\t\t\"Set DOKPLOY_API_TOKEN (and DOKPLOY_ENDPOINT, or deploy.dokploy.endpoint in gkm.config.ts), run `gkm login --provider dokploy`, or pass them through the deploy's CredentialProvider.\",\n\tregistry:\n\t\t'Add the registry in Dokploy (Settings → Docker Registry) and set deploy.dokploy.registryId, set DOCKER_REGISTRY_USERNAME and DOCKER_REGISTRY_PASSWORD, or run `gkm deploy` at a terminal to be asked for them.',\n\taws: \"Set AWS_PROFILE to the stage account's profile, or AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY (and AWS_SESSION_TOKEN) — in CI, the keys aws-actions/configure-aws-credentials exports — or pass them through the deploy's CredentialProvider.\",\n};\n\n/** A deploy needed a credential, and its provider had none. */\nexport class MissingCredential extends Error {\n\tconstructor(\n\t\treadonly kind: CredentialKind,\n\t\t/** What it was for: the Dokploy endpoint or the registry URL. */\n\t\treadonly target: string | undefined,\n\t\t/** How to supply it. */\n\t\treadonly howToProvide: string = HOW_TO_PROVIDE[kind] ??\n\t\t\t\"Pass it through the deploy's CredentialProvider.\",\n\t) {\n\t\tconst what =\n\t\t\tkind === 'dokploy'\n\t\t\t\t? `No Dokploy credentials${target ? ` for ${target}` : ''}.`\n\t\t\t\t: kind === 'registry'\n\t\t\t\t\t? `Dokploy has no registry for ${target ?? 'the configured registry'}, and there are no credentials to create one with.`\n\t\t\t\t\t: kind === 'aws'\n\t\t\t\t\t\t? `No AWS credentials${target ? ` for the \"${target}\" stage` : ''}.`\n\t\t\t\t\t\t: `No \"${String(kind)}\" credentials${target ? ` for ${target}` : ''}.`;\n\t\tsuper(`${what} ${howToProvide}`);\n\t\tthis.name = 'MissingCredential';\n\t}\n}\n\n/** Whatever was asked for, from the first provider that has it. */\nexport function chainCredentials(\n\t...providers: CredentialProvider[]\n): CredentialProvider {\n\treturn {\n\t\tasync get(request, options) {\n\t\t\tfor (const provider of providers) {\n\t\t\t\tconst found = await provider.get(request, options);\n\t\t\t\tif (found) return found;\n\t\t\t}\n\t\t\treturn undefined;\n\t\t},\n\t};\n}\n\nexport interface StoredCredentialsOptions {\n\t/** Where to read the variables from. Defaults to `process.env`. */\n\tenv?: NodeJS.ProcessEnv;\n\t/**\n\t * The CLI's home, whose `credentials.json` holds stored logins. Defaults\n\t * to `GKM_HOME`, else `~/.gkm`.\n\t */\n\thome?: string;\n}\n\n/**\n * Credentials from the environment, then from what `gkm login` stored. What\n * `deploy()` uses when it is handed no provider.\n *\n * - Dokploy: `DOKPLOY_API_TOKEN` / `DOKPLOY_ENDPOINT`, each falling back to\n * the stored login, and the endpoint finally to `deploy.dokploy.endpoint`.\n * Each half is read on its own: a token is a secret and belongs in the\n * environment, an endpoint is configuration and belongs in the config.\n * - Registry: `DOCKER_REGISTRY_USERNAME` / `DOCKER_REGISTRY_PASSWORD`.\n * - AWS: `AWS_PROFILE` when it is set, and then nothing else; otherwise\n * `AWS_ACCESS_KEY_ID` / `AWS_SECRET_ACCESS_KEY` / `AWS_SESSION_TOKEN`. The\n * profile winning is `secretsStoreFor`'s rule: a profile is a choice of\n * account someone made, exported keys may be left over from another.\n * Read here, by name, and handed only to the command that deploys — the\n * sandbox every other step runs in passes no `AWS_*` on.\n */\nexport function storedCredentials(\n\toptions: StoredCredentialsOptions = {},\n): CredentialProvider {\n\tconst env = options.env ?? process.env;\n\n\treturn {\n\t\tasync get(asked) {\n\t\t\t// The union, so a switch on `kind` narrows it: `K` itself never is.\n\t\t\tconst request = asked as CredentialRequest;\n\t\t\tswitch (request.kind) {\n\t\t\t\tcase 'dokploy': {\n\t\t\t\t\tconst stored = (\n\t\t\t\t\t\tawait readCredentials(\n\t\t\t\t\t\t\toptions.home ? { home: options.home } : undefined,\n\t\t\t\t\t\t)\n\t\t\t\t\t).dokploy;\n\t\t\t\t\tconst token = env.DOKPLOY_API_TOKEN ?? stored?.token;\n\t\t\t\t\tconst endpoint =\n\t\t\t\t\t\tenv.DOKPLOY_ENDPOINT ?? stored?.endpoint ?? request.endpoint;\n\t\t\t\t\tif (!token || !endpoint) return undefined;\n\t\t\t\t\treturn { endpoint: endpoint.replace(/\\/$/, ''), token } as never;\n\t\t\t\t}\n\t\t\t\tcase 'registry': {\n\t\t\t\t\tconst username = env.DOCKER_REGISTRY_USERNAME;\n\t\t\t\t\tconst password = env.DOCKER_REGISTRY_PASSWORD;\n\t\t\t\t\tif (!username || !password) return undefined;\n\t\t\t\t\treturn { username, password } as never;\n\t\t\t\t}\n\t\t\t\tcase 'aws': {\n\t\t\t\t\tconst region = env.AWS_REGION ?? env.AWS_DEFAULT_REGION;\n\t\t\t\t\tconst where = region ? { region } : {};\n\t\t\t\t\tif (env.AWS_PROFILE) {\n\t\t\t\t\t\treturn { profile: env.AWS_PROFILE, ...where } as never;\n\t\t\t\t\t}\n\t\t\t\t\tconst accessKeyId = env.AWS_ACCESS_KEY_ID;\n\t\t\t\t\tconst secretAccessKey = env.AWS_SECRET_ACCESS_KEY;\n\t\t\t\t\tif (!accessKeyId || !secretAccessKey) return undefined;\n\t\t\t\t\treturn {\n\t\t\t\t\t\taccessKeyId,\n\t\t\t\t\t\tsecretAccessKey,\n\t\t\t\t\t\t...(env.AWS_SESSION_TOKEN\n\t\t\t\t\t\t\t? { sessionToken: env.AWS_SESSION_TOKEN }\n\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t...where,\n\t\t\t\t\t} as never;\n\t\t\t\t}\n\t\t\t}\n\t\t\t// A kind a target declared: nothing stored here can answer it.\n\t\t\treturn undefined;\n\t\t},\n\t};\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAsGA,MAAM,iBAA0D;CAC/D,SACC;CACD,UACC;CACD,KAAK;AACN;;AAGA,IAAa,oBAAb,cAAuC,MAAM;CAElC;CAEA;CAEA;CALV,YACC,MAEA,QAEA,eAAgC,eAAe,SAC9C,oDACA;EACD,MAAM,OACL,SAAS,YACN,yBAAyB,SAAS,QAAQ,WAAW,GAAG,KACxD,SAAS,aACR,+BAA+B,UAAU,0BAA0B,sDACnE,SAAS,QACR,qBAAqB,SAAS,aAAa,OAAO,WAAW,GAAG,KAChE,OAAO,OAAO,IAAI,EAAE,eAAe,SAAS,QAAQ,WAAW,GAAG;EACxE,MAAM,GAAG,KAAK,GAAG,cAAc;EAftB,KAAA,OAAA;EAEA,KAAA,SAAA;EAEA,KAAA,eAAA;EAYT,KAAK,OAAO;CACb;AACD;;AAGA,SAAgB,iBACf,GAAG,WACkB;CACrB,OAAO,EACN,MAAM,IAAI,SAAS,SAAS;EAC3B,KAAK,MAAM,YAAY,WAAW;GACjC,MAAM,QAAQ,MAAM,SAAS,IAAI,SAAS,OAAO;GACjD,IAAI,OAAO,OAAO;EACnB;CAED,EACD;AACD;;;;;;;;;;;;;;;;;AA4BA,SAAgB,kBACf,UAAoC,CAAC,GAChB;CACrB,MAAM,MAAM,QAAQ,OAAO,QAAQ;CAEnC,OAAO,EACN,MAAM,IAAI,OAAO;EAEhB,MAAM,UAAU;EAChB,QAAQ,QAAQ,MAAhB;GACC,KAAK,WAAW;IACf,MAAM,UACL,MAAMA,cAAAA,gBACL,QAAQ,OAAO,EAAE,MAAM,QAAQ,KAAK,IAAI,KAAA,CACzC,EAAA,CACC;IACF,MAAM,QAAQ,IAAI,qBAAqB,QAAQ;IAC/C,MAAM,WACL,IAAI,oBAAoB,QAAQ,YAAY,QAAQ;IACrD,IAAI,CAAC,SAAS,CAAC,UAAU,OAAO,KAAA;IAChC,OAAO;KAAE,UAAU,SAAS,QAAQ,OAAO,EAAE;KAAG;IAAM;GACvD;GACA,KAAK,YAAY;IAChB,MAAM,WAAW,IAAI;IACrB,MAAM,WAAW,IAAI;IACrB,IAAI,CAAC,YAAY,CAAC,UAAU,OAAO,KAAA;IACnC,OAAO;KAAE;KAAU;IAAS;GAC7B;GACA,KAAK,OAAO;IACX,MAAM,SAAS,IAAI,cAAc,IAAI;IACrC,MAAM,QAAQ,SAAS,EAAE,OAAO,IAAI,CAAC;IACrC,IAAI,IAAI,aACP,OAAO;KAAE,SAAS,IAAI;KAAa,GAAG;IAAM;IAE7C,MAAM,cAAc,IAAI;IACxB,MAAM,kBAAkB,IAAI;IAC5B,IAAI,CAAC,eAAe,CAAC,iBAAiB,OAAO,KAAA;IAC7C,OAAO;KACN;KACA;KACA,GAAI,IAAI,oBACL,EAAE,cAAc,IAAI,kBAAkB,IACtC,CAAC;KACJ,GAAG;IACJ;GACD;EACD;CAGD,EACD;AACD"}
|