@geekmidas/cli 10.0.0-alpha.85 → 10.0.0-alpha.87
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-dxkwynSs.cjs → HostingerProvider-BCB1QJj1.cjs} +2 -2
- package/dist/{HostingerProvider-dxkwynSs.cjs.map → HostingerProvider-BCB1QJj1.cjs.map} +1 -1
- package/dist/{HostingerProvider-D85ayzTi.mjs → HostingerProvider-CVc3rLPB.mjs} +2 -2
- package/dist/{HostingerProvider-D85ayzTi.mjs.map → HostingerProvider-CVc3rLPB.mjs.map} +1 -1
- package/dist/{LegacyStateStore-CJ2o18nT.mjs → LegacyStateStore-B0R4LlX_.mjs} +2 -2
- package/dist/{LegacyStateStore-CJ2o18nT.mjs.map → LegacyStateStore-B0R4LlX_.mjs.map} +1 -1
- package/dist/{LegacyStateStore-xGxPwP-m.cjs → LegacyStateStore-CWG_HVC0.cjs} +7 -7
- package/dist/{LegacyStateStore-xGxPwP-m.cjs.map → LegacyStateStore-CWG_HVC0.cjs.map} +1 -1
- package/dist/{LocalStateStore-CkjFD-4b.cjs → LocalStateStore-CLwa2DpK.cjs} +8 -8
- package/dist/{LocalStateStore-CkjFD-4b.cjs.map → LocalStateStore-CLwa2DpK.cjs.map} +1 -1
- package/dist/{LocalStateStore-ByVjUIvS.mjs → LocalStateStore-Q4vXg2ua.mjs} +2 -2
- package/dist/{LocalStateStore-ByVjUIvS.mjs.map → LocalStateStore-Q4vXg2ua.mjs.map} +1 -1
- package/dist/{S3StateStore-iqMezbES.cjs → S3StateStore-Cea9_5gF.cjs} +5 -5
- package/dist/{S3StateStore-iqMezbES.cjs.map → S3StateStore-Cea9_5gF.cjs.map} +1 -1
- package/dist/{S3StateStore-kpFlGVLE.mjs → S3StateStore-CksKYKRa.mjs} +2 -2
- package/dist/{S3StateStore-kpFlGVLE.mjs.map → S3StateStore-CksKYKRa.mjs.map} +1 -1
- package/dist/{SSMStateStore-Bkz5ztfo.mjs → SSMStateStore-BszSCAS2.mjs} +2 -2
- package/dist/{SSMStateStore-Bkz5ztfo.mjs.map → SSMStateStore-BszSCAS2.mjs.map} +1 -1
- package/dist/{SSMStateStore-Z2OMVCtq.cjs → SSMStateStore-aBOTF018.cjs} +9 -9
- package/dist/{SSMStateStore-Z2OMVCtq.cjs.map → SSMStateStore-aBOTF018.cjs.map} +1 -1
- package/dist/{add-CM7RLj89.cjs → add-5MZAXybv.cjs} +8 -5
- package/dist/add-5MZAXybv.cjs.map +1 -0
- package/dist/{add-jpX7M8RB.mjs → add-D_jNqnfZ.mjs} +8 -5
- package/dist/add-D_jNqnfZ.mjs.map +1 -0
- package/dist/{bundler-lEBH8ENP.cjs → bundler-1REMhKps.cjs} +5 -5
- package/dist/{bundler-lEBH8ENP.cjs.map → bundler-1REMhKps.cjs.map} +1 -1
- package/dist/{bundler-CaAKsbcN.mjs → bundler-BMo-JP5s.mjs} +5 -5
- package/dist/{bundler-CaAKsbcN.mjs.map → bundler-BMo-JP5s.mjs.map} +1 -1
- package/dist/{cli-BV3jzbk0.mjs → cli-7edNFwGk.mjs} +5 -13
- package/dist/cli-7edNFwGk.mjs.map +1 -0
- package/dist/{cli-DQCBcNlN.cjs → cli-BL8ZHsLQ.cjs} +5 -13
- package/dist/cli-BL8ZHsLQ.cjs.map +1 -0
- package/dist/{compose-CQK6StuE.mjs → compose-DbJhRQlh.mjs} +24 -11
- package/dist/compose-DbJhRQlh.mjs.map +1 -0
- package/dist/{compose-CSR0yEJm.cjs → compose-xw86kVYi.cjs} +34 -21
- package/dist/compose-xw86kVYi.cjs.map +1 -0
- package/dist/{config-BzIfP8a-.d.cts → config-CpjsVxru.d.cts} +3 -3
- package/dist/{config-BzIfP8a-.d.cts.map → config-CpjsVxru.d.cts.map} +1 -1
- package/dist/{config-DhdzmndX.d.mts → config-DrCAyoH5.d.mts} +3 -3
- package/dist/{config-DhdzmndX.d.mts.map → config-DrCAyoH5.d.mts.map} +1 -1
- package/dist/config.cjs +1 -1
- package/dist/config.d.cts +3 -3
- package/dist/config.d.mts +3 -3
- package/dist/config.mjs +1 -1
- package/dist/{credentials-pAtB70k3.mjs → credentials-BgJ5mS_d.mjs} +91 -181
- package/dist/credentials-BgJ5mS_d.mjs.map +1 -0
- package/dist/{credentials-BVs7mrtw.cjs → credentials-tr26-KML.cjs} +122 -188
- package/dist/credentials-tr26-KML.cjs.map +1 -0
- package/dist/deploy/public.cjs +24 -20
- package/dist/deploy/public.cjs.map +1 -1
- package/dist/deploy/public.d.cts +95 -13
- package/dist/deploy/public.d.cts.map +1 -1
- package/dist/deploy/public.d.mts +95 -13
- package/dist/deploy/public.d.mts.map +1 -1
- package/dist/deploy/public.mjs +11 -10
- package/dist/deploy/public.mjs.map +1 -1
- package/dist/{deploy-BuU117CA.mjs → deploy-BTfmXd3g.mjs} +9 -9
- package/dist/deploy-BTfmXd3g.mjs.map +1 -0
- package/dist/{deploy-Bw2Mm3x3.cjs → deploy-CtbZj4zN.cjs} +13 -13
- package/dist/deploy-CtbZj4zN.cjs.map +1 -0
- package/dist/{dokploy-C81J1MVK.cjs → dokploy-TGk3oPwr.cjs} +2 -2
- package/dist/{dokploy-C81J1MVK.cjs.map → dokploy-TGk3oPwr.cjs.map} +1 -1
- package/dist/{dokploy-DWALWk7u.mjs → dokploy-r69yZaIt.mjs} +2 -2
- package/dist/{dokploy-DWALWk7u.mjs.map → dokploy-r69yZaIt.mjs.map} +1 -1
- package/dist/{encryption-DpAum8W7.mjs → encryption-C-8Tt5Hh.mjs} +3 -3
- package/dist/{encryption-DpAum8W7.mjs.map → encryption-C-8Tt5Hh.mjs.map} +1 -1
- package/dist/{encryption-B8h8Tna0.cjs → encryption-CDn7BH5O.cjs} +3 -3
- package/dist/{encryption-B8h8Tna0.cjs.map → encryption-CDn7BH5O.cjs.map} +1 -1
- package/dist/{engine-B01fjhLt.cjs → engine-Bt5k_UMq.cjs} +33 -23
- package/dist/engine-Bt5k_UMq.cjs.map +1 -0
- package/dist/{engine-BXCtEhSH.mjs → engine-jAINgiOB.mjs} +28 -18
- package/dist/engine-jAINgiOB.mjs.map +1 -0
- package/dist/{github-8B68qrLs.mjs → github-CZHAtwPT.mjs} +3 -3
- package/dist/{github-8B68qrLs.mjs.map → github-CZHAtwPT.mjs.map} +1 -1
- package/dist/{github-Cis9tYur.cjs → github-DFhuJQlp.cjs} +3 -3
- package/dist/{github-Cis9tYur.cjs.map → github-DFhuJQlp.cjs.map} +1 -1
- package/dist/{index-f77HdqN5.d.mts → index-MnUJuUlj.d.mts} +2 -2
- package/dist/{index-D1ODeM9s.d.cts.map → index-MnUJuUlj.d.mts.map} +1 -1
- package/dist/{index-D1ODeM9s.d.cts → index-elsLJTUv.d.cts} +2 -2
- package/dist/{index-f77HdqN5.d.mts.map → index-elsLJTUv.d.cts.map} +1 -1
- package/dist/index.cjs +106 -68
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +100 -62
- package/dist/index.mjs.map +1 -1
- package/dist/local-Ciq-bTPr.mjs +224 -0
- package/dist/local-Ciq-bTPr.mjs.map +1 -0
- package/dist/local-Cq8qdgNX.cjs +241 -0
- package/dist/local-Cq8qdgNX.cjs.map +1 -0
- package/dist/{migrate-CPWn7A51.cjs → migrate-B16qgYTn.cjs} +3 -3
- package/dist/{migrate-CPWn7A51.cjs.map → migrate-B16qgYTn.cjs.map} +1 -1
- package/dist/{migrate-BBCp5blp.cjs → migrate-B1rVlwj1.cjs} +3 -3
- package/dist/{migrate-BBCp5blp.cjs.map → migrate-B1rVlwj1.cjs.map} +1 -1
- package/dist/{migrate-sDq7NApJ.mjs → migrate-Dn7nK6sh.mjs} +3 -3
- package/dist/{migrate-sDq7NApJ.mjs.map → migrate-Dn7nK6sh.mjs.map} +1 -1
- package/dist/{migrate-BeTWLSeC.mjs → migrate-ggkPVJRC.mjs} +3 -3
- package/dist/{migrate-BeTWLSeC.mjs.map → migrate-ggkPVJRC.mjs.map} +1 -1
- package/dist/openapi.cjs +1 -1
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/openapi.mjs +1 -1
- package/dist/reconcile/public.d.cts +1 -1
- package/dist/reconcile/public.d.mts +1 -1
- package/dist/{resolve-CXbwJ_iu.mjs → resolve-BSke45k6.mjs} +4 -4
- package/dist/{resolve-CXbwJ_iu.mjs.map → resolve-BSke45k6.mjs.map} +1 -1
- package/dist/{resolve-CztIqBOI.cjs → resolve-BXeXzpmU.cjs} +4 -4
- package/dist/{resolve-CztIqBOI.cjs.map → resolve-BXeXzpmU.cjs.map} +1 -1
- package/dist/{rollback-ByyFE5lV.mjs → rollback-CEE6dse4.mjs} +6 -6
- package/dist/{rollback-ByyFE5lV.mjs.map → rollback-CEE6dse4.mjs.map} +1 -1
- package/dist/{rollback-CyIJ4xGJ.cjs → rollback-DFMiBWf0.cjs} +8 -8
- package/dist/{rollback-CyIJ4xGJ.cjs.map → rollback-DFMiBWf0.cjs.map} +1 -1
- package/dist/{runtime-CNu2C2hB.d.mts → runtime-D48ZKk8_.d.cts} +2 -2
- package/dist/{runtime-CNu2C2hB.d.mts.map → runtime-D48ZKk8_.d.cts.map} +1 -1
- package/dist/{runtime-DMLgL26v.d.cts → runtime-HabiNBtJ.d.mts} +2 -2
- package/dist/{runtime-DMLgL26v.d.cts.map → runtime-HabiNBtJ.d.mts.map} +1 -1
- package/dist/{seeds-9C5CkDon.mjs → seeds-BYdrtZsC.mjs} +1525 -53
- package/dist/seeds-BYdrtZsC.mjs.map +1 -0
- package/dist/{seeds-B_Beuv0o.cjs → seeds-CsI0A_Ef.cjs} +1628 -65
- package/dist/seeds-CsI0A_Ef.cjs.map +1 -0
- package/dist/{sniffer-DgBkaLAu.mjs → sniffer-B0EU73bj.mjs} +5 -360
- package/dist/sniffer-B0EU73bj.mjs.map +1 -0
- package/dist/{sniffer-3fA0iExx.cjs → sniffer-DJYupBeK.cjs} +4 -413
- package/dist/sniffer-DJYupBeK.cjs.map +1 -0
- package/dist/{sst-DRi3H5TB.mjs → sst-D1Qj4sIs.mjs} +3 -3
- package/dist/{sst-DRi3H5TB.mjs.map → sst-D1Qj4sIs.mjs.map} +1 -1
- package/dist/{sst-hBrKouBy.cjs → sst-DAqUoZKG.cjs} +5 -5
- package/dist/{sst-hBrKouBy.cjs.map → sst-DAqUoZKG.cjs.map} +1 -1
- package/dist/{stages-CS1sjcRb.cjs → stages-Brn3vcdy.cjs} +2 -2
- package/dist/{stages-CS1sjcRb.cjs.map → stages-Brn3vcdy.cjs.map} +1 -1
- package/dist/{stages-CSXt7N2b.mjs → stages-DoHHz6pS.mjs} +2 -2
- package/dist/{stages-CSXt7N2b.mjs.map → stages-DoHHz6pS.mjs.map} +1 -1
- package/dist/target/public.cjs +1 -1
- package/dist/target/public.d.cts +2 -2
- package/dist/target/public.d.mts +2 -2
- package/dist/target/public.mjs +1 -1
- package/dist/{terminal-BdvrmkaC.mjs → terminal-BTbZlrFV.mjs} +2 -2
- package/dist/{terminal-BdvrmkaC.mjs.map → terminal-BTbZlrFV.mjs.map} +1 -1
- package/dist/{terminal-BL5E0xbR.cjs → terminal-COv23heb.cjs} +2 -2
- package/dist/{terminal-BL5E0xbR.cjs.map → terminal-COv23heb.cjs.map} +1 -1
- package/dist/{types-BZn-8ssD.d.mts → types-0oAyFOfR.d.cts} +48 -49
- package/dist/types-0oAyFOfR.d.cts.map +1 -0
- package/dist/{types-DiZj6hqp.d.cts → types-BZ01tZs-.d.mts} +47 -50
- package/dist/types-BZ01tZs-.d.mts.map +1 -0
- 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-Bic_R3tz.cjs → workspace-CT0JM3Y8.cjs} +333 -1
- package/dist/workspace-CT0JM3Y8.cjs.map +1 -0
- package/dist/{workspace-Drz4jsvy.mjs → workspace-D2CYLMCW.mjs} +250 -2
- package/dist/workspace-D2CYLMCW.mjs.map +1 -0
- package/package.json +15 -15
- package/dist/add-CM7RLj89.cjs.map +0 -1
- package/dist/add-jpX7M8RB.mjs.map +0 -1
- package/dist/cli-BV3jzbk0.mjs.map +0 -1
- package/dist/cli-DQCBcNlN.cjs.map +0 -1
- package/dist/compose-CQK6StuE.mjs.map +0 -1
- package/dist/compose-CSR0yEJm.cjs.map +0 -1
- package/dist/credentials-BVs7mrtw.cjs.map +0 -1
- package/dist/credentials-pAtB70k3.mjs.map +0 -1
- package/dist/deploy-BuU117CA.mjs.map +0 -1
- package/dist/deploy-Bw2Mm3x3.cjs.map +0 -1
- package/dist/engine-B01fjhLt.cjs.map +0 -1
- package/dist/engine-BXCtEhSH.mjs.map +0 -1
- package/dist/run-BJLIerMM.mjs +0 -133
- package/dist/run-BJLIerMM.mjs.map +0 -1
- package/dist/run-fVZ-rLnc.cjs +0 -174
- package/dist/run-fVZ-rLnc.cjs.map +0 -1
- package/dist/seeds-9C5CkDon.mjs.map +0 -1
- package/dist/seeds-B_Beuv0o.cjs.map +0 -1
- package/dist/sniffer-3fA0iExx.cjs.map +0 -1
- package/dist/sniffer-DgBkaLAu.mjs.map +0 -1
- package/dist/types-BZn-8ssD.d.mts.map +0 -1
- package/dist/types-DiZj6hqp.d.cts.map +0 -1
- package/dist/workspace-Bic_R3tz.cjs.map +0 -1
- package/dist/workspace-Drz4jsvy.mjs.map +0 -1
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { a as CommandTimedOut } from "./credentials-BgJ5mS_d.mjs";
|
|
2
|
+
import { a as assertSecretNames, o as confineCwd, t as SECRETS_DIR_ENV } from "./sandbox-Dm247NGx.mjs";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { spawn } from "node:child_process";
|
|
7
|
+
//#region src/sandbox/env.ts
|
|
8
|
+
/**
|
|
9
|
+
* What of the host's environment a local sandbox passes on.
|
|
10
|
+
*
|
|
11
|
+
* An allowlist, not a denylist: a credential's variable has any name its
|
|
12
|
+
* owner chose (`DOKPLOY_TOKEN`, `AWS_SECRET_ACCESS_KEY`, `MY_VAULT_TOKEN`), so
|
|
13
|
+
* the only safe default is to name what a program needs to *run* and pass
|
|
14
|
+
* nothing else.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Variables a command needs to find programs, write temp files, print in the
|
|
18
|
+
* user's locale and reach the network through the host's proxy — and nothing
|
|
19
|
+
* that authenticates as anyone.
|
|
20
|
+
*
|
|
21
|
+
* Deliberately left out:
|
|
22
|
+
* - `NODE_OPTIONS`: it can `--require` or `--import` code into every node
|
|
23
|
+
* process, and CI often holds `--import tsx` there. A step that needs a
|
|
24
|
+
* loader passes its own.
|
|
25
|
+
* - `NODE_AUTH_TOKEN`, `NPM_TOKEN`, `npm_config_*`: registry tokens
|
|
26
|
+
* (`actions/setup-node` writes `NODE_AUTH_TOKEN`), which is why there is no
|
|
27
|
+
* `NODE_*` wildcard.
|
|
28
|
+
* - `AWS_*`, `DOCKER_*`, `DOKPLOY_*`, `GITHUB_TOKEN`, `TURBO_TOKEN`, `GKM_*`:
|
|
29
|
+
* credentials, or where the deploy keeps them.
|
|
30
|
+
* - `SHELL`, `EDITOR` and the rest of a login session: nothing here starts a
|
|
31
|
+
* shell.
|
|
32
|
+
*
|
|
33
|
+
* Proxies are passed: behind one, nothing installs or builds without it. A
|
|
34
|
+
* proxy URL with a password in it reaches the command too — a host that keeps
|
|
35
|
+
* one there and builds code it does not trust should pass a sandbox of its
|
|
36
|
+
* own.
|
|
37
|
+
*/
|
|
38
|
+
const SANDBOX_ENV_ALLOWLIST = [
|
|
39
|
+
"PATH",
|
|
40
|
+
"HOME",
|
|
41
|
+
"USER",
|
|
42
|
+
"LOGNAME",
|
|
43
|
+
"TMPDIR",
|
|
44
|
+
"TMP",
|
|
45
|
+
"TEMP",
|
|
46
|
+
"LANG",
|
|
47
|
+
"LANGUAGE",
|
|
48
|
+
"TZ",
|
|
49
|
+
/^LC_[A-Z]+$/,
|
|
50
|
+
"TERM",
|
|
51
|
+
"COLORTERM",
|
|
52
|
+
"NO_COLOR",
|
|
53
|
+
"FORCE_COLOR",
|
|
54
|
+
"CI",
|
|
55
|
+
"NODE_ENV",
|
|
56
|
+
"NODE_EXTRA_CA_CERTS",
|
|
57
|
+
"COREPACK_HOME",
|
|
58
|
+
"PNPM_HOME",
|
|
59
|
+
"XDG_CACHE_HOME",
|
|
60
|
+
"XDG_DATA_HOME",
|
|
61
|
+
"HTTP_PROXY",
|
|
62
|
+
"HTTPS_PROXY",
|
|
63
|
+
"NO_PROXY",
|
|
64
|
+
"http_proxy",
|
|
65
|
+
"https_proxy",
|
|
66
|
+
"no_proxy",
|
|
67
|
+
"SystemRoot",
|
|
68
|
+
"SYSTEMROOT",
|
|
69
|
+
"windir",
|
|
70
|
+
"WINDIR",
|
|
71
|
+
"ComSpec",
|
|
72
|
+
"COMSPEC",
|
|
73
|
+
"PATHEXT",
|
|
74
|
+
"USERPROFILE",
|
|
75
|
+
"APPDATA",
|
|
76
|
+
"LOCALAPPDATA"
|
|
77
|
+
];
|
|
78
|
+
function allowed(name) {
|
|
79
|
+
return SANDBOX_ENV_ALLOWLIST.some((entry) => typeof entry === "string" ? entry === name : entry.test(name));
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The allowlisted part of `source`, plus each of `extra` the host chose to
|
|
83
|
+
* pass on by name.
|
|
84
|
+
*/
|
|
85
|
+
function allowlistedEnv(source = process.env, extra = []) {
|
|
86
|
+
const env = {};
|
|
87
|
+
for (const [name, value] of Object.entries(source)) {
|
|
88
|
+
if (value === void 0) continue;
|
|
89
|
+
if (allowed(name) || extra.includes(name)) env[name] = value;
|
|
90
|
+
}
|
|
91
|
+
return env;
|
|
92
|
+
}
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/sandbox/local.ts
|
|
95
|
+
/** How long a timed-out command has to exit on SIGTERM before SIGKILL. */
|
|
96
|
+
const KILL_GRACE_MS = 5e3;
|
|
97
|
+
/** Where `output` sends a child's streams. */
|
|
98
|
+
const STDIO = {
|
|
99
|
+
capture: [
|
|
100
|
+
"ignore",
|
|
101
|
+
"pipe",
|
|
102
|
+
"pipe"
|
|
103
|
+
],
|
|
104
|
+
inherit: "inherit",
|
|
105
|
+
stderr: [
|
|
106
|
+
"ignore",
|
|
107
|
+
2,
|
|
108
|
+
2
|
|
109
|
+
],
|
|
110
|
+
ignore: "ignore"
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* A child process on this machine, as this user, with an allowlisted
|
|
114
|
+
* environment.
|
|
115
|
+
*
|
|
116
|
+
* What it withholds from the command: the host's environment beyond
|
|
117
|
+
* {@link SANDBOX_ENV_ALLOWLIST}, a shell, a working directory outside the
|
|
118
|
+
* project, and any time past the timeout. What it does not: the host's
|
|
119
|
+
* filesystem, which the command can read and write as the user running the
|
|
120
|
+
* deploy. So it is not `isolating`, and it is the default for projects the
|
|
121
|
+
* host already trusts — the developer's own, a team's own CI. Code nobody
|
|
122
|
+
* vetted belongs in a sandbox that is.
|
|
123
|
+
*
|
|
124
|
+
* A timed-out command is sent SIGTERM, then SIGKILL; processes it started
|
|
125
|
+
* itself are its to stop.
|
|
126
|
+
*/
|
|
127
|
+
var LocalSandbox = class {
|
|
128
|
+
root;
|
|
129
|
+
isolating = false;
|
|
130
|
+
env;
|
|
131
|
+
constructor(options) {
|
|
132
|
+
this.root = resolve(options.root);
|
|
133
|
+
this.env = {
|
|
134
|
+
...allowlistedEnv(options.hostEnv ?? process.env, options.passEnv),
|
|
135
|
+
...options.home ? {
|
|
136
|
+
HOME: options.home,
|
|
137
|
+
USERPROFILE: options.home
|
|
138
|
+
} : {}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
async exec(command, args, options) {
|
|
142
|
+
const cwd = confineCwd(this.root, options.cwd);
|
|
143
|
+
assertSecretNames(options.secrets);
|
|
144
|
+
options.signal?.throwIfAborted();
|
|
145
|
+
const secretsDir = await mountSecrets(options.secrets);
|
|
146
|
+
try {
|
|
147
|
+
return await spawnConfined(command, args, {
|
|
148
|
+
...options,
|
|
149
|
+
cwd,
|
|
150
|
+
env: secretsDir ? {
|
|
151
|
+
...options.env,
|
|
152
|
+
[SECRETS_DIR_ENV]: secretsDir
|
|
153
|
+
} : options.env
|
|
154
|
+
});
|
|
155
|
+
} finally {
|
|
156
|
+
if (secretsDir) await rm(secretsDir, {
|
|
157
|
+
recursive: true,
|
|
158
|
+
force: true
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
/**
|
|
164
|
+
* Each secret as a 0600 file in a fresh 0700 directory outside the project —
|
|
165
|
+
* where nothing the build writes, packs or commits will pick it up.
|
|
166
|
+
*/
|
|
167
|
+
async function mountSecrets(secrets) {
|
|
168
|
+
if (!secrets || Object.keys(secrets).length === 0) return void 0;
|
|
169
|
+
const dir = await mkdtemp(join(tmpdir(), "gkm-secrets-"));
|
|
170
|
+
for (const [name, value] of Object.entries(secrets)) await writeFile(join(dir, name), value, { mode: 384 });
|
|
171
|
+
return dir;
|
|
172
|
+
}
|
|
173
|
+
function spawnConfined(command, args, options) {
|
|
174
|
+
const { output = "capture", signal, timeoutMs } = options;
|
|
175
|
+
return new Promise((resolvePromise, reject) => {
|
|
176
|
+
const child = spawn(command, [...args], {
|
|
177
|
+
cwd: options.cwd,
|
|
178
|
+
env: { ...options.env },
|
|
179
|
+
stdio: STDIO[output],
|
|
180
|
+
shell: false,
|
|
181
|
+
...signal ? { signal } : {}
|
|
182
|
+
});
|
|
183
|
+
let stdout = "";
|
|
184
|
+
let stderr = "";
|
|
185
|
+
child.stdout?.on("data", (chunk) => {
|
|
186
|
+
stdout += chunk.toString();
|
|
187
|
+
});
|
|
188
|
+
child.stderr?.on("data", (chunk) => {
|
|
189
|
+
stderr += chunk.toString();
|
|
190
|
+
});
|
|
191
|
+
let timedOut = false;
|
|
192
|
+
let killer;
|
|
193
|
+
const timer = setTimeout(() => {
|
|
194
|
+
timedOut = true;
|
|
195
|
+
child.kill("SIGTERM");
|
|
196
|
+
killer = setTimeout(() => child.kill("SIGKILL"), KILL_GRACE_MS);
|
|
197
|
+
killer.unref?.();
|
|
198
|
+
}, timeoutMs);
|
|
199
|
+
timer.unref?.();
|
|
200
|
+
const settle = () => {
|
|
201
|
+
clearTimeout(timer);
|
|
202
|
+
if (killer) clearTimeout(killer);
|
|
203
|
+
};
|
|
204
|
+
child.on("error", (error) => {
|
|
205
|
+
settle();
|
|
206
|
+
reject(signal?.aborted ? signal.reason : error);
|
|
207
|
+
});
|
|
208
|
+
child.on("close", (code, killedBy) => {
|
|
209
|
+
settle();
|
|
210
|
+
if (signal?.aborted) reject(signal.reason);
|
|
211
|
+
else if (timedOut) reject(new CommandTimedOut(command, args, timeoutMs));
|
|
212
|
+
else resolvePromise({
|
|
213
|
+
exitCode: code,
|
|
214
|
+
signal: killedBy ?? null,
|
|
215
|
+
stdout,
|
|
216
|
+
stderr
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
//#endregion
|
|
222
|
+
export { SANDBOX_ENV_ALLOWLIST as n, allowlistedEnv as r, LocalSandbox as t };
|
|
223
|
+
|
|
224
|
+
//# sourceMappingURL=local-Ciq-bTPr.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-Ciq-bTPr.mjs","names":[],"sources":["../src/sandbox/env.ts","../src/sandbox/local.ts"],"sourcesContent":["/**\n * What of the host's environment a local sandbox passes on.\n *\n * An allowlist, not a denylist: a credential's variable has any name its\n * owner chose (`DOKPLOY_TOKEN`, `AWS_SECRET_ACCESS_KEY`, `MY_VAULT_TOKEN`), so\n * the only safe default is to name what a program needs to *run* and pass\n * nothing else.\n */\n\n/**\n * Variables a command needs to find programs, write temp files, print in the\n * user's locale and reach the network through the host's proxy — and nothing\n * that authenticates as anyone.\n *\n * Deliberately left out:\n * - `NODE_OPTIONS`: it can `--require` or `--import` code into every node\n * process, and CI often holds `--import tsx` there. A step that needs a\n * loader passes its own.\n * - `NODE_AUTH_TOKEN`, `NPM_TOKEN`, `npm_config_*`: registry tokens\n * (`actions/setup-node` writes `NODE_AUTH_TOKEN`), which is why there is no\n * `NODE_*` wildcard.\n * - `AWS_*`, `DOCKER_*`, `DOKPLOY_*`, `GITHUB_TOKEN`, `TURBO_TOKEN`, `GKM_*`:\n * credentials, or where the deploy keeps them.\n * - `SHELL`, `EDITOR` and the rest of a login session: nothing here starts a\n * shell.\n *\n * Proxies are passed: behind one, nothing installs or builds without it. A\n * proxy URL with a password in it reaches the command too — a host that keeps\n * one there and builds code it does not trust should pass a sandbox of its\n * own.\n */\nexport const SANDBOX_ENV_ALLOWLIST: readonly (string | RegExp)[] = [\n\t// Finding programs and a home to cache in.\n\t'PATH',\n\t'HOME',\n\t'USER',\n\t'LOGNAME',\n\t'TMPDIR',\n\t'TMP',\n\t'TEMP',\n\t// Locale and terminal: output a person reads should look the same.\n\t'LANG',\n\t'LANGUAGE',\n\t'TZ',\n\t/^LC_[A-Z]+$/,\n\t'TERM',\n\t'COLORTERM',\n\t'NO_COLOR',\n\t'FORCE_COLOR',\n\t'CI',\n\t// Node: the mode a build runs in, and the CAs a corporate network adds.\n\t'NODE_ENV',\n\t'NODE_EXTRA_CA_CERTS',\n\t// Package managers' own homes, so a build reuses the host's store.\n\t'COREPACK_HOME',\n\t'PNPM_HOME',\n\t'XDG_CACHE_HOME',\n\t'XDG_DATA_HOME',\n\t// The network, through the host's proxy.\n\t'HTTP_PROXY',\n\t'HTTPS_PROXY',\n\t'NO_PROXY',\n\t'http_proxy',\n\t'https_proxy',\n\t'no_proxy',\n\t// Windows: without these, nothing starts.\n\t'SystemRoot',\n\t'SYSTEMROOT',\n\t'windir',\n\t'WINDIR',\n\t'ComSpec',\n\t'COMSPEC',\n\t'PATHEXT',\n\t'USERPROFILE',\n\t'APPDATA',\n\t'LOCALAPPDATA',\n];\n\nfunction allowed(name: string): boolean {\n\treturn SANDBOX_ENV_ALLOWLIST.some((entry) =>\n\t\ttypeof entry === 'string' ? entry === name : entry.test(name),\n\t);\n}\n\n/**\n * The allowlisted part of `source`, plus each of `extra` the host chose to\n * pass on by name.\n */\nexport function allowlistedEnv(\n\tsource: NodeJS.ProcessEnv = process.env,\n\textra: readonly string[] = [],\n): Record<string, string> {\n\tconst env: Record<string, string> = {};\n\tfor (const [name, value] of Object.entries(source)) {\n\t\tif (value === undefined) continue;\n\t\tif (allowed(name) || extra.includes(name)) env[name] = value;\n\t}\n\treturn env;\n}\n","import { type SpawnOptions, spawn } from 'node:child_process';\nimport { mkdtemp, rm, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join, resolve } from 'node:path';\nimport { CommandTimedOut } from '../run';\nimport { allowlistedEnv } from './env';\nimport {\n\tassertSecretNames,\n\tconfineCwd,\n\ttype Sandbox,\n\ttype SandboxExecOptions,\n\ttype SandboxOutput,\n\ttype SandboxResult,\n\tSECRETS_DIR_ENV,\n} from './sandbox';\n\n/** How long a timed-out command has to exit on SIGTERM before SIGKILL. */\nconst KILL_GRACE_MS = 5_000;\n\nexport interface LocalSandboxOptions {\n\t/** The project. Every command runs at or below it. */\n\troot: string;\n\t/**\n\t * `HOME` for every command. Defaults to the host's, because a build\n\t * reuses the caches kept there — the pnpm store, corepack's package\n\t * managers, turbo's cache — and a cold one re-downloads all of them.\n\t * Point it at an empty directory to keep `~/.aws`, `~/.npmrc` and\n\t * `~/.docker` out of reach of an SDK's default lookup — though, sharing\n\t * the host's filesystem, a command that goes looking still finds them.\n\t */\n\thome?: string;\n\t/**\n\t * Host variables to pass on beyond the allowlist, by name — a remote\n\t * cache's `TURBO_TOKEN`, for a build the host trusts with it.\n\t */\n\tpassEnv?: readonly string[];\n\t/** Where the allowlisted variables are read from. Defaults to `process.env`. */\n\thostEnv?: NodeJS.ProcessEnv;\n}\n\n/** Where `output` sends a child's streams. */\nconst STDIO: Record<SandboxOutput, SpawnOptions['stdio']> = {\n\tcapture: ['ignore', 'pipe', 'pipe'],\n\tinherit: 'inherit',\n\tstderr: ['ignore', 2, 2],\n\tignore: 'ignore',\n};\n\n/**\n * A child process on this machine, as this user, with an allowlisted\n * environment.\n *\n * What it withholds from the command: the host's environment beyond\n * {@link SANDBOX_ENV_ALLOWLIST}, a shell, a working directory outside the\n * project, and any time past the timeout. What it does not: the host's\n * filesystem, which the command can read and write as the user running the\n * deploy. So it is not `isolating`, and it is the default for projects the\n * host already trusts — the developer's own, a team's own CI. Code nobody\n * vetted belongs in a sandbox that is.\n *\n * A timed-out command is sent SIGTERM, then SIGKILL; processes it started\n * itself are its to stop.\n */\nexport class LocalSandbox implements Sandbox {\n\treadonly root: string;\n\treadonly isolating = false;\n\treadonly env: Readonly<Record<string, string>>;\n\n\tconstructor(options: LocalSandboxOptions) {\n\t\tthis.root = resolve(options.root);\n\t\tthis.env = {\n\t\t\t...allowlistedEnv(options.hostEnv ?? process.env, options.passEnv),\n\t\t\t...(options.home\n\t\t\t\t? { HOME: options.home, USERPROFILE: options.home }\n\t\t\t\t: {}),\n\t\t};\n\t}\n\n\tasync exec(\n\t\tcommand: string,\n\t\targs: readonly string[],\n\t\toptions: SandboxExecOptions,\n\t): Promise<SandboxResult> {\n\t\tconst cwd = confineCwd(this.root, options.cwd);\n\t\tassertSecretNames(options.secrets);\n\t\toptions.signal?.throwIfAborted();\n\n\t\tconst secretsDir = await mountSecrets(options.secrets);\n\t\ttry {\n\t\t\treturn await spawnConfined(command, args, {\n\t\t\t\t...options,\n\t\t\t\tcwd,\n\t\t\t\tenv: secretsDir\n\t\t\t\t\t? { ...options.env, [SECRETS_DIR_ENV]: secretsDir }\n\t\t\t\t\t: options.env,\n\t\t\t});\n\t\t} finally {\n\t\t\tif (secretsDir) await rm(secretsDir, { recursive: true, force: true });\n\t\t}\n\t}\n}\n\n/**\n * Each secret as a 0600 file in a fresh 0700 directory outside the project —\n * where nothing the build writes, packs or commits will pick it up.\n */\nasync function mountSecrets(\n\tsecrets: Readonly<Record<string, string>> | undefined,\n): Promise<string | undefined> {\n\tif (!secrets || Object.keys(secrets).length === 0) return undefined;\n\n\tconst dir = await mkdtemp(join(tmpdir(), 'gkm-secrets-'));\n\tfor (const [name, value] of Object.entries(secrets)) {\n\t\tawait writeFile(join(dir, name), value, { mode: 0o600 });\n\t}\n\treturn dir;\n}\n\nfunction spawnConfined(\n\tcommand: string,\n\targs: readonly string[],\n\toptions: SandboxExecOptions,\n): Promise<SandboxResult> {\n\tconst { output = 'capture', signal, timeoutMs } = options;\n\n\treturn new Promise<SandboxResult>((resolvePromise, reject) => {\n\t\tconst child = spawn(command, [...args], {\n\t\t\tcwd: options.cwd,\n\t\t\t// The whole environment, as given. `spawn` reads `process.env` only\n\t\t\t// when this is left out.\n\t\t\tenv: { ...options.env },\n\t\t\tstdio: STDIO[output],\n\t\t\tshell: false,\n\t\t\t...(signal ? { signal } : {}),\n\t\t});\n\n\t\tlet stdout = '';\n\t\tlet stderr = '';\n\t\tchild.stdout?.on('data', (chunk: Buffer) => {\n\t\t\tstdout += chunk.toString();\n\t\t});\n\t\tchild.stderr?.on('data', (chunk: Buffer) => {\n\t\t\tstderr += chunk.toString();\n\t\t});\n\n\t\tlet timedOut = false;\n\t\tlet killer: NodeJS.Timeout | undefined;\n\t\tconst timer = setTimeout(() => {\n\t\t\ttimedOut = true;\n\t\t\tchild.kill('SIGTERM');\n\t\t\t// A command that ignores SIGTERM would otherwise outlive its timeout.\n\t\t\tkiller = setTimeout(() => child.kill('SIGKILL'), KILL_GRACE_MS);\n\t\t\tkiller.unref?.();\n\t\t}, timeoutMs);\n\t\ttimer.unref?.();\n\n\t\tconst settle = () => {\n\t\t\tclearTimeout(timer);\n\t\t\tif (killer) clearTimeout(killer);\n\t\t};\n\n\t\tchild.on('error', (error) => {\n\t\t\tsettle();\n\t\t\t// Node reports an abort as its own `AbortError`; the caller asked\n\t\t\t// with a reason, and that is what it gets back.\n\t\t\treject(signal?.aborted ? signal.reason : error);\n\t\t});\n\n\t\tchild.on('close', (code: number | null, killedBy: string | null) => {\n\t\t\tsettle();\n\t\t\tif (signal?.aborted) reject(signal.reason);\n\t\t\telse if (timedOut) reject(new CommandTimedOut(command, args, timeoutMs));\n\t\t\telse\n\t\t\t\tresolvePromise({\n\t\t\t\t\texitCode: code,\n\t\t\t\t\tsignal: killedBy ?? null,\n\t\t\t\t\tstdout,\n\t\t\t\t\tstderr,\n\t\t\t\t});\n\t\t});\n\t});\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+BA,MAAa,wBAAsD;CAElE;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CAEA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CAEA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACD;AAEA,SAAS,QAAQ,MAAuB;CACvC,OAAO,sBAAsB,MAAM,UAClC,OAAO,UAAU,WAAW,UAAU,OAAO,MAAM,KAAK,IAAI,CAC7D;AACD;;;;;AAMA,SAAgB,eACf,SAA4B,QAAQ,KACpC,QAA2B,CAAC,GACH;CACzB,MAAM,MAA8B,CAAC;CACrC,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,MAAM,GAAG;EACnD,IAAI,UAAU,KAAA,GAAW;EACzB,IAAI,QAAQ,IAAI,KAAK,MAAM,SAAS,IAAI,GAAG,IAAI,QAAQ;CACxD;CACA,OAAO;AACR;;;;ACjFA,MAAM,gBAAgB;;AAwBtB,MAAM,QAAsD;CAC3D,SAAS;EAAC;EAAU;EAAQ;CAAM;CAClC,SAAS;CACT,QAAQ;EAAC;EAAU;EAAG;CAAC;CACvB,QAAQ;AACT;;;;;;;;;;;;;;;;AAiBA,IAAa,eAAb,MAA6C;CAC5C;CACA,YAAqB;CACrB;CAEA,YAAY,SAA8B;EACzC,KAAK,OAAO,QAAQ,QAAQ,IAAI;EAChC,KAAK,MAAM;GACV,GAAG,eAAe,QAAQ,WAAW,QAAQ,KAAK,QAAQ,OAAO;GACjE,GAAI,QAAQ,OACT;IAAE,MAAM,QAAQ;IAAM,aAAa,QAAQ;GAAK,IAChD,CAAC;EACL;CACD;CAEA,MAAM,KACL,SACA,MACA,SACyB;EACzB,MAAM,MAAM,WAAW,KAAK,MAAM,QAAQ,GAAG;EAC7C,kBAAkB,QAAQ,OAAO;EACjC,QAAQ,QAAQ,eAAe;EAE/B,MAAM,aAAa,MAAM,aAAa,QAAQ,OAAO;EACrD,IAAI;GACH,OAAO,MAAM,cAAc,SAAS,MAAM;IACzC,GAAG;IACH;IACA,KAAK,aACF;KAAE,GAAG,QAAQ;MAAM,kBAAkB;IAAW,IAChD,QAAQ;GACZ,CAAC;EACF,UAAU;GACT,IAAI,YAAY,MAAM,GAAG,YAAY;IAAE,WAAW;IAAM,OAAO;GAAK,CAAC;EACtE;CACD;AACD;;;;;AAMA,eAAe,aACd,SAC8B;CAC9B,IAAI,CAAC,WAAW,OAAO,KAAK,OAAO,CAAC,CAAC,WAAW,GAAG,OAAO,KAAA;CAE1D,MAAM,MAAM,MAAM,QAAQ,KAAK,OAAO,GAAG,cAAc,CAAC;CACxD,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,OAAO,GACjD,MAAM,UAAU,KAAK,KAAK,IAAI,GAAG,OAAO,EAAE,MAAM,IAAM,CAAC;CAExD,OAAO;AACR;AAEA,SAAS,cACR,SACA,MACA,SACyB;CACzB,MAAM,EAAE,SAAS,WAAW,QAAQ,cAAc;CAElD,OAAO,IAAI,SAAwB,gBAAgB,WAAW;EAC7D,MAAM,QAAQ,MAAM,SAAS,CAAC,GAAG,IAAI,GAAG;GACvC,KAAK,QAAQ;GAGb,KAAK,EAAE,GAAG,QAAQ,IAAI;GACtB,OAAO,MAAM;GACb,OAAO;GACP,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;EAC5B,CAAC;EAED,IAAI,SAAS;EACb,IAAI,SAAS;EACb,MAAM,QAAQ,GAAG,SAAS,UAAkB;GAC3C,UAAU,MAAM,SAAS;EAC1B,CAAC;EACD,MAAM,QAAQ,GAAG,SAAS,UAAkB;GAC3C,UAAU,MAAM,SAAS;EAC1B,CAAC;EAED,IAAI,WAAW;EACf,IAAI;EACJ,MAAM,QAAQ,iBAAiB;GAC9B,WAAW;GACX,MAAM,KAAK,SAAS;GAEpB,SAAS,iBAAiB,MAAM,KAAK,SAAS,GAAG,aAAa;GAC9D,OAAO,QAAQ;EAChB,GAAG,SAAS;EACZ,MAAM,QAAQ;EAEd,MAAM,eAAe;GACpB,aAAa,KAAK;GAClB,IAAI,QAAQ,aAAa,MAAM;EAChC;EAEA,MAAM,GAAG,UAAU,UAAU;GAC5B,OAAO;GAGP,OAAO,QAAQ,UAAU,OAAO,SAAS,KAAK;EAC/C,CAAC;EAED,MAAM,GAAG,UAAU,MAAqB,aAA4B;GACnE,OAAO;GACP,IAAI,QAAQ,SAAS,OAAO,OAAO,MAAM;QACpC,IAAI,UAAU,OAAO,IAAI,gBAAgB,SAAS,MAAM,SAAS,CAAC;QAEtE,eAAe;IACd,UAAU;IACV,QAAQ,YAAY;IACpB;IACA;GACD,CAAC;EACH,CAAC;CACF,CAAC;AACF"}
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
const require_credentials = require("./credentials-tr26-KML.cjs");
|
|
2
|
+
const require_sandbox = require("./sandbox-CL--ZDui.cjs");
|
|
3
|
+
let node_path = require("node:path");
|
|
4
|
+
let node_fs_promises = require("node:fs/promises");
|
|
5
|
+
let node_os = require("node:os");
|
|
6
|
+
let node_child_process = require("node:child_process");
|
|
7
|
+
//#region src/sandbox/env.ts
|
|
8
|
+
/**
|
|
9
|
+
* What of the host's environment a local sandbox passes on.
|
|
10
|
+
*
|
|
11
|
+
* An allowlist, not a denylist: a credential's variable has any name its
|
|
12
|
+
* owner chose (`DOKPLOY_TOKEN`, `AWS_SECRET_ACCESS_KEY`, `MY_VAULT_TOKEN`), so
|
|
13
|
+
* the only safe default is to name what a program needs to *run* and pass
|
|
14
|
+
* nothing else.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Variables a command needs to find programs, write temp files, print in the
|
|
18
|
+
* user's locale and reach the network through the host's proxy — and nothing
|
|
19
|
+
* that authenticates as anyone.
|
|
20
|
+
*
|
|
21
|
+
* Deliberately left out:
|
|
22
|
+
* - `NODE_OPTIONS`: it can `--require` or `--import` code into every node
|
|
23
|
+
* process, and CI often holds `--import tsx` there. A step that needs a
|
|
24
|
+
* loader passes its own.
|
|
25
|
+
* - `NODE_AUTH_TOKEN`, `NPM_TOKEN`, `npm_config_*`: registry tokens
|
|
26
|
+
* (`actions/setup-node` writes `NODE_AUTH_TOKEN`), which is why there is no
|
|
27
|
+
* `NODE_*` wildcard.
|
|
28
|
+
* - `AWS_*`, `DOCKER_*`, `DOKPLOY_*`, `GITHUB_TOKEN`, `TURBO_TOKEN`, `GKM_*`:
|
|
29
|
+
* credentials, or where the deploy keeps them.
|
|
30
|
+
* - `SHELL`, `EDITOR` and the rest of a login session: nothing here starts a
|
|
31
|
+
* shell.
|
|
32
|
+
*
|
|
33
|
+
* Proxies are passed: behind one, nothing installs or builds without it. A
|
|
34
|
+
* proxy URL with a password in it reaches the command too — a host that keeps
|
|
35
|
+
* one there and builds code it does not trust should pass a sandbox of its
|
|
36
|
+
* own.
|
|
37
|
+
*/
|
|
38
|
+
const SANDBOX_ENV_ALLOWLIST = [
|
|
39
|
+
"PATH",
|
|
40
|
+
"HOME",
|
|
41
|
+
"USER",
|
|
42
|
+
"LOGNAME",
|
|
43
|
+
"TMPDIR",
|
|
44
|
+
"TMP",
|
|
45
|
+
"TEMP",
|
|
46
|
+
"LANG",
|
|
47
|
+
"LANGUAGE",
|
|
48
|
+
"TZ",
|
|
49
|
+
/^LC_[A-Z]+$/,
|
|
50
|
+
"TERM",
|
|
51
|
+
"COLORTERM",
|
|
52
|
+
"NO_COLOR",
|
|
53
|
+
"FORCE_COLOR",
|
|
54
|
+
"CI",
|
|
55
|
+
"NODE_ENV",
|
|
56
|
+
"NODE_EXTRA_CA_CERTS",
|
|
57
|
+
"COREPACK_HOME",
|
|
58
|
+
"PNPM_HOME",
|
|
59
|
+
"XDG_CACHE_HOME",
|
|
60
|
+
"XDG_DATA_HOME",
|
|
61
|
+
"HTTP_PROXY",
|
|
62
|
+
"HTTPS_PROXY",
|
|
63
|
+
"NO_PROXY",
|
|
64
|
+
"http_proxy",
|
|
65
|
+
"https_proxy",
|
|
66
|
+
"no_proxy",
|
|
67
|
+
"SystemRoot",
|
|
68
|
+
"SYSTEMROOT",
|
|
69
|
+
"windir",
|
|
70
|
+
"WINDIR",
|
|
71
|
+
"ComSpec",
|
|
72
|
+
"COMSPEC",
|
|
73
|
+
"PATHEXT",
|
|
74
|
+
"USERPROFILE",
|
|
75
|
+
"APPDATA",
|
|
76
|
+
"LOCALAPPDATA"
|
|
77
|
+
];
|
|
78
|
+
function allowed(name) {
|
|
79
|
+
return SANDBOX_ENV_ALLOWLIST.some((entry) => typeof entry === "string" ? entry === name : entry.test(name));
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* The allowlisted part of `source`, plus each of `extra` the host chose to
|
|
83
|
+
* pass on by name.
|
|
84
|
+
*/
|
|
85
|
+
function allowlistedEnv(source = process.env, extra = []) {
|
|
86
|
+
const env = {};
|
|
87
|
+
for (const [name, value] of Object.entries(source)) {
|
|
88
|
+
if (value === void 0) continue;
|
|
89
|
+
if (allowed(name) || extra.includes(name)) env[name] = value;
|
|
90
|
+
}
|
|
91
|
+
return env;
|
|
92
|
+
}
|
|
93
|
+
//#endregion
|
|
94
|
+
//#region src/sandbox/local.ts
|
|
95
|
+
/** How long a timed-out command has to exit on SIGTERM before SIGKILL. */
|
|
96
|
+
const KILL_GRACE_MS = 5e3;
|
|
97
|
+
/** Where `output` sends a child's streams. */
|
|
98
|
+
const STDIO = {
|
|
99
|
+
capture: [
|
|
100
|
+
"ignore",
|
|
101
|
+
"pipe",
|
|
102
|
+
"pipe"
|
|
103
|
+
],
|
|
104
|
+
inherit: "inherit",
|
|
105
|
+
stderr: [
|
|
106
|
+
"ignore",
|
|
107
|
+
2,
|
|
108
|
+
2
|
|
109
|
+
],
|
|
110
|
+
ignore: "ignore"
|
|
111
|
+
};
|
|
112
|
+
/**
|
|
113
|
+
* A child process on this machine, as this user, with an allowlisted
|
|
114
|
+
* environment.
|
|
115
|
+
*
|
|
116
|
+
* What it withholds from the command: the host's environment beyond
|
|
117
|
+
* {@link SANDBOX_ENV_ALLOWLIST}, a shell, a working directory outside the
|
|
118
|
+
* project, and any time past the timeout. What it does not: the host's
|
|
119
|
+
* filesystem, which the command can read and write as the user running the
|
|
120
|
+
* deploy. So it is not `isolating`, and it is the default for projects the
|
|
121
|
+
* host already trusts — the developer's own, a team's own CI. Code nobody
|
|
122
|
+
* vetted belongs in a sandbox that is.
|
|
123
|
+
*
|
|
124
|
+
* A timed-out command is sent SIGTERM, then SIGKILL; processes it started
|
|
125
|
+
* itself are its to stop.
|
|
126
|
+
*/
|
|
127
|
+
var LocalSandbox = class {
|
|
128
|
+
root;
|
|
129
|
+
isolating = false;
|
|
130
|
+
env;
|
|
131
|
+
constructor(options) {
|
|
132
|
+
this.root = (0, node_path.resolve)(options.root);
|
|
133
|
+
this.env = {
|
|
134
|
+
...allowlistedEnv(options.hostEnv ?? process.env, options.passEnv),
|
|
135
|
+
...options.home ? {
|
|
136
|
+
HOME: options.home,
|
|
137
|
+
USERPROFILE: options.home
|
|
138
|
+
} : {}
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
async exec(command, args, options) {
|
|
142
|
+
const cwd = require_sandbox.confineCwd(this.root, options.cwd);
|
|
143
|
+
require_sandbox.assertSecretNames(options.secrets);
|
|
144
|
+
options.signal?.throwIfAborted();
|
|
145
|
+
const secretsDir = await mountSecrets(options.secrets);
|
|
146
|
+
try {
|
|
147
|
+
return await spawnConfined(command, args, {
|
|
148
|
+
...options,
|
|
149
|
+
cwd,
|
|
150
|
+
env: secretsDir ? {
|
|
151
|
+
...options.env,
|
|
152
|
+
[require_sandbox.SECRETS_DIR_ENV]: secretsDir
|
|
153
|
+
} : options.env
|
|
154
|
+
});
|
|
155
|
+
} finally {
|
|
156
|
+
if (secretsDir) await (0, node_fs_promises.rm)(secretsDir, {
|
|
157
|
+
recursive: true,
|
|
158
|
+
force: true
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
/**
|
|
164
|
+
* Each secret as a 0600 file in a fresh 0700 directory outside the project —
|
|
165
|
+
* where nothing the build writes, packs or commits will pick it up.
|
|
166
|
+
*/
|
|
167
|
+
async function mountSecrets(secrets) {
|
|
168
|
+
if (!secrets || Object.keys(secrets).length === 0) return void 0;
|
|
169
|
+
const dir = await (0, node_fs_promises.mkdtemp)((0, node_path.join)((0, node_os.tmpdir)(), "gkm-secrets-"));
|
|
170
|
+
for (const [name, value] of Object.entries(secrets)) await (0, node_fs_promises.writeFile)((0, node_path.join)(dir, name), value, { mode: 384 });
|
|
171
|
+
return dir;
|
|
172
|
+
}
|
|
173
|
+
function spawnConfined(command, args, options) {
|
|
174
|
+
const { output = "capture", signal, timeoutMs } = options;
|
|
175
|
+
return new Promise((resolvePromise, reject) => {
|
|
176
|
+
const child = (0, node_child_process.spawn)(command, [...args], {
|
|
177
|
+
cwd: options.cwd,
|
|
178
|
+
env: { ...options.env },
|
|
179
|
+
stdio: STDIO[output],
|
|
180
|
+
shell: false,
|
|
181
|
+
...signal ? { signal } : {}
|
|
182
|
+
});
|
|
183
|
+
let stdout = "";
|
|
184
|
+
let stderr = "";
|
|
185
|
+
child.stdout?.on("data", (chunk) => {
|
|
186
|
+
stdout += chunk.toString();
|
|
187
|
+
});
|
|
188
|
+
child.stderr?.on("data", (chunk) => {
|
|
189
|
+
stderr += chunk.toString();
|
|
190
|
+
});
|
|
191
|
+
let timedOut = false;
|
|
192
|
+
let killer;
|
|
193
|
+
const timer = setTimeout(() => {
|
|
194
|
+
timedOut = true;
|
|
195
|
+
child.kill("SIGTERM");
|
|
196
|
+
killer = setTimeout(() => child.kill("SIGKILL"), KILL_GRACE_MS);
|
|
197
|
+
killer.unref?.();
|
|
198
|
+
}, timeoutMs);
|
|
199
|
+
timer.unref?.();
|
|
200
|
+
const settle = () => {
|
|
201
|
+
clearTimeout(timer);
|
|
202
|
+
if (killer) clearTimeout(killer);
|
|
203
|
+
};
|
|
204
|
+
child.on("error", (error) => {
|
|
205
|
+
settle();
|
|
206
|
+
reject(signal?.aborted ? signal.reason : error);
|
|
207
|
+
});
|
|
208
|
+
child.on("close", (code, killedBy) => {
|
|
209
|
+
settle();
|
|
210
|
+
if (signal?.aborted) reject(signal.reason);
|
|
211
|
+
else if (timedOut) reject(new require_credentials.CommandTimedOut(command, args, timeoutMs));
|
|
212
|
+
else resolvePromise({
|
|
213
|
+
exitCode: code,
|
|
214
|
+
signal: killedBy ?? null,
|
|
215
|
+
stdout,
|
|
216
|
+
stderr
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
//#endregion
|
|
222
|
+
Object.defineProperty(exports, "LocalSandbox", {
|
|
223
|
+
enumerable: true,
|
|
224
|
+
get: function() {
|
|
225
|
+
return LocalSandbox;
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
Object.defineProperty(exports, "SANDBOX_ENV_ALLOWLIST", {
|
|
229
|
+
enumerable: true,
|
|
230
|
+
get: function() {
|
|
231
|
+
return SANDBOX_ENV_ALLOWLIST;
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
Object.defineProperty(exports, "allowlistedEnv", {
|
|
235
|
+
enumerable: true,
|
|
236
|
+
get: function() {
|
|
237
|
+
return allowlistedEnv;
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
//# sourceMappingURL=local-Cq8qdgNX.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-Cq8qdgNX.cjs","names":["resolve","confineCwd","SECRETS_DIR_ENV","rm","mkdtemp","join","tmpdir","writeFile","spawn","CommandTimedOut"],"sources":["../src/sandbox/env.ts","../src/sandbox/local.ts"],"sourcesContent":["/**\n * What of the host's environment a local sandbox passes on.\n *\n * An allowlist, not a denylist: a credential's variable has any name its\n * owner chose (`DOKPLOY_TOKEN`, `AWS_SECRET_ACCESS_KEY`, `MY_VAULT_TOKEN`), so\n * the only safe default is to name what a program needs to *run* and pass\n * nothing else.\n */\n\n/**\n * Variables a command needs to find programs, write temp files, print in the\n * user's locale and reach the network through the host's proxy — and nothing\n * that authenticates as anyone.\n *\n * Deliberately left out:\n * - `NODE_OPTIONS`: it can `--require` or `--import` code into every node\n * process, and CI often holds `--import tsx` there. A step that needs a\n * loader passes its own.\n * - `NODE_AUTH_TOKEN`, `NPM_TOKEN`, `npm_config_*`: registry tokens\n * (`actions/setup-node` writes `NODE_AUTH_TOKEN`), which is why there is no\n * `NODE_*` wildcard.\n * - `AWS_*`, `DOCKER_*`, `DOKPLOY_*`, `GITHUB_TOKEN`, `TURBO_TOKEN`, `GKM_*`:\n * credentials, or where the deploy keeps them.\n * - `SHELL`, `EDITOR` and the rest of a login session: nothing here starts a\n * shell.\n *\n * Proxies are passed: behind one, nothing installs or builds without it. A\n * proxy URL with a password in it reaches the command too — a host that keeps\n * one there and builds code it does not trust should pass a sandbox of its\n * own.\n */\nexport const SANDBOX_ENV_ALLOWLIST: readonly (string | RegExp)[] = [\n\t// Finding programs and a home to cache in.\n\t'PATH',\n\t'HOME',\n\t'USER',\n\t'LOGNAME',\n\t'TMPDIR',\n\t'TMP',\n\t'TEMP',\n\t// Locale and terminal: output a person reads should look the same.\n\t'LANG',\n\t'LANGUAGE',\n\t'TZ',\n\t/^LC_[A-Z]+$/,\n\t'TERM',\n\t'COLORTERM',\n\t'NO_COLOR',\n\t'FORCE_COLOR',\n\t'CI',\n\t// Node: the mode a build runs in, and the CAs a corporate network adds.\n\t'NODE_ENV',\n\t'NODE_EXTRA_CA_CERTS',\n\t// Package managers' own homes, so a build reuses the host's store.\n\t'COREPACK_HOME',\n\t'PNPM_HOME',\n\t'XDG_CACHE_HOME',\n\t'XDG_DATA_HOME',\n\t// The network, through the host's proxy.\n\t'HTTP_PROXY',\n\t'HTTPS_PROXY',\n\t'NO_PROXY',\n\t'http_proxy',\n\t'https_proxy',\n\t'no_proxy',\n\t// Windows: without these, nothing starts.\n\t'SystemRoot',\n\t'SYSTEMROOT',\n\t'windir',\n\t'WINDIR',\n\t'ComSpec',\n\t'COMSPEC',\n\t'PATHEXT',\n\t'USERPROFILE',\n\t'APPDATA',\n\t'LOCALAPPDATA',\n];\n\nfunction allowed(name: string): boolean {\n\treturn SANDBOX_ENV_ALLOWLIST.some((entry) =>\n\t\ttypeof entry === 'string' ? entry === name : entry.test(name),\n\t);\n}\n\n/**\n * The allowlisted part of `source`, plus each of `extra` the host chose to\n * pass on by name.\n */\nexport function allowlistedEnv(\n\tsource: NodeJS.ProcessEnv = process.env,\n\textra: readonly string[] = [],\n): Record<string, string> {\n\tconst env: Record<string, string> = {};\n\tfor (const [name, value] of Object.entries(source)) {\n\t\tif (value === undefined) continue;\n\t\tif (allowed(name) || extra.includes(name)) env[name] = value;\n\t}\n\treturn env;\n}\n","import { type SpawnOptions, spawn } from 'node:child_process';\nimport { mkdtemp, rm, writeFile } from 'node:fs/promises';\nimport { tmpdir } from 'node:os';\nimport { join, resolve } from 'node:path';\nimport { CommandTimedOut } from '../run';\nimport { allowlistedEnv } from './env';\nimport {\n\tassertSecretNames,\n\tconfineCwd,\n\ttype Sandbox,\n\ttype SandboxExecOptions,\n\ttype SandboxOutput,\n\ttype SandboxResult,\n\tSECRETS_DIR_ENV,\n} from './sandbox';\n\n/** How long a timed-out command has to exit on SIGTERM before SIGKILL. */\nconst KILL_GRACE_MS = 5_000;\n\nexport interface LocalSandboxOptions {\n\t/** The project. Every command runs at or below it. */\n\troot: string;\n\t/**\n\t * `HOME` for every command. Defaults to the host's, because a build\n\t * reuses the caches kept there — the pnpm store, corepack's package\n\t * managers, turbo's cache — and a cold one re-downloads all of them.\n\t * Point it at an empty directory to keep `~/.aws`, `~/.npmrc` and\n\t * `~/.docker` out of reach of an SDK's default lookup — though, sharing\n\t * the host's filesystem, a command that goes looking still finds them.\n\t */\n\thome?: string;\n\t/**\n\t * Host variables to pass on beyond the allowlist, by name — a remote\n\t * cache's `TURBO_TOKEN`, for a build the host trusts with it.\n\t */\n\tpassEnv?: readonly string[];\n\t/** Where the allowlisted variables are read from. Defaults to `process.env`. */\n\thostEnv?: NodeJS.ProcessEnv;\n}\n\n/** Where `output` sends a child's streams. */\nconst STDIO: Record<SandboxOutput, SpawnOptions['stdio']> = {\n\tcapture: ['ignore', 'pipe', 'pipe'],\n\tinherit: 'inherit',\n\tstderr: ['ignore', 2, 2],\n\tignore: 'ignore',\n};\n\n/**\n * A child process on this machine, as this user, with an allowlisted\n * environment.\n *\n * What it withholds from the command: the host's environment beyond\n * {@link SANDBOX_ENV_ALLOWLIST}, a shell, a working directory outside the\n * project, and any time past the timeout. What it does not: the host's\n * filesystem, which the command can read and write as the user running the\n * deploy. So it is not `isolating`, and it is the default for projects the\n * host already trusts — the developer's own, a team's own CI. Code nobody\n * vetted belongs in a sandbox that is.\n *\n * A timed-out command is sent SIGTERM, then SIGKILL; processes it started\n * itself are its to stop.\n */\nexport class LocalSandbox implements Sandbox {\n\treadonly root: string;\n\treadonly isolating = false;\n\treadonly env: Readonly<Record<string, string>>;\n\n\tconstructor(options: LocalSandboxOptions) {\n\t\tthis.root = resolve(options.root);\n\t\tthis.env = {\n\t\t\t...allowlistedEnv(options.hostEnv ?? process.env, options.passEnv),\n\t\t\t...(options.home\n\t\t\t\t? { HOME: options.home, USERPROFILE: options.home }\n\t\t\t\t: {}),\n\t\t};\n\t}\n\n\tasync exec(\n\t\tcommand: string,\n\t\targs: readonly string[],\n\t\toptions: SandboxExecOptions,\n\t): Promise<SandboxResult> {\n\t\tconst cwd = confineCwd(this.root, options.cwd);\n\t\tassertSecretNames(options.secrets);\n\t\toptions.signal?.throwIfAborted();\n\n\t\tconst secretsDir = await mountSecrets(options.secrets);\n\t\ttry {\n\t\t\treturn await spawnConfined(command, args, {\n\t\t\t\t...options,\n\t\t\t\tcwd,\n\t\t\t\tenv: secretsDir\n\t\t\t\t\t? { ...options.env, [SECRETS_DIR_ENV]: secretsDir }\n\t\t\t\t\t: options.env,\n\t\t\t});\n\t\t} finally {\n\t\t\tif (secretsDir) await rm(secretsDir, { recursive: true, force: true });\n\t\t}\n\t}\n}\n\n/**\n * Each secret as a 0600 file in a fresh 0700 directory outside the project —\n * where nothing the build writes, packs or commits will pick it up.\n */\nasync function mountSecrets(\n\tsecrets: Readonly<Record<string, string>> | undefined,\n): Promise<string | undefined> {\n\tif (!secrets || Object.keys(secrets).length === 0) return undefined;\n\n\tconst dir = await mkdtemp(join(tmpdir(), 'gkm-secrets-'));\n\tfor (const [name, value] of Object.entries(secrets)) {\n\t\tawait writeFile(join(dir, name), value, { mode: 0o600 });\n\t}\n\treturn dir;\n}\n\nfunction spawnConfined(\n\tcommand: string,\n\targs: readonly string[],\n\toptions: SandboxExecOptions,\n): Promise<SandboxResult> {\n\tconst { output = 'capture', signal, timeoutMs } = options;\n\n\treturn new Promise<SandboxResult>((resolvePromise, reject) => {\n\t\tconst child = spawn(command, [...args], {\n\t\t\tcwd: options.cwd,\n\t\t\t// The whole environment, as given. `spawn` reads `process.env` only\n\t\t\t// when this is left out.\n\t\t\tenv: { ...options.env },\n\t\t\tstdio: STDIO[output],\n\t\t\tshell: false,\n\t\t\t...(signal ? { signal } : {}),\n\t\t});\n\n\t\tlet stdout = '';\n\t\tlet stderr = '';\n\t\tchild.stdout?.on('data', (chunk: Buffer) => {\n\t\t\tstdout += chunk.toString();\n\t\t});\n\t\tchild.stderr?.on('data', (chunk: Buffer) => {\n\t\t\tstderr += chunk.toString();\n\t\t});\n\n\t\tlet timedOut = false;\n\t\tlet killer: NodeJS.Timeout | undefined;\n\t\tconst timer = setTimeout(() => {\n\t\t\ttimedOut = true;\n\t\t\tchild.kill('SIGTERM');\n\t\t\t// A command that ignores SIGTERM would otherwise outlive its timeout.\n\t\t\tkiller = setTimeout(() => child.kill('SIGKILL'), KILL_GRACE_MS);\n\t\t\tkiller.unref?.();\n\t\t}, timeoutMs);\n\t\ttimer.unref?.();\n\n\t\tconst settle = () => {\n\t\t\tclearTimeout(timer);\n\t\t\tif (killer) clearTimeout(killer);\n\t\t};\n\n\t\tchild.on('error', (error) => {\n\t\t\tsettle();\n\t\t\t// Node reports an abort as its own `AbortError`; the caller asked\n\t\t\t// with a reason, and that is what it gets back.\n\t\t\treject(signal?.aborted ? signal.reason : error);\n\t\t});\n\n\t\tchild.on('close', (code: number | null, killedBy: string | null) => {\n\t\t\tsettle();\n\t\t\tif (signal?.aborted) reject(signal.reason);\n\t\t\telse if (timedOut) reject(new CommandTimedOut(command, args, timeoutMs));\n\t\t\telse\n\t\t\t\tresolvePromise({\n\t\t\t\t\texitCode: code,\n\t\t\t\t\tsignal: killedBy ?? null,\n\t\t\t\t\tstdout,\n\t\t\t\t\tstderr,\n\t\t\t\t});\n\t\t});\n\t});\n}\n"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
const require_workspace = require("./workspace-
|
|
1
|
+
const require_workspace = require("./workspace-CT0JM3Y8.cjs");
|
|
2
2
|
const require_config = require("./config.cjs");
|
|
3
|
-
const require_seeds = require("./seeds-
|
|
3
|
+
const require_seeds = require("./seeds-CsI0A_Ef.cjs");
|
|
4
4
|
//#region src/secrets/migrate.ts
|
|
5
5
|
/**
|
|
6
6
|
* `gkm secrets:migrate` — copy a deployed stage's secrets from the store
|
|
@@ -90,4 +90,4 @@ async function secretsMigrateCommand(options) {
|
|
|
90
90
|
//#endregion
|
|
91
91
|
exports.secretsMigrateCommand = secretsMigrateCommand;
|
|
92
92
|
|
|
93
|
-
//# sourceMappingURL=migrate-
|
|
93
|
+
//# sourceMappingURL=migrate-B16qgYTn.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migrate-
|
|
1
|
+
{"version":3,"file":"migrate-B16qgYTn.cjs","names":["SECRETS_STORE_PROVIDERS","UnknownSecretsStoreProvider","loadWorkspaceSettings","secretsStoreFor","storeFromConfig"],"sources":["../src/secrets/migrate.ts"],"sourcesContent":["/**\n * `gkm secrets:migrate` — copy a deployed stage's secrets from the store\n * `secrets.store` names to another one, whole: service passwords, URLs and\n * custom keys alike, so nothing is regenerated and running services keep\n * their credentials.\n *\n * It copies and nothing more. The source is left as it was, and gkm.config.ts\n * is not edited: pointing `secrets.store` at the new store is the switch, and\n * the old copy is deleted by hand once deploys read from the new one.\n */\n\nimport { loadWorkspaceSettings } from '../config.js';\nimport { assertDeployedStage } from '../workspace/stages.js';\nimport {\n\tSECRETS_STORE_PROVIDERS,\n\ttype SecretsStoreConfig,\n\ttype SecretsStoreOptions,\n\ttype SecretsStoreProvider,\n\tsecretsStoreFor,\n\tstoreFromConfig,\n\tUnknownSecretsStoreProvider,\n} from './store.js';\n\nconst logger = console;\n\nexport interface SecretsMigrateOptions {\n\tstage: string;\n\t/** The store to copy to: `'file'`, `'ssm'` or `'secrets-manager'`. */\n\tto: string;\n\t/** The target's AWS region; defaults to the configured store's. */\n\tregion?: string;\n\t/** Overwrite a stage the target already holds. */\n\tforce?: boolean;\n\t/** The AWS profile for the stage's account, for both stores. */\n\tprofile?: string;\n}\n\n/** An AWS store to copy to, with no region given and none configured. */\nexport class MigrateTargetNeedsRegion extends Error {\n\tconstructor(readonly to: string) {\n\t\tsuper(\n\t\t\t`Copying to ${to} needs a region, and secrets.store has none to reuse. ` +\n\t\t\t\t`Pass it: gkm secrets:migrate --to ${to} --region <region> --stage <stage>.`,\n\t\t);\n\t\tthis.name = 'MigrateTargetNeedsRegion';\n\t}\n}\n\n/** The target is the store the stage is already read from. */\nexport class MigrateTargetIsSource extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly to: string,\n\t) {\n\t\tsuper(\n\t\t\t`Stage \"${stage}\" is already kept in ${to}, so there is nothing to copy. ` +\n\t\t\t\t'Name a different store with --to, or a different region with --region.',\n\t\t);\n\t\tthis.name = 'MigrateTargetIsSource';\n\t}\n}\n\n/** The configured store holds nothing for the stage. */\nexport class NoSecretsToMigrate extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly store: string,\n\t) {\n\t\tsuper(\n\t\t\t`The ${store} store holds no secrets for stage \"${stage}\", so there is nothing to copy. ` +\n\t\t\t\t`Check secrets.store in gkm.config.ts still names the store the stage is in, ` +\n\t\t\t\t`or create the stage there with gkm secrets:init --stage ${stage}.`,\n\t\t);\n\t\tthis.name = 'NoSecretsToMigrate';\n\t}\n}\n\n/** The target already holds the stage, and `--force` was not given. */\nexport class MigrateTargetHoldsStage extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly to: string,\n\t) {\n\t\tsuper(\n\t\t\t`The ${to} store already holds secrets for stage \"${stage}\". ` +\n\t\t\t\t'Pass --force to replace them with the ones being copied.',\n\t\t);\n\t\tthis.name = 'MigrateTargetHoldsStage';\n\t}\n}\n\n/** The store a `--to` name and region describe. */\nfunction targetConfig(\n\tto: string,\n\tregion: string | undefined,\n\tconfigured: SecretsStoreConfig,\n): SecretsStoreConfig {\n\tif (!(SECRETS_STORE_PROVIDERS as readonly string[]).includes(to)) {\n\t\tthrow new UnknownSecretsStoreProvider(to);\n\t}\n\tconst provider = to as SecretsStoreProvider;\n\tif (provider === 'file') return 'file';\n\n\tconst fallback =\n\t\ttypeof configured === 'object' && 'region' in configured\n\t\t\t? configured.region\n\t\t\t: undefined;\n\tconst resolved = region ?? fallback;\n\tif (!resolved) throw new MigrateTargetNeedsRegion(provider);\n\treturn { provider, region: resolved };\n}\n\nexport async function secretsMigrateCommand(\n\toptions: SecretsMigrateOptions,\n): Promise<void> {\n\tconst { stage, to } = options;\n\tconst workspace = await loadWorkspaceSettings();\n\tassertDeployedStage(workspace.stages, stage);\n\n\tconst configured = workspace.secrets.store ?? 'file';\n\tconst target = targetConfig(to, options.region, configured);\n\tconst sameStore =\n\t\ttypeof configured === 'string' || typeof target === 'string'\n\t\t\t? configured === target\n\t\t\t: configured.provider === target.provider &&\n\t\t\t\t'region' in configured &&\n\t\t\t\t'region' in target &&\n\t\t\t\tconfigured.region === target.region;\n\tif (sameStore) throw new MigrateTargetIsSource(stage, to);\n\n\tconst storeOptions: SecretsStoreOptions = options.profile\n\t\t? { profile: options.profile }\n\t\t: {};\n\tconst source = await secretsStoreFor(workspace, stage, storeOptions);\n\tconst destination = await storeFromConfig(workspace, target, storeOptions);\n\n\tconst secrets = await source.read(stage);\n\tif (!secrets) throw new NoSecretsToMigrate(stage, source.name);\n\n\tif (!options.force && (await destination.read(stage))) {\n\t\tthrow new MigrateTargetHoldsStage(stage, destination.name);\n\t}\n\n\tawait destination.write(stage, secrets);\n\n\tconst shape =\n\t\ttypeof target === 'object' && 'region' in target\n\t\t\t? `{ provider: '${target.provider}', region: '${target.region}' }`\n\t\t\t: `'file'`;\n\tlogger.log(\n\t\t`\\n✓ Copied the secrets for stage \"${stage}\" from ${source.name} to ${destination.name}`,\n\t);\n\tlogger.log(\n\t\t`\\n Next: set secrets.store to ${shape} in gkm.config.ts, so commands and deploys read from ${destination.name}.`,\n\t);\n\tlogger.log(\n\t\t` The copy in ${source.name} is left as it was; delete it once deploys read from ${destination.name}.`,\n\t);\n}\n"],"mappings":";;;;;;;;;;;;;;AAuBA,MAAM,SAAS;;AAef,IAAa,2BAAb,cAA8C,MAAM;CAC9B;CAArB,YAAY,IAAqB;EAChC,MACC,cAAc,GAAG,0FACqB,GAAG,oCAC1C;EAJoB,KAAA,KAAA;EAKpB,KAAK,OAAO;CACb;AACD;;AAGA,IAAa,wBAAb,cAA2C,MAAM;CAEtC;CACA;CAFV,YACC,OACA,IACC;EACD,MACC,UAAU,MAAM,uBAAuB,GAAG,sGAE3C;EANS,KAAA,QAAA;EACA,KAAA,KAAA;EAMT,KAAK,OAAO;CACb;AACD;;AAGA,IAAa,qBAAb,cAAwC,MAAM;CAEnC;CACA;CAFV,YACC,OACA,OACC;EACD,MACC,OAAO,MAAM,qCAAqC,MAAM,sKAEI,MAAM,EACnE;EAPS,KAAA,QAAA;EACA,KAAA,QAAA;EAOT,KAAK,OAAO;CACb;AACD;;AAGA,IAAa,0BAAb,cAA6C,MAAM;CAExC;CACA;CAFV,YACC,OACA,IACC;EACD,MACC,OAAO,GAAG,0CAA0C,MAAM,4DAE3D;EANS,KAAA,QAAA;EACA,KAAA,KAAA;EAMT,KAAK,OAAO;CACb;AACD;;AAGA,SAAS,aACR,IACA,QACA,YACqB;CACrB,IAAI,CAAEA,kBAAAA,wBAA8C,SAAS,EAAE,GAC9D,MAAM,IAAIC,kBAAAA,4BAA4B,EAAE;CAEzC,MAAM,WAAW;CACjB,IAAI,aAAa,QAAQ,OAAO;CAEhC,MAAM,WACL,OAAO,eAAe,YAAY,YAAY,aAC3C,WAAW,SACX,KAAA;CACJ,MAAM,WAAW,UAAU;CAC3B,IAAI,CAAC,UAAU,MAAM,IAAI,yBAAyB,QAAQ;CAC1D,OAAO;EAAE;EAAU,QAAQ;CAAS;AACrC;AAEA,eAAsB,sBACrB,SACgB;CAChB,MAAM,EAAE,OAAO,OAAO;CACtB,MAAM,YAAY,MAAMC,eAAAA,sBAAsB;CAC9C,kBAAA,oBAAoB,UAAU,QAAQ,KAAK;CAE3C,MAAM,aAAa,UAAU,QAAQ,SAAS;CAC9C,MAAM,SAAS,aAAa,IAAI,QAAQ,QAAQ,UAAU;CAQ1D,IANC,OAAO,eAAe,YAAY,OAAO,WAAW,WACjD,eAAe,SACf,WAAW,aAAa,OAAO,YAChC,YAAY,cACZ,YAAY,UACZ,WAAW,WAAW,OAAO,QACjB,MAAM,IAAI,sBAAsB,OAAO,EAAE;CAExD,MAAM,eAAoC,QAAQ,UAC/C,EAAE,SAAS,QAAQ,QAAQ,IAC3B,CAAC;CACJ,MAAM,SAAS,MAAMC,cAAAA,gBAAgB,WAAW,OAAO,YAAY;CACnE,MAAM,cAAc,MAAMC,cAAAA,gBAAgB,WAAW,QAAQ,YAAY;CAEzE,MAAM,UAAU,MAAM,OAAO,KAAK,KAAK;CACvC,IAAI,CAAC,SAAS,MAAM,IAAI,mBAAmB,OAAO,OAAO,IAAI;CAE7D,IAAI,CAAC,QAAQ,SAAU,MAAM,YAAY,KAAK,KAAK,GAClD,MAAM,IAAI,wBAAwB,OAAO,YAAY,IAAI;CAG1D,MAAM,YAAY,MAAM,OAAO,OAAO;CAEtC,MAAM,QACL,OAAO,WAAW,YAAY,YAAY,SACvC,gBAAgB,OAAO,SAAS,cAAc,OAAO,OAAO,OAC5D;CACJ,OAAO,IACN,qCAAqC,MAAM,SAAS,OAAO,KAAK,MAAM,YAAY,MACnF;CACA,OAAO,IACN,kCAAkC,MAAM,uDAAuD,YAAY,KAAK,EACjH;CACA,OAAO,IACN,iBAAiB,OAAO,KAAK,uDAAuD,YAAY,KAAK,EACtG;AACD"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const require_discover = require("./discover-D1It5r_h.cjs");
|
|
2
|
-
const require_workspace = require("./workspace-
|
|
2
|
+
const require_workspace = require("./workspace-CT0JM3Y8.cjs");
|
|
3
3
|
const require_config = require("./config.cjs");
|
|
4
|
-
const require_seeds = require("./seeds-
|
|
4
|
+
const require_seeds = require("./seeds-CsI0A_Ef.cjs");
|
|
5
5
|
const require_databases = require("./databases-BY-pEImp.cjs");
|
|
6
6
|
const require_index = require("./index.cjs");
|
|
7
7
|
let node_path = require("node:path");
|
|
@@ -267,4 +267,4 @@ exports.prepareDevDatabases = prepareDevDatabases;
|
|
|
267
267
|
exports.reportPendingMigrations = reportPendingMigrations;
|
|
268
268
|
exports.seedCommand = seedCommand;
|
|
269
269
|
|
|
270
|
-
//# sourceMappingURL=migrate-
|
|
270
|
+
//# sourceMappingURL=migrate-B1rVlwj1.cjs.map
|