@geekmidas/cli 10.0.0-alpha.64 → 10.0.0-alpha.65
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/{HostingerProvider-Bx2lHHvu.cjs → HostingerProvider-Dlmbfass.cjs} +3 -3
- package/dist/{HostingerProvider-Bx2lHHvu.cjs.map → HostingerProvider-Dlmbfass.cjs.map} +1 -1
- package/dist/{HostingerProvider-Cd4aS5Kj.mjs → HostingerProvider-x9gQTAGZ.mjs} +2 -2
- package/dist/{HostingerProvider-Cd4aS5Kj.mjs.map → HostingerProvider-x9gQTAGZ.mjs.map} +1 -1
- package/dist/{LegacyStateStore-C2abV49N.cjs → LegacyStateStore-BGoo__sO.cjs} +2 -2
- package/dist/{LegacyStateStore-C2abV49N.cjs.map → LegacyStateStore-BGoo__sO.cjs.map} +1 -1
- package/dist/{LegacyStateStore-DIgkGaUP.mjs → LegacyStateStore-BUu1nQ5u.mjs} +2 -2
- package/dist/{LegacyStateStore-DIgkGaUP.mjs.map → LegacyStateStore-BUu1nQ5u.mjs.map} +1 -1
- package/dist/{LocalStateStore-DiJqKyPy.cjs → LocalStateStore-93urXyQe.cjs} +2 -2
- package/dist/{LocalStateStore-DiJqKyPy.cjs.map → LocalStateStore-93urXyQe.cjs.map} +1 -1
- package/dist/{LocalStateStore-Cy1ZBW7U.mjs → LocalStateStore-BRhMQRuy.mjs} +2 -2
- package/dist/{LocalStateStore-Cy1ZBW7U.mjs.map → LocalStateStore-BRhMQRuy.mjs.map} +1 -1
- package/dist/{S3StateStore-Ds0V1Lgm.cjs → S3StateStore-F4dxw6kS.cjs} +2 -2
- package/dist/{S3StateStore-Ds0V1Lgm.cjs.map → S3StateStore-F4dxw6kS.cjs.map} +1 -1
- package/dist/{S3StateStore-D3x6ivBL.mjs → S3StateStore-pw1GhWwt.mjs} +2 -2
- package/dist/{S3StateStore-D3x6ivBL.mjs.map → S3StateStore-pw1GhWwt.mjs.map} +1 -1
- package/dist/{SSMStateStore-CLipvx47.cjs → SSMStateStore-Bt9acuAF.cjs} +2 -2
- package/dist/{SSMStateStore-CLipvx47.cjs.map → SSMStateStore-Bt9acuAF.cjs.map} +1 -1
- package/dist/{SSMStateStore-DfF174xQ.mjs → SSMStateStore-Dmj7zzFO.mjs} +2 -2
- package/dist/{SSMStateStore-DfF174xQ.mjs.map → SSMStateStore-Dmj7zzFO.mjs.map} +1 -1
- package/dist/{bundler-C8uJf324.cjs → bundler-D6MlIfBT.cjs} +3 -3
- package/dist/{bundler-C8uJf324.cjs.map → bundler-D6MlIfBT.cjs.map} +1 -1
- package/dist/{bundler-DYgm9xsd.mjs → bundler-D_14JLZF.mjs} +3 -3
- package/dist/{bundler-DYgm9xsd.mjs.map → bundler-D_14JLZF.mjs.map} +1 -1
- package/dist/{cli-D3jofJ6j.cjs → cli-BfH1deAT.cjs} +6 -6
- package/dist/{cli-D3jofJ6j.cjs.map → cli-BfH1deAT.cjs.map} +1 -1
- package/dist/{cli-Bogz-zEj.mjs → cli-P56vvrDQ.mjs} +5 -5
- package/dist/{cli-Bogz-zEj.mjs.map → cli-P56vvrDQ.mjs.map} +1 -1
- package/dist/compose-391oEKj1.cjs +1982 -0
- package/dist/compose-391oEKj1.cjs.map +1 -0
- package/dist/compose-DprE3ASm.mjs +1862 -0
- package/dist/compose-DprE3ASm.mjs.map +1 -0
- package/dist/{config-DqNRiQOp.d.mts → config-BonvTW8p.d.mts} +3 -3
- package/dist/{config-Be-4_I6H.d.cts.map → config-BonvTW8p.d.mts.map} +1 -1
- package/dist/{config-Be-4_I6H.d.cts → config-V_X_BMpm.d.cts} +3 -3
- package/dist/{config-DqNRiQOp.d.mts.map → config-V_X_BMpm.d.cts.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-B70vmLcm.cjs → credentials-Bzbg23n8.cjs} +176 -3
- package/dist/credentials-Bzbg23n8.cjs.map +1 -0
- package/dist/{credentials-s0wMeZBU.mjs → credentials-Dq1MnWQf.mjs} +128 -3
- package/dist/credentials-Dq1MnWQf.mjs.map +1 -0
- package/dist/{define-CYs0PEfZ.mjs → define-DR8xGYfL.mjs} +5 -1
- package/dist/define-DR8xGYfL.mjs.map +1 -0
- package/dist/{define-zUsP72ym.cjs → define-DzRz8FgH.cjs} +5 -1
- package/dist/define-DzRz8FgH.cjs.map +1 -0
- package/dist/deploy/public.cjs +18 -18
- package/dist/deploy/public.cjs.map +1 -1
- package/dist/deploy/public.d.cts +7 -4
- package/dist/deploy/public.d.cts.map +1 -1
- package/dist/deploy/public.d.mts +7 -4
- package/dist/deploy/public.d.mts.map +1 -1
- package/dist/deploy/public.mjs +11 -11
- package/dist/{deploy-Dc2LBJaK.mjs → deploy-9MBG_QLd.mjs} +8 -7
- package/dist/deploy-9MBG_QLd.mjs.map +1 -0
- package/dist/{deploy-yUcR2OlW.cjs → deploy-B80idPUZ.cjs} +11 -10
- package/dist/deploy-B80idPUZ.cjs.map +1 -0
- package/dist/{deploy-ZRXIPp1c.mjs → deploy-CZ5nlpsu.mjs} +13 -119
- package/dist/deploy-CZ5nlpsu.mjs.map +1 -0
- package/dist/{deploy-55rJkdKz.cjs → deploy-Cy8igpQF.cjs} +39 -145
- package/dist/deploy-Cy8igpQF.cjs.map +1 -0
- package/dist/{dokploy-CepdXuMl.mjs → dokploy-BdtBePjp.mjs} +3 -3
- package/dist/{dokploy-CepdXuMl.mjs.map → dokploy-BdtBePjp.mjs.map} +1 -1
- package/dist/{dokploy-D1d37rvM.cjs → dokploy-iFY8MY-L.cjs} +3 -3
- package/dist/{dokploy-D1d37rvM.cjs.map → dokploy-iFY8MY-L.cjs.map} +1 -1
- package/dist/{encryption-C9X98u2F.cjs → encryption-B1wJwgae.cjs} +5 -5
- package/dist/{encryption-C9X98u2F.cjs.map → encryption-B1wJwgae.cjs.map} +1 -1
- package/dist/{encryption-CKHjMpCr.mjs → encryption-DF_hgIpl.mjs} +4 -4
- package/dist/{encryption-CKHjMpCr.mjs.map → encryption-DF_hgIpl.mjs.map} +1 -1
- package/dist/{github-D-byNI7T.mjs → github-B0YYE98x.mjs} +3 -3
- package/dist/{github-D-byNI7T.mjs.map → github-B0YYE98x.mjs.map} +1 -1
- package/dist/{github-Dd3tMa1u.cjs → github-CYcmTdFs.cjs} +3 -3
- package/dist/{github-Dd3tMa1u.cjs.map → github-CYcmTdFs.cjs.map} +1 -1
- package/dist/{index-CfE9Y885.d.cts → index-DPUAGJaK.d.cts} +16 -3
- package/dist/index-DPUAGJaK.d.cts.map +1 -0
- package/dist/{index-CJ2qsSpp.d.mts → index-DwKD_8lt.d.mts} +16 -3
- package/dist/index-DwKD_8lt.d.mts.map +1 -0
- package/dist/index.cjs +233 -1667
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +196 -1614
- package/dist/index.mjs.map +1 -1
- package/dist/{local-C7wnrcYK.cjs → local-BdH4XN0E.cjs} +1 -178
- package/dist/local-BdH4XN0E.cjs.map +1 -0
- package/dist/{local-Cd8ZYQG3.mjs → local-CWR4Y_kW.mjs} +3 -126
- package/dist/local-CWR4Y_kW.mjs.map +1 -0
- package/dist/{migrate-B4gngTLe.mjs → migrate-B2qL5wj4.mjs} +6 -5
- package/dist/{migrate-B4gngTLe.mjs.map → migrate-B2qL5wj4.mjs.map} +1 -1
- package/dist/{migrate-tx9-P9uz.cjs → migrate-q4pPAXtO.cjs} +19 -18
- package/dist/{migrate-tx9-P9uz.cjs.map → migrate-q4pPAXtO.cjs.map} +1 -1
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/provider-C9_-45d5.cjs +56 -0
- package/dist/provider-C9_-45d5.cjs.map +1 -0
- package/dist/provider-q4lsYzYG.mjs +45 -0
- package/dist/provider-q4lsYzYG.mjs.map +1 -0
- package/dist/reconcile/public.d.cts +1 -1
- package/dist/reconcile/public.d.mts +1 -1
- package/dist/resolve-DLkIXTct.mjs +262 -0
- package/dist/resolve-DLkIXTct.mjs.map +1 -0
- package/dist/resolve-N_i4XNd-.cjs +315 -0
- package/dist/resolve-N_i4XNd-.cjs.map +1 -0
- package/dist/{runtime-CJEYK4Gz.d.mts → runtime-BnD7Nn96.d.cts} +2 -2
- package/dist/{runtime-CJEYK4Gz.d.mts.map → runtime-BnD7Nn96.d.cts.map} +1 -1
- package/dist/{runtime-Cjq1jlVX.d.cts → runtime-j-tmFL65.d.mts} +2 -2
- package/dist/{runtime-Cjq1jlVX.d.cts.map → runtime-j-tmFL65.d.mts.map} +1 -1
- package/dist/sniffer-BSwaL-dX.cjs +2236 -0
- package/dist/sniffer-BSwaL-dX.cjs.map +1 -0
- package/dist/sniffer-BZ-OJO4Y.mjs +2068 -0
- package/dist/sniffer-BZ-OJO4Y.mjs.map +1 -0
- package/dist/{sst-Cper040I.mjs → sst-B2HbrDR3.mjs} +4 -4
- package/dist/{sst-Cper040I.mjs.map → sst-B2HbrDR3.mjs.map} +1 -1
- package/dist/{sst-BdALKEas.cjs → sst-DcfsVH6x.cjs} +4 -4
- package/dist/{sst-BdALKEas.cjs.map → sst-DcfsVH6x.cjs.map} +1 -1
- package/dist/{sniffer-49KPbDgq.mjs → state-B8YwmUZU.mjs} +878 -2465
- package/dist/state-B8YwmUZU.mjs.map +1 -0
- package/dist/{sniffer-ByhbEW5A.cjs → state-DF26e_lo.cjs} +811 -2525
- package/dist/state-DF26e_lo.cjs.map +1 -0
- package/dist/{store-D_2ByRtW.cjs → store-CJF1Tqkq.cjs} +2 -2
- package/dist/{store-D_2ByRtW.cjs.map → store-CJF1Tqkq.cjs.map} +1 -1
- package/dist/{store-BaaonhkJ.mjs → store-CLiHnqOY.mjs} +2 -2
- package/dist/{store-BaaonhkJ.mjs.map → store-CLiHnqOY.mjs.map} +1 -1
- package/dist/target/public.cjs +6 -6
- package/dist/target/public.d.cts +2 -2
- package/dist/target/public.d.mts +2 -2
- package/dist/target/public.mjs +3 -3
- package/dist/terminal-Bs5E1oxm.cjs +88 -0
- package/dist/terminal-Bs5E1oxm.cjs.map +1 -0
- package/dist/terminal-CfbiYUbZ.mjs +84 -0
- package/dist/terminal-CfbiYUbZ.mjs.map +1 -0
- package/dist/{types-B_bEQvb9.d.mts → types-BCzMWBb6.d.mts} +43 -11
- package/dist/types-BCzMWBb6.d.mts.map +1 -0
- package/dist/{types-DkrDOMxw.d.cts → types-ByMWkXKP.d.cts} +43 -11
- package/dist/types-ByMWkXKP.d.cts.map +1 -0
- package/dist/workspace/index.cjs +3 -1
- package/dist/workspace/index.d.cts +3 -3
- package/dist/workspace/index.d.mts +3 -3
- package/dist/workspace/index.mjs +2 -2
- package/dist/{workspace-BwgWJK7h.cjs → workspace-BewAHZIo.cjs} +38 -3
- package/dist/workspace-BewAHZIo.cjs.map +1 -0
- package/dist/{workspace-Bwit4q9O.mjs → workspace-C06_pW4g.mjs} +27 -4
- package/dist/workspace-C06_pW4g.mjs.map +1 -0
- package/package.json +13 -13
- package/dist/credentials-B70vmLcm.cjs.map +0 -1
- package/dist/credentials-s0wMeZBU.mjs.map +0 -1
- package/dist/define-CYs0PEfZ.mjs.map +0 -1
- package/dist/define-zUsP72ym.cjs.map +0 -1
- package/dist/deploy-55rJkdKz.cjs.map +0 -1
- package/dist/deploy-Dc2LBJaK.mjs.map +0 -1
- package/dist/deploy-ZRXIPp1c.mjs.map +0 -1
- package/dist/deploy-yUcR2OlW.cjs.map +0 -1
- package/dist/index-CJ2qsSpp.d.mts.map +0 -1
- package/dist/index-CfE9Y885.d.cts.map +0 -1
- package/dist/local-C7wnrcYK.cjs.map +0 -1
- package/dist/local-Cd8ZYQG3.mjs.map +0 -1
- package/dist/resolve-BXsNQjMR.cjs +0 -220
- package/dist/resolve-BXsNQjMR.cjs.map +0 -1
- package/dist/resolve-DZ2IJ0qY.mjs +0 -173
- package/dist/resolve-DZ2IJ0qY.mjs.map +0 -1
- package/dist/runtime-BGNDrHzh.cjs +0 -163
- package/dist/runtime-BGNDrHzh.cjs.map +0 -1
- package/dist/runtime-Cmn-FeYO.mjs +0 -134
- package/dist/runtime-Cmn-FeYO.mjs.map +0 -1
- package/dist/sniffer-49KPbDgq.mjs.map +0 -1
- package/dist/sniffer-ByhbEW5A.cjs.map +0 -1
- package/dist/terminal-Bdv--jhY.cjs +0 -481
- package/dist/terminal-Bdv--jhY.cjs.map +0 -1
- package/dist/terminal-D7Ntz6WE.mjs +0 -440
- package/dist/terminal-D7Ntz6WE.mjs.map +0 -1
- package/dist/types-B_bEQvb9.d.mts.map +0 -1
- package/dist/types-DkrDOMxw.d.cts.map +0 -1
- package/dist/workspace-BwgWJK7h.cjs.map +0 -1
- package/dist/workspace-Bwit4q9O.mjs.map +0 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
const require_define = require("./define-
|
|
2
|
-
const require_deploy = require("./deploy-
|
|
1
|
+
const require_define = require("./define-DzRz8FgH.cjs");
|
|
2
|
+
const require_deploy = require("./deploy-Cy8igpQF.cjs");
|
|
3
3
|
//#region src/target/dokploy.ts
|
|
4
4
|
/**
|
|
5
5
|
* The built-in `dokploy` target: the Dokploy engine `gkm deploy` has always
|
|
@@ -29,4 +29,4 @@ const dokployTarget = require_define.defineTarget({
|
|
|
29
29
|
//#endregion
|
|
30
30
|
exports.dokployTarget = dokployTarget;
|
|
31
31
|
|
|
32
|
-
//# sourceMappingURL=dokploy-
|
|
32
|
+
//# sourceMappingURL=dokploy-iFY8MY-L.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dokploy-
|
|
1
|
+
{"version":3,"file":"dokploy-iFY8MY-L.cjs","names":["defineTarget","validateDokploy","planDokploy","provisionDokploy","releaseDokploy","verifyDokploy","dokployResult"],"sources":["../src/target/dokploy.ts"],"sourcesContent":["/**\n * The built-in `dokploy` target: the Dokploy engine `gkm deploy` has always\n * run, behind the phases.\n *\n * What it does is unchanged — the same requests, the same images, the same\n * progress lines — and so is what it does not do yet: it builds each image\n * inside `release`, beside its application, has no rollback, and leaves\n * migrations to the app. Verification, rollback and migrations are #151's.\n */\n\nimport {\n\ttype DokployRun,\n\tdokployResult,\n\tplanDokploy,\n\tprovisionDokploy,\n\treleaseDokploy,\n\tvalidateDokploy,\n\tverifyDokploy,\n} from '../deploy/index';\nimport { defineTarget } from './define';\n\nexport const dokployTarget = defineTarget<undefined, DokployRun>({\n\tname: 'dokploy',\n\truntime: 'server',\n\tcapabilities: { rollback: false, migrations: 'app', images: true },\n\tcredentials: ['dokploy', 'registry'],\n\tvalidate: (ctx) => validateDokploy(ctx),\n\tplan: (_ctx, run) => planDokploy(run),\n\tprovision: (_ctx, run) => provisionDokploy(run),\n\trelease: (_ctx, run) => releaseDokploy(run),\n\tverify: (_ctx, run) => verifyDokploy(run),\n\tresult: (_ctx, run) => dokployResult(run),\n});\n"],"mappings":";;;;;;;;;;;;AAqBA,MAAa,gBAAgBA,eAAAA,aAAoC;CAChE,MAAM;CACN,SAAS;CACT,cAAc;EAAE,UAAU;EAAO,YAAY;EAAO,QAAQ;CAAK;CACjE,aAAa,CAAC,WAAW,UAAU;CACnC,WAAW,QAAQC,eAAAA,gBAAgB,GAAG;CACtC,OAAO,MAAM,QAAQC,eAAAA,YAAY,GAAG;CACpC,YAAY,MAAM,QAAQC,eAAAA,iBAAiB,GAAG;CAC9C,UAAU,MAAM,QAAQC,eAAAA,eAAe,GAAG;CAC1C,SAAS,MAAM,QAAQC,eAAAA,cAAc,GAAG;CACxC,SAAS,MAAM,QAAQC,eAAAA,cAAc,GAAG;AACzC,CAAC"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
const
|
|
2
|
-
const require_workspace = require("./workspace-BwgWJK7h.cjs");
|
|
1
|
+
const require_workspace = require("./workspace-BewAHZIo.cjs");
|
|
3
2
|
const require_config = require("./config.cjs");
|
|
4
3
|
const require_storage = require("./storage-CSGoC3bK.cjs");
|
|
5
|
-
const require_store = require("./store-
|
|
4
|
+
const require_store = require("./store-CJF1Tqkq.cjs");
|
|
5
|
+
const require_state = require("./state-DF26e_lo.cjs");
|
|
6
6
|
let _geekmidas_manifest = require("@geekmidas/manifest");
|
|
7
7
|
let _geekmidas_constructs_encryption = require("@geekmidas/constructs/encryption");
|
|
8
8
|
//#region src/encryption/index.ts
|
|
@@ -74,7 +74,7 @@ async function updateKeyring(construct, { stage }, context, update) {
|
|
|
74
74
|
const id = (0, _geekmidas_manifest.canonicalId)(construct);
|
|
75
75
|
if (stage === workspace.stages.local) throw new LocalKeyringIsDerived(id, stage);
|
|
76
76
|
require_workspace.assertDeployedStage(workspace.stages, stage);
|
|
77
|
-
if (
|
|
77
|
+
if (require_state.providerOf(workspace) === "aws") throw new KmsRotatesItself(id);
|
|
78
78
|
const store = context.store ?? await require_store.secretsStoreFor(workspace, stage);
|
|
79
79
|
const key = (0, _geekmidas_manifest.provideKey)(id, "url");
|
|
80
80
|
const secrets = await store.read(stage);
|
|
@@ -88,4 +88,4 @@ async function updateKeyring(construct, { stage }, context, update) {
|
|
|
88
88
|
exports.encryptionRetireCommand = encryptionRetireCommand;
|
|
89
89
|
exports.encryptionRotateCommand = encryptionRotateCommand;
|
|
90
90
|
|
|
91
|
-
//# sourceMappingURL=encryption-
|
|
91
|
+
//# sourceMappingURL=encryption-B1wJwgae.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"encryption-
|
|
1
|
+
{"version":3,"file":"encryption-B1wJwgae.cjs","names":["rotateKeyring","parseKeyring","retireKey","loadWorkspaceSettings","canonicalId","providerOf","secretsStoreFor","provideKey","withCustomSecret"],"sources":["../src/encryption/index.ts"],"sourcesContent":["/**\n * `gkm encryption:rotate` and `gkm encryption:retire` — the keyring of an\n * `Encryption` construct on a server stage.\n *\n * Rotating adds a key in front and keeps every older one, so what they wrote\n * still opens. Retiring removes one, and is the half that makes rotating worth\n * doing: until a key is gone, it still opens everything it ever wrote. It is\n * never automatic, because the only thing that knows nothing is left under a\n * key is the sweep that re-encrypted the column — so it is done after one,\n * deliberately, by name.\n *\n * Neither applies everywhere, and each says so rather than doing something\n * that looks right: a local stage's keyring is derived and would be replaced on\n * the next `gkm dev`, and on AWS the key is KMS's, which rotates it itself and\n * never strands a value.\n */\n\nimport {\n\tparseKeyring,\n\tretireKey,\n\trotateKeyring,\n} from '@geekmidas/constructs/encryption';\nimport { canonicalId, provideKey } from '@geekmidas/manifest';\nimport { loadWorkspaceSettings } from '../config';\nimport { withCustomSecret } from '../secrets/storage.js';\nimport { type SecretsStore, secretsStoreFor } from '../secrets/store.js';\nimport { providerOf } from '../workspace/backends.js';\nimport { assertDeployedStage } from '../workspace/stages.js';\nimport type { NormalizedWorkspace } from '../workspace/types.js';\n\nconst logger = console;\n\nexport class LocalKeyringIsDerived extends Error {\n\tconstructor(\n\t\treadonly construct: string,\n\t\treadonly stage: string,\n\t) {\n\t\tsuper(\n\t\t\t`'${stage}' is the local stage, where ${construct}'s keyring is derived from the project and stage — nothing stored there would be read. Rotate a deployed stage: gkm encryption:rotate ${construct} --stage <stage>.`,\n\t\t);\n\t\tthis.name = 'LocalKeyringIsDerived';\n\t}\n}\n\nexport class KmsRotatesItself extends Error {\n\tconstructor(readonly construct: string) {\n\t\tsuper(\n\t\t\t`${construct} is a KMS key on AWS: KMS rotates it yearly and keeps every version, so no value is ever stranded and there is no key to retire. Nothing to do here.`,\n\t\t);\n\t\tthis.name = 'KmsRotatesItself';\n\t}\n}\n\nexport class NoKeyringYet extends Error {\n\tconstructor(\n\t\treadonly construct: string,\n\t\treadonly stage: string,\n\t\treadonly key: string,\n\t) {\n\t\tsuper(\n\t\t\t`'${stage}' has no ${key} yet. The first deploy generates ${construct}'s keyring: gkm deploy --stage ${stage}.`,\n\t\t);\n\t\tthis.name = 'NoKeyringYet';\n\t}\n}\n\nexport interface EncryptionCommandOptions {\n\tstage: string;\n}\n\n/** Where a command reads and writes; the real ones unless a test says. */\nexport interface EncryptionCommandContext {\n\tworkspace?: NormalizedWorkspace;\n\tstore?: SecretsStore;\n}\n\nexport async function encryptionRotateCommand(\n\tconstruct: string,\n\toptions: EncryptionCommandOptions,\n\tcontext: EncryptionCommandContext = {},\n): Promise<string> {\n\treturn updateKeyring(construct, options, context, (id, url) => {\n\t\tconst next = rotateKeyring(id, url);\n\t\tconst [current, ...older] = parseKeyring(id, next).keys.map(({ id }) => id);\n\n\t\tlogger.log(`\\n✓ ${id} on '${options.stage}' now writes with '${current}'.`);\n\t\tlogger.log(\n\t\t\t` Still opening: ${older.join(', ')}. Redeploy to start writing with '${current}', reencrypt what is stored, then: gkm encryption:retire ${id} <key> --stage ${options.stage}`,\n\t\t);\n\t\treturn next;\n\t});\n}\n\nexport async function encryptionRetireCommand(\n\tconstruct: string,\n\tkeyId: string,\n\toptions: EncryptionCommandOptions,\n\tcontext: EncryptionCommandContext = {},\n): Promise<string> {\n\treturn updateKeyring(construct, options, context, (id, url) => {\n\t\tconst next = retireKey(id, url, keyId);\n\n\t\tlogger.log(\n\t\t\t`\\n✓ ${id} on '${options.stage}' no longer holds '${keyId}'. Anything still under it cannot be read after the next deploy.`,\n\t\t);\n\t\treturn next;\n\t});\n}\n\nasync function updateKeyring(\n\tconstruct: string,\n\t{ stage }: EncryptionCommandOptions,\n\tcontext: EncryptionCommandContext,\n\tupdate: (id: string, url: string) => string,\n): Promise<string> {\n\tconst workspace = context.workspace ?? (await loadWorkspaceSettings());\n\tconst id = canonicalId(construct);\n\n\tif (stage === workspace.stages.local) {\n\t\tthrow new LocalKeyringIsDerived(id, stage);\n\t}\n\tassertDeployedStage(workspace.stages, stage);\n\tif (providerOf(workspace) === 'aws') throw new KmsRotatesItself(id);\n\n\tconst store = context.store ?? (await secretsStoreFor(workspace, stage));\n\tconst key = provideKey(id, 'url');\n\tconst secrets = await store.read(stage);\n\tconst url = secrets?.custom[key];\n\tif (!secrets || !url) throw new NoKeyringYet(id, stage, key);\n\n\tconst next = update(id, url);\n\tawait store.write(stage, withCustomSecret(secrets, key, next));\n\treturn next;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA8BA,MAAM,SAAS;AAEf,IAAa,wBAAb,cAA2C,MAAM;CAEtC;CACA;CAFV,YACC,WACA,OACC;EACD,MACC,IAAI,MAAM,8BAA8B,UAAU,wIAAwI,UAAU,kBACrM;EALS,KAAA,YAAA;EACA,KAAA,QAAA;EAKT,KAAK,OAAO;CACb;AACD;AAEA,IAAa,mBAAb,cAAsC,MAAM;CACtB;CAArB,YAAY,WAA4B;EACvC,MACC,GAAG,UAAU,qJACd;EAHoB,KAAA,YAAA;EAIpB,KAAK,OAAO;CACb;AACD;AAEA,IAAa,eAAb,cAAkC,MAAM;CAE7B;CACA;CACA;CAHV,YACC,WACA,OACA,KACC;EACD,MACC,IAAI,MAAM,WAAW,IAAI,mCAAmC,UAAU,iCAAiC,MAAM,EAC9G;EANS,KAAA,YAAA;EACA,KAAA,QAAA;EACA,KAAA,MAAA;EAKT,KAAK,OAAO;CACb;AACD;AAYA,eAAsB,wBACrB,WACA,SACA,UAAoC,CAAC,GACnB;CAClB,OAAO,cAAc,WAAW,SAAS,UAAU,IAAI,QAAQ;EAC9D,MAAM,QAAA,GAAOA,iCAAAA,cAAAA,CAAc,IAAI,GAAG;EAClC,MAAM,CAAC,SAAS,GAAG,UAAA,GAASC,iCAAAA,aAAAA,CAAa,IAAI,IAAI,CAAC,CAAC,KAAK,KAAK,EAAE,SAAS,EAAE;EAE1E,OAAO,IAAI,OAAO,GAAG,OAAO,QAAQ,MAAM,qBAAqB,QAAQ,GAAG;EAC1E,OAAO,IACN,oBAAoB,MAAM,KAAK,IAAI,EAAE,oCAAoC,QAAQ,2DAA2D,GAAG,iBAAiB,QAAQ,OACzK;EACA,OAAO;CACR,CAAC;AACF;AAEA,eAAsB,wBACrB,WACA,OACA,SACA,UAAoC,CAAC,GACnB;CAClB,OAAO,cAAc,WAAW,SAAS,UAAU,IAAI,QAAQ;EAC9D,MAAM,QAAA,GAAOC,iCAAAA,UAAAA,CAAU,IAAI,KAAK,KAAK;EAErC,OAAO,IACN,OAAO,GAAG,OAAO,QAAQ,MAAM,qBAAqB,MAAM,iEAC3D;EACA,OAAO;CACR,CAAC;AACF;AAEA,eAAe,cACd,WACA,EAAE,SACF,SACA,QACkB;CAClB,MAAM,YAAY,QAAQ,aAAc,MAAMC,eAAAA,sBAAsB;CACpE,MAAM,MAAA,GAAKC,oBAAAA,YAAAA,CAAY,SAAS;CAEhC,IAAI,UAAU,UAAU,OAAO,OAC9B,MAAM,IAAI,sBAAsB,IAAI,KAAK;CAE1C,kBAAA,oBAAoB,UAAU,QAAQ,KAAK;CAC3C,IAAIC,cAAAA,WAAW,SAAS,MAAM,OAAO,MAAM,IAAI,iBAAiB,EAAE;CAElE,MAAM,QAAQ,QAAQ,SAAU,MAAMC,cAAAA,gBAAgB,WAAW,KAAK;CACtE,MAAM,OAAA,GAAMC,oBAAAA,WAAAA,CAAW,IAAI,KAAK;CAChC,MAAM,UAAU,MAAM,MAAM,KAAK,KAAK;CACtC,MAAM,MAAM,SAAS,OAAO;CAC5B,IAAI,CAAC,WAAW,CAAC,KAAK,MAAM,IAAI,aAAa,IAAI,OAAO,GAAG;CAE3D,MAAM,OAAO,OAAO,IAAI,GAAG;CAC3B,MAAM,MAAM,MAAM,OAAOC,gBAAAA,iBAAiB,SAAS,KAAK,IAAI,CAAC;CAC7D,OAAO;AACR"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { D as assertDeployedStage } from "./workspace-Bwit4q9O.mjs";
|
|
1
|
+
import { k as assertDeployedStage } from "./workspace-C06_pW4g.mjs";
|
|
3
2
|
import { loadWorkspaceSettings } from "./config.mjs";
|
|
4
3
|
import { a as withCustomSecret } from "./storage-BzaUxmqe.mjs";
|
|
5
|
-
import { n as secretsStoreFor } from "./store-
|
|
4
|
+
import { n as secretsStoreFor } from "./store-CLiHnqOY.mjs";
|
|
5
|
+
import { J as providerOf } from "./state-B8YwmUZU.mjs";
|
|
6
6
|
import { canonicalId, provideKey } from "@geekmidas/manifest";
|
|
7
7
|
import { parseKeyring, retireKey, rotateKeyring } from "@geekmidas/constructs/encryption";
|
|
8
8
|
//#region src/encryption/index.ts
|
|
@@ -87,4 +87,4 @@ async function updateKeyring(construct, { stage }, context, update) {
|
|
|
87
87
|
//#endregion
|
|
88
88
|
export { encryptionRetireCommand, encryptionRotateCommand };
|
|
89
89
|
|
|
90
|
-
//# sourceMappingURL=encryption-
|
|
90
|
+
//# sourceMappingURL=encryption-DF_hgIpl.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"encryption-
|
|
1
|
+
{"version":3,"file":"encryption-DF_hgIpl.mjs","names":[],"sources":["../src/encryption/index.ts"],"sourcesContent":["/**\n * `gkm encryption:rotate` and `gkm encryption:retire` — the keyring of an\n * `Encryption` construct on a server stage.\n *\n * Rotating adds a key in front and keeps every older one, so what they wrote\n * still opens. Retiring removes one, and is the half that makes rotating worth\n * doing: until a key is gone, it still opens everything it ever wrote. It is\n * never automatic, because the only thing that knows nothing is left under a\n * key is the sweep that re-encrypted the column — so it is done after one,\n * deliberately, by name.\n *\n * Neither applies everywhere, and each says so rather than doing something\n * that looks right: a local stage's keyring is derived and would be replaced on\n * the next `gkm dev`, and on AWS the key is KMS's, which rotates it itself and\n * never strands a value.\n */\n\nimport {\n\tparseKeyring,\n\tretireKey,\n\trotateKeyring,\n} from '@geekmidas/constructs/encryption';\nimport { canonicalId, provideKey } from '@geekmidas/manifest';\nimport { loadWorkspaceSettings } from '../config';\nimport { withCustomSecret } from '../secrets/storage.js';\nimport { type SecretsStore, secretsStoreFor } from '../secrets/store.js';\nimport { providerOf } from '../workspace/backends.js';\nimport { assertDeployedStage } from '../workspace/stages.js';\nimport type { NormalizedWorkspace } from '../workspace/types.js';\n\nconst logger = console;\n\nexport class LocalKeyringIsDerived extends Error {\n\tconstructor(\n\t\treadonly construct: string,\n\t\treadonly stage: string,\n\t) {\n\t\tsuper(\n\t\t\t`'${stage}' is the local stage, where ${construct}'s keyring is derived from the project and stage — nothing stored there would be read. Rotate a deployed stage: gkm encryption:rotate ${construct} --stage <stage>.`,\n\t\t);\n\t\tthis.name = 'LocalKeyringIsDerived';\n\t}\n}\n\nexport class KmsRotatesItself extends Error {\n\tconstructor(readonly construct: string) {\n\t\tsuper(\n\t\t\t`${construct} is a KMS key on AWS: KMS rotates it yearly and keeps every version, so no value is ever stranded and there is no key to retire. Nothing to do here.`,\n\t\t);\n\t\tthis.name = 'KmsRotatesItself';\n\t}\n}\n\nexport class NoKeyringYet extends Error {\n\tconstructor(\n\t\treadonly construct: string,\n\t\treadonly stage: string,\n\t\treadonly key: string,\n\t) {\n\t\tsuper(\n\t\t\t`'${stage}' has no ${key} yet. The first deploy generates ${construct}'s keyring: gkm deploy --stage ${stage}.`,\n\t\t);\n\t\tthis.name = 'NoKeyringYet';\n\t}\n}\n\nexport interface EncryptionCommandOptions {\n\tstage: string;\n}\n\n/** Where a command reads and writes; the real ones unless a test says. */\nexport interface EncryptionCommandContext {\n\tworkspace?: NormalizedWorkspace;\n\tstore?: SecretsStore;\n}\n\nexport async function encryptionRotateCommand(\n\tconstruct: string,\n\toptions: EncryptionCommandOptions,\n\tcontext: EncryptionCommandContext = {},\n): Promise<string> {\n\treturn updateKeyring(construct, options, context, (id, url) => {\n\t\tconst next = rotateKeyring(id, url);\n\t\tconst [current, ...older] = parseKeyring(id, next).keys.map(({ id }) => id);\n\n\t\tlogger.log(`\\n✓ ${id} on '${options.stage}' now writes with '${current}'.`);\n\t\tlogger.log(\n\t\t\t` Still opening: ${older.join(', ')}. Redeploy to start writing with '${current}', reencrypt what is stored, then: gkm encryption:retire ${id} <key> --stage ${options.stage}`,\n\t\t);\n\t\treturn next;\n\t});\n}\n\nexport async function encryptionRetireCommand(\n\tconstruct: string,\n\tkeyId: string,\n\toptions: EncryptionCommandOptions,\n\tcontext: EncryptionCommandContext = {},\n): Promise<string> {\n\treturn updateKeyring(construct, options, context, (id, url) => {\n\t\tconst next = retireKey(id, url, keyId);\n\n\t\tlogger.log(\n\t\t\t`\\n✓ ${id} on '${options.stage}' no longer holds '${keyId}'. Anything still under it cannot be read after the next deploy.`,\n\t\t);\n\t\treturn next;\n\t});\n}\n\nasync function updateKeyring(\n\tconstruct: string,\n\t{ stage }: EncryptionCommandOptions,\n\tcontext: EncryptionCommandContext,\n\tupdate: (id: string, url: string) => string,\n): Promise<string> {\n\tconst workspace = context.workspace ?? (await loadWorkspaceSettings());\n\tconst id = canonicalId(construct);\n\n\tif (stage === workspace.stages.local) {\n\t\tthrow new LocalKeyringIsDerived(id, stage);\n\t}\n\tassertDeployedStage(workspace.stages, stage);\n\tif (providerOf(workspace) === 'aws') throw new KmsRotatesItself(id);\n\n\tconst store = context.store ?? (await secretsStoreFor(workspace, stage));\n\tconst key = provideKey(id, 'url');\n\tconst secrets = await store.read(stage);\n\tconst url = secrets?.custom[key];\n\tif (!secrets || !url) throw new NoKeyringYet(id, stage, key);\n\n\tconst next = update(id, url);\n\tawait store.write(stage, withCustomSecret(secrets, key, next));\n\treturn next;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;AA8BA,MAAM,SAAS;AAEf,IAAa,wBAAb,cAA2C,MAAM;CAEtC;CACA;CAFV,YACC,WACA,OACC;EACD,MACC,IAAI,MAAM,8BAA8B,UAAU,wIAAwI,UAAU,kBACrM;EALS,KAAA,YAAA;EACA,KAAA,QAAA;EAKT,KAAK,OAAO;CACb;AACD;AAEA,IAAa,mBAAb,cAAsC,MAAM;CACtB;CAArB,YAAY,WAA4B;EACvC,MACC,GAAG,UAAU,qJACd;EAHoB,KAAA,YAAA;EAIpB,KAAK,OAAO;CACb;AACD;AAEA,IAAa,eAAb,cAAkC,MAAM;CAE7B;CACA;CACA;CAHV,YACC,WACA,OACA,KACC;EACD,MACC,IAAI,MAAM,WAAW,IAAI,mCAAmC,UAAU,iCAAiC,MAAM,EAC9G;EANS,KAAA,YAAA;EACA,KAAA,QAAA;EACA,KAAA,MAAA;EAKT,KAAK,OAAO;CACb;AACD;AAYA,eAAsB,wBACrB,WACA,SACA,UAAoC,CAAC,GACnB;CAClB,OAAO,cAAc,WAAW,SAAS,UAAU,IAAI,QAAQ;EAC9D,MAAM,OAAO,cAAc,IAAI,GAAG;EAClC,MAAM,CAAC,SAAS,GAAG,SAAS,aAAa,IAAI,IAAI,CAAC,CAAC,KAAK,KAAK,EAAE,SAAS,EAAE;EAE1E,OAAO,IAAI,OAAO,GAAG,OAAO,QAAQ,MAAM,qBAAqB,QAAQ,GAAG;EAC1E,OAAO,IACN,oBAAoB,MAAM,KAAK,IAAI,EAAE,oCAAoC,QAAQ,2DAA2D,GAAG,iBAAiB,QAAQ,OACzK;EACA,OAAO;CACR,CAAC;AACF;AAEA,eAAsB,wBACrB,WACA,OACA,SACA,UAAoC,CAAC,GACnB;CAClB,OAAO,cAAc,WAAW,SAAS,UAAU,IAAI,QAAQ;EAC9D,MAAM,OAAO,UAAU,IAAI,KAAK,KAAK;EAErC,OAAO,IACN,OAAO,GAAG,OAAO,QAAQ,MAAM,qBAAqB,MAAM,iEAC3D;EACA,OAAO;CACR,CAAC;AACF;AAEA,eAAe,cACd,WACA,EAAE,SACF,SACA,QACkB;CAClB,MAAM,YAAY,QAAQ,aAAc,MAAM,sBAAsB;CACpE,MAAM,KAAK,YAAY,SAAS;CAEhC,IAAI,UAAU,UAAU,OAAO,OAC9B,MAAM,IAAI,sBAAsB,IAAI,KAAK;CAE1C,oBAAoB,UAAU,QAAQ,KAAK;CAC3C,IAAI,WAAW,SAAS,MAAM,OAAO,MAAM,IAAI,iBAAiB,EAAE;CAElE,MAAM,QAAQ,QAAQ,SAAU,MAAM,gBAAgB,WAAW,KAAK;CACtE,MAAM,MAAM,WAAW,IAAI,KAAK;CAChC,MAAM,UAAU,MAAM,MAAM,KAAK,KAAK;CACtC,MAAM,MAAM,SAAS,OAAO;CAC5B,IAAI,CAAC,WAAW,CAAC,KAAK,MAAM,IAAI,aAAa,IAAI,OAAO,GAAG;CAE3D,MAAM,OAAO,OAAO,IAAI,GAAG;CAC3B,MAAM,MAAM,MAAM,OAAO,iBAAiB,SAAS,KAAK,IAAI,CAAC;CAC7D,OAAO;AACR"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { k as assertDeployedStage } from "./workspace-C06_pW4g.mjs";
|
|
2
2
|
import { loadWorkspaceConfig } from "./config.mjs";
|
|
3
|
-
import { a as getKeyPath, c as readKey, n as secretsStoreFor, o as keystoreProject, t as isRemoteStore } from "./store-
|
|
3
|
+
import { a as getKeyPath, c as readKey, n as secretsStoreFor, o as keystoreProject, t as isRemoteStore } from "./store-CLiHnqOY.mjs";
|
|
4
4
|
import { execFileSync } from "node:child_process";
|
|
5
5
|
import { AttachRolePolicyCommand, CreateOpenIDConnectProviderCommand, CreateRoleCommand, GetRoleCommand, IAMClient, ListOpenIDConnectProvidersCommand, UpdateAssumeRolePolicyCommand } from "@aws-sdk/client-iam";
|
|
6
6
|
//#region src/deploy/github.ts
|
|
@@ -210,4 +210,4 @@ var SsoSessionExpired = class extends Error {
|
|
|
210
210
|
//#endregion
|
|
211
211
|
export { deployGithubCommand };
|
|
212
212
|
|
|
213
|
-
//# sourceMappingURL=github-
|
|
213
|
+
//# sourceMappingURL=github-B0YYE98x.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"github-D-byNI7T.mjs","names":[],"sources":["../src/deploy/github.ts"],"sourcesContent":["/**\n * `gkm deploy:github` — let GitHub Actions deploy one stage, with no keys.\n *\n * A stage usually lives in its own AWS account — staging in one, production in\n * another — so this runs once per stage, with that account's profile:\n *\n * gkm deploy:github --stage staging --profile acme-dev\n * gkm deploy:github --stage prod --profile acme-prod\n *\n * In the stage's account it makes sure GitHub's OIDC provider exists, and a\n * role that only this repository's `<stage>` environment can assume. On\n * GitHub it creates that environment and gives it what the generated deploy\n * workflow reads: `AWS_ROLE_ARN`, and — when the stage's secrets are kept in\n * the local file — its key as `GKM_SECRETS_KEY`. With `secrets.store` set to\n * SSM the deploy job pulls them with the role instead, so no key is handed to\n * GitHub, and this pushes the stage's local secrets to SSM in the same account,\n * with the same profile. Re-running it converges rather than duplicating.\n */\n\nimport { execFileSync } from 'node:child_process';\nimport {\n\tAttachRolePolicyCommand,\n\tCreateOpenIDConnectProviderCommand,\n\tCreateRoleCommand,\n\tGetRoleCommand,\n\tIAMClient,\n\ttype IAMClientConfig,\n\tListOpenIDConnectProvidersCommand,\n\tUpdateAssumeRolePolicyCommand,\n} from '@aws-sdk/client-iam';\nimport { loadWorkspaceConfig } from '../config.js';\nimport { getKeyPath, keystoreProject, readKey } from '../secrets/keystore.js';\nimport { isRemoteStore, secretsStoreFor } from '../secrets/store.js';\nimport { assertDeployedStage } from '../workspace/stages.js';\n\nconst logger = console;\n\nconst GITHUB_OIDC_HOST = 'token.actions.githubusercontent.com';\nconst AUDIENCE = 'sts.amazonaws.com';\n\n/**\n * What SST needs to create a stack is most of AWS, so this is the default —\n * said out loud in the output, and replaceable with `--policy-arn`.\n */\nexport const DEFAULT_POLICY_ARN = 'arn:aws:iam::aws:policy/AdministratorAccess';\n\nexport interface DeployGithubOptions {\n\tstage: string;\n\t/** The AWS profile for the stage's account. */\n\tprofile?: string;\n\t/** `owner/name`; defaults to the repository `gh` sees here. */\n\trepo?: string;\n\t/** The policy the deploy role gets. */\n\tpolicyArn?: string;\n\tdryRun?: boolean;\n}\n\n/** Runs `gh` with the given arguments and optional stdin, returning stdout. */\nexport type Gh = (args: string[], input?: string) => string;\n\nconst defaultGh: Gh = (args, input) =>\n\texecFileSync('gh', args, {\n\t\tencoding: 'utf-8',\n\t\t...(input === undefined\n\t\t\t? {}\n\t\t\t: { input, stdio: ['pipe', 'pipe', 'pipe'] as const }),\n\t}).trim();\n\n/** The role a stage deploys as: `<project>-github-<stage>`, within IAM's 64. */\nexport function roleName(project: string, stage: string): string {\n\treturn `${project}-github-${stage}`\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9+=,.@_-]/g, '-')\n\t\t.slice(0, 64);\n}\n\n/**\n * Who may assume the role: a workflow job running in this repository's\n * `<stage>` environment, and nothing else — not another branch's job, not\n * another environment's. That is what keeps a staging deploy from using the\n * production role.\n */\nexport function trustPolicy(\n\tproviderArn: string,\n\trepo: string,\n\tstage: string,\n): string {\n\treturn JSON.stringify({\n\t\tVersion: '2012-10-17',\n\t\tStatement: [\n\t\t\t{\n\t\t\t\tEffect: 'Allow',\n\t\t\t\tPrincipal: { Federated: providerArn },\n\t\t\t\tAction: 'sts:AssumeRoleWithWebIdentity',\n\t\t\t\tCondition: {\n\t\t\t\t\tStringEquals: {\n\t\t\t\t\t\t[`${GITHUB_OIDC_HOST}:aud`]: AUDIENCE,\n\t\t\t\t\t\t[`${GITHUB_OIDC_HOST}:sub`]: `repo:${repo}:environment:${stage}`,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t],\n\t});\n}\n\n/** GitHub's OIDC provider in this account, created if it is not there. */\nexport async function ensureOidcProvider(iam: IAMClient): Promise<string> {\n\tconst { OpenIDConnectProviderList = [] } = await iam.send(\n\t\tnew ListOpenIDConnectProvidersCommand({}),\n\t);\n\tconst existing = OpenIDConnectProviderList.find((p) =>\n\t\tp.Arn?.endsWith(`oidc-provider/${GITHUB_OIDC_HOST}`),\n\t)?.Arn;\n\tif (existing) return existing;\n\n\tconst created = await iam.send(\n\t\tnew CreateOpenIDConnectProviderCommand({\n\t\t\tUrl: `https://${GITHUB_OIDC_HOST}`,\n\t\t\tClientIDList: [AUDIENCE],\n\t\t}),\n\t);\n\treturn created.OpenIDConnectProviderArn!;\n}\n\n/** The deploy role, created or brought back to this trust and policy. */\nexport async function ensureRole(\n\tiam: IAMClient,\n\tname: string,\n\ttrust: string,\n\tpolicyArn: string,\n): Promise<string> {\n\tlet arn: string;\n\ttry {\n\t\tconst { Role } = await iam.send(new GetRoleCommand({ RoleName: name }));\n\t\tawait iam.send(\n\t\t\tnew UpdateAssumeRolePolicyCommand({\n\t\t\t\tRoleName: name,\n\t\t\t\tPolicyDocument: trust,\n\t\t\t}),\n\t\t);\n\t\tarn = Role!.Arn!;\n\t} catch (error) {\n\t\tif ((error as { name?: string }).name !== 'NoSuchEntityException') {\n\t\t\tthrow error;\n\t\t}\n\t\tconst { Role } = await iam.send(\n\t\t\tnew CreateRoleCommand({\n\t\t\t\tRoleName: name,\n\t\t\t\tAssumeRolePolicyDocument: trust,\n\t\t\t\tDescription: 'Deploys from GitHub Actions (gkm deploy:github)',\n\t\t\t\t// SST deploys can run long; the default hour is tight.\n\t\t\t\tMaxSessionDuration: 3600 * 2,\n\t\t\t}),\n\t\t);\n\t\tarn = Role!.Arn!;\n\t}\n\n\t// Attaching an attached policy is a no-op, so this converges too.\n\tawait iam.send(\n\t\tnew AttachRolePolicyCommand({ RoleName: name, PolicyArn: policyArn }),\n\t);\n\treturn arn;\n}\n\n/**\n * An IAM client for the stage's account.\n *\n * `fromIni` for a named profile, deliberately: it resolves exactly that\n * profile — keys, assume-role, `credential_process` or SSO, whichever\n * ~/.aws/config says — and never falls back to AWS_* in the environment. The\n * provider chain checks the environment first, so exported staging keys would\n * quietly win over `--profile prod` and the production role would land in the\n * staging account.\n */\nexport async function iamFor(\n\tprofile?: string,\n\tendpoint?: string,\n): Promise<IAMClient> {\n\tconst config: IAMClientConfig = { region: 'us-east-1' };\n\tif (profile) {\n\t\tconst { fromIni } = await import('@aws-sdk/credential-providers');\n\t\tconfig.credentials = fromIni({ profile });\n\t}\n\tif (endpoint) {\n\t\tconfig.endpoint = endpoint;\n\t\tconfig.credentials = { accessKeyId: 'test', secretAccessKey: 'test' };\n\t}\n\treturn new IAMClient(config);\n}\n\nexport async function deployGithubCommand(\n\toptions: DeployGithubOptions,\n\tdeps: { iam?: IAMClient; gh?: Gh; cwd?: string } = {},\n): Promise<{ roleArn?: string; repo: string }> {\n\tconst gh = deps.gh ?? defaultGh;\n\tconst { workspace } = await loadWorkspaceConfig(deps.cwd);\n\tassertDeployedStage(workspace.stages, options.stage);\n\n\tconst repo =\n\t\toptions.repo ??\n\t\tgh(['repo', 'view', '--json', 'nameWithOwner', '-q', '.nameWithOwner']);\n\tconst role = roleName(workspace.name, options.stage);\n\tconst policyArn = options.policyArn ?? DEFAULT_POLICY_ARN;\n\t// Read through the keystore so a key still at the place keys used to be kept\n\t// is found, and copied to where it is kept now.\n\tconst key = await readKey(options.stage, keystoreProject(workspace));\n\tconst keyPath = getKeyPath(options.stage, keystoreProject(workspace));\n\tconst hasKey = key !== null;\n\t// A stage whose secrets are in a store is read from there by the deploy job,\n\t// with the role; it needs no key on GitHub.\n\tconst remote = isRemoteStore(workspace, options.stage);\n\t// Read from the stage's own store, with the same credentials the role is\n\t// created with: what the deploy job will find there.\n\tconst store = await secretsStoreFor(workspace, options.stage, {\n\t\t...(options.profile ? { profile: options.profile } : {}),\n\t});\n\tconst hasSecrets = remote && (await store.read(options.stage)) !== null;\n\tconst via = options.profile\n\t\t? `profile \"${options.profile}\"`\n\t\t: 'the default credentials';\n\n\tlogger.log(`\\n🔐 GitHub → AWS for stage \"${options.stage}\"\\n`);\n\tlogger.log(` Repository: ${repo}`);\n\tlogger.log(` AWS profile: ${options.profile ?? '(default credentials)'}`);\n\tlogger.log(` Role: ${role}`);\n\tlogger.log(\n\t\t` Policy: ${policyArn}${options.policyArn ? '' : ' (default; --policy-arn to narrow)'}`,\n\t);\n\tlogger.log(` Trusted by: repo:${repo}:environment:${options.stage} only`);\n\tif (remote) {\n\t\tlogger.log(\n\t\t\t` Secrets: ${hasSecrets ? `in the store (${store.name}), read with ${via}` : `none in the store yet — gkm secrets:set <KEY> '…' --stage ${options.stage}`}`,\n\t\t);\n\t} else {\n\t\tlogger.log(\n\t\t\t` Secrets key: ${hasKey ? keyPath : `none at ${keyPath} — run gkm secrets:init --stage ${options.stage} first`}`,\n\t\t);\n\t}\n\n\tif (options.dryRun) {\n\t\tlogger.log('\\n --dry-run: nothing changed.\\n');\n\t\treturn { repo };\n\t}\n\n\tconst iam = deps.iam ?? (await iamFor(options.profile));\n\tconst providerArn = await ensureOidcProvider(iam).catch((error) => {\n\t\t// The first AWS call is where an expired SSO login surfaces; the SDK's\n\t\t// message does not say which profile, and this command knows.\n\t\tif (/SSO session/i.test(String(error?.message)) && options.profile) {\n\t\t\tthrow new SsoSessionExpired(options.profile);\n\t\t}\n\t\tthrow error;\n\t});\n\tconst roleArn = await ensureRole(\n\t\tiam,\n\t\trole,\n\t\ttrustPolicy(providerArn, repo, options.stage),\n\t\tpolicyArn,\n\t);\n\tlogger.log(`\\n ✓ AWS: ${roleArn}`);\n\n\t// The environment the generated deploy workflow runs the stage in.\n\tgh(['api', '--method', 'PUT', `repos/${repo}/environments/${options.stage}`]);\n\tgh([\n\t\t'variable',\n\t\t'set',\n\t\t'AWS_ROLE_ARN',\n\t\t'--env',\n\t\toptions.stage,\n\t\t'--repo',\n\t\trepo,\n\t\t'--body',\n\t\troleArn,\n\t]);\n\tlogger.log(` ✓ GitHub: environment \"${options.stage}\", AWS_ROLE_ARN`);\n\n\tif (remote) {\n\t\t// Nothing to send: the deploy job reads the stage's store itself.\n\t\tif (!hasSecrets) {\n\t\t\tlogger.log(\n\t\t\t\t` ⚠ The \"${options.stage}\" store holds no secrets yet; set them with gkm secrets:set <KEY> '…' --stage ${options.stage}${options.profile ? ` (AWS_PROFILE=${options.profile})` : ''}.`,\n\t\t\t);\n\t\t}\n\t} else if (hasKey) {\n\t\t// Through stdin, so the key is never an argument a process list shows.\n\t\tgh(\n\t\t\t[\n\t\t\t\t'secret',\n\t\t\t\t'set',\n\t\t\t\t'GKM_SECRETS_KEY',\n\t\t\t\t'--env',\n\t\t\t\toptions.stage,\n\t\t\t\t'--repo',\n\t\t\t\trepo,\n\t\t\t],\n\t\t\tkey!,\n\t\t);\n\t\tlogger.log(' ✓ GitHub: GKM_SECRETS_KEY');\n\t} else {\n\t\tlogger.log(\n\t\t\t` ⚠ No secrets key for \"${options.stage}\"; run gkm secrets:init --stage ${options.stage}, then this again.`,\n\t\t);\n\t}\n\n\tlogger.log(\n\t\t`\\n Protect it: add required reviewers to the \"${options.stage}\" environment in the repository settings.\\n`,\n\t);\n\treturn { roleArn, repo };\n}\n\n/** The profile's SSO login has lapsed; the fix is one command away. */\nexport class SsoSessionExpired extends Error {\n\tconstructor(readonly profile: string) {\n\t\tsuper(\n\t\t\t`The SSO session for profile \"${profile}\" has expired or is invalid. Run: aws sso login --profile ${profile}`,\n\t\t);\n\t\tthis.name = 'SsoSessionExpired';\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"github-B0YYE98x.mjs","names":[],"sources":["../src/deploy/github.ts"],"sourcesContent":["/**\n * `gkm deploy:github` — let GitHub Actions deploy one stage, with no keys.\n *\n * A stage usually lives in its own AWS account — staging in one, production in\n * another — so this runs once per stage, with that account's profile:\n *\n * gkm deploy:github --stage staging --profile acme-dev\n * gkm deploy:github --stage prod --profile acme-prod\n *\n * In the stage's account it makes sure GitHub's OIDC provider exists, and a\n * role that only this repository's `<stage>` environment can assume. On\n * GitHub it creates that environment and gives it what the generated deploy\n * workflow reads: `AWS_ROLE_ARN`, and — when the stage's secrets are kept in\n * the local file — its key as `GKM_SECRETS_KEY`. With `secrets.store` set to\n * SSM the deploy job pulls them with the role instead, so no key is handed to\n * GitHub, and this pushes the stage's local secrets to SSM in the same account,\n * with the same profile. Re-running it converges rather than duplicating.\n */\n\nimport { execFileSync } from 'node:child_process';\nimport {\n\tAttachRolePolicyCommand,\n\tCreateOpenIDConnectProviderCommand,\n\tCreateRoleCommand,\n\tGetRoleCommand,\n\tIAMClient,\n\ttype IAMClientConfig,\n\tListOpenIDConnectProvidersCommand,\n\tUpdateAssumeRolePolicyCommand,\n} from '@aws-sdk/client-iam';\nimport { loadWorkspaceConfig } from '../config.js';\nimport { getKeyPath, keystoreProject, readKey } from '../secrets/keystore.js';\nimport { isRemoteStore, secretsStoreFor } from '../secrets/store.js';\nimport { assertDeployedStage } from '../workspace/stages.js';\n\nconst logger = console;\n\nconst GITHUB_OIDC_HOST = 'token.actions.githubusercontent.com';\nconst AUDIENCE = 'sts.amazonaws.com';\n\n/**\n * What SST needs to create a stack is most of AWS, so this is the default —\n * said out loud in the output, and replaceable with `--policy-arn`.\n */\nexport const DEFAULT_POLICY_ARN = 'arn:aws:iam::aws:policy/AdministratorAccess';\n\nexport interface DeployGithubOptions {\n\tstage: string;\n\t/** The AWS profile for the stage's account. */\n\tprofile?: string;\n\t/** `owner/name`; defaults to the repository `gh` sees here. */\n\trepo?: string;\n\t/** The policy the deploy role gets. */\n\tpolicyArn?: string;\n\tdryRun?: boolean;\n}\n\n/** Runs `gh` with the given arguments and optional stdin, returning stdout. */\nexport type Gh = (args: string[], input?: string) => string;\n\nconst defaultGh: Gh = (args, input) =>\n\texecFileSync('gh', args, {\n\t\tencoding: 'utf-8',\n\t\t...(input === undefined\n\t\t\t? {}\n\t\t\t: { input, stdio: ['pipe', 'pipe', 'pipe'] as const }),\n\t}).trim();\n\n/** The role a stage deploys as: `<project>-github-<stage>`, within IAM's 64. */\nexport function roleName(project: string, stage: string): string {\n\treturn `${project}-github-${stage}`\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9+=,.@_-]/g, '-')\n\t\t.slice(0, 64);\n}\n\n/**\n * Who may assume the role: a workflow job running in this repository's\n * `<stage>` environment, and nothing else — not another branch's job, not\n * another environment's. That is what keeps a staging deploy from using the\n * production role.\n */\nexport function trustPolicy(\n\tproviderArn: string,\n\trepo: string,\n\tstage: string,\n): string {\n\treturn JSON.stringify({\n\t\tVersion: '2012-10-17',\n\t\tStatement: [\n\t\t\t{\n\t\t\t\tEffect: 'Allow',\n\t\t\t\tPrincipal: { Federated: providerArn },\n\t\t\t\tAction: 'sts:AssumeRoleWithWebIdentity',\n\t\t\t\tCondition: {\n\t\t\t\t\tStringEquals: {\n\t\t\t\t\t\t[`${GITHUB_OIDC_HOST}:aud`]: AUDIENCE,\n\t\t\t\t\t\t[`${GITHUB_OIDC_HOST}:sub`]: `repo:${repo}:environment:${stage}`,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t],\n\t});\n}\n\n/** GitHub's OIDC provider in this account, created if it is not there. */\nexport async function ensureOidcProvider(iam: IAMClient): Promise<string> {\n\tconst { OpenIDConnectProviderList = [] } = await iam.send(\n\t\tnew ListOpenIDConnectProvidersCommand({}),\n\t);\n\tconst existing = OpenIDConnectProviderList.find((p) =>\n\t\tp.Arn?.endsWith(`oidc-provider/${GITHUB_OIDC_HOST}`),\n\t)?.Arn;\n\tif (existing) return existing;\n\n\tconst created = await iam.send(\n\t\tnew CreateOpenIDConnectProviderCommand({\n\t\t\tUrl: `https://${GITHUB_OIDC_HOST}`,\n\t\t\tClientIDList: [AUDIENCE],\n\t\t}),\n\t);\n\treturn created.OpenIDConnectProviderArn!;\n}\n\n/** The deploy role, created or brought back to this trust and policy. */\nexport async function ensureRole(\n\tiam: IAMClient,\n\tname: string,\n\ttrust: string,\n\tpolicyArn: string,\n): Promise<string> {\n\tlet arn: string;\n\ttry {\n\t\tconst { Role } = await iam.send(new GetRoleCommand({ RoleName: name }));\n\t\tawait iam.send(\n\t\t\tnew UpdateAssumeRolePolicyCommand({\n\t\t\t\tRoleName: name,\n\t\t\t\tPolicyDocument: trust,\n\t\t\t}),\n\t\t);\n\t\tarn = Role!.Arn!;\n\t} catch (error) {\n\t\tif ((error as { name?: string }).name !== 'NoSuchEntityException') {\n\t\t\tthrow error;\n\t\t}\n\t\tconst { Role } = await iam.send(\n\t\t\tnew CreateRoleCommand({\n\t\t\t\tRoleName: name,\n\t\t\t\tAssumeRolePolicyDocument: trust,\n\t\t\t\tDescription: 'Deploys from GitHub Actions (gkm deploy:github)',\n\t\t\t\t// SST deploys can run long; the default hour is tight.\n\t\t\t\tMaxSessionDuration: 3600 * 2,\n\t\t\t}),\n\t\t);\n\t\tarn = Role!.Arn!;\n\t}\n\n\t// Attaching an attached policy is a no-op, so this converges too.\n\tawait iam.send(\n\t\tnew AttachRolePolicyCommand({ RoleName: name, PolicyArn: policyArn }),\n\t);\n\treturn arn;\n}\n\n/**\n * An IAM client for the stage's account.\n *\n * `fromIni` for a named profile, deliberately: it resolves exactly that\n * profile — keys, assume-role, `credential_process` or SSO, whichever\n * ~/.aws/config says — and never falls back to AWS_* in the environment. The\n * provider chain checks the environment first, so exported staging keys would\n * quietly win over `--profile prod` and the production role would land in the\n * staging account.\n */\nexport async function iamFor(\n\tprofile?: string,\n\tendpoint?: string,\n): Promise<IAMClient> {\n\tconst config: IAMClientConfig = { region: 'us-east-1' };\n\tif (profile) {\n\t\tconst { fromIni } = await import('@aws-sdk/credential-providers');\n\t\tconfig.credentials = fromIni({ profile });\n\t}\n\tif (endpoint) {\n\t\tconfig.endpoint = endpoint;\n\t\tconfig.credentials = { accessKeyId: 'test', secretAccessKey: 'test' };\n\t}\n\treturn new IAMClient(config);\n}\n\nexport async function deployGithubCommand(\n\toptions: DeployGithubOptions,\n\tdeps: { iam?: IAMClient; gh?: Gh; cwd?: string } = {},\n): Promise<{ roleArn?: string; repo: string }> {\n\tconst gh = deps.gh ?? defaultGh;\n\tconst { workspace } = await loadWorkspaceConfig(deps.cwd);\n\tassertDeployedStage(workspace.stages, options.stage);\n\n\tconst repo =\n\t\toptions.repo ??\n\t\tgh(['repo', 'view', '--json', 'nameWithOwner', '-q', '.nameWithOwner']);\n\tconst role = roleName(workspace.name, options.stage);\n\tconst policyArn = options.policyArn ?? DEFAULT_POLICY_ARN;\n\t// Read through the keystore so a key still at the place keys used to be kept\n\t// is found, and copied to where it is kept now.\n\tconst key = await readKey(options.stage, keystoreProject(workspace));\n\tconst keyPath = getKeyPath(options.stage, keystoreProject(workspace));\n\tconst hasKey = key !== null;\n\t// A stage whose secrets are in a store is read from there by the deploy job,\n\t// with the role; it needs no key on GitHub.\n\tconst remote = isRemoteStore(workspace, options.stage);\n\t// Read from the stage's own store, with the same credentials the role is\n\t// created with: what the deploy job will find there.\n\tconst store = await secretsStoreFor(workspace, options.stage, {\n\t\t...(options.profile ? { profile: options.profile } : {}),\n\t});\n\tconst hasSecrets = remote && (await store.read(options.stage)) !== null;\n\tconst via = options.profile\n\t\t? `profile \"${options.profile}\"`\n\t\t: 'the default credentials';\n\n\tlogger.log(`\\n🔐 GitHub → AWS for stage \"${options.stage}\"\\n`);\n\tlogger.log(` Repository: ${repo}`);\n\tlogger.log(` AWS profile: ${options.profile ?? '(default credentials)'}`);\n\tlogger.log(` Role: ${role}`);\n\tlogger.log(\n\t\t` Policy: ${policyArn}${options.policyArn ? '' : ' (default; --policy-arn to narrow)'}`,\n\t);\n\tlogger.log(` Trusted by: repo:${repo}:environment:${options.stage} only`);\n\tif (remote) {\n\t\tlogger.log(\n\t\t\t` Secrets: ${hasSecrets ? `in the store (${store.name}), read with ${via}` : `none in the store yet — gkm secrets:set <KEY> '…' --stage ${options.stage}`}`,\n\t\t);\n\t} else {\n\t\tlogger.log(\n\t\t\t` Secrets key: ${hasKey ? keyPath : `none at ${keyPath} — run gkm secrets:init --stage ${options.stage} first`}`,\n\t\t);\n\t}\n\n\tif (options.dryRun) {\n\t\tlogger.log('\\n --dry-run: nothing changed.\\n');\n\t\treturn { repo };\n\t}\n\n\tconst iam = deps.iam ?? (await iamFor(options.profile));\n\tconst providerArn = await ensureOidcProvider(iam).catch((error) => {\n\t\t// The first AWS call is where an expired SSO login surfaces; the SDK's\n\t\t// message does not say which profile, and this command knows.\n\t\tif (/SSO session/i.test(String(error?.message)) && options.profile) {\n\t\t\tthrow new SsoSessionExpired(options.profile);\n\t\t}\n\t\tthrow error;\n\t});\n\tconst roleArn = await ensureRole(\n\t\tiam,\n\t\trole,\n\t\ttrustPolicy(providerArn, repo, options.stage),\n\t\tpolicyArn,\n\t);\n\tlogger.log(`\\n ✓ AWS: ${roleArn}`);\n\n\t// The environment the generated deploy workflow runs the stage in.\n\tgh(['api', '--method', 'PUT', `repos/${repo}/environments/${options.stage}`]);\n\tgh([\n\t\t'variable',\n\t\t'set',\n\t\t'AWS_ROLE_ARN',\n\t\t'--env',\n\t\toptions.stage,\n\t\t'--repo',\n\t\trepo,\n\t\t'--body',\n\t\troleArn,\n\t]);\n\tlogger.log(` ✓ GitHub: environment \"${options.stage}\", AWS_ROLE_ARN`);\n\n\tif (remote) {\n\t\t// Nothing to send: the deploy job reads the stage's store itself.\n\t\tif (!hasSecrets) {\n\t\t\tlogger.log(\n\t\t\t\t` ⚠ The \"${options.stage}\" store holds no secrets yet; set them with gkm secrets:set <KEY> '…' --stage ${options.stage}${options.profile ? ` (AWS_PROFILE=${options.profile})` : ''}.`,\n\t\t\t);\n\t\t}\n\t} else if (hasKey) {\n\t\t// Through stdin, so the key is never an argument a process list shows.\n\t\tgh(\n\t\t\t[\n\t\t\t\t'secret',\n\t\t\t\t'set',\n\t\t\t\t'GKM_SECRETS_KEY',\n\t\t\t\t'--env',\n\t\t\t\toptions.stage,\n\t\t\t\t'--repo',\n\t\t\t\trepo,\n\t\t\t],\n\t\t\tkey!,\n\t\t);\n\t\tlogger.log(' ✓ GitHub: GKM_SECRETS_KEY');\n\t} else {\n\t\tlogger.log(\n\t\t\t` ⚠ No secrets key for \"${options.stage}\"; run gkm secrets:init --stage ${options.stage}, then this again.`,\n\t\t);\n\t}\n\n\tlogger.log(\n\t\t`\\n Protect it: add required reviewers to the \"${options.stage}\" environment in the repository settings.\\n`,\n\t);\n\treturn { roleArn, repo };\n}\n\n/** The profile's SSO login has lapsed; the fix is one command away. */\nexport class SsoSessionExpired extends Error {\n\tconstructor(readonly profile: string) {\n\t\tsuper(\n\t\t\t`The SSO session for profile \"${profile}\" has expired or is invalid. Run: aws sso login --profile ${profile}`,\n\t\t);\n\t\tthis.name = 'SsoSessionExpired';\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-BewAHZIo.cjs");
|
|
2
2
|
const require_config = require("./config.cjs");
|
|
3
|
-
const require_store = require("./store-
|
|
3
|
+
const require_store = require("./store-CJF1Tqkq.cjs");
|
|
4
4
|
let node_child_process = require("node:child_process");
|
|
5
5
|
let _aws_sdk_client_iam = require("@aws-sdk/client-iam");
|
|
6
6
|
//#region src/deploy/github.ts
|
|
@@ -210,4 +210,4 @@ var SsoSessionExpired = class extends Error {
|
|
|
210
210
|
//#endregion
|
|
211
211
|
exports.deployGithubCommand = deployGithubCommand;
|
|
212
212
|
|
|
213
|
-
//# sourceMappingURL=github-
|
|
213
|
+
//# sourceMappingURL=github-CYcmTdFs.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"github-Dd3tMa1u.cjs","names":["execFileSync","ListOpenIDConnectProvidersCommand","CreateOpenIDConnectProviderCommand","GetRoleCommand","UpdateAssumeRolePolicyCommand","CreateRoleCommand","AttachRolePolicyCommand","IAMClient","loadWorkspaceConfig","readKey","keystoreProject","getKeyPath","isRemoteStore","secretsStoreFor"],"sources":["../src/deploy/github.ts"],"sourcesContent":["/**\n * `gkm deploy:github` — let GitHub Actions deploy one stage, with no keys.\n *\n * A stage usually lives in its own AWS account — staging in one, production in\n * another — so this runs once per stage, with that account's profile:\n *\n * gkm deploy:github --stage staging --profile acme-dev\n * gkm deploy:github --stage prod --profile acme-prod\n *\n * In the stage's account it makes sure GitHub's OIDC provider exists, and a\n * role that only this repository's `<stage>` environment can assume. On\n * GitHub it creates that environment and gives it what the generated deploy\n * workflow reads: `AWS_ROLE_ARN`, and — when the stage's secrets are kept in\n * the local file — its key as `GKM_SECRETS_KEY`. With `secrets.store` set to\n * SSM the deploy job pulls them with the role instead, so no key is handed to\n * GitHub, and this pushes the stage's local secrets to SSM in the same account,\n * with the same profile. Re-running it converges rather than duplicating.\n */\n\nimport { execFileSync } from 'node:child_process';\nimport {\n\tAttachRolePolicyCommand,\n\tCreateOpenIDConnectProviderCommand,\n\tCreateRoleCommand,\n\tGetRoleCommand,\n\tIAMClient,\n\ttype IAMClientConfig,\n\tListOpenIDConnectProvidersCommand,\n\tUpdateAssumeRolePolicyCommand,\n} from '@aws-sdk/client-iam';\nimport { loadWorkspaceConfig } from '../config.js';\nimport { getKeyPath, keystoreProject, readKey } from '../secrets/keystore.js';\nimport { isRemoteStore, secretsStoreFor } from '../secrets/store.js';\nimport { assertDeployedStage } from '../workspace/stages.js';\n\nconst logger = console;\n\nconst GITHUB_OIDC_HOST = 'token.actions.githubusercontent.com';\nconst AUDIENCE = 'sts.amazonaws.com';\n\n/**\n * What SST needs to create a stack is most of AWS, so this is the default —\n * said out loud in the output, and replaceable with `--policy-arn`.\n */\nexport const DEFAULT_POLICY_ARN = 'arn:aws:iam::aws:policy/AdministratorAccess';\n\nexport interface DeployGithubOptions {\n\tstage: string;\n\t/** The AWS profile for the stage's account. */\n\tprofile?: string;\n\t/** `owner/name`; defaults to the repository `gh` sees here. */\n\trepo?: string;\n\t/** The policy the deploy role gets. */\n\tpolicyArn?: string;\n\tdryRun?: boolean;\n}\n\n/** Runs `gh` with the given arguments and optional stdin, returning stdout. */\nexport type Gh = (args: string[], input?: string) => string;\n\nconst defaultGh: Gh = (args, input) =>\n\texecFileSync('gh', args, {\n\t\tencoding: 'utf-8',\n\t\t...(input === undefined\n\t\t\t? {}\n\t\t\t: { input, stdio: ['pipe', 'pipe', 'pipe'] as const }),\n\t}).trim();\n\n/** The role a stage deploys as: `<project>-github-<stage>`, within IAM's 64. */\nexport function roleName(project: string, stage: string): string {\n\treturn `${project}-github-${stage}`\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9+=,.@_-]/g, '-')\n\t\t.slice(0, 64);\n}\n\n/**\n * Who may assume the role: a workflow job running in this repository's\n * `<stage>` environment, and nothing else — not another branch's job, not\n * another environment's. That is what keeps a staging deploy from using the\n * production role.\n */\nexport function trustPolicy(\n\tproviderArn: string,\n\trepo: string,\n\tstage: string,\n): string {\n\treturn JSON.stringify({\n\t\tVersion: '2012-10-17',\n\t\tStatement: [\n\t\t\t{\n\t\t\t\tEffect: 'Allow',\n\t\t\t\tPrincipal: { Federated: providerArn },\n\t\t\t\tAction: 'sts:AssumeRoleWithWebIdentity',\n\t\t\t\tCondition: {\n\t\t\t\t\tStringEquals: {\n\t\t\t\t\t\t[`${GITHUB_OIDC_HOST}:aud`]: AUDIENCE,\n\t\t\t\t\t\t[`${GITHUB_OIDC_HOST}:sub`]: `repo:${repo}:environment:${stage}`,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t],\n\t});\n}\n\n/** GitHub's OIDC provider in this account, created if it is not there. */\nexport async function ensureOidcProvider(iam: IAMClient): Promise<string> {\n\tconst { OpenIDConnectProviderList = [] } = await iam.send(\n\t\tnew ListOpenIDConnectProvidersCommand({}),\n\t);\n\tconst existing = OpenIDConnectProviderList.find((p) =>\n\t\tp.Arn?.endsWith(`oidc-provider/${GITHUB_OIDC_HOST}`),\n\t)?.Arn;\n\tif (existing) return existing;\n\n\tconst created = await iam.send(\n\t\tnew CreateOpenIDConnectProviderCommand({\n\t\t\tUrl: `https://${GITHUB_OIDC_HOST}`,\n\t\t\tClientIDList: [AUDIENCE],\n\t\t}),\n\t);\n\treturn created.OpenIDConnectProviderArn!;\n}\n\n/** The deploy role, created or brought back to this trust and policy. */\nexport async function ensureRole(\n\tiam: IAMClient,\n\tname: string,\n\ttrust: string,\n\tpolicyArn: string,\n): Promise<string> {\n\tlet arn: string;\n\ttry {\n\t\tconst { Role } = await iam.send(new GetRoleCommand({ RoleName: name }));\n\t\tawait iam.send(\n\t\t\tnew UpdateAssumeRolePolicyCommand({\n\t\t\t\tRoleName: name,\n\t\t\t\tPolicyDocument: trust,\n\t\t\t}),\n\t\t);\n\t\tarn = Role!.Arn!;\n\t} catch (error) {\n\t\tif ((error as { name?: string }).name !== 'NoSuchEntityException') {\n\t\t\tthrow error;\n\t\t}\n\t\tconst { Role } = await iam.send(\n\t\t\tnew CreateRoleCommand({\n\t\t\t\tRoleName: name,\n\t\t\t\tAssumeRolePolicyDocument: trust,\n\t\t\t\tDescription: 'Deploys from GitHub Actions (gkm deploy:github)',\n\t\t\t\t// SST deploys can run long; the default hour is tight.\n\t\t\t\tMaxSessionDuration: 3600 * 2,\n\t\t\t}),\n\t\t);\n\t\tarn = Role!.Arn!;\n\t}\n\n\t// Attaching an attached policy is a no-op, so this converges too.\n\tawait iam.send(\n\t\tnew AttachRolePolicyCommand({ RoleName: name, PolicyArn: policyArn }),\n\t);\n\treturn arn;\n}\n\n/**\n * An IAM client for the stage's account.\n *\n * `fromIni` for a named profile, deliberately: it resolves exactly that\n * profile — keys, assume-role, `credential_process` or SSO, whichever\n * ~/.aws/config says — and never falls back to AWS_* in the environment. The\n * provider chain checks the environment first, so exported staging keys would\n * quietly win over `--profile prod` and the production role would land in the\n * staging account.\n */\nexport async function iamFor(\n\tprofile?: string,\n\tendpoint?: string,\n): Promise<IAMClient> {\n\tconst config: IAMClientConfig = { region: 'us-east-1' };\n\tif (profile) {\n\t\tconst { fromIni } = await import('@aws-sdk/credential-providers');\n\t\tconfig.credentials = fromIni({ profile });\n\t}\n\tif (endpoint) {\n\t\tconfig.endpoint = endpoint;\n\t\tconfig.credentials = { accessKeyId: 'test', secretAccessKey: 'test' };\n\t}\n\treturn new IAMClient(config);\n}\n\nexport async function deployGithubCommand(\n\toptions: DeployGithubOptions,\n\tdeps: { iam?: IAMClient; gh?: Gh; cwd?: string } = {},\n): Promise<{ roleArn?: string; repo: string }> {\n\tconst gh = deps.gh ?? defaultGh;\n\tconst { workspace } = await loadWorkspaceConfig(deps.cwd);\n\tassertDeployedStage(workspace.stages, options.stage);\n\n\tconst repo =\n\t\toptions.repo ??\n\t\tgh(['repo', 'view', '--json', 'nameWithOwner', '-q', '.nameWithOwner']);\n\tconst role = roleName(workspace.name, options.stage);\n\tconst policyArn = options.policyArn ?? DEFAULT_POLICY_ARN;\n\t// Read through the keystore so a key still at the place keys used to be kept\n\t// is found, and copied to where it is kept now.\n\tconst key = await readKey(options.stage, keystoreProject(workspace));\n\tconst keyPath = getKeyPath(options.stage, keystoreProject(workspace));\n\tconst hasKey = key !== null;\n\t// A stage whose secrets are in a store is read from there by the deploy job,\n\t// with the role; it needs no key on GitHub.\n\tconst remote = isRemoteStore(workspace, options.stage);\n\t// Read from the stage's own store, with the same credentials the role is\n\t// created with: what the deploy job will find there.\n\tconst store = await secretsStoreFor(workspace, options.stage, {\n\t\t...(options.profile ? { profile: options.profile } : {}),\n\t});\n\tconst hasSecrets = remote && (await store.read(options.stage)) !== null;\n\tconst via = options.profile\n\t\t? `profile \"${options.profile}\"`\n\t\t: 'the default credentials';\n\n\tlogger.log(`\\n🔐 GitHub → AWS for stage \"${options.stage}\"\\n`);\n\tlogger.log(` Repository: ${repo}`);\n\tlogger.log(` AWS profile: ${options.profile ?? '(default credentials)'}`);\n\tlogger.log(` Role: ${role}`);\n\tlogger.log(\n\t\t` Policy: ${policyArn}${options.policyArn ? '' : ' (default; --policy-arn to narrow)'}`,\n\t);\n\tlogger.log(` Trusted by: repo:${repo}:environment:${options.stage} only`);\n\tif (remote) {\n\t\tlogger.log(\n\t\t\t` Secrets: ${hasSecrets ? `in the store (${store.name}), read with ${via}` : `none in the store yet — gkm secrets:set <KEY> '…' --stage ${options.stage}`}`,\n\t\t);\n\t} else {\n\t\tlogger.log(\n\t\t\t` Secrets key: ${hasKey ? keyPath : `none at ${keyPath} — run gkm secrets:init --stage ${options.stage} first`}`,\n\t\t);\n\t}\n\n\tif (options.dryRun) {\n\t\tlogger.log('\\n --dry-run: nothing changed.\\n');\n\t\treturn { repo };\n\t}\n\n\tconst iam = deps.iam ?? (await iamFor(options.profile));\n\tconst providerArn = await ensureOidcProvider(iam).catch((error) => {\n\t\t// The first AWS call is where an expired SSO login surfaces; the SDK's\n\t\t// message does not say which profile, and this command knows.\n\t\tif (/SSO session/i.test(String(error?.message)) && options.profile) {\n\t\t\tthrow new SsoSessionExpired(options.profile);\n\t\t}\n\t\tthrow error;\n\t});\n\tconst roleArn = await ensureRole(\n\t\tiam,\n\t\trole,\n\t\ttrustPolicy(providerArn, repo, options.stage),\n\t\tpolicyArn,\n\t);\n\tlogger.log(`\\n ✓ AWS: ${roleArn}`);\n\n\t// The environment the generated deploy workflow runs the stage in.\n\tgh(['api', '--method', 'PUT', `repos/${repo}/environments/${options.stage}`]);\n\tgh([\n\t\t'variable',\n\t\t'set',\n\t\t'AWS_ROLE_ARN',\n\t\t'--env',\n\t\toptions.stage,\n\t\t'--repo',\n\t\trepo,\n\t\t'--body',\n\t\troleArn,\n\t]);\n\tlogger.log(` ✓ GitHub: environment \"${options.stage}\", AWS_ROLE_ARN`);\n\n\tif (remote) {\n\t\t// Nothing to send: the deploy job reads the stage's store itself.\n\t\tif (!hasSecrets) {\n\t\t\tlogger.log(\n\t\t\t\t` ⚠ The \"${options.stage}\" store holds no secrets yet; set them with gkm secrets:set <KEY> '…' --stage ${options.stage}${options.profile ? ` (AWS_PROFILE=${options.profile})` : ''}.`,\n\t\t\t);\n\t\t}\n\t} else if (hasKey) {\n\t\t// Through stdin, so the key is never an argument a process list shows.\n\t\tgh(\n\t\t\t[\n\t\t\t\t'secret',\n\t\t\t\t'set',\n\t\t\t\t'GKM_SECRETS_KEY',\n\t\t\t\t'--env',\n\t\t\t\toptions.stage,\n\t\t\t\t'--repo',\n\t\t\t\trepo,\n\t\t\t],\n\t\t\tkey!,\n\t\t);\n\t\tlogger.log(' ✓ GitHub: GKM_SECRETS_KEY');\n\t} else {\n\t\tlogger.log(\n\t\t\t` ⚠ No secrets key for \"${options.stage}\"; run gkm secrets:init --stage ${options.stage}, then this again.`,\n\t\t);\n\t}\n\n\tlogger.log(\n\t\t`\\n Protect it: add required reviewers to the \"${options.stage}\" environment in the repository settings.\\n`,\n\t);\n\treturn { roleArn, repo };\n}\n\n/** The profile's SSO login has lapsed; the fix is one command away. */\nexport class SsoSessionExpired extends Error {\n\tconstructor(readonly profile: string) {\n\t\tsuper(\n\t\t\t`The SSO session for profile \"${profile}\" has expired or is invalid. Run: aws sso login --profile ${profile}`,\n\t\t);\n\t\tthis.name = 'SsoSessionExpired';\n\t}\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"github-CYcmTdFs.cjs","names":["execFileSync","ListOpenIDConnectProvidersCommand","CreateOpenIDConnectProviderCommand","GetRoleCommand","UpdateAssumeRolePolicyCommand","CreateRoleCommand","AttachRolePolicyCommand","IAMClient","loadWorkspaceConfig","readKey","keystoreProject","getKeyPath","isRemoteStore","secretsStoreFor"],"sources":["../src/deploy/github.ts"],"sourcesContent":["/**\n * `gkm deploy:github` — let GitHub Actions deploy one stage, with no keys.\n *\n * A stage usually lives in its own AWS account — staging in one, production in\n * another — so this runs once per stage, with that account's profile:\n *\n * gkm deploy:github --stage staging --profile acme-dev\n * gkm deploy:github --stage prod --profile acme-prod\n *\n * In the stage's account it makes sure GitHub's OIDC provider exists, and a\n * role that only this repository's `<stage>` environment can assume. On\n * GitHub it creates that environment and gives it what the generated deploy\n * workflow reads: `AWS_ROLE_ARN`, and — when the stage's secrets are kept in\n * the local file — its key as `GKM_SECRETS_KEY`. With `secrets.store` set to\n * SSM the deploy job pulls them with the role instead, so no key is handed to\n * GitHub, and this pushes the stage's local secrets to SSM in the same account,\n * with the same profile. Re-running it converges rather than duplicating.\n */\n\nimport { execFileSync } from 'node:child_process';\nimport {\n\tAttachRolePolicyCommand,\n\tCreateOpenIDConnectProviderCommand,\n\tCreateRoleCommand,\n\tGetRoleCommand,\n\tIAMClient,\n\ttype IAMClientConfig,\n\tListOpenIDConnectProvidersCommand,\n\tUpdateAssumeRolePolicyCommand,\n} from '@aws-sdk/client-iam';\nimport { loadWorkspaceConfig } from '../config.js';\nimport { getKeyPath, keystoreProject, readKey } from '../secrets/keystore.js';\nimport { isRemoteStore, secretsStoreFor } from '../secrets/store.js';\nimport { assertDeployedStage } from '../workspace/stages.js';\n\nconst logger = console;\n\nconst GITHUB_OIDC_HOST = 'token.actions.githubusercontent.com';\nconst AUDIENCE = 'sts.amazonaws.com';\n\n/**\n * What SST needs to create a stack is most of AWS, so this is the default —\n * said out loud in the output, and replaceable with `--policy-arn`.\n */\nexport const DEFAULT_POLICY_ARN = 'arn:aws:iam::aws:policy/AdministratorAccess';\n\nexport interface DeployGithubOptions {\n\tstage: string;\n\t/** The AWS profile for the stage's account. */\n\tprofile?: string;\n\t/** `owner/name`; defaults to the repository `gh` sees here. */\n\trepo?: string;\n\t/** The policy the deploy role gets. */\n\tpolicyArn?: string;\n\tdryRun?: boolean;\n}\n\n/** Runs `gh` with the given arguments and optional stdin, returning stdout. */\nexport type Gh = (args: string[], input?: string) => string;\n\nconst defaultGh: Gh = (args, input) =>\n\texecFileSync('gh', args, {\n\t\tencoding: 'utf-8',\n\t\t...(input === undefined\n\t\t\t? {}\n\t\t\t: { input, stdio: ['pipe', 'pipe', 'pipe'] as const }),\n\t}).trim();\n\n/** The role a stage deploys as: `<project>-github-<stage>`, within IAM's 64. */\nexport function roleName(project: string, stage: string): string {\n\treturn `${project}-github-${stage}`\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9+=,.@_-]/g, '-')\n\t\t.slice(0, 64);\n}\n\n/**\n * Who may assume the role: a workflow job running in this repository's\n * `<stage>` environment, and nothing else — not another branch's job, not\n * another environment's. That is what keeps a staging deploy from using the\n * production role.\n */\nexport function trustPolicy(\n\tproviderArn: string,\n\trepo: string,\n\tstage: string,\n): string {\n\treturn JSON.stringify({\n\t\tVersion: '2012-10-17',\n\t\tStatement: [\n\t\t\t{\n\t\t\t\tEffect: 'Allow',\n\t\t\t\tPrincipal: { Federated: providerArn },\n\t\t\t\tAction: 'sts:AssumeRoleWithWebIdentity',\n\t\t\t\tCondition: {\n\t\t\t\t\tStringEquals: {\n\t\t\t\t\t\t[`${GITHUB_OIDC_HOST}:aud`]: AUDIENCE,\n\t\t\t\t\t\t[`${GITHUB_OIDC_HOST}:sub`]: `repo:${repo}:environment:${stage}`,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t],\n\t});\n}\n\n/** GitHub's OIDC provider in this account, created if it is not there. */\nexport async function ensureOidcProvider(iam: IAMClient): Promise<string> {\n\tconst { OpenIDConnectProviderList = [] } = await iam.send(\n\t\tnew ListOpenIDConnectProvidersCommand({}),\n\t);\n\tconst existing = OpenIDConnectProviderList.find((p) =>\n\t\tp.Arn?.endsWith(`oidc-provider/${GITHUB_OIDC_HOST}`),\n\t)?.Arn;\n\tif (existing) return existing;\n\n\tconst created = await iam.send(\n\t\tnew CreateOpenIDConnectProviderCommand({\n\t\t\tUrl: `https://${GITHUB_OIDC_HOST}`,\n\t\t\tClientIDList: [AUDIENCE],\n\t\t}),\n\t);\n\treturn created.OpenIDConnectProviderArn!;\n}\n\n/** The deploy role, created or brought back to this trust and policy. */\nexport async function ensureRole(\n\tiam: IAMClient,\n\tname: string,\n\ttrust: string,\n\tpolicyArn: string,\n): Promise<string> {\n\tlet arn: string;\n\ttry {\n\t\tconst { Role } = await iam.send(new GetRoleCommand({ RoleName: name }));\n\t\tawait iam.send(\n\t\t\tnew UpdateAssumeRolePolicyCommand({\n\t\t\t\tRoleName: name,\n\t\t\t\tPolicyDocument: trust,\n\t\t\t}),\n\t\t);\n\t\tarn = Role!.Arn!;\n\t} catch (error) {\n\t\tif ((error as { name?: string }).name !== 'NoSuchEntityException') {\n\t\t\tthrow error;\n\t\t}\n\t\tconst { Role } = await iam.send(\n\t\t\tnew CreateRoleCommand({\n\t\t\t\tRoleName: name,\n\t\t\t\tAssumeRolePolicyDocument: trust,\n\t\t\t\tDescription: 'Deploys from GitHub Actions (gkm deploy:github)',\n\t\t\t\t// SST deploys can run long; the default hour is tight.\n\t\t\t\tMaxSessionDuration: 3600 * 2,\n\t\t\t}),\n\t\t);\n\t\tarn = Role!.Arn!;\n\t}\n\n\t// Attaching an attached policy is a no-op, so this converges too.\n\tawait iam.send(\n\t\tnew AttachRolePolicyCommand({ RoleName: name, PolicyArn: policyArn }),\n\t);\n\treturn arn;\n}\n\n/**\n * An IAM client for the stage's account.\n *\n * `fromIni` for a named profile, deliberately: it resolves exactly that\n * profile — keys, assume-role, `credential_process` or SSO, whichever\n * ~/.aws/config says — and never falls back to AWS_* in the environment. The\n * provider chain checks the environment first, so exported staging keys would\n * quietly win over `--profile prod` and the production role would land in the\n * staging account.\n */\nexport async function iamFor(\n\tprofile?: string,\n\tendpoint?: string,\n): Promise<IAMClient> {\n\tconst config: IAMClientConfig = { region: 'us-east-1' };\n\tif (profile) {\n\t\tconst { fromIni } = await import('@aws-sdk/credential-providers');\n\t\tconfig.credentials = fromIni({ profile });\n\t}\n\tif (endpoint) {\n\t\tconfig.endpoint = endpoint;\n\t\tconfig.credentials = { accessKeyId: 'test', secretAccessKey: 'test' };\n\t}\n\treturn new IAMClient(config);\n}\n\nexport async function deployGithubCommand(\n\toptions: DeployGithubOptions,\n\tdeps: { iam?: IAMClient; gh?: Gh; cwd?: string } = {},\n): Promise<{ roleArn?: string; repo: string }> {\n\tconst gh = deps.gh ?? defaultGh;\n\tconst { workspace } = await loadWorkspaceConfig(deps.cwd);\n\tassertDeployedStage(workspace.stages, options.stage);\n\n\tconst repo =\n\t\toptions.repo ??\n\t\tgh(['repo', 'view', '--json', 'nameWithOwner', '-q', '.nameWithOwner']);\n\tconst role = roleName(workspace.name, options.stage);\n\tconst policyArn = options.policyArn ?? DEFAULT_POLICY_ARN;\n\t// Read through the keystore so a key still at the place keys used to be kept\n\t// is found, and copied to where it is kept now.\n\tconst key = await readKey(options.stage, keystoreProject(workspace));\n\tconst keyPath = getKeyPath(options.stage, keystoreProject(workspace));\n\tconst hasKey = key !== null;\n\t// A stage whose secrets are in a store is read from there by the deploy job,\n\t// with the role; it needs no key on GitHub.\n\tconst remote = isRemoteStore(workspace, options.stage);\n\t// Read from the stage's own store, with the same credentials the role is\n\t// created with: what the deploy job will find there.\n\tconst store = await secretsStoreFor(workspace, options.stage, {\n\t\t...(options.profile ? { profile: options.profile } : {}),\n\t});\n\tconst hasSecrets = remote && (await store.read(options.stage)) !== null;\n\tconst via = options.profile\n\t\t? `profile \"${options.profile}\"`\n\t\t: 'the default credentials';\n\n\tlogger.log(`\\n🔐 GitHub → AWS for stage \"${options.stage}\"\\n`);\n\tlogger.log(` Repository: ${repo}`);\n\tlogger.log(` AWS profile: ${options.profile ?? '(default credentials)'}`);\n\tlogger.log(` Role: ${role}`);\n\tlogger.log(\n\t\t` Policy: ${policyArn}${options.policyArn ? '' : ' (default; --policy-arn to narrow)'}`,\n\t);\n\tlogger.log(` Trusted by: repo:${repo}:environment:${options.stage} only`);\n\tif (remote) {\n\t\tlogger.log(\n\t\t\t` Secrets: ${hasSecrets ? `in the store (${store.name}), read with ${via}` : `none in the store yet — gkm secrets:set <KEY> '…' --stage ${options.stage}`}`,\n\t\t);\n\t} else {\n\t\tlogger.log(\n\t\t\t` Secrets key: ${hasKey ? keyPath : `none at ${keyPath} — run gkm secrets:init --stage ${options.stage} first`}`,\n\t\t);\n\t}\n\n\tif (options.dryRun) {\n\t\tlogger.log('\\n --dry-run: nothing changed.\\n');\n\t\treturn { repo };\n\t}\n\n\tconst iam = deps.iam ?? (await iamFor(options.profile));\n\tconst providerArn = await ensureOidcProvider(iam).catch((error) => {\n\t\t// The first AWS call is where an expired SSO login surfaces; the SDK's\n\t\t// message does not say which profile, and this command knows.\n\t\tif (/SSO session/i.test(String(error?.message)) && options.profile) {\n\t\t\tthrow new SsoSessionExpired(options.profile);\n\t\t}\n\t\tthrow error;\n\t});\n\tconst roleArn = await ensureRole(\n\t\tiam,\n\t\trole,\n\t\ttrustPolicy(providerArn, repo, options.stage),\n\t\tpolicyArn,\n\t);\n\tlogger.log(`\\n ✓ AWS: ${roleArn}`);\n\n\t// The environment the generated deploy workflow runs the stage in.\n\tgh(['api', '--method', 'PUT', `repos/${repo}/environments/${options.stage}`]);\n\tgh([\n\t\t'variable',\n\t\t'set',\n\t\t'AWS_ROLE_ARN',\n\t\t'--env',\n\t\toptions.stage,\n\t\t'--repo',\n\t\trepo,\n\t\t'--body',\n\t\troleArn,\n\t]);\n\tlogger.log(` ✓ GitHub: environment \"${options.stage}\", AWS_ROLE_ARN`);\n\n\tif (remote) {\n\t\t// Nothing to send: the deploy job reads the stage's store itself.\n\t\tif (!hasSecrets) {\n\t\t\tlogger.log(\n\t\t\t\t` ⚠ The \"${options.stage}\" store holds no secrets yet; set them with gkm secrets:set <KEY> '…' --stage ${options.stage}${options.profile ? ` (AWS_PROFILE=${options.profile})` : ''}.`,\n\t\t\t);\n\t\t}\n\t} else if (hasKey) {\n\t\t// Through stdin, so the key is never an argument a process list shows.\n\t\tgh(\n\t\t\t[\n\t\t\t\t'secret',\n\t\t\t\t'set',\n\t\t\t\t'GKM_SECRETS_KEY',\n\t\t\t\t'--env',\n\t\t\t\toptions.stage,\n\t\t\t\t'--repo',\n\t\t\t\trepo,\n\t\t\t],\n\t\t\tkey!,\n\t\t);\n\t\tlogger.log(' ✓ GitHub: GKM_SECRETS_KEY');\n\t} else {\n\t\tlogger.log(\n\t\t\t` ⚠ No secrets key for \"${options.stage}\"; run gkm secrets:init --stage ${options.stage}, then this again.`,\n\t\t);\n\t}\n\n\tlogger.log(\n\t\t`\\n Protect it: add required reviewers to the \"${options.stage}\" environment in the repository settings.\\n`,\n\t);\n\treturn { roleArn, repo };\n}\n\n/** The profile's SSO login has lapsed; the fix is one command away. */\nexport class SsoSessionExpired extends Error {\n\tconstructor(readonly profile: string) {\n\t\tsuper(\n\t\t\t`The SSO session for profile \"${profile}\" has expired or is invalid. Run: aws sso login --profile ${profile}`,\n\t\t);\n\t\tthis.name = 'SsoSessionExpired';\n\t}\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as WorkspaceConfig, D as Framework, _ as LoadedConfig, a as Routes, b as NormalizedWorkspace, g as InferredWorkspaceConfig, l as AppsRecord, r as GkmConfig, w as WorkspaceInput } from "./types-
|
|
1
|
+
import { C as WorkspaceConfig, D as Framework, _ as LoadedConfig, a as Routes, b as NormalizedWorkspace, g as InferredWorkspaceConfig, l as AppsRecord, r as GkmConfig, w as WorkspaceInput } from "./types-ByMWkXKP.cjs";
|
|
2
2
|
//#region src/workspace/publicEnv.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* All public env-var prefixes the toolbox understands.
|
|
@@ -82,6 +82,19 @@ declare function wrapSingleAppAsWorkspace(config: GkmConfig, cwd: string): Norma
|
|
|
82
82
|
* Process a loaded configuration (either single-app or workspace).
|
|
83
83
|
* Returns a normalized workspace in both cases.
|
|
84
84
|
*/
|
|
85
|
+
/**
|
|
86
|
+
* `deploy.dokploy.registry`, which became `deploy.registry`: every target
|
|
87
|
+
* pushes and pulls through the one registry, so it is not Dokploy's to hold.
|
|
88
|
+
*
|
|
89
|
+
* Refused by name rather than dropped: the schema strips keys it does not
|
|
90
|
+
* know, and a registry silently gone would deploy images under no registry.
|
|
91
|
+
*/
|
|
92
|
+
declare class DokployRegistryMoved extends Error {
|
|
93
|
+
readonly registry: unknown;
|
|
94
|
+
constructor(registry: unknown);
|
|
95
|
+
}
|
|
96
|
+
/** Refuse a deploy key that has moved, naming where it lives now. */
|
|
97
|
+
declare function assertNoMovedDeployKeys(deploy: unknown): void;
|
|
85
98
|
declare function processConfig(config: GkmConfig | WorkspaceConfig, cwd: string): LoadedConfig;
|
|
86
99
|
/**
|
|
87
100
|
* The workspace's own constructs globs, absolute.
|
|
@@ -152,5 +165,5 @@ declare function getEndpointForStage(dokployConfig: {
|
|
|
152
165
|
endpoints?: Record<string, string>;
|
|
153
166
|
} | undefined, stage: string): string | undefined;
|
|
154
167
|
//#endregion
|
|
155
|
-
export {
|
|
156
|
-
//# sourceMappingURL=index-
|
|
168
|
+
export { constructPatterns as a, getAppGkmConfig as c, processConfig as d, workspaceConstructGlobs as f, stripPublicPrefix as g, getPublicEnvPrefix as h, assertNoMovedDeployKeys as i, getEndpointForStage as l, PUBLIC_ENV_PREFIXES as m, allConstructGlobs as n, defineWorkspace as o, wrapSingleAppAsWorkspace as p, appConstructGlobs as r, getAppBuildOrder as s, DokployRegistryMoved as t, normalizeWorkspace as u };
|
|
169
|
+
//# sourceMappingURL=index-DPUAGJaK.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-DPUAGJaK.d.cts","names":[],"sources":["../src/workspace/publicEnv.ts","../src/workspace/index.ts"],"mappings":";;;;;;;;;cASa;;;;;;;;;;;;;;;iBAoBG,mBAAmB,YAAY;;;;;iBAoB/B,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBC2ElB,sBAAsB,cAAc,YACnD,QAAQ,eAAe,SACrB,wBAAwB;;;;iBAgCX,mBACf,QAAQ,iBACR,cACE;;;;;iBAgDa,yBACf,QAAQ,WACR,cACE;;;;;;;;;;;;cA8DU,6BAA6B;WACpB;EAArB,YAAqB;;;iBAUN,wBAAwB;iBAOxB,cACf,QAAQ,YAAY,iBACpB,cACE;;;;;;;iBA+Ba,wBACf,WAAW;;;;;;;;;;;;iBAkBI,kBACf,WAAW,qBACX;;;;;;;iBAsBe,kBAAkB,WAAW;;iBAa7B,kBAAkB,QAAQ;;;;;iBAS1B,gBACf,WAAW,qBACX,kBACE;;;;;iBAgCa,iBAAiB,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAoD5B,oBACf;EACK;EAAmB,YAAY;eAEpC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as WorkspaceConfig, D as Framework, _ as LoadedConfig, a as Routes, b as NormalizedWorkspace, g as InferredWorkspaceConfig, l as AppsRecord, r as GkmConfig, w as WorkspaceInput } from "./types-
|
|
1
|
+
import { C as WorkspaceConfig, D as Framework, _ as LoadedConfig, a as Routes, b as NormalizedWorkspace, g as InferredWorkspaceConfig, l as AppsRecord, r as GkmConfig, w as WorkspaceInput } from "./types-BCzMWBb6.mjs";
|
|
2
2
|
//#region src/workspace/publicEnv.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* All public env-var prefixes the toolbox understands.
|
|
@@ -82,6 +82,19 @@ declare function wrapSingleAppAsWorkspace(config: GkmConfig, cwd: string): Norma
|
|
|
82
82
|
* Process a loaded configuration (either single-app or workspace).
|
|
83
83
|
* Returns a normalized workspace in both cases.
|
|
84
84
|
*/
|
|
85
|
+
/**
|
|
86
|
+
* `deploy.dokploy.registry`, which became `deploy.registry`: every target
|
|
87
|
+
* pushes and pulls through the one registry, so it is not Dokploy's to hold.
|
|
88
|
+
*
|
|
89
|
+
* Refused by name rather than dropped: the schema strips keys it does not
|
|
90
|
+
* know, and a registry silently gone would deploy images under no registry.
|
|
91
|
+
*/
|
|
92
|
+
declare class DokployRegistryMoved extends Error {
|
|
93
|
+
readonly registry: unknown;
|
|
94
|
+
constructor(registry: unknown);
|
|
95
|
+
}
|
|
96
|
+
/** Refuse a deploy key that has moved, naming where it lives now. */
|
|
97
|
+
declare function assertNoMovedDeployKeys(deploy: unknown): void;
|
|
85
98
|
declare function processConfig(config: GkmConfig | WorkspaceConfig, cwd: string): LoadedConfig;
|
|
86
99
|
/**
|
|
87
100
|
* The workspace's own constructs globs, absolute.
|
|
@@ -152,5 +165,5 @@ declare function getEndpointForStage(dokployConfig: {
|
|
|
152
165
|
endpoints?: Record<string, string>;
|
|
153
166
|
} | undefined, stage: string): string | undefined;
|
|
154
167
|
//#endregion
|
|
155
|
-
export {
|
|
156
|
-
//# sourceMappingURL=index-
|
|
168
|
+
export { constructPatterns as a, getAppGkmConfig as c, processConfig as d, workspaceConstructGlobs as f, stripPublicPrefix as g, getPublicEnvPrefix as h, assertNoMovedDeployKeys as i, getEndpointForStage as l, PUBLIC_ENV_PREFIXES as m, allConstructGlobs as n, defineWorkspace as o, wrapSingleAppAsWorkspace as p, appConstructGlobs as r, getAppBuildOrder as s, DokployRegistryMoved as t, normalizeWorkspace as u };
|
|
169
|
+
//# sourceMappingURL=index-DwKD_8lt.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-DwKD_8lt.d.mts","names":[],"sources":["../src/workspace/publicEnv.ts","../src/workspace/index.ts"],"mappings":";;;;;;;;;cASa;;;;;;;;;;;;;;;iBAoBG,mBAAmB,YAAY;;;;;iBAoB/B,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBC2ElB,sBAAsB,cAAc,YACnD,QAAQ,eAAe,SACrB,wBAAwB;;;;iBAgCX,mBACf,QAAQ,iBACR,cACE;;;;;iBAgDa,yBACf,QAAQ,WACR,cACE;;;;;;;;;;;;cA8DU,6BAA6B;WACpB;EAArB,YAAqB;;;iBAUN,wBAAwB;iBAOxB,cACf,QAAQ,YAAY,iBACpB,cACE;;;;;;;iBA+Ba,wBACf,WAAW;;;;;;;;;;;;iBAkBI,kBACf,WAAW,qBACX;;;;;;;iBAsBe,kBAAkB,WAAW;;iBAa7B,kBAAkB,QAAQ;;;;;iBAS1B,gBACf,WAAW,qBACX,kBACE;;;;;iBAgCa,iBAAiB,WAAW;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAoD5B,oBACf;EACK;EAAmB,YAAY;eAEpC"}
|