@geekmidas/cli 10.0.0-alpha.3 → 10.0.0-alpha.30
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 +25 -48
- package/bin/adjacent-tsconfig.mjs +97 -0
- package/bin/gkm.mjs +7 -1
- package/dist/{HostingerProvider-h3EbEp-Z.cjs → HostingerProvider-BZFGYYVA.cjs} +21 -22
- package/dist/HostingerProvider-BZFGYYVA.cjs.map +1 -0
- package/dist/{HostingerProvider-Cft7lEYr.mjs → HostingerProvider-GF6NAZAK.mjs} +20 -21
- package/dist/HostingerProvider-GF6NAZAK.mjs.map +1 -0
- package/dist/{LocalStateProvider-Db9SEjyH.cjs → LocalStateProvider-7HR6SS2t.cjs} +11 -6
- package/dist/LocalStateProvider-7HR6SS2t.cjs.map +1 -0
- package/dist/{LocalStateProvider-Bn6tS-oL.mjs → LocalStateProvider-D7QyJoLM.mjs} +10 -4
- package/dist/LocalStateProvider-D7QyJoLM.mjs.map +1 -0
- package/dist/{openapi-c9bpieI5.cjs → OpenApiTsGenerator-D1nhN98m.cjs} +135 -298
- package/dist/OpenApiTsGenerator-D1nhN98m.cjs.map +1 -0
- package/dist/{openapi-CY9xFkTH.mjs → OpenApiTsGenerator-hDINScnz.mjs} +106 -250
- package/dist/OpenApiTsGenerator-hDINScnz.mjs.map +1 -0
- package/dist/{Route53Provider-RdpUDJQk.mjs → Route53Provider-Cvj40A8I.mjs} +12 -12
- package/dist/Route53Provider-Cvj40A8I.mjs.map +1 -0
- package/dist/{Route53Provider-di2fMM5N.cjs → Route53Provider-DVILBsvV.cjs} +20 -21
- package/dist/Route53Provider-DVILBsvV.cjs.map +1 -0
- package/dist/{SSMStateProvider-BRt2py-A.mjs → SSMStateProvider-B0Hu1MKZ.mjs} +13 -4
- package/dist/SSMStateProvider-B0Hu1MKZ.mjs.map +1 -0
- package/dist/{SSMStateProvider-DyX8s9HI.cjs → SSMStateProvider-VrQ-yeqd.cjs} +17 -9
- package/dist/SSMStateProvider-VrQ-yeqd.cjs.map +1 -0
- package/dist/{backup-provisioner-BrsHwYG0.cjs → backup-provisioner-BmhIO_Yr.cjs} +21 -19
- package/dist/backup-provisioner-BmhIO_Yr.cjs.map +1 -0
- package/dist/{backup-provisioner-Bgj5Vi_L.mjs → backup-provisioner-DIVCbfee.mjs} +10 -7
- package/dist/backup-provisioner-DIVCbfee.mjs.map +1 -0
- package/dist/{bundler-BY1OmV1e.cjs → bundler-CYF5mWi9.cjs} +10 -28
- package/dist/bundler-CYF5mWi9.cjs.map +1 -0
- package/dist/{bundler-Be8Zg4Wd.mjs → bundler-D9IPNvDt.mjs} +9 -26
- package/dist/bundler-D9IPNvDt.mjs.map +1 -0
- package/dist/{config-CzWnfcXM.mjs → config-BH3Ki69F.mjs} +49 -89
- package/dist/config-BH3Ki69F.mjs.map +1 -0
- package/dist/{config-BholeD-0.cjs → config-URVD4bTj.cjs} +101 -148
- package/dist/config-URVD4bTj.cjs.map +1 -0
- package/dist/config.cjs +5 -5
- package/dist/config.d.cts +25 -17
- package/dist/config.d.cts.map +1 -1
- package/dist/config.d.mts +25 -17
- package/dist/config.d.mts.map +1 -1
- package/dist/config.mjs +3 -5
- package/dist/deploy/sniffer-envkit-patch.cjs +18 -10
- package/dist/deploy/sniffer-envkit-patch.cjs.map +1 -1
- package/dist/deploy/sniffer-envkit-patch.d.cts +0 -2
- package/dist/deploy/sniffer-envkit-patch.d.cts.map +1 -1
- package/dist/deploy/sniffer-envkit-patch.d.mts +0 -2
- package/dist/deploy/sniffer-envkit-patch.d.mts.map +1 -1
- package/dist/deploy/sniffer-envkit-patch.mjs +10 -2
- package/dist/deploy/sniffer-envkit-patch.mjs.map +1 -1
- package/dist/deploy/sniffer-hooks.cjs +13 -7
- package/dist/deploy/sniffer-hooks.cjs.map +1 -1
- package/dist/deploy/sniffer-hooks.d.cts +1 -2
- package/dist/deploy/sniffer-hooks.d.cts.map +1 -1
- package/dist/deploy/sniffer-hooks.d.mts +1 -2
- package/dist/deploy/sniffer-hooks.d.mts.map +1 -1
- package/dist/deploy/sniffer-hooks.mjs +9 -2
- package/dist/deploy/sniffer-hooks.mjs.map +1 -1
- package/dist/deploy/sniffer-loader.cjs +16 -7
- package/dist/deploy/sniffer-loader.cjs.map +1 -1
- package/dist/deploy/sniffer-loader.d.cts +1 -1
- package/dist/deploy/sniffer-loader.d.mts +1 -1
- package/dist/deploy/sniffer-loader.mjs +13 -2
- package/dist/deploy/sniffer-loader.mjs.map +1 -1
- package/dist/deploy/sniffer-routes-worker.cjs +11 -11
- package/dist/deploy/sniffer-routes-worker.cjs.map +1 -1
- package/dist/deploy/sniffer-routes-worker.d.cts +1 -1
- package/dist/deploy/sniffer-routes-worker.d.mts +1 -1
- package/dist/deploy/sniffer-routes-worker.mjs +3 -3
- package/dist/deploy/sniffer-routes-worker.mjs.map +1 -1
- package/dist/deploy/sniffer-worker.cjs +15 -6
- package/dist/deploy/sniffer-worker.cjs.map +1 -1
- package/dist/deploy/sniffer-worker.d.cts +1 -3
- package/dist/deploy/sniffer-worker.d.cts.map +1 -1
- package/dist/deploy/sniffer-worker.d.mts +1 -3
- package/dist/deploy/sniffer-worker.d.mts.map +1 -1
- package/dist/deploy/sniffer-worker.mjs +15 -4
- package/dist/deploy/sniffer-worker.mjs.map +1 -1
- package/dist/{discover-CAlFgYec.mjs → discover-CMxesskG.mjs} +66 -22
- package/dist/discover-CMxesskG.mjs.map +1 -0
- package/dist/{discover-DTmFeloL.cjs → discover-CroTIm0K.cjs} +117 -66
- package/dist/discover-CroTIm0K.cjs.map +1 -0
- package/dist/{emit-CbcRolsa.cjs → emit-BdoPFjZf.cjs} +53 -36
- package/dist/emit-BdoPFjZf.cjs.map +1 -0
- package/dist/{emit-CiZS_5Ub.mjs → emit-Bzq7JsMP.mjs} +24 -6
- package/dist/emit-Bzq7JsMP.mjs.map +1 -0
- package/dist/github-B2fis34P.cjs +216 -0
- package/dist/github-B2fis34P.cjs.map +1 -0
- package/dist/github-BrDmlm1x.mjs +216 -0
- package/dist/github-BrDmlm1x.mjs.map +1 -0
- package/dist/{index-CHB4tSzX.d.cts → index-p89EzILa.d.mts} +4 -23
- package/dist/index-p89EzILa.d.mts.map +1 -0
- package/dist/{index-BThUzEIp.d.mts → index-rU9u761a.d.cts} +4 -23
- package/dist/index-rU9u761a.d.cts.map +1 -0
- package/dist/index.cjs +6001 -4452
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +5895 -4367
- package/dist/index.mjs.map +1 -1
- package/dist/migrate-BuCx7tIv.cjs +498 -0
- package/dist/migrate-BuCx7tIv.cjs.map +1 -0
- package/dist/migrate-CBICVMAi.mjs +493 -0
- package/dist/migrate-CBICVMAi.mjs.map +1 -0
- package/dist/openapi.cjs +197 -8
- package/dist/openapi.cjs.map +1 -0
- package/dist/openapi.d.cts +47 -14
- package/dist/openapi.d.cts.map +1 -1
- package/dist/openapi.d.mts +47 -14
- package/dist/openapi.d.mts.map +1 -1
- package/dist/openapi.mjs +191 -5
- package/dist/openapi.mjs.map +1 -0
- package/dist/ready-CvP3JVEw.mjs +355 -0
- package/dist/ready-CvP3JVEw.mjs.map +1 -0
- package/dist/ready-DXtNfk7v.cjs +390 -0
- package/dist/ready-DXtNfk7v.cjs.map +1 -0
- package/dist/reconcile/public.cjs +4 -4
- package/dist/reconcile/public.d.cts +27 -26
- package/dist/reconcile/public.d.cts.map +1 -1
- package/dist/reconcile/public.d.mts +27 -26
- package/dist/reconcile/public.d.mts.map +1 -1
- package/dist/reconcile/public.mjs +3 -4
- package/dist/reconcile-BxZTV2vl.cjs +332 -0
- package/dist/reconcile-BxZTV2vl.cjs.map +1 -0
- package/dist/reconcile-yNq6Shau.mjs +279 -0
- package/dist/reconcile-yNq6Shau.mjs.map +1 -0
- package/dist/rolldown-runtime-BhDjJH2R.mjs +15 -0
- package/dist/{chunk-CUT6urMc.cjs → rolldown-runtime-C0BPl7ul.cjs} +23 -10
- package/dist/ssm-BKN1Cg4z.mjs +60 -0
- package/dist/ssm-BKN1Cg4z.mjs.map +1 -0
- package/dist/ssm-FxeSAt35.cjs +60 -0
- package/dist/ssm-FxeSAt35.cjs.map +1 -0
- package/dist/{storage-Bi24WfoW.mjs → storage-BjTLjDDp.mjs} +22 -22
- package/dist/storage-BjTLjDDp.mjs.map +1 -0
- package/dist/{storage-BJmzYxt8.cjs → storage-C2JC31V9.cjs} +65 -84
- package/dist/storage-C2JC31V9.cjs.map +1 -0
- package/dist/types-CzK6hsXP.d.mts +6286 -0
- package/dist/types-CzK6hsXP.d.mts.map +1 -0
- package/dist/types-DOOAevjG.d.cts +6287 -0
- package/dist/types-DOOAevjG.d.cts.map +1 -0
- package/dist/vitest.cjs +61 -0
- package/dist/vitest.cjs.map +1 -0
- package/dist/vitest.d.cts +28 -0
- package/dist/vitest.d.cts.map +1 -0
- package/dist/vitest.d.mts +28 -0
- package/dist/vitest.d.mts.map +1 -0
- package/dist/vitest.mjs +61 -0
- package/dist/vitest.mjs.map +1 -0
- package/dist/workspace/index.cjs +3 -4
- package/dist/workspace/index.d.cts +3 -3
- package/dist/workspace/index.d.mts +3 -3
- package/dist/workspace/index.mjs +2 -3
- package/dist/workspace-Bav3TVrP.cjs +6710 -0
- package/dist/workspace-Bav3TVrP.cjs.map +1 -0
- package/dist/{credentials-DQmbRnr1.cjs → workspace-BhiMkRuA.cjs} +2076 -1963
- package/dist/workspace-BhiMkRuA.cjs.map +1 -0
- package/dist/{credentials-BZcp3CGx.mjs → workspace-C97QUCIf.mjs} +1957 -1837
- package/dist/workspace-C97QUCIf.mjs.map +1 -0
- package/dist/workspace-T4IVGSoB.mjs +6538 -0
- package/dist/workspace-T4IVGSoB.mjs.map +1 -0
- package/package.json +45 -38
- package/dist/CachedStateProvider-BLo7zv7S.cjs +0 -3
- package/dist/CachedStateProvider-BxoAm-B2.mjs +0 -3
- package/dist/CachedStateProvider-CqPdW7CB.cjs +0 -60
- package/dist/CachedStateProvider-CqPdW7CB.cjs.map +0 -1
- package/dist/CachedStateProvider-DTniehMP.mjs +0 -54
- package/dist/CachedStateProvider-DTniehMP.mjs.map +0 -1
- package/dist/HostingerProvider-Cft7lEYr.mjs.map +0 -1
- package/dist/HostingerProvider-h3EbEp-Z.cjs.map +0 -1
- package/dist/LocalStateProvider-Bn6tS-oL.mjs.map +0 -1
- package/dist/LocalStateProvider-Db9SEjyH.cjs.map +0 -1
- package/dist/Route53Provider-RdpUDJQk.mjs.map +0 -1
- package/dist/Route53Provider-di2fMM5N.cjs.map +0 -1
- package/dist/SSMStateProvider-BRt2py-A.mjs.map +0 -1
- package/dist/SSMStateProvider-DyX8s9HI.cjs.map +0 -1
- package/dist/backup-provisioner-Bgj5Vi_L.mjs.map +0 -1
- package/dist/backup-provisioner-BrsHwYG0.cjs.map +0 -1
- package/dist/bundler-BY1OmV1e.cjs.map +0 -1
- package/dist/bundler-Be8Zg4Wd.mjs.map +0 -1
- package/dist/chunk-Duj1WY3L.mjs +0 -7
- package/dist/config-BholeD-0.cjs.map +0 -1
- package/dist/config-CzWnfcXM.mjs.map +0 -1
- package/dist/credentials-B5zRHdAl.mjs +0 -141
- package/dist/credentials-B5zRHdAl.mjs.map +0 -1
- package/dist/credentials-BZcp3CGx.mjs.map +0 -1
- package/dist/credentials-BapzjyV5.cjs +0 -195
- package/dist/credentials-BapzjyV5.cjs.map +0 -1
- package/dist/credentials-DQmbRnr1.cjs.map +0 -1
- package/dist/discover-CAlFgYec.mjs.map +0 -1
- package/dist/discover-DTmFeloL.cjs.map +0 -1
- package/dist/dokploy-api-BS2xnJLZ.mjs +0 -3
- package/dist/dokploy-api-BsVH0y_s.cjs +0 -718
- package/dist/dokploy-api-BsVH0y_s.cjs.map +0 -1
- package/dist/dokploy-api-BuxDRo3l.cjs +0 -3
- package/dist/dokploy-api-aTFPz7_4.mjs +0 -706
- package/dist/dokploy-api-aTFPz7_4.mjs.map +0 -1
- package/dist/emit-CbcRolsa.cjs.map +0 -1
- package/dist/emit-CiZS_5Ub.mjs.map +0 -1
- package/dist/encryption-C6jaU0hE.cjs +0 -54
- package/dist/encryption-C6jaU0hE.cjs.map +0 -1
- package/dist/encryption-CHzzeR8a.cjs +0 -4
- package/dist/encryption-CQUncu7v.mjs +0 -3
- package/dist/encryption-D-abyhZx.mjs +0 -42
- package/dist/encryption-D-abyhZx.mjs.map +0 -1
- package/dist/fullstack-secrets-C-GGohke.cjs +0 -111
- package/dist/fullstack-secrets-C-GGohke.cjs.map +0 -1
- package/dist/fullstack-secrets-D1o9-G5l.mjs +0 -87
- package/dist/fullstack-secrets-D1o9-G5l.mjs.map +0 -1
- package/dist/generator-CNzBw7g5.cjs +0 -289
- package/dist/generator-CNzBw7g5.cjs.map +0 -1
- package/dist/generator-VE2JrFlY.mjs +0 -253
- package/dist/generator-VE2JrFlY.mjs.map +0 -1
- package/dist/index-BThUzEIp.d.mts.map +0 -1
- package/dist/index-CHB4tSzX.d.cts.map +0 -1
- package/dist/openapi-CY9xFkTH.mjs.map +0 -1
- package/dist/openapi-c9bpieI5.cjs.map +0 -1
- package/dist/openapi-react-query-Byltvm_7.mjs +0 -167
- package/dist/openapi-react-query-Byltvm_7.mjs.map +0 -1
- package/dist/openapi-react-query-CmJ1xDbn.cjs +0 -173
- package/dist/openapi-react-query-CmJ1xDbn.cjs.map +0 -1
- package/dist/openapi-react-query.cjs +0 -4
- package/dist/openapi-react-query.d.cts +0 -11
- package/dist/openapi-react-query.d.cts.map +0 -1
- package/dist/openapi-react-query.d.mts +0 -11
- package/dist/openapi-react-query.d.mts.map +0 -1
- package/dist/openapi-react-query.mjs +0 -4
- package/dist/reconcile-CFs8C_1a.mjs +0 -37
- package/dist/reconcile-CFs8C_1a.mjs.map +0 -1
- package/dist/reconcile-D0pimNiy.cjs +0 -37
- package/dist/reconcile-D0pimNiy.cjs.map +0 -1
- package/dist/setup-Bc73t0ve.mjs +0 -443
- package/dist/setup-Bc73t0ve.mjs.map +0 -1
- package/dist/setup-C48C9hto.cjs +0 -473
- package/dist/setup-C48C9hto.cjs.map +0 -1
- package/dist/setup-DCuudnCl.mjs +0 -11
- package/dist/setup-fG_ivuxO.cjs +0 -11
- package/dist/storage-BJmzYxt8.cjs.map +0 -1
- package/dist/storage-BM9hGpgv.cjs +0 -7
- package/dist/storage-Bi24WfoW.mjs.map +0 -1
- package/dist/storage-CYlGwWTj.mjs +0 -3
- package/dist/sync-CmEuNZsB.mjs +0 -76
- package/dist/sync-CmEuNZsB.mjs.map +0 -1
- package/dist/sync-D306yOWu.cjs +0 -4
- package/dist/sync-SizaChmC.cjs +0 -93
- package/dist/sync-SizaChmC.cjs.map +0 -1
- package/dist/sync-hOqAWHR6.mjs +0 -3
- package/dist/types-C-arnQ1r.d.mts +0 -2184
- package/dist/types-C-arnQ1r.d.mts.map +0 -1
- package/dist/types-abJQuk7m.d.cts +0 -2184
- package/dist/types-abJQuk7m.d.cts.map +0 -1
- package/dist/workspace-7FqprxuJ.mjs +0 -4323
- package/dist/workspace-7FqprxuJ.mjs.map +0 -1
- package/dist/workspace-BO_KWohI.cjs +0 -9
- package/dist/workspace-DK4WWJIn.mjs +0 -8
- package/dist/workspace-XvqO9jIv.cjs +0 -4472
- package/dist/workspace-XvqO9jIv.cjs.map +0 -1
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-BhDjJH2R.mjs";
|
|
2
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { randomBytes } from "node:crypto";
|
|
5
|
+
//#region src/secrets/generator.ts
|
|
6
|
+
/**
|
|
7
|
+
* Generate a secure random password using URL-safe base64 characters.
|
|
8
|
+
* @param length Password length (default: 32)
|
|
9
|
+
*/
|
|
10
|
+
function generateSecurePassword(length = 32) {
|
|
11
|
+
return randomBytes(Math.ceil(length * 3 / 4)).toString("base64url").slice(0, length);
|
|
12
|
+
}
|
|
13
|
+
/** Default service configurations (localhost for local dev via Docker port mapping) */
|
|
14
|
+
const SERVICE_DEFAULTS = {
|
|
15
|
+
postgres: {
|
|
16
|
+
host: "localhost",
|
|
17
|
+
port: 5432,
|
|
18
|
+
username: "app",
|
|
19
|
+
database: "app"
|
|
20
|
+
},
|
|
21
|
+
redis: {
|
|
22
|
+
host: "localhost",
|
|
23
|
+
port: 6379,
|
|
24
|
+
username: "default"
|
|
25
|
+
},
|
|
26
|
+
minio: {
|
|
27
|
+
host: "localhost",
|
|
28
|
+
port: 9e3,
|
|
29
|
+
username: "app",
|
|
30
|
+
bucket: "app"
|
|
31
|
+
},
|
|
32
|
+
mailpit: {
|
|
33
|
+
host: "localhost",
|
|
34
|
+
port: 1025,
|
|
35
|
+
username: "app"
|
|
36
|
+
},
|
|
37
|
+
localstack: {
|
|
38
|
+
host: "localhost",
|
|
39
|
+
port: 4566,
|
|
40
|
+
username: "localstack",
|
|
41
|
+
region: "us-east-1"
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
/** Default credentials for pgboss (not a Docker service, reuses postgres) */
|
|
45
|
+
const PGBOSS_DEFAULTS = {
|
|
46
|
+
host: "localhost",
|
|
47
|
+
port: 5432,
|
|
48
|
+
username: "pgboss",
|
|
49
|
+
database: "app"
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Generate credentials for a specific service.
|
|
53
|
+
*/
|
|
54
|
+
function generateServiceCredentials(service) {
|
|
55
|
+
return {
|
|
56
|
+
...SERVICE_DEFAULTS[service],
|
|
57
|
+
password: generateSecurePassword()
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Generate credentials for multiple services.
|
|
62
|
+
*/
|
|
63
|
+
function generateServicesCredentials(services) {
|
|
64
|
+
const result = {};
|
|
65
|
+
for (const service of services) result[service] = generateServiceCredentials(service);
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Generate connection URL for PostgreSQL.
|
|
70
|
+
*/
|
|
71
|
+
function generatePostgresUrl(creds) {
|
|
72
|
+
const { username, password, host, port, database } = creds;
|
|
73
|
+
return `postgresql://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}`;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Generate connection URL for Redis.
|
|
77
|
+
*/
|
|
78
|
+
function generateRedisUrl(creds) {
|
|
79
|
+
const { password, host, port } = creds;
|
|
80
|
+
return `redis://:${encodeURIComponent(password)}@${host}:${port}`;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Generate endpoint URL for MinIO (S3-compatible).
|
|
84
|
+
*/
|
|
85
|
+
function generateMinioEndpoint(creds) {
|
|
86
|
+
const { host, port } = creds;
|
|
87
|
+
return `http://${host}:${port}`;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Generate connection URL for pg-boss (uses PostgreSQL protocol).
|
|
91
|
+
* Format: pgboss://user:pass@host:port/db?schema=pgboss
|
|
92
|
+
*/
|
|
93
|
+
function generatePgBossUrl(creds) {
|
|
94
|
+
const { username, password, host, port, database } = creds;
|
|
95
|
+
return `pgboss://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}?schema=pgboss`;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Generate connection URLs from service credentials.
|
|
99
|
+
*/
|
|
100
|
+
function generateConnectionUrls(services) {
|
|
101
|
+
const urls = {};
|
|
102
|
+
if (services.postgres) urls.DATABASE_URL = generatePostgresUrl(services.postgres);
|
|
103
|
+
if (services.redis) urls.REDIS_URL = generateRedisUrl(services.redis);
|
|
104
|
+
if (services.minio) urls.STORAGE_ENDPOINT = generateMinioEndpoint(services.minio);
|
|
105
|
+
if (services.pgboss) {
|
|
106
|
+
const pgbossUrl = generatePgBossUrl(services.pgboss);
|
|
107
|
+
urls.EVENT_PUBLISHER_CONNECTION_STRING = pgbossUrl;
|
|
108
|
+
urls.EVENT_SUBSCRIBER_CONNECTION_STRING = pgbossUrl;
|
|
109
|
+
}
|
|
110
|
+
if (services.mailpit) {
|
|
111
|
+
urls.SMTP_HOST = services.mailpit.host;
|
|
112
|
+
urls.SMTP_PORT = String(services.mailpit.port);
|
|
113
|
+
}
|
|
114
|
+
return urls;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Create a new StageSecrets object with generated credentials.
|
|
118
|
+
* @param stage - The deployment stage (e.g., 'development', 'production')
|
|
119
|
+
* @param services - List of services to generate credentials for
|
|
120
|
+
* @param options - Optional configuration
|
|
121
|
+
* @param options.projectName - Project name used to derive the database name (e.g., 'myapp' → 'myapp_dev')
|
|
122
|
+
*/
|
|
123
|
+
function createStageSecrets(stage, services, options) {
|
|
124
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
125
|
+
const serviceCredentials = generateServicesCredentials(services);
|
|
126
|
+
if (options?.projectName) {
|
|
127
|
+
if (serviceCredentials.postgres) serviceCredentials.postgres.database = `${options.projectName.replace(/-/g, "_")}_dev`;
|
|
128
|
+
if (serviceCredentials.minio) {
|
|
129
|
+
serviceCredentials.minio.bucket = options.projectName;
|
|
130
|
+
serviceCredentials.minio.username = options.projectName;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (serviceCredentials.postgres && !serviceCredentials.pgboss) serviceCredentials.pgboss = {
|
|
134
|
+
...PGBOSS_DEFAULTS,
|
|
135
|
+
password: generateSecurePassword(),
|
|
136
|
+
host: serviceCredentials.postgres.host,
|
|
137
|
+
port: serviceCredentials.postgres.port,
|
|
138
|
+
database: serviceCredentials.postgres.database
|
|
139
|
+
};
|
|
140
|
+
return {
|
|
141
|
+
stage,
|
|
142
|
+
createdAt: now,
|
|
143
|
+
updatedAt: now,
|
|
144
|
+
services: serviceCredentials,
|
|
145
|
+
urls: generateConnectionUrls(serviceCredentials),
|
|
146
|
+
custom: {}
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Rotate password for a specific service.
|
|
151
|
+
*/
|
|
152
|
+
function rotateServicePassword(secrets, service) {
|
|
153
|
+
const currentCreds = secrets.services[service];
|
|
154
|
+
if (!currentCreds) throw new Error(`Service "${service}" not configured in secrets`);
|
|
155
|
+
const newCreds = {
|
|
156
|
+
...currentCreds,
|
|
157
|
+
password: generateSecurePassword()
|
|
158
|
+
};
|
|
159
|
+
const newServices = {
|
|
160
|
+
...secrets.services,
|
|
161
|
+
[service]: newCreds
|
|
162
|
+
};
|
|
163
|
+
return {
|
|
164
|
+
...secrets,
|
|
165
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
166
|
+
services: newServices,
|
|
167
|
+
urls: generateConnectionUrls(newServices)
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
//#endregion
|
|
171
|
+
//#region src/setup/fullstack-secrets.ts
|
|
172
|
+
/**
|
|
173
|
+
* Generate a secure random password for database users.
|
|
174
|
+
* Uses a combination of timestamp and random bytes for uniqueness.
|
|
175
|
+
*/
|
|
176
|
+
function generateDbPassword() {
|
|
177
|
+
return `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}${Math.random().toString(36).slice(2)}`;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Generate database URL for an app.
|
|
181
|
+
* All apps connect to the same database, but use different users/schemas.
|
|
182
|
+
*/
|
|
183
|
+
function generateDbUrl(appName, password, projectName, host = "localhost", port = 5432) {
|
|
184
|
+
return `postgresql://${appName.replace(/-/g, "_")}:${password}@${host}:${port}/${`${projectName.replace(/-/g, "_")}_dev`}`;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Generate fullstack-aware custom secrets for a workspace.
|
|
188
|
+
*
|
|
189
|
+
* Generates:
|
|
190
|
+
* - Common secrets: PORT, LOG_LEVEL, JWT_SECRET (no `NODE_ENV` — the command
|
|
191
|
+
* decides that; see `gkm init`)
|
|
192
|
+
* - Per-app database passwords and URLs for backend apps with db service
|
|
193
|
+
* - Better-auth secrets for apps using the better-auth framework
|
|
194
|
+
*/
|
|
195
|
+
function generateFullstackCustomSecrets(workspace, containers = []) {
|
|
196
|
+
const hasDb = containers.includes("postgres");
|
|
197
|
+
const customs = {
|
|
198
|
+
PORT: "3000",
|
|
199
|
+
LOG_LEVEL: "debug",
|
|
200
|
+
JWT_SECRET: `dev-${Date.now()}-${Math.random().toString(36).slice(2)}`
|
|
201
|
+
};
|
|
202
|
+
if (!hasDb) return customs;
|
|
203
|
+
const frontendPorts = [];
|
|
204
|
+
for (const [appName, appConfig] of Object.entries(workspace.apps)) {
|
|
205
|
+
if (appConfig.type === "web") {
|
|
206
|
+
frontendPorts.push(appConfig.port);
|
|
207
|
+
const upperName = appName.toUpperCase();
|
|
208
|
+
customs[`${upperName}_URL`] = `http://localhost:${appConfig.port}`;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (appConfig.type === "mobile") continue;
|
|
212
|
+
const password = generateDbPassword();
|
|
213
|
+
const upperName = appName.toUpperCase();
|
|
214
|
+
customs[`${upperName}_DATABASE_URL`] = generateDbUrl(appName, password, workspace.name);
|
|
215
|
+
customs[`${upperName}_DB_PASSWORD`] = password;
|
|
216
|
+
if (appConfig.framework === "better-auth") {
|
|
217
|
+
customs.AUTH_PORT = String(appConfig.port);
|
|
218
|
+
customs.AUTH_URL = `http://localhost:${appConfig.port}`;
|
|
219
|
+
customs.BETTER_AUTH_SECRET = `better-auth-${Date.now()}-${generateSecurePassword(16)}`;
|
|
220
|
+
customs.BETTER_AUTH_URL = `http://localhost:${appConfig.port}`;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
if (customs.BETTER_AUTH_SECRET) customs.BETTER_AUTH_TRUSTED_ORIGINS = Object.values(workspace.apps).map((a) => a.port).map((p) => `http://localhost:${p}`).join(",");
|
|
224
|
+
return customs;
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Extract *_DB_PASSWORD keys from secrets and write docker/.env.
|
|
228
|
+
*
|
|
229
|
+
* The docker/.env file contains database passwords that the PostgreSQL
|
|
230
|
+
* init script reads to create per-app database users.
|
|
231
|
+
*/
|
|
232
|
+
async function writeDockerEnvFromSecrets(secrets, workspaceRoot) {
|
|
233
|
+
const dbPasswordEntries = Object.entries(secrets.custom).filter(([key]) => key.endsWith("_DB_PASSWORD"));
|
|
234
|
+
if (secrets.services.pgboss && !dbPasswordEntries.some(([key]) => key === "PGBOSS_DB_PASSWORD")) dbPasswordEntries.push(["PGBOSS_DB_PASSWORD", secrets.services.pgboss.password]);
|
|
235
|
+
if (dbPasswordEntries.length === 0) return;
|
|
236
|
+
const envContent = `# Auto-generated docker environment file
|
|
237
|
+
# Contains database passwords for docker-compose postgres init
|
|
238
|
+
# This file is gitignored - do not commit to version control
|
|
239
|
+
${dbPasswordEntries.map(([key, value]) => `${key}=${value}`).join("\n")}
|
|
240
|
+
`;
|
|
241
|
+
const envPath = join(workspaceRoot, "docker", ".env");
|
|
242
|
+
await mkdir(dirname(envPath), { recursive: true });
|
|
243
|
+
await writeFile(envPath, envContent);
|
|
244
|
+
}
|
|
245
|
+
//#endregion
|
|
246
|
+
//#region src/secrets/reconcile.ts
|
|
247
|
+
var reconcile_exports = /* @__PURE__ */ __exportAll({ reconcileMissingSecrets: () => reconcileMissingSecrets });
|
|
248
|
+
/**
|
|
249
|
+
* Reconcile missing custom secrets for a workspace.
|
|
250
|
+
*
|
|
251
|
+
* Compares current secrets against what generateFullstackCustomSecrets()
|
|
252
|
+
* would produce and backfills any missing keys without overwriting
|
|
253
|
+
* existing values.
|
|
254
|
+
*
|
|
255
|
+
* @returns ReconcileResult if keys were added, null if secrets are up-to-date
|
|
256
|
+
*/
|
|
257
|
+
function reconcileMissingSecrets(secrets, workspace, containers = []) {
|
|
258
|
+
if (!(Object.keys(workspace.apps).length > 1)) return null;
|
|
259
|
+
const expectedCustom = generateFullstackCustomSecrets(workspace, containers);
|
|
260
|
+
const addedKeys = [];
|
|
261
|
+
const mergedCustom = { ...secrets.custom };
|
|
262
|
+
for (const [key, value] of Object.entries(expectedCustom)) if (!(key in mergedCustom)) {
|
|
263
|
+
mergedCustom[key] = value;
|
|
264
|
+
addedKeys.push(key);
|
|
265
|
+
}
|
|
266
|
+
if (addedKeys.length === 0) return null;
|
|
267
|
+
return {
|
|
268
|
+
secrets: {
|
|
269
|
+
...secrets,
|
|
270
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
271
|
+
custom: mergedCustom
|
|
272
|
+
},
|
|
273
|
+
addedKeys
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
//#endregion
|
|
277
|
+
export { createStageSecrets as a, generateServiceCredentials as c, writeDockerEnvFromSecrets as i, rotateServicePassword as l, reconcile_exports as n, generateConnectionUrls as o, generateFullstackCustomSecrets as r, generateSecurePassword as s, reconcileMissingSecrets as t };
|
|
278
|
+
|
|
279
|
+
//# sourceMappingURL=reconcile-yNq6Shau.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reconcile-yNq6Shau.mjs","names":[],"sources":["../src/secrets/generator.ts","../src/setup/fullstack-secrets.ts","../src/secrets/reconcile.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport type {\n\tSecretServiceName,\n\tServiceCredentials,\n\tStageSecrets,\n} from './types';\n\n/**\n * Generate a secure random password using URL-safe base64 characters.\n * @param length Password length (default: 32)\n */\nexport function generateSecurePassword(length = 32): string {\n\treturn randomBytes(Math.ceil((length * 3) / 4))\n\t\t.toString('base64url')\n\t\t.slice(0, length);\n}\n\n/** Default service configurations (localhost for local dev via Docker port mapping) */\nconst SERVICE_DEFAULTS: Record<\n\tSecretServiceName,\n\tOmit<ServiceCredentials, 'password'>\n> = {\n\tpostgres: {\n\t\thost: 'localhost',\n\t\tport: 5432,\n\t\tusername: 'app',\n\t\tdatabase: 'app',\n\t},\n\tredis: {\n\t\thost: 'localhost',\n\t\tport: 6379,\n\t\tusername: 'default',\n\t},\n\tminio: {\n\t\thost: 'localhost',\n\t\tport: 9000,\n\t\tusername: 'app',\n\t\tbucket: 'app',\n\t},\n\tmailpit: {\n\t\thost: 'localhost',\n\t\tport: 1025,\n\t\tusername: 'app',\n\t},\n\tlocalstack: {\n\t\thost: 'localhost',\n\t\tport: 4566,\n\t\tusername: 'localstack',\n\t\tregion: 'us-east-1',\n\t},\n};\n\n/** Default credentials for pgboss (not a Docker service, reuses postgres) */\nconst PGBOSS_DEFAULTS: Omit<ServiceCredentials, 'password'> = {\n\thost: 'localhost',\n\tport: 5432,\n\tusername: 'pgboss',\n\tdatabase: 'app',\n};\n\n/**\n * Generate credentials for a specific service.\n */\nexport function generateServiceCredentials(\n\tservice: SecretServiceName,\n): ServiceCredentials {\n\tconst defaults = SERVICE_DEFAULTS[service];\n\treturn {\n\t\t...defaults,\n\t\tpassword: generateSecurePassword(),\n\t};\n}\n\n/**\n * Generate credentials for multiple services.\n */\nexport function generateServicesCredentials(\n\tservices: SecretServiceName[],\n): StageSecrets['services'] {\n\tconst result: StageSecrets['services'] = {};\n\n\tfor (const service of services) {\n\t\tresult[service] = generateServiceCredentials(service);\n\t}\n\n\treturn result;\n}\n\n/**\n * Generate connection URL for PostgreSQL.\n */\nexport function generatePostgresUrl(creds: ServiceCredentials): string {\n\tconst { username, password, host, port, database } = creds;\n\treturn `postgresql://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}`;\n}\n\n/**\n * Generate connection URL for Redis.\n */\nexport function generateRedisUrl(creds: ServiceCredentials): string {\n\tconst { password, host, port } = creds;\n\treturn `redis://:${encodeURIComponent(password)}@${host}:${port}`;\n}\n\n/**\n * Generate endpoint URL for MinIO (S3-compatible).\n */\nexport function generateMinioEndpoint(creds: ServiceCredentials): string {\n\tconst { host, port } = creds;\n\treturn `http://${host}:${port}`;\n}\n\n/**\n * Generate a LocalStack-compatible access key ID.\n * Must start with 'LSIA' prefix and be at least 20 characters.\n * @see https://docs.localstack.cloud/aws/capabilities/config/credentials/\n */\nexport function generateLocalStackAccessKeyId(): string {\n\tconst suffix = randomBytes(12).toString('base64url').slice(0, 16);\n\treturn `LSIA${suffix}`;\n}\n\n/**\n * Generate connection URL for pg-boss (uses PostgreSQL protocol).\n * Format: pgboss://user:pass@host:port/db?schema=pgboss\n */\nexport function generatePgBossUrl(creds: ServiceCredentials): string {\n\tconst { username, password, host, port, database } = creds;\n\treturn `pgboss://${username}:${encodeURIComponent(password)}@${host}:${port}/${database}?schema=pgboss`;\n}\n\n/**\n * Generate connection URLs from service credentials.\n */\nexport function generateConnectionUrls(\n\tservices: StageSecrets['services'],\n): StageSecrets['urls'] {\n\tconst urls: StageSecrets['urls'] = {};\n\n\tif (services.postgres) {\n\t\turls.DATABASE_URL = generatePostgresUrl(services.postgres);\n\t}\n\n\tif (services.redis) {\n\t\turls.REDIS_URL = generateRedisUrl(services.redis);\n\t}\n\n\tif (services.minio) {\n\t\turls.STORAGE_ENDPOINT = generateMinioEndpoint(services.minio);\n\t}\n\n\t// A topic's or queue's own broker URL is reconcile's, derived from the\n\t// declaration. This pair is what a worker's crons schedule through.\n\tif (services.pgboss) {\n\t\tconst pgbossUrl = generatePgBossUrl(services.pgboss);\n\t\turls.EVENT_PUBLISHER_CONNECTION_STRING = pgbossUrl;\n\t\turls.EVENT_SUBSCRIBER_CONNECTION_STRING = pgbossUrl;\n\t}\n\n\tif (services.mailpit) {\n\t\turls.SMTP_HOST = services.mailpit.host;\n\t\turls.SMTP_PORT = String(services.mailpit.port);\n\t}\n\n\treturn urls;\n}\n\n/**\n * Generate LocalStack service credentials with LSIA-prefixed access key.\n */\nexport function generateLocalStackCredentials(): ServiceCredentials {\n\tconst defaults = SERVICE_DEFAULTS.localstack;\n\treturn {\n\t\t...defaults,\n\t\tpassword: generateSecurePassword(),\n\t\taccessKeyId: generateLocalStackAccessKeyId(),\n\t};\n}\n\n/**\n * Create a new StageSecrets object with generated credentials.\n * @param stage - The deployment stage (e.g., 'development', 'production')\n * @param services - List of services to generate credentials for\n * @param options - Optional configuration\n * @param options.projectName - Project name used to derive the database name (e.g., 'myapp' → 'myapp_dev')\n */\nexport function createStageSecrets(\n\tstage: string,\n\tservices: SecretServiceName[],\n\toptions?: { projectName?: string },\n): StageSecrets {\n\tconst now = new Date().toISOString();\n\tconst serviceCredentials = generateServicesCredentials(services);\n\n\t// Override service defaults with project-derived names if provided\n\tif (options?.projectName) {\n\t\tif (serviceCredentials.postgres) {\n\t\t\tserviceCredentials.postgres.database = `${options.projectName.replace(/-/g, '_')}_dev`;\n\t\t}\n\t\tif (serviceCredentials.minio) {\n\t\t\tserviceCredentials.minio.bucket = options.projectName;\n\t\t\tserviceCredentials.minio.username = options.projectName;\n\t\t}\n\t}\n\n\t// Always create pgboss credentials when postgres is available.\n\t// pgboss reuses postgres host/port/database but with a dedicated user,\n\t// so subscribers work out of the box without explicit events config.\n\tif (serviceCredentials.postgres && !serviceCredentials.pgboss) {\n\t\tserviceCredentials.pgboss = {\n\t\t\t...PGBOSS_DEFAULTS,\n\t\t\tpassword: generateSecurePassword(),\n\t\t\thost: serviceCredentials.postgres.host,\n\t\t\tport: serviceCredentials.postgres.port,\n\t\t\tdatabase: serviceCredentials.postgres.database,\n\t\t};\n\t}\n\n\tconst urls = generateConnectionUrls(serviceCredentials);\n\n\treturn {\n\t\tstage,\n\t\tcreatedAt: now,\n\t\tupdatedAt: now,\n\t\tservices: serviceCredentials,\n\t\turls,\n\t\tcustom: {},\n\t};\n}\n\n/**\n * Rotate password for a specific service.\n */\nexport function rotateServicePassword(\n\tsecrets: StageSecrets,\n\tservice: SecretServiceName,\n): StageSecrets {\n\tconst currentCreds = secrets.services[service];\n\tif (!currentCreds) {\n\t\tthrow new Error(`Service \"${service}\" not configured in secrets`);\n\t}\n\n\tconst newCreds: ServiceCredentials = {\n\t\t...currentCreds,\n\t\tpassword: generateSecurePassword(),\n\t};\n\n\tconst newServices = {\n\t\t...secrets.services,\n\t\t[service]: newCreds,\n\t};\n\n\treturn {\n\t\t...secrets,\n\t\tupdatedAt: new Date().toISOString(),\n\t\tservices: newServices,\n\t\turls: generateConnectionUrls(newServices),\n\t};\n}\n","import { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, join } from 'node:path';\nimport { generateSecurePassword } from '../secrets/generator.js';\nimport type { StageSecrets } from '../secrets/types.js';\nimport type { NormalizedWorkspace } from '../workspace/types.js';\n\n/**\n * Generate a secure random password for database users.\n * Uses a combination of timestamp and random bytes for uniqueness.\n */\nexport function generateDbPassword(): string {\n\treturn `${Date.now().toString(36)}${Math.random().toString(36).slice(2)}${Math.random().toString(36).slice(2)}`;\n}\n\n/**\n * Generate database URL for an app.\n * All apps connect to the same database, but use different users/schemas.\n */\nexport function generateDbUrl(\n\tappName: string,\n\tpassword: string,\n\tprojectName: string,\n\thost = 'localhost',\n\tport = 5432,\n): string {\n\tconst userName = appName.replace(/-/g, '_');\n\tconst dbName = `${projectName.replace(/-/g, '_')}_dev`;\n\treturn `postgresql://${userName}:${password}@${host}:${port}/${dbName}`;\n}\n\n/**\n * Generate fullstack-aware custom secrets for a workspace.\n *\n * Generates:\n * - Common secrets: PORT, LOG_LEVEL, JWT_SECRET (no `NODE_ENV` — the command\n * decides that; see `gkm init`)\n * - Per-app database passwords and URLs for backend apps with db service\n * - Better-auth secrets for apps using the better-auth framework\n */\nexport function generateFullstackCustomSecrets(\n\tworkspace: NormalizedWorkspace,\n\tcontainers: readonly string[] = [],\n): Record<string, string> {\n\t// A Postgres exists because something declared a database, not because\n\t// config carried a flag saying so.\n\tconst hasDb = containers.includes('postgres');\n\tconst customs: Record<string, string> = {\n\t\tPORT: '3000',\n\t\tLOG_LEVEL: 'debug',\n\t\tJWT_SECRET: `dev-${Date.now()}-${Math.random().toString(36).slice(2)}`,\n\t};\n\n\tif (!hasDb) {\n\t\treturn customs;\n\t}\n\n\t// Collect all frontend ports for trusted origins\n\tconst frontendPorts: number[] = [];\n\n\tfor (const [appName, appConfig] of Object.entries(workspace.apps)) {\n\t\tif (appConfig.type === 'web') {\n\t\t\tfrontendPorts.push(appConfig.port);\n\t\t\tconst upperName = appName.toUpperCase();\n\t\t\tcustoms[`${upperName}_URL`] = `http://localhost:${appConfig.port}`;\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Mobile apps (Expo, etc.) don't run on a host port the backend\n\t\t// cares about for CORS, and don't need DB credentials.\n\t\tif (appConfig.type === 'mobile') {\n\t\t\tcontinue;\n\t\t}\n\n\t\t// Backend apps with database: generate per-app DB passwords and URLs\n\t\tconst password = generateDbPassword();\n\t\tconst upperName = appName.toUpperCase();\n\n\t\tcustoms[`${upperName}_DATABASE_URL`] = generateDbUrl(\n\t\t\tappName,\n\t\t\tpassword,\n\t\t\tworkspace.name,\n\t\t);\n\t\tcustoms[`${upperName}_DB_PASSWORD`] = password;\n\n\t\t// Better-auth framework secrets\n\t\tif (appConfig.framework === 'better-auth') {\n\t\t\tcustoms.AUTH_PORT = String(appConfig.port);\n\t\t\tcustoms.AUTH_URL = `http://localhost:${appConfig.port}`;\n\t\t\tcustoms.BETTER_AUTH_SECRET = `better-auth-${Date.now()}-${generateSecurePassword(16)}`;\n\t\t\tcustoms.BETTER_AUTH_URL = `http://localhost:${appConfig.port}`;\n\t\t}\n\t}\n\n\t// Generate trusted origins for better-auth (all app ports)\n\tif (customs.BETTER_AUTH_SECRET) {\n\t\tconst allPorts = Object.values(workspace.apps).map((a) => a.port);\n\t\tcustoms.BETTER_AUTH_TRUSTED_ORIGINS = allPorts\n\t\t\t.map((p) => `http://localhost:${p}`)\n\t\t\t.join(',');\n\t}\n\n\treturn customs;\n}\n\n/**\n * Extract *_DB_PASSWORD keys from secrets and write docker/.env.\n *\n * The docker/.env file contains database passwords that the PostgreSQL\n * init script reads to create per-app database users.\n */\nexport async function writeDockerEnvFromSecrets(\n\tsecrets: StageSecrets,\n\tworkspaceRoot: string,\n): Promise<void> {\n\tconst dbPasswordEntries = Object.entries(secrets.custom).filter(([key]) =>\n\t\tkey.endsWith('_DB_PASSWORD'),\n\t);\n\n\t// Always include pgboss password when pgboss credentials exist\n\tif (\n\t\tsecrets.services.pgboss &&\n\t\t!dbPasswordEntries.some(([key]) => key === 'PGBOSS_DB_PASSWORD')\n\t) {\n\t\tdbPasswordEntries.push([\n\t\t\t'PGBOSS_DB_PASSWORD',\n\t\t\tsecrets.services.pgboss.password,\n\t\t]);\n\t}\n\n\tif (dbPasswordEntries.length === 0) {\n\t\treturn;\n\t}\n\n\tconst envContent = `# Auto-generated docker environment file\n# Contains database passwords for docker-compose postgres init\n# This file is gitignored - do not commit to version control\n${dbPasswordEntries.map(([key, value]) => `${key}=${value}`).join('\\n')}\n`;\n\n\tconst envPath = join(workspaceRoot, 'docker', '.env');\n\tawait mkdir(dirname(envPath), { recursive: true });\n\tawait writeFile(envPath, envContent);\n}\n","import { generateFullstackCustomSecrets } from '../setup/fullstack-secrets.js';\nimport type { NormalizedWorkspace } from '../workspace/types.js';\nimport type { StageSecrets } from './types.js';\n\nexport interface ReconcileResult {\n\t/** The updated secrets with missing keys backfilled */\n\tsecrets: StageSecrets;\n\t/** Keys that were added */\n\taddedKeys: string[];\n}\n\n/**\n * Reconcile missing custom secrets for a workspace.\n *\n * Compares current secrets against what generateFullstackCustomSecrets()\n * would produce and backfills any missing keys without overwriting\n * existing values.\n *\n * @returns ReconcileResult if keys were added, null if secrets are up-to-date\n */\nexport function reconcileMissingSecrets(\n\tsecrets: StageSecrets,\n\tworkspace: NormalizedWorkspace,\n\tcontainers: readonly string[] = [],\n): ReconcileResult | null {\n\tconst isMultiApp = Object.keys(workspace.apps).length > 1;\n\tif (!isMultiApp) {\n\t\treturn null;\n\t}\n\n\tconst expectedCustom = generateFullstackCustomSecrets(workspace, containers);\n\tconst addedKeys: string[] = [];\n\tconst mergedCustom = { ...secrets.custom };\n\n\tfor (const [key, value] of Object.entries(expectedCustom)) {\n\t\tif (!(key in mergedCustom)) {\n\t\t\tmergedCustom[key] = value;\n\t\t\taddedKeys.push(key);\n\t\t}\n\t}\n\n\tif (addedKeys.length === 0) {\n\t\treturn null;\n\t}\n\n\treturn {\n\t\tsecrets: {\n\t\t\t...secrets,\n\t\t\tupdatedAt: new Date().toISOString(),\n\t\t\tcustom: mergedCustom,\n\t\t},\n\t\taddedKeys,\n\t};\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __exportAll = (all, no_symbols) => {
|
|
5
|
+
let target = {};
|
|
6
|
+
for (var name in all) __defProp(target, name, {
|
|
7
|
+
get: all[name],
|
|
8
|
+
enumerable: true
|
|
9
|
+
});
|
|
10
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
11
|
+
return target;
|
|
12
|
+
};
|
|
13
|
+
var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
|
|
14
|
+
//#endregion
|
|
15
|
+
export { __require as n, __exportAll as t };
|
|
@@ -1,10 +1,19 @@
|
|
|
1
|
-
//#region
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
2
|
var __create = Object.create;
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
4
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
5
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
6
|
var __getProtoOf = Object.getPrototypeOf;
|
|
7
7
|
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __exportAll = (all, no_symbols) => {
|
|
9
|
+
let target = {};
|
|
10
|
+
for (var name in all) __defProp(target, name, {
|
|
11
|
+
get: all[name],
|
|
12
|
+
enumerable: true
|
|
13
|
+
});
|
|
14
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
15
|
+
return target;
|
|
16
|
+
};
|
|
8
17
|
var __copyProps = (to, from, except, desc) => {
|
|
9
18
|
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
10
19
|
key = keys[i];
|
|
@@ -15,16 +24,20 @@ var __copyProps = (to, from, except, desc) => {
|
|
|
15
24
|
}
|
|
16
25
|
return to;
|
|
17
26
|
};
|
|
18
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
27
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
|
|
19
28
|
value: mod,
|
|
20
29
|
enumerable: true
|
|
21
30
|
}) : target, mod));
|
|
22
|
-
|
|
23
31
|
//#endregion
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
32
|
+
Object.defineProperty(exports, "__exportAll", {
|
|
33
|
+
enumerable: true,
|
|
34
|
+
get: function() {
|
|
35
|
+
return __exportAll;
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
Object.defineProperty(exports, "__toESM", {
|
|
39
|
+
enumerable: true,
|
|
40
|
+
get: function() {
|
|
41
|
+
return __toESM;
|
|
42
|
+
}
|
|
43
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { GetParameterCommand, ParameterNotFound, PutParameterCommand, SSMClient } from "@aws-sdk/client-ssm";
|
|
2
|
+
//#region src/secrets/ssm.ts
|
|
3
|
+
/**
|
|
4
|
+
* A stage's secrets in SSM Parameter Store, in the stage's own AWS account.
|
|
5
|
+
*
|
|
6
|
+
* One parameter per stage, `/gkm/<project>/<stage>/secrets`, a SecureString —
|
|
7
|
+
* encrypted at rest by KMS, so nothing about it needs a key file anywhere.
|
|
8
|
+
* Which account it is in is whichever credentials are active: the stage's
|
|
9
|
+
* OIDC role in a deploy job, or `--profile` on a developer's machine. With
|
|
10
|
+
* staging and production in different accounts, each stage's secrets sit
|
|
11
|
+
* beside its infrastructure, and a staging role cannot read production's.
|
|
12
|
+
*/
|
|
13
|
+
/** The parameter a stage's secrets are kept in. */
|
|
14
|
+
function secretsParameterName(project, stage) {
|
|
15
|
+
return `/gkm/${project}/${stage}/secrets`;
|
|
16
|
+
}
|
|
17
|
+
var SsmSecretsStore = class {
|
|
18
|
+
options;
|
|
19
|
+
client;
|
|
20
|
+
constructor(options) {
|
|
21
|
+
this.options = options;
|
|
22
|
+
}
|
|
23
|
+
async ssm() {
|
|
24
|
+
if (this.client) return this.client;
|
|
25
|
+
const config = { region: this.options.region };
|
|
26
|
+
if (this.options.profile) {
|
|
27
|
+
const { fromIni } = await import("@aws-sdk/credential-providers");
|
|
28
|
+
config.credentials = fromIni({ profile: this.options.profile });
|
|
29
|
+
}
|
|
30
|
+
if (this.options.endpoint) config.endpoint = this.options.endpoint;
|
|
31
|
+
this.client = new SSMClient(config);
|
|
32
|
+
return this.client;
|
|
33
|
+
}
|
|
34
|
+
async pull(stage) {
|
|
35
|
+
const ssm = await this.ssm();
|
|
36
|
+
try {
|
|
37
|
+
const { Parameter } = await ssm.send(new GetParameterCommand({
|
|
38
|
+
Name: secretsParameterName(this.options.project, stage),
|
|
39
|
+
WithDecryption: true
|
|
40
|
+
}));
|
|
41
|
+
return Parameter?.Value ? JSON.parse(Parameter.Value) : null;
|
|
42
|
+
} catch (error) {
|
|
43
|
+
if (error instanceof ParameterNotFound) return null;
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async push(stage, secrets) {
|
|
48
|
+
await (await this.ssm()).send(new PutParameterCommand({
|
|
49
|
+
Name: secretsParameterName(this.options.project, stage),
|
|
50
|
+
Value: JSON.stringify(secrets),
|
|
51
|
+
Type: "SecureString",
|
|
52
|
+
Overwrite: true,
|
|
53
|
+
Description: `gkm secrets for ${this.options.project}/${stage}`
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
//#endregion
|
|
58
|
+
export { SsmSecretsStore };
|
|
59
|
+
|
|
60
|
+
//# sourceMappingURL=ssm-BKN1Cg4z.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssm-BKN1Cg4z.mjs","names":[],"sources":["../src/secrets/ssm.ts"],"sourcesContent":["/**\n * A stage's secrets in SSM Parameter Store, in the stage's own AWS account.\n *\n * One parameter per stage, `/gkm/<project>/<stage>/secrets`, a SecureString —\n * encrypted at rest by KMS, so nothing about it needs a key file anywhere.\n * Which account it is in is whichever credentials are active: the stage's\n * OIDC role in a deploy job, or `--profile` on a developer's machine. With\n * staging and production in different accounts, each stage's secrets sit\n * beside its infrastructure, and a staging role cannot read production's.\n */\n\nimport {\n\tGetParameterCommand,\n\tParameterNotFound,\n\tPutParameterCommand,\n\tSSMClient,\n\ttype SSMClientConfig,\n} from '@aws-sdk/client-ssm';\nimport type { SecretsStore } from './store.js';\nimport type { StageSecrets } from './types.js';\n\nexport interface SsmSecretsStoreOptions {\n\t/** The workspace name, which scopes the parameter path. */\n\tproject: string;\n\tregion: string;\n\t/**\n\t * A named AWS profile, resolved with `fromIni` only. The default chain\n\t * reads AWS_* from the environment first, so exported staging keys would\n\t * win over `--profile prod` and put production's secrets in staging.\n\t */\n\tprofile?: string;\n\t/** For tests: an endpoint such as the AWS emulator. */\n\tendpoint?: string;\n}\n\n/** The parameter a stage's secrets are kept in. */\nexport function secretsParameterName(project: string, stage: string): string {\n\treturn `/gkm/${project}/${stage}/secrets`;\n}\n\nexport class SsmSecretsStore implements SecretsStore {\n\tprivate client?: SSMClient;\n\n\tconstructor(private readonly options: SsmSecretsStoreOptions) {}\n\n\tprivate async ssm(): Promise<SSMClient> {\n\t\tif (this.client) return this.client;\n\n\t\tconst config: SSMClientConfig = { region: this.options.region };\n\t\tif (this.options.profile) {\n\t\t\tconst { fromIni } = await import('@aws-sdk/credential-providers');\n\t\t\tconfig.credentials = fromIni({ profile: this.options.profile });\n\t\t}\n\t\tif (this.options.endpoint) {\n\t\t\tconfig.endpoint = this.options.endpoint;\n\t\t}\n\n\t\tthis.client = new SSMClient(config);\n\t\treturn this.client;\n\t}\n\n\tasync pull(stage: string): Promise<StageSecrets | null> {\n\t\tconst ssm = await this.ssm();\n\n\t\ttry {\n\t\t\tconst { Parameter } = await ssm.send(\n\t\t\t\tnew GetParameterCommand({\n\t\t\t\t\tName: secretsParameterName(this.options.project, stage),\n\t\t\t\t\tWithDecryption: true,\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn Parameter?.Value\n\t\t\t\t? (JSON.parse(Parameter.Value) as StageSecrets)\n\t\t\t\t: null;\n\t\t} catch (error) {\n\t\t\tif (error instanceof ParameterNotFound) return null;\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tasync push(stage: string, secrets: StageSecrets): Promise<void> {\n\t\tconst ssm = await this.ssm();\n\n\t\tawait ssm.send(\n\t\t\tnew PutParameterCommand({\n\t\t\t\tName: secretsParameterName(this.options.project, stage),\n\t\t\t\tValue: JSON.stringify(secrets),\n\t\t\t\tType: 'SecureString',\n\t\t\t\tOverwrite: true,\n\t\t\t\tDescription: `gkm secrets for ${this.options.project}/${stage}`,\n\t\t\t}),\n\t\t);\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;AAoCA,SAAgB,qBAAqB,SAAiB,OAAuB;CAC5E,OAAO,QAAQ,QAAQ,GAAG,MAAM;AACjC;AAEA,IAAa,kBAAb,MAAqD;CAGvB;CAF7B;CAEA,YAAY,SAAkD;EAAjC,KAAA,UAAA;CAAkC;CAE/D,MAAc,MAA0B;EACvC,IAAI,KAAK,QAAQ,OAAO,KAAK;EAE7B,MAAM,SAA0B,EAAE,QAAQ,KAAK,QAAQ,OAAO;EAC9D,IAAI,KAAK,QAAQ,SAAS;GACzB,MAAM,EAAE,YAAY,MAAM,OAAO;GACjC,OAAO,cAAc,QAAQ,EAAE,SAAS,KAAK,QAAQ,QAAQ,CAAC;EAC/D;EACA,IAAI,KAAK,QAAQ,UAChB,OAAO,WAAW,KAAK,QAAQ;EAGhC,KAAK,SAAS,IAAI,UAAU,MAAM;EAClC,OAAO,KAAK;CACb;CAEA,MAAM,KAAK,OAA6C;EACvD,MAAM,MAAM,MAAM,KAAK,IAAI;EAE3B,IAAI;GACH,MAAM,EAAE,cAAc,MAAM,IAAI,KAC/B,IAAI,oBAAoB;IACvB,MAAM,qBAAqB,KAAK,QAAQ,SAAS,KAAK;IACtD,gBAAgB;GACjB,CAAC,CACF;GACA,OAAO,WAAW,QACd,KAAK,MAAM,UAAU,KAAK,IAC3B;EACJ,SAAS,OAAO;GACf,IAAI,iBAAiB,mBAAmB,OAAO;GAC/C,MAAM;EACP;CACD;CAEA,MAAM,KAAK,OAAe,SAAsC;EAG/D,OAAM,MAFY,KAAK,IAAI,EAAA,CAEjB,KACT,IAAI,oBAAoB;GACvB,MAAM,qBAAqB,KAAK,QAAQ,SAAS,KAAK;GACtD,OAAO,KAAK,UAAU,OAAO;GAC7B,MAAM;GACN,WAAW;GACX,aAAa,mBAAmB,KAAK,QAAQ,QAAQ,GAAG;EACzD,CAAC,CACF;CACD;AACD"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
let _aws_sdk_client_ssm = require("@aws-sdk/client-ssm");
|
|
2
|
+
//#region src/secrets/ssm.ts
|
|
3
|
+
/**
|
|
4
|
+
* A stage's secrets in SSM Parameter Store, in the stage's own AWS account.
|
|
5
|
+
*
|
|
6
|
+
* One parameter per stage, `/gkm/<project>/<stage>/secrets`, a SecureString —
|
|
7
|
+
* encrypted at rest by KMS, so nothing about it needs a key file anywhere.
|
|
8
|
+
* Which account it is in is whichever credentials are active: the stage's
|
|
9
|
+
* OIDC role in a deploy job, or `--profile` on a developer's machine. With
|
|
10
|
+
* staging and production in different accounts, each stage's secrets sit
|
|
11
|
+
* beside its infrastructure, and a staging role cannot read production's.
|
|
12
|
+
*/
|
|
13
|
+
/** The parameter a stage's secrets are kept in. */
|
|
14
|
+
function secretsParameterName(project, stage) {
|
|
15
|
+
return `/gkm/${project}/${stage}/secrets`;
|
|
16
|
+
}
|
|
17
|
+
var SsmSecretsStore = class {
|
|
18
|
+
options;
|
|
19
|
+
client;
|
|
20
|
+
constructor(options) {
|
|
21
|
+
this.options = options;
|
|
22
|
+
}
|
|
23
|
+
async ssm() {
|
|
24
|
+
if (this.client) return this.client;
|
|
25
|
+
const config = { region: this.options.region };
|
|
26
|
+
if (this.options.profile) {
|
|
27
|
+
const { fromIni } = await import("@aws-sdk/credential-providers");
|
|
28
|
+
config.credentials = fromIni({ profile: this.options.profile });
|
|
29
|
+
}
|
|
30
|
+
if (this.options.endpoint) config.endpoint = this.options.endpoint;
|
|
31
|
+
this.client = new _aws_sdk_client_ssm.SSMClient(config);
|
|
32
|
+
return this.client;
|
|
33
|
+
}
|
|
34
|
+
async pull(stage) {
|
|
35
|
+
const ssm = await this.ssm();
|
|
36
|
+
try {
|
|
37
|
+
const { Parameter } = await ssm.send(new _aws_sdk_client_ssm.GetParameterCommand({
|
|
38
|
+
Name: secretsParameterName(this.options.project, stage),
|
|
39
|
+
WithDecryption: true
|
|
40
|
+
}));
|
|
41
|
+
return Parameter?.Value ? JSON.parse(Parameter.Value) : null;
|
|
42
|
+
} catch (error) {
|
|
43
|
+
if (error instanceof _aws_sdk_client_ssm.ParameterNotFound) return null;
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
async push(stage, secrets) {
|
|
48
|
+
await (await this.ssm()).send(new _aws_sdk_client_ssm.PutParameterCommand({
|
|
49
|
+
Name: secretsParameterName(this.options.project, stage),
|
|
50
|
+
Value: JSON.stringify(secrets),
|
|
51
|
+
Type: "SecureString",
|
|
52
|
+
Overwrite: true,
|
|
53
|
+
Description: `gkm secrets for ${this.options.project}/${stage}`
|
|
54
|
+
}));
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
//#endregion
|
|
58
|
+
exports.SsmSecretsStore = SsmSecretsStore;
|
|
59
|
+
|
|
60
|
+
//# sourceMappingURL=ssm-FxeSAt35.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssm-FxeSAt35.cjs","names":["SSMClient","GetParameterCommand","ParameterNotFound","PutParameterCommand"],"sources":["../src/secrets/ssm.ts"],"sourcesContent":["/**\n * A stage's secrets in SSM Parameter Store, in the stage's own AWS account.\n *\n * One parameter per stage, `/gkm/<project>/<stage>/secrets`, a SecureString —\n * encrypted at rest by KMS, so nothing about it needs a key file anywhere.\n * Which account it is in is whichever credentials are active: the stage's\n * OIDC role in a deploy job, or `--profile` on a developer's machine. With\n * staging and production in different accounts, each stage's secrets sit\n * beside its infrastructure, and a staging role cannot read production's.\n */\n\nimport {\n\tGetParameterCommand,\n\tParameterNotFound,\n\tPutParameterCommand,\n\tSSMClient,\n\ttype SSMClientConfig,\n} from '@aws-sdk/client-ssm';\nimport type { SecretsStore } from './store.js';\nimport type { StageSecrets } from './types.js';\n\nexport interface SsmSecretsStoreOptions {\n\t/** The workspace name, which scopes the parameter path. */\n\tproject: string;\n\tregion: string;\n\t/**\n\t * A named AWS profile, resolved with `fromIni` only. The default chain\n\t * reads AWS_* from the environment first, so exported staging keys would\n\t * win over `--profile prod` and put production's secrets in staging.\n\t */\n\tprofile?: string;\n\t/** For tests: an endpoint such as the AWS emulator. */\n\tendpoint?: string;\n}\n\n/** The parameter a stage's secrets are kept in. */\nexport function secretsParameterName(project: string, stage: string): string {\n\treturn `/gkm/${project}/${stage}/secrets`;\n}\n\nexport class SsmSecretsStore implements SecretsStore {\n\tprivate client?: SSMClient;\n\n\tconstructor(private readonly options: SsmSecretsStoreOptions) {}\n\n\tprivate async ssm(): Promise<SSMClient> {\n\t\tif (this.client) return this.client;\n\n\t\tconst config: SSMClientConfig = { region: this.options.region };\n\t\tif (this.options.profile) {\n\t\t\tconst { fromIni } = await import('@aws-sdk/credential-providers');\n\t\t\tconfig.credentials = fromIni({ profile: this.options.profile });\n\t\t}\n\t\tif (this.options.endpoint) {\n\t\t\tconfig.endpoint = this.options.endpoint;\n\t\t}\n\n\t\tthis.client = new SSMClient(config);\n\t\treturn this.client;\n\t}\n\n\tasync pull(stage: string): Promise<StageSecrets | null> {\n\t\tconst ssm = await this.ssm();\n\n\t\ttry {\n\t\t\tconst { Parameter } = await ssm.send(\n\t\t\t\tnew GetParameterCommand({\n\t\t\t\t\tName: secretsParameterName(this.options.project, stage),\n\t\t\t\t\tWithDecryption: true,\n\t\t\t\t}),\n\t\t\t);\n\t\t\treturn Parameter?.Value\n\t\t\t\t? (JSON.parse(Parameter.Value) as StageSecrets)\n\t\t\t\t: null;\n\t\t} catch (error) {\n\t\t\tif (error instanceof ParameterNotFound) return null;\n\t\t\tthrow error;\n\t\t}\n\t}\n\n\tasync push(stage: string, secrets: StageSecrets): Promise<void> {\n\t\tconst ssm = await this.ssm();\n\n\t\tawait ssm.send(\n\t\t\tnew PutParameterCommand({\n\t\t\t\tName: secretsParameterName(this.options.project, stage),\n\t\t\t\tValue: JSON.stringify(secrets),\n\t\t\t\tType: 'SecureString',\n\t\t\t\tOverwrite: true,\n\t\t\t\tDescription: `gkm secrets for ${this.options.project}/${stage}`,\n\t\t\t}),\n\t\t);\n\t}\n}\n"],"mappings":";;;;;;;;;;;;;AAoCA,SAAgB,qBAAqB,SAAiB,OAAuB;CAC5E,OAAO,QAAQ,QAAQ,GAAG,MAAM;AACjC;AAEA,IAAa,kBAAb,MAAqD;CAGvB;CAF7B;CAEA,YAAY,SAAkD;EAAjC,KAAA,UAAA;CAAkC;CAE/D,MAAc,MAA0B;EACvC,IAAI,KAAK,QAAQ,OAAO,KAAK;EAE7B,MAAM,SAA0B,EAAE,QAAQ,KAAK,QAAQ,OAAO;EAC9D,IAAI,KAAK,QAAQ,SAAS;GACzB,MAAM,EAAE,YAAY,MAAM,OAAO;GACjC,OAAO,cAAc,QAAQ,EAAE,SAAS,KAAK,QAAQ,QAAQ,CAAC;EAC/D;EACA,IAAI,KAAK,QAAQ,UAChB,OAAO,WAAW,KAAK,QAAQ;EAGhC,KAAK,SAAS,IAAIA,oBAAAA,UAAU,MAAM;EAClC,OAAO,KAAK;CACb;CAEA,MAAM,KAAK,OAA6C;EACvD,MAAM,MAAM,MAAM,KAAK,IAAI;EAE3B,IAAI;GACH,MAAM,EAAE,cAAc,MAAM,IAAI,KAC/B,IAAIC,oBAAAA,oBAAoB;IACvB,MAAM,qBAAqB,KAAK,QAAQ,SAAS,KAAK;IACtD,gBAAgB;GACjB,CAAC,CACF;GACA,OAAO,WAAW,QACd,KAAK,MAAM,UAAU,KAAK,IAC3B;EACJ,SAAS,OAAO;GACf,IAAI,iBAAiBC,oBAAAA,mBAAmB,OAAO;GAC/C,MAAM;EACP;CACD;CAEA,MAAM,KAAK,OAAe,SAAsC;EAG/D,OAAM,MAFY,KAAK,IAAI,EAAA,CAEjB,KACT,IAAIC,oBAAAA,oBAAoB;GACvB,MAAM,qBAAqB,KAAK,QAAQ,SAAS,KAAK;GACtD,OAAO,KAAK,UAAU,OAAO;GAC7B,MAAM;GACN,WAAW;GACX,aAAa,mBAAmB,KAAK,QAAQ,QAAQ,GAAG;EACzD,CAAC,CACF;CACD;AACD"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
import { t as __exportAll } from "./rolldown-runtime-BhDjJH2R.mjs";
|
|
1
2
|
import { existsSync } from "node:fs";
|
|
2
|
-
import { basename, join } from "node:path";
|
|
3
3
|
import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
|
+
import { basename, join } from "node:path";
|
|
5
6
|
import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
|
|
6
|
-
|
|
7
7
|
//#region src/secrets/keystore.ts
|
|
8
8
|
/** Key length for AES-256 encryption */
|
|
9
9
|
const KEY_LENGTH = 32;
|
|
@@ -61,8 +61,7 @@ async function generateKey(stage, projectName) {
|
|
|
61
61
|
async function readKey(stage, projectName) {
|
|
62
62
|
const keyPath = getKeyPath(stage, projectName);
|
|
63
63
|
if (!existsSync(keyPath)) return null;
|
|
64
|
-
|
|
65
|
-
return key.trim();
|
|
64
|
+
return (await readFile(keyPath, "utf-8")).trim();
|
|
66
65
|
}
|
|
67
66
|
/**
|
|
68
67
|
* Get or create a key for a stage.
|
|
@@ -77,15 +76,25 @@ async function getOrCreateKey(stage, projectName) {
|
|
|
77
76
|
if (existingKey) return existingKey;
|
|
78
77
|
return generateKey(stage, projectName);
|
|
79
78
|
}
|
|
80
|
-
|
|
81
79
|
//#endregion
|
|
82
80
|
//#region src/secrets/storage.ts
|
|
81
|
+
var storage_exports = /* @__PURE__ */ __exportAll({
|
|
82
|
+
getSecretsDir: () => getSecretsDir,
|
|
83
|
+
getSecretsPath: () => getSecretsPath,
|
|
84
|
+
initStageSecrets: () => initStageSecrets,
|
|
85
|
+
maskPassword: () => maskPassword,
|
|
86
|
+
readStageSecrets: () => readStageSecrets,
|
|
87
|
+
secretsExist: () => secretsExist,
|
|
88
|
+
setCustomSecret: () => setCustomSecret,
|
|
89
|
+
toEmbeddableSecrets: () => toEmbeddableSecrets,
|
|
90
|
+
validateEnvironmentVariables: () => validateEnvironmentVariables,
|
|
91
|
+
writeStageSecrets: () => writeStageSecrets
|
|
92
|
+
});
|
|
83
93
|
/** Default secrets directory relative to project root */
|
|
84
94
|
const SECRETS_DIR = ".gkm/secrets";
|
|
85
95
|
/** AES-256-GCM configuration */
|
|
86
96
|
const ALGORITHM = "aes-256-gcm";
|
|
87
97
|
const IV_LENGTH = 12;
|
|
88
|
-
const AUTH_TAG_LENGTH = 16;
|
|
89
98
|
/**
|
|
90
99
|
* Get the secrets directory path.
|
|
91
100
|
*/
|
|
@@ -128,10 +137,9 @@ function encryptSecretsData(secrets, keyHex) {
|
|
|
128
137
|
const cipher = createCipheriv(ALGORITHM, key, iv);
|
|
129
138
|
const ciphertext = Buffer.concat([cipher.update(plaintext, "utf-8"), cipher.final()]);
|
|
130
139
|
const authTag = cipher.getAuthTag();
|
|
131
|
-
const combined = Buffer.concat([ciphertext, authTag]);
|
|
132
140
|
return {
|
|
133
141
|
version: 1,
|
|
134
|
-
encrypted:
|
|
142
|
+
encrypted: Buffer.concat([ciphertext, authTag]).toString("base64"),
|
|
135
143
|
iv: iv.toString("hex")
|
|
136
144
|
};
|
|
137
145
|
}
|
|
@@ -142,8 +150,8 @@ function decryptSecretsData(data, keyHex) {
|
|
|
142
150
|
const key = Buffer.from(keyHex, "hex");
|
|
143
151
|
const ivBuffer = Buffer.from(data.iv, "hex");
|
|
144
152
|
const combined = Buffer.from(data.encrypted, "base64");
|
|
145
|
-
const ciphertext = combined.subarray(0, -
|
|
146
|
-
const authTag = combined.subarray(-
|
|
153
|
+
const ciphertext = combined.subarray(0, -16);
|
|
154
|
+
const authTag = combined.subarray(-16);
|
|
147
155
|
const decipher = createDecipheriv(ALGORITHM, key, ivBuffer);
|
|
148
156
|
decipher.setAuthTag(authTag);
|
|
149
157
|
const plaintext = Buffer.concat([decipher.update(ciphertext), decipher.final()]);
|
|
@@ -177,8 +185,7 @@ async function writeStageSecrets(secrets, cwd = process.cwd()) {
|
|
|
177
185
|
const path = getSecretsPath(secrets.stage, cwd);
|
|
178
186
|
const projectName = basename(cwd);
|
|
179
187
|
await mkdir(dir, { recursive: true });
|
|
180
|
-
const
|
|
181
|
-
const encrypted = encryptSecretsData(secrets, key);
|
|
188
|
+
const encrypted = encryptSecretsData(secrets, await getOrCreateKey(secrets.stage, projectName));
|
|
182
189
|
await writeFile(path, JSON.stringify(encrypted, null, 2), "utf-8");
|
|
183
190
|
}
|
|
184
191
|
/**
|
|
@@ -201,13 +208,6 @@ function toEmbeddableSecrets(secrets) {
|
|
|
201
208
|
REDIS_HOST: secrets.services.redis.host,
|
|
202
209
|
REDIS_PORT: String(secrets.services.redis.port)
|
|
203
210
|
},
|
|
204
|
-
...secrets.services.rabbitmq && {
|
|
205
|
-
RABBITMQ_USER: secrets.services.rabbitmq.username,
|
|
206
|
-
RABBITMQ_PASSWORD: secrets.services.rabbitmq.password,
|
|
207
|
-
RABBITMQ_HOST: secrets.services.rabbitmq.host,
|
|
208
|
-
RABBITMQ_PORT: String(secrets.services.rabbitmq.port),
|
|
209
|
-
RABBITMQ_VHOST: secrets.services.rabbitmq.vhost ?? "/"
|
|
210
|
-
},
|
|
211
211
|
...secrets.services.minio && {
|
|
212
212
|
STORAGE_ACCESS_KEY_ID: secrets.services.minio.username,
|
|
213
213
|
STORAGE_SECRET_ACCESS_KEY: secrets.services.minio.password,
|
|
@@ -291,7 +291,7 @@ function validateEnvironmentVariables(requiredVars, secrets) {
|
|
|
291
291
|
required: [...requiredVars].sort()
|
|
292
292
|
};
|
|
293
293
|
}
|
|
294
|
-
|
|
295
294
|
//#endregion
|
|
296
|
-
export {
|
|
297
|
-
|
|
295
|
+
export { storage_exports as a, getKeyPath as c, setCustomSecret as i, readStageSecrets as n, toEmbeddableSecrets as o, secretsExist as r, writeStageSecrets as s, maskPassword as t };
|
|
296
|
+
|
|
297
|
+
//# sourceMappingURL=storage-BjTLjDDp.mjs.map
|