@geekmidas/cli 10.0.0-alpha.63 → 10.0.0-alpha.65
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{HostingerProvider-mJ7YruxH.cjs → HostingerProvider-Dlmbfass.cjs} +3 -3
- package/dist/{HostingerProvider-mJ7YruxH.cjs.map → HostingerProvider-Dlmbfass.cjs.map} +1 -1
- package/dist/{HostingerProvider-DBBwpDjn.mjs → HostingerProvider-x9gQTAGZ.mjs} +2 -2
- package/dist/{HostingerProvider-DBBwpDjn.mjs.map → HostingerProvider-x9gQTAGZ.mjs.map} +1 -1
- package/dist/{LegacyStateStore-CP7SZwTP.cjs → LegacyStateStore-BGoo__sO.cjs} +2 -2
- package/dist/{LegacyStateStore-CP7SZwTP.cjs.map → LegacyStateStore-BGoo__sO.cjs.map} +1 -1
- package/dist/{LegacyStateStore-Q3GmKOdw.mjs → LegacyStateStore-BUu1nQ5u.mjs} +2 -2
- package/dist/{LegacyStateStore-Q3GmKOdw.mjs.map → LegacyStateStore-BUu1nQ5u.mjs.map} +1 -1
- package/dist/{LocalStateStore-sS8mvLeq.cjs → LocalStateStore-93urXyQe.cjs} +2 -2
- package/dist/{LocalStateStore-sS8mvLeq.cjs.map → LocalStateStore-93urXyQe.cjs.map} +1 -1
- package/dist/{LocalStateStore-BGIXcrvX.mjs → LocalStateStore-BRhMQRuy.mjs} +2 -2
- package/dist/{LocalStateStore-BGIXcrvX.mjs.map → LocalStateStore-BRhMQRuy.mjs.map} +1 -1
- package/dist/{OpenApiTsGenerator-CjHWFRDd.cjs → OpenApiTsGenerator-BWuDRHe9.cjs} +2 -2
- package/dist/{OpenApiTsGenerator-CjHWFRDd.cjs.map → OpenApiTsGenerator-BWuDRHe9.cjs.map} +1 -1
- package/dist/{OpenApiTsGenerator-CnAwhGxi.mjs → OpenApiTsGenerator-C6v-IeHt.mjs} +2 -2
- package/dist/{OpenApiTsGenerator-CnAwhGxi.mjs.map → OpenApiTsGenerator-C6v-IeHt.mjs.map} +1 -1
- package/dist/{S3StateStore-6jqITH2F.cjs → S3StateStore-F4dxw6kS.cjs} +2 -2
- package/dist/{S3StateStore-6jqITH2F.cjs.map → S3StateStore-F4dxw6kS.cjs.map} +1 -1
- package/dist/{S3StateStore-D7l_lWJ3.mjs → S3StateStore-pw1GhWwt.mjs} +2 -2
- package/dist/{S3StateStore-D7l_lWJ3.mjs.map → S3StateStore-pw1GhWwt.mjs.map} +1 -1
- package/dist/{SSMStateStore-B__F7IJa.cjs → SSMStateStore-Bt9acuAF.cjs} +2 -2
- package/dist/{SSMStateStore-B__F7IJa.cjs.map → SSMStateStore-Bt9acuAF.cjs.map} +1 -1
- package/dist/{SSMStateStore-CAQDVpoc.mjs → SSMStateStore-Dmj7zzFO.mjs} +2 -2
- package/dist/{SSMStateStore-CAQDVpoc.mjs.map → SSMStateStore-Dmj7zzFO.mjs.map} +1 -1
- package/dist/{bundler-DLfOn0ls.cjs → bundler-D6MlIfBT.cjs} +3 -3
- package/dist/{bundler-DLfOn0ls.cjs.map → bundler-D6MlIfBT.cjs.map} +1 -1
- package/dist/{bundler-DyWI41Iy.mjs → bundler-D_14JLZF.mjs} +3 -3
- package/dist/{bundler-DyWI41Iy.mjs.map → bundler-D_14JLZF.mjs.map} +1 -1
- package/dist/{cli-D-ZM-X2s.cjs → cli-BfH1deAT.cjs} +8 -7
- package/dist/{cli-D-ZM-X2s.cjs.map → cli-BfH1deAT.cjs.map} +1 -1
- package/dist/{cli-PkVF28LM.mjs → cli-P56vvrDQ.mjs} +5 -4
- package/dist/{cli-PkVF28LM.mjs.map → cli-P56vvrDQ.mjs.map} +1 -1
- package/dist/compose-391oEKj1.cjs +1982 -0
- package/dist/compose-391oEKj1.cjs.map +1 -0
- package/dist/compose-DprE3ASm.mjs +1862 -0
- package/dist/compose-DprE3ASm.mjs.map +1 -0
- package/dist/{config-C2BUosLm.d.mts → config-BonvTW8p.d.mts} +3 -3
- package/dist/{config-C2BUosLm.d.mts.map → config-BonvTW8p.d.mts.map} +1 -1
- package/dist/{config-CemHVcIr.d.cts → config-V_X_BMpm.d.cts} +3 -3
- package/dist/{config-CemHVcIr.d.cts.map → config-V_X_BMpm.d.cts.map} +1 -1
- package/dist/config.cjs +416 -17
- package/dist/config.cjs.map +1 -0
- package/dist/config.d.cts +3 -3
- package/dist/config.d.mts +3 -3
- package/dist/config.mjs +401 -2
- package/dist/config.mjs.map +1 -0
- package/dist/credentials-Bzbg23n8.cjs +298 -0
- package/dist/credentials-Bzbg23n8.cjs.map +1 -0
- package/dist/credentials-Dq1MnWQf.mjs +233 -0
- package/dist/credentials-Dq1MnWQf.mjs.map +1 -0
- package/dist/{define-CHBIEc5j.mjs → define-DR8xGYfL.mjs} +6 -3
- package/dist/define-DR8xGYfL.mjs.map +1 -0
- package/dist/{define-Cv_QvDrg.cjs → define-DzRz8FgH.cjs} +6 -3
- package/dist/define-DzRz8FgH.cjs.map +1 -0
- package/dist/deploy/public.cjs +39 -33
- package/dist/deploy/public.cjs.map +1 -1
- package/dist/deploy/public.d.cts +44 -5
- package/dist/deploy/public.d.cts.map +1 -1
- package/dist/deploy/public.d.mts +44 -5
- package/dist/deploy/public.d.mts.map +1 -1
- package/dist/deploy/public.mjs +15 -12
- package/dist/deploy/public.mjs.map +1 -1
- package/dist/{deploy-CGW-QY9D.mjs → deploy-9MBG_QLd.mjs} +11 -9
- package/dist/deploy-9MBG_QLd.mjs.map +1 -0
- package/dist/{deploy-BiMrP9i7.cjs → deploy-B80idPUZ.cjs} +17 -15
- package/dist/deploy-B80idPUZ.cjs.map +1 -0
- package/dist/{deploy-CG8bfFij.mjs → deploy-CZ5nlpsu.mjs} +16 -119
- package/dist/deploy-CZ5nlpsu.mjs.map +1 -0
- package/dist/{deploy-ABBI0lf1.cjs → deploy-Cy8igpQF.cjs} +48 -151
- package/dist/deploy-Cy8igpQF.cjs.map +1 -0
- package/dist/derive-B4vDpzgP.mjs +150 -0
- package/dist/derive-B4vDpzgP.mjs.map +1 -0
- package/dist/derive-DAbo49sm.cjs +161 -0
- package/dist/derive-DAbo49sm.cjs.map +1 -0
- package/dist/{discover-C_6COHEv.cjs → discover-DHdECueV.cjs} +6 -157
- package/dist/discover-DHdECueV.cjs.map +1 -0
- package/dist/{discover-BaT6bpnA.mjs → discover-LMrqsWSU.mjs} +8 -117
- package/dist/discover-LMrqsWSU.mjs.map +1 -0
- package/dist/{dokploy-CwdmlyQL.mjs → dokploy-BdtBePjp.mjs} +3 -3
- package/dist/{dokploy-CwdmlyQL.mjs.map → dokploy-BdtBePjp.mjs.map} +1 -1
- package/dist/{dokploy-QJxMhxdf.cjs → dokploy-iFY8MY-L.cjs} +3 -3
- package/dist/{dokploy-QJxMhxdf.cjs.map → dokploy-iFY8MY-L.cjs.map} +1 -1
- package/dist/{encryption-DXw3jj0c.cjs → encryption-B1wJwgae.cjs} +6 -6
- package/dist/{encryption-DXw3jj0c.cjs.map → encryption-B1wJwgae.cjs.map} +1 -1
- package/dist/{encryption-CbQG_ayW.mjs → encryption-DF_hgIpl.mjs} +5 -5
- package/dist/{encryption-CbQG_ayW.mjs.map → encryption-DF_hgIpl.mjs.map} +1 -1
- package/dist/{github-ciwbIwVa.mjs → github-B0YYE98x.mjs} +4 -4
- package/dist/{github-ciwbIwVa.mjs.map → github-B0YYE98x.mjs.map} +1 -1
- package/dist/{github-BH_mETav.cjs → github-CYcmTdFs.cjs} +4 -4
- package/dist/{github-BH_mETav.cjs.map → github-CYcmTdFs.cjs.map} +1 -1
- package/dist/{index-DrKzpHl-.d.mts → index-DPUAGJaK.d.cts} +16 -3
- package/dist/index-DPUAGJaK.d.cts.map +1 -0
- package/dist/{index-BtXDZQuF.d.cts → index-DwKD_8lt.d.mts} +16 -3
- package/dist/index-DwKD_8lt.d.mts.map +1 -0
- package/dist/index.cjs +280 -1849
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +221 -1774
- package/dist/index.mjs.map +1 -1
- package/dist/local-BdH4XN0E.cjs +409 -0
- package/dist/local-BdH4XN0E.cjs.map +1 -0
- package/dist/local-CWR4Y_kW.mjs +350 -0
- package/dist/local-CWR4Y_kW.mjs.map +1 -0
- package/dist/{migrate-ImBKTBcW.mjs → migrate-B2qL5wj4.mjs} +8 -7
- package/dist/{migrate-ImBKTBcW.mjs.map → migrate-B2qL5wj4.mjs.map} +1 -1
- package/dist/{migrate-6oa9y_f2.cjs → migrate-q4pPAXtO.cjs} +21 -20
- package/dist/{migrate-6oa9y_f2.cjs.map → migrate-q4pPAXtO.cjs.map} +1 -1
- package/dist/openapi.cjs +2 -2
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/openapi.mjs +2 -2
- package/dist/provider-C9_-45d5.cjs +56 -0
- package/dist/provider-C9_-45d5.cjs.map +1 -0
- package/dist/provider-q4lsYzYG.mjs +45 -0
- package/dist/provider-q4lsYzYG.mjs.map +1 -0
- package/dist/{ready-Bpdnn2ON.mjs → ready-bQfu9GES.mjs} +3 -3
- package/dist/{ready-Bpdnn2ON.mjs.map → ready-bQfu9GES.mjs.map} +1 -1
- package/dist/{ready-BiG0rt4h.cjs → ready-brEhh68f.cjs} +3 -3
- package/dist/{ready-BiG0rt4h.cjs.map → ready-brEhh68f.cjs.map} +1 -1
- package/dist/reconcile/public.cjs +1 -1
- package/dist/reconcile/public.d.cts +1 -1
- package/dist/reconcile/public.d.mts +1 -1
- package/dist/reconcile/public.mjs +1 -1
- package/dist/resolve-DLkIXTct.mjs +262 -0
- package/dist/resolve-DLkIXTct.mjs.map +1 -0
- package/dist/resolve-N_i4XNd-.cjs +315 -0
- package/dist/resolve-N_i4XNd-.cjs.map +1 -0
- package/dist/{runtime-Dn_n-29S.d.cts → runtime-BnD7Nn96.d.cts} +2 -2
- package/dist/{runtime-CXPlkZzs.d.mts.map → runtime-BnD7Nn96.d.cts.map} +1 -1
- package/dist/{runtime-CXPlkZzs.d.mts → runtime-j-tmFL65.d.mts} +2 -2
- package/dist/{runtime-Dn_n-29S.d.cts.map → runtime-j-tmFL65.d.mts.map} +1 -1
- package/dist/sandbox/discover-worker.cjs +1 -1
- package/dist/sandbox/discover-worker.mjs +1 -1
- package/dist/sandbox-CL--ZDui.cjs +164 -0
- package/dist/sandbox-CL--ZDui.cjs.map +1 -0
- package/dist/sandbox-Dm247NGx.mjs +117 -0
- package/dist/sandbox-Dm247NGx.mjs.map +1 -0
- package/dist/sniffer-BSwaL-dX.cjs +2236 -0
- package/dist/sniffer-BSwaL-dX.cjs.map +1 -0
- package/dist/sniffer-BZ-OJO4Y.mjs +2068 -0
- package/dist/sniffer-BZ-OJO4Y.mjs.map +1 -0
- package/dist/sst-B2HbrDR3.mjs +430 -0
- package/dist/sst-B2HbrDR3.mjs.map +1 -0
- package/dist/sst-DcfsVH6x.cjs +453 -0
- package/dist/sst-DcfsVH6x.cjs.map +1 -0
- package/dist/{sniffer-aO_-s5vq.mjs → state-B8YwmUZU.mjs} +1204 -3236
- package/dist/state-B8YwmUZU.mjs.map +1 -0
- package/dist/{sniffer-D_SYc_pO.cjs → state-DF26e_lo.cjs} +1251 -3494
- package/dist/state-DF26e_lo.cjs.map +1 -0
- package/dist/{store-KvNQwYaB.cjs → store-CJF1Tqkq.cjs} +4 -4
- package/dist/{store-KvNQwYaB.cjs.map → store-CJF1Tqkq.cjs.map} +1 -1
- package/dist/{store-BS8A7NcA.mjs → store-CLiHnqOY.mjs} +2 -2
- package/dist/{store-BS8A7NcA.mjs.map → store-CLiHnqOY.mjs.map} +1 -1
- package/dist/target/public.cjs +6 -6
- package/dist/target/public.d.cts +3 -3
- package/dist/target/public.d.mts +3 -3
- package/dist/target/public.mjs +3 -3
- package/dist/terminal-Bs5E1oxm.cjs +88 -0
- package/dist/terminal-Bs5E1oxm.cjs.map +1 -0
- package/dist/terminal-CfbiYUbZ.mjs +84 -0
- package/dist/terminal-CfbiYUbZ.mjs.map +1 -0
- package/dist/{types-DIA7Li-J.d.cts → types-BCzMWBb6.d.mts} +84 -17
- package/dist/types-BCzMWBb6.d.mts.map +1 -0
- package/dist/{types-CfLKGfC_.d.mts → types-ByMWkXKP.d.cts} +85 -16
- package/dist/types-ByMWkXKP.d.cts.map +1 -0
- package/dist/utils-D2Nk1Nv2.cjs +205 -0
- package/dist/utils-D2Nk1Nv2.cjs.map +1 -0
- package/dist/utils-D_fxUqvU.mjs +156 -0
- package/dist/utils-D_fxUqvU.mjs.map +1 -0
- package/dist/vitest.cjs +1 -1
- package/dist/vitest.mjs +1 -1
- package/dist/workspace/index.cjs +3 -1
- package/dist/workspace/index.d.cts +3 -3
- package/dist/workspace/index.d.mts +3 -3
- package/dist/workspace/index.mjs +2 -2
- package/dist/{workspace-Kl4NG9IH.cjs → workspace-BewAHZIo.cjs} +41 -6
- package/dist/workspace-BewAHZIo.cjs.map +1 -0
- package/dist/{workspace-Dh7rr7l4.mjs → workspace-C06_pW4g.mjs} +30 -7
- package/dist/workspace-C06_pW4g.mjs.map +1 -0
- package/package.json +13 -13
- package/dist/config-BEJYKd4s.cjs +0 -648
- package/dist/config-BEJYKd4s.cjs.map +0 -1
- package/dist/config-LKKCVCnC.mjs +0 -547
- package/dist/config-LKKCVCnC.mjs.map +0 -1
- package/dist/define-CHBIEc5j.mjs.map +0 -1
- package/dist/define-Cv_QvDrg.cjs.map +0 -1
- package/dist/deploy-ABBI0lf1.cjs.map +0 -1
- package/dist/deploy-BiMrP9i7.cjs.map +0 -1
- package/dist/deploy-CG8bfFij.mjs.map +0 -1
- package/dist/deploy-CGW-QY9D.mjs.map +0 -1
- package/dist/discover-BaT6bpnA.mjs.map +0 -1
- package/dist/discover-C_6COHEv.cjs.map +0 -1
- package/dist/home-DOzBaWaL.cjs +0 -33
- package/dist/home-DOzBaWaL.cjs.map +0 -1
- package/dist/home-GI7pJl1M.mjs +0 -22
- package/dist/home-GI7pJl1M.mjs.map +0 -1
- package/dist/index-BtXDZQuF.d.cts.map +0 -1
- package/dist/index-DrKzpHl-.d.mts.map +0 -1
- package/dist/resolve-B1T0hUaR.mjs +0 -170
- package/dist/resolve-B1T0hUaR.mjs.map +0 -1
- package/dist/resolve-xytBTtG4.cjs +0 -217
- package/dist/resolve-xytBTtG4.cjs.map +0 -1
- package/dist/runtime-CMwtq0AL.cjs +0 -163
- package/dist/runtime-CMwtq0AL.cjs.map +0 -1
- package/dist/runtime-DbakJS8u.mjs +0 -134
- package/dist/runtime-DbakJS8u.mjs.map +0 -1
- package/dist/sniffer-D_SYc_pO.cjs.map +0 -1
- package/dist/sniffer-aO_-s5vq.mjs.map +0 -1
- package/dist/terminal-D0f1u0LG.cjs +0 -576
- package/dist/terminal-D0f1u0LG.cjs.map +0 -1
- package/dist/terminal-gMxAo9Dj.mjs +0 -517
- package/dist/terminal-gMxAo9Dj.mjs.map +0 -1
- package/dist/types-CfLKGfC_.d.mts.map +0 -1
- package/dist/types-DIA7Li-J.d.cts.map +0 -1
- package/dist/workspace-Dh7rr7l4.mjs.map +0 -1
- package/dist/workspace-Kl4NG9IH.cjs.map +0 -1
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
2
|
+
import { existsSync, realpathSync } from "node:fs";
|
|
3
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
4
|
+
//#region src/sandbox/sandbox.ts
|
|
5
|
+
/**
|
|
6
|
+
* Where a deploy runs the project's own code.
|
|
7
|
+
*
|
|
8
|
+
* A deploy holds credentials — Dokploy's token, a registry login, an AWS key —
|
|
9
|
+
* and loading `gkm.config.ts`, sniffing an app's environment and building it
|
|
10
|
+
* all execute code from the repository being deployed. On a developer's own
|
|
11
|
+
* laptop that is the developer's code. On a CI job for a fork, or a shared
|
|
12
|
+
* build runner, it is not, and that code should not be able to read the
|
|
13
|
+
* credentials used to deploy it, hang the deploy, or reach outside the
|
|
14
|
+
* checkout.
|
|
15
|
+
*
|
|
16
|
+
* So every step that runs project code goes through a `Sandbox`: one call,
|
|
17
|
+
* with an argument array rather than a shell line, the complete environment
|
|
18
|
+
* the step needs rather than the host's, a timeout it cannot outlive, secrets
|
|
19
|
+
* as files rather than variables, and a working directory inside the project.
|
|
20
|
+
* The steps that need credentials — provisioning, pushing, releasing — are
|
|
21
|
+
* the deploy's own code and never run in one.
|
|
22
|
+
*
|
|
23
|
+
* `LocalSandbox` is the default: a child process on this machine with an
|
|
24
|
+
* allowlisted environment. A host that builds repositories it does not trust
|
|
25
|
+
* passes its own — a container per build, a VM — through `deploy()`'s
|
|
26
|
+
* `sandbox`.
|
|
27
|
+
*/
|
|
28
|
+
/** The variable naming the directory a command's secrets are mounted in. */
|
|
29
|
+
const SECRETS_DIR_ENV = "GKM_SECRETS_DIR";
|
|
30
|
+
/** A command was asked to run outside the project. */
|
|
31
|
+
var SandboxCwdEscape = class extends Error {
|
|
32
|
+
cwd;
|
|
33
|
+
root;
|
|
34
|
+
constructor(cwd, root) {
|
|
35
|
+
super(`Refusing to run in '${cwd}': it is outside the project at '${root}'. A sandboxed step only runs inside the project it was given — check the app paths in gkm.config.ts for \`..\` or an absolute path.`);
|
|
36
|
+
this.cwd = cwd;
|
|
37
|
+
this.root = root;
|
|
38
|
+
this.name = "SandboxCwdEscape";
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
/** A path with its symlinks resolved where it exists — `/tmp` is `/private/tmp`. */
|
|
42
|
+
function real(path) {
|
|
43
|
+
return existsSync(path) ? realpathSync(path) : path;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* `cwd` as an absolute directory inside `root`, or {@link SandboxCwdEscape}.
|
|
47
|
+
*
|
|
48
|
+
* Symlinks are resolved on both sides first, so a link inside the project
|
|
49
|
+
* that points out of it is an escape, and a project reached through a link
|
|
50
|
+
* (`/tmp` on macOS) is still itself. For a sandbox of a host's own to call
|
|
51
|
+
* before it runs anything.
|
|
52
|
+
*/
|
|
53
|
+
function confineCwd(root, cwd) {
|
|
54
|
+
const absolute = resolve(root, cwd);
|
|
55
|
+
const from = relative(real(resolve(root)), real(absolute));
|
|
56
|
+
if (from === ".." || from.startsWith(`..${sep}`) || isAbsolute(from)) throw new SandboxCwdEscape(cwd, root);
|
|
57
|
+
return absolute;
|
|
58
|
+
}
|
|
59
|
+
/** A secret's file name: one path segment, so it cannot be written elsewhere. */
|
|
60
|
+
var SecretNameInvalid = class extends Error {
|
|
61
|
+
secretName;
|
|
62
|
+
constructor(secretName) {
|
|
63
|
+
super(`'${secretName}' cannot be a secret's name: it becomes a file name in the secrets directory, so it must be one segment of letters, digits, '.', '-' or '_' and not start with '.'.`);
|
|
64
|
+
this.secretName = secretName;
|
|
65
|
+
this.name = "SecretNameInvalid";
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
const SECRET_NAME = /^[A-Za-z0-9_-][A-Za-z0-9._-]*$/;
|
|
69
|
+
/** Every secret name, checked before anything is written. */
|
|
70
|
+
function assertSecretNames(secrets) {
|
|
71
|
+
for (const name of Object.keys(secrets ?? {})) if (!SECRET_NAME.test(name)) throw new SecretNameInvalid(name);
|
|
72
|
+
}
|
|
73
|
+
const active = new AsyncLocalStorage();
|
|
74
|
+
/**
|
|
75
|
+
* Run `fn` with `sandbox` as the one its steps use.
|
|
76
|
+
*
|
|
77
|
+
* Per run rather than global, as `withOutput` routes a run's lines: two
|
|
78
|
+
* deploys in one host process each build in their own sandbox. A step deep in
|
|
79
|
+
* the engine — discovering constructs, sniffing an app — asks
|
|
80
|
+
* {@link activeSandbox} rather than every function between `deploy()` and it
|
|
81
|
+
* taking one more parameter.
|
|
82
|
+
*/
|
|
83
|
+
function withSandbox(sandbox, fn) {
|
|
84
|
+
return active.run({
|
|
85
|
+
sandbox,
|
|
86
|
+
answers: /* @__PURE__ */ new Map()
|
|
87
|
+
}, fn);
|
|
88
|
+
}
|
|
89
|
+
/** The sandbox of the run this is part of, if it was started with one. */
|
|
90
|
+
function activeSandbox() {
|
|
91
|
+
return active.getStore()?.sandbox;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* `ask()`'s answer, asked once per run.
|
|
95
|
+
*
|
|
96
|
+
* A deploy loads its config and discovers its constructs from several places
|
|
97
|
+
* — the first load, the engine, the Dockerfile generator — and in a sandbox
|
|
98
|
+
* each was a child process of its own, for the same answer: the checkout does
|
|
99
|
+
* not change while it deploys. Within a run in `sandbox`, the first answer
|
|
100
|
+
* for `key` is the one every later ask gets. Outside one, or for another
|
|
101
|
+
* sandbox, `ask()` runs each time.
|
|
102
|
+
*/
|
|
103
|
+
function oncePerRun(sandbox, key, ask) {
|
|
104
|
+
const run = active.getStore();
|
|
105
|
+
if (!run || run.sandbox !== sandbox) return ask();
|
|
106
|
+
let answer = run.answers.get(key);
|
|
107
|
+
if (!answer) {
|
|
108
|
+
answer = ask();
|
|
109
|
+
run.answers.set(key, answer);
|
|
110
|
+
answer.catch(() => run.answers.delete(key));
|
|
111
|
+
}
|
|
112
|
+
return answer;
|
|
113
|
+
}
|
|
114
|
+
//#endregion
|
|
115
|
+
export { assertSecretNames as a, withSandbox as c, activeSandbox as i, SandboxCwdEscape as n, confineCwd as o, SecretNameInvalid as r, oncePerRun as s, SECRETS_DIR_ENV as t };
|
|
116
|
+
|
|
117
|
+
//# sourceMappingURL=sandbox-Dm247NGx.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sandbox-Dm247NGx.mjs","names":[],"sources":["../src/sandbox/sandbox.ts"],"sourcesContent":["/**\n * Where a deploy runs the project's own code.\n *\n * A deploy holds credentials — Dokploy's token, a registry login, an AWS key —\n * and loading `gkm.config.ts`, sniffing an app's environment and building it\n * all execute code from the repository being deployed. On a developer's own\n * laptop that is the developer's code. On a CI job for a fork, or a shared\n * build runner, it is not, and that code should not be able to read the\n * credentials used to deploy it, hang the deploy, or reach outside the\n * checkout.\n *\n * So every step that runs project code goes through a `Sandbox`: one call,\n * with an argument array rather than a shell line, the complete environment\n * the step needs rather than the host's, a timeout it cannot outlive, secrets\n * as files rather than variables, and a working directory inside the project.\n * The steps that need credentials — provisioning, pushing, releasing — are\n * the deploy's own code and never run in one.\n *\n * `LocalSandbox` is the default: a child process on this machine with an\n * allowlisted environment. A host that builds repositories it does not trust\n * passes its own — a container per build, a VM — through `deploy()`'s\n * `sandbox`.\n */\n\nimport { AsyncLocalStorage } from 'node:async_hooks';\nimport { existsSync, realpathSync } from 'node:fs';\nimport { isAbsolute, relative, resolve, sep } from 'node:path';\n\n/** Where a command's output goes. */\nexport type SandboxOutput = 'capture' | 'inherit' | 'stderr' | 'ignore';\n\nexport interface SandboxExecOptions {\n\t/**\n\t * Where the command runs: the project root or a directory below it, as an\n\t * absolute path or relative to the root. Anywhere else is refused with\n\t * {@link SandboxCwdEscape}.\n\t */\n\tcwd: string;\n\t/**\n\t * The command's whole environment. Nothing of the host's is added to it:\n\t * start from {@link Sandbox.env} and add what the step needs.\n\t */\n\tenv: Readonly<Record<string, string>>;\n\t/**\n\t * How long the command may run before it is killed. Required: a step that\n\t * waits forever — a sniffed entry that opens a server, a build stuck on a\n\t * prompt — would otherwise hold the deploy, and its lock, forever.\n\t */\n\ttimeoutMs: number;\n\t/**\n\t * Files the command can read, by name. Mounted in a directory of their own\n\t * — named by `GKM_SECRETS_DIR` in the command's environment — and removed\n\t * when it exits. Never set as variables, which every child inherits, a\n\t * crash report prints and `/proc/<pid>/environ` shows.\n\t */\n\tsecrets?: Readonly<Record<string, string>>;\n\t/**\n\t * `capture` (the default) returns stdout and stderr in the result;\n\t * `inherit` gives the command the host's terminal; `stderr` sends both\n\t * streams to the host's stderr; `ignore` discards them.\n\t */\n\toutput?: SandboxOutput;\n\t/** Kills the command when aborted; `exec` rejects with the signal's reason. */\n\tsignal?: AbortSignal;\n}\n\n/** How a command ended. A non-zero exit is a result, not an error. */\nexport interface SandboxResult {\n\texitCode: number | null;\n\tsignal: string | null;\n\t/** What it wrote to stdout, when its output was captured. */\n\tstdout: string;\n\t/** What it wrote to stderr, when its output was captured. */\n\tstderr: string;\n}\n\n/**\n * Runs a command against the project, and nothing else of the host's.\n *\n * `exec` resolves with the result however the command exited. It rejects\n * with {@link CommandTimedOut} once the timeout passes — after the command is\n * gone — with {@link SandboxCwdEscape} for a directory outside the project,\n * with the signal's reason when aborted, and with the spawn error when the\n * program cannot be started.\n */\nexport interface Sandbox {\n\t/** The project: the directory every command runs at or below. */\n\treadonly root: string;\n\t/**\n\t * Whether the code it runs is kept from the host: its files, its\n\t * processes, its memory.\n\t *\n\t * An isolating sandbox shares nothing but what crosses `exec`, so a config\n\t * reaches the deploy as data — a live object in it (a custom state store,\n\t * an inline target) cannot cross, and is refused with\n\t * `ConfigObjectNotSerializable`. A non-isolating one, like\n\t * {@link LocalSandbox}, runs on the host as the host's user; a project\n\t * whose config holds live objects is then loaded in-process as before,\n\t * because a project trusted to run there is trusted to do that.\n\t */\n\treadonly isolating: boolean;\n\t/**\n\t * The environment a command starts from: what any program needs to run\n\t * here — `PATH`, `HOME`, the locale — and no credential.\n\t */\n\treadonly env: Readonly<Record<string, string>>;\n\texec(\n\t\tcommand: string,\n\t\targs: readonly string[],\n\t\toptions: SandboxExecOptions,\n\t): Promise<SandboxResult>;\n}\n\n/** The variable naming the directory a command's secrets are mounted in. */\nexport const SECRETS_DIR_ENV = 'GKM_SECRETS_DIR';\n\n/** A command was asked to run outside the project. */\nexport class SandboxCwdEscape extends Error {\n\tconstructor(\n\t\treadonly cwd: string,\n\t\treadonly root: string,\n\t) {\n\t\tsuper(\n\t\t\t`Refusing to run in '${cwd}': it is outside the project at '${root}'. ` +\n\t\t\t\t'A sandboxed step only runs inside the project it was given — check ' +\n\t\t\t\t'the app paths in gkm.config.ts for `..` or an absolute path.',\n\t\t);\n\t\tthis.name = 'SandboxCwdEscape';\n\t}\n}\n\n/** A path with its symlinks resolved where it exists — `/tmp` is `/private/tmp`. */\nfunction real(path: string): string {\n\treturn existsSync(path) ? realpathSync(path) : path;\n}\n\n/**\n * `cwd` as an absolute directory inside `root`, or {@link SandboxCwdEscape}.\n *\n * Symlinks are resolved on both sides first, so a link inside the project\n * that points out of it is an escape, and a project reached through a link\n * (`/tmp` on macOS) is still itself. For a sandbox of a host's own to call\n * before it runs anything.\n */\nexport function confineCwd(root: string, cwd: string): string {\n\tconst absolute = resolve(root, cwd);\n\tconst from = relative(real(resolve(root)), real(absolute));\n\tif (from === '..' || from.startsWith(`..${sep}`) || isAbsolute(from)) {\n\t\tthrow new SandboxCwdEscape(cwd, root);\n\t}\n\treturn absolute;\n}\n\n/** A secret's file name: one path segment, so it cannot be written elsewhere. */\nexport class SecretNameInvalid extends Error {\n\tconstructor(readonly secretName: string) {\n\t\tsuper(\n\t\t\t`'${secretName}' cannot be a secret's name: it becomes a file name in ` +\n\t\t\t\t'the secrets directory, so it must be one segment of letters, digits, ' +\n\t\t\t\t\"'.', '-' or '_' and not start with '.'.\",\n\t\t);\n\t\tthis.name = 'SecretNameInvalid';\n\t}\n}\n\nconst SECRET_NAME = /^[A-Za-z0-9_-][A-Za-z0-9._-]*$/;\n\n/** Every secret name, checked before anything is written. */\nexport function assertSecretNames(\n\tsecrets: Readonly<Record<string, string>> | undefined,\n): void {\n\tfor (const name of Object.keys(secrets ?? {})) {\n\t\tif (!SECRET_NAME.test(name)) throw new SecretNameInvalid(name);\n\t}\n}\n\n/** A run's sandbox, and what its workers already answered. */\ninterface SandboxRun {\n\tsandbox: Sandbox;\n\tanswers: Map<string, Promise<unknown>>;\n}\n\nconst active = new AsyncLocalStorage<SandboxRun>();\n\n/**\n * Run `fn` with `sandbox` as the one its steps use.\n *\n * Per run rather than global, as `withOutput` routes a run's lines: two\n * deploys in one host process each build in their own sandbox. A step deep in\n * the engine — discovering constructs, sniffing an app — asks\n * {@link activeSandbox} rather than every function between `deploy()` and it\n * taking one more parameter.\n */\nexport function withSandbox<T>(sandbox: Sandbox, fn: () => T): T {\n\treturn active.run({ sandbox, answers: new Map() }, fn);\n}\n\n/** The sandbox of the run this is part of, if it was started with one. */\nexport function activeSandbox(): Sandbox | undefined {\n\treturn active.getStore()?.sandbox;\n}\n\n/**\n * `ask()`'s answer, asked once per run.\n *\n * A deploy loads its config and discovers its constructs from several places\n * — the first load, the engine, the Dockerfile generator — and in a sandbox\n * each was a child process of its own, for the same answer: the checkout does\n * not change while it deploys. Within a run in `sandbox`, the first answer\n * for `key` is the one every later ask gets. Outside one, or for another\n * sandbox, `ask()` runs each time.\n */\nexport function oncePerRun<T>(\n\tsandbox: Sandbox,\n\tkey: string,\n\task: () => Promise<T>,\n): Promise<T> {\n\tconst run = active.getStore();\n\tif (!run || run.sandbox !== sandbox) return ask();\n\n\tlet answer = run.answers.get(key) as Promise<T> | undefined;\n\tif (!answer) {\n\t\tanswer = ask();\n\t\trun.answers.set(key, answer);\n\t\t// A failure is not remembered: the next ask tries again.\n\t\tanswer.catch(() => run.answers.delete(key));\n\t}\n\treturn answer;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkHA,MAAa,kBAAkB;;AAG/B,IAAa,mBAAb,cAAsC,MAAM;CAEjC;CACA;CAFV,YACC,KACA,MACC;EACD,MACC,uBAAuB,IAAI,mCAAmC,KAAK,qIAGpE;EAPS,KAAA,MAAA;EACA,KAAA,OAAA;EAOT,KAAK,OAAO;CACb;AACD;;AAGA,SAAS,KAAK,MAAsB;CACnC,OAAO,WAAW,IAAI,IAAI,aAAa,IAAI,IAAI;AAChD;;;;;;;;;AAUA,SAAgB,WAAW,MAAc,KAAqB;CAC7D,MAAM,WAAW,QAAQ,MAAM,GAAG;CAClC,MAAM,OAAO,SAAS,KAAK,QAAQ,IAAI,CAAC,GAAG,KAAK,QAAQ,CAAC;CACzD,IAAI,SAAS,QAAQ,KAAK,WAAW,KAAK,KAAK,KAAK,WAAW,IAAI,GAClE,MAAM,IAAI,iBAAiB,KAAK,IAAI;CAErC,OAAO;AACR;;AAGA,IAAa,oBAAb,cAAuC,MAAM;CACvB;CAArB,YAAY,YAA6B;EACxC,MACC,IAAI,WAAW,oKAGhB;EALoB,KAAA,aAAA;EAMpB,KAAK,OAAO;CACb;AACD;AAEA,MAAM,cAAc;;AAGpB,SAAgB,kBACf,SACO;CACP,KAAK,MAAM,QAAQ,OAAO,KAAK,WAAW,CAAC,CAAC,GAC3C,IAAI,CAAC,YAAY,KAAK,IAAI,GAAG,MAAM,IAAI,kBAAkB,IAAI;AAE/D;AAQA,MAAM,SAAS,IAAI,kBAA8B;;;;;;;;;;AAWjD,SAAgB,YAAe,SAAkB,IAAgB;CAChE,OAAO,OAAO,IAAI;EAAE;EAAS,yBAAS,IAAI,IAAI;CAAE,GAAG,EAAE;AACtD;;AAGA,SAAgB,gBAAqC;CACpD,OAAO,OAAO,SAAS,CAAC,EAAE;AAC3B;;;;;;;;;;;AAYA,SAAgB,WACf,SACA,KACA,KACa;CACb,MAAM,MAAM,OAAO,SAAS;CAC5B,IAAI,CAAC,OAAO,IAAI,YAAY,SAAS,OAAO,IAAI;CAEhD,IAAI,SAAS,IAAI,QAAQ,IAAI,GAAG;CAChC,IAAI,CAAC,QAAQ;EACZ,SAAS,IAAI;EACb,IAAI,QAAQ,IAAI,KAAK,MAAM;EAE3B,OAAO,YAAY,IAAI,QAAQ,OAAO,GAAG,CAAC;CAC3C;CACA,OAAO;AACR"}
|