@geekmidas/cli 10.0.0-alpha.73 → 10.0.0-alpha.75
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{LegacyStateStore-D_78eaHM.cjs → LegacyStateStore-BFnXUFlx.cjs} +2 -2
- package/dist/{LegacyStateStore-D_78eaHM.cjs.map → LegacyStateStore-BFnXUFlx.cjs.map} +1 -1
- package/dist/{LegacyStateStore-DTo7z924.mjs → LegacyStateStore-cpt3K1vx.mjs} +2 -2
- package/dist/{LegacyStateStore-DTo7z924.mjs.map → LegacyStateStore-cpt3K1vx.mjs.map} +1 -1
- package/dist/{LocalStateStore-CNjgkRk4.mjs → LocalStateStore-RH9F2ZEy.mjs} +2 -2
- package/dist/{LocalStateStore-CNjgkRk4.mjs.map → LocalStateStore-RH9F2ZEy.mjs.map} +1 -1
- package/dist/{LocalStateStore-BhrlQIXD.cjs → LocalStateStore-zQGZ9b4J.cjs} +2 -2
- package/dist/{LocalStateStore-BhrlQIXD.cjs.map → LocalStateStore-zQGZ9b4J.cjs.map} +1 -1
- package/dist/{S3StateStore-oCxRs6HM.cjs → S3StateStore-CT-ZMlQY.cjs} +2 -2
- package/dist/{S3StateStore-oCxRs6HM.cjs.map → S3StateStore-CT-ZMlQY.cjs.map} +1 -1
- package/dist/{S3StateStore-g2P7T_Mo.mjs → S3StateStore-DBeBc0c0.mjs} +2 -2
- package/dist/{S3StateStore-g2P7T_Mo.mjs.map → S3StateStore-DBeBc0c0.mjs.map} +1 -1
- package/dist/{SSMStateStore-DXp0m7XI.cjs → SSMStateStore-HmsH3c4v.cjs} +2 -2
- package/dist/{SSMStateStore-DXp0m7XI.cjs.map → SSMStateStore-HmsH3c4v.cjs.map} +1 -1
- package/dist/{SSMStateStore-CYenunmK.mjs → SSMStateStore-rnzNfOaV.mjs} +2 -2
- package/dist/{SSMStateStore-CYenunmK.mjs.map → SSMStateStore-rnzNfOaV.mjs.map} +1 -1
- package/dist/{SubscriberGenerator-DZk_pKDh.mjs → SubscriberGenerator-5looQ3gB.mjs} +18 -2
- package/dist/SubscriberGenerator-5looQ3gB.mjs.map +1 -0
- package/dist/{SubscriberGenerator-Bixm28fN.cjs → SubscriberGenerator-CajwOa5W.cjs} +29 -1
- package/dist/SubscriberGenerator-CajwOa5W.cjs.map +1 -0
- package/dist/{add-C1ojB2Yy.cjs → add-CHBin8TF.cjs} +2 -2
- package/dist/{add-C1ojB2Yy.cjs.map → add-CHBin8TF.cjs.map} +1 -1
- package/dist/{add-BiPlBUq3.mjs → add-Cvzdw8cf.mjs} +2 -2
- package/dist/{add-BiPlBUq3.mjs.map → add-Cvzdw8cf.mjs.map} +1 -1
- package/dist/{bundler-C35h0WKt.cjs → bundler-_8AEZhrs.cjs} +5 -5
- package/dist/{bundler-C35h0WKt.cjs.map → bundler-_8AEZhrs.cjs.map} +1 -1
- package/dist/{bundler-C2LZoLdP.mjs → bundler-xjp5RwRS.mjs} +5 -5
- package/dist/{bundler-C2LZoLdP.mjs.map → bundler-xjp5RwRS.mjs.map} +1 -1
- package/dist/{cli-DJVL2sL9.cjs → cli-B4ktxAsM.cjs} +3 -3
- package/dist/{cli-DJVL2sL9.cjs.map → cli-B4ktxAsM.cjs.map} +1 -1
- package/dist/{cli-BfdfyTF6.mjs → cli-BTGfA0B7.mjs} +3 -3
- package/dist/{cli-BfdfyTF6.mjs.map → cli-BTGfA0B7.mjs.map} +1 -1
- package/dist/{compose-9II-0IsK.mjs → compose-DdH2JQJD.mjs} +418 -159
- package/dist/compose-DdH2JQJD.mjs.map +1 -0
- package/dist/{compose-CNnw8meq.cjs → compose-UKyv_1yU.cjs} +440 -157
- package/dist/compose-UKyv_1yU.cjs.map +1 -0
- package/dist/{config-RLuR2o9A.d.cts → config-B38YWicj.d.cts} +3 -3
- package/dist/{config-B8OR_TrO.d.mts.map → config-B38YWicj.d.cts.map} +1 -1
- package/dist/{config-B8OR_TrO.d.mts → config-DA0kRTQe.d.mts} +3 -3
- package/dist/{config-RLuR2o9A.d.cts.map → config-DA0kRTQe.d.mts.map} +1 -1
- package/dist/config.d.cts +3 -3
- package/dist/config.d.mts +3 -3
- package/dist/deploy/public.cjs +7 -6
- package/dist/deploy/public.d.cts +14 -3
- package/dist/deploy/public.d.cts.map +1 -1
- package/dist/deploy/public.d.mts +14 -3
- package/dist/deploy/public.d.mts.map +1 -1
- package/dist/deploy/public.mjs +7 -7
- package/dist/{deploy-nmov4j0B.mjs → deploy-CuSsiFIc.mjs} +21 -5
- package/dist/deploy-CuSsiFIc.mjs.map +1 -0
- package/dist/{deploy-BitZLdnY.cjs → deploy-XUZoA1us.cjs} +26 -4
- package/dist/deploy-XUZoA1us.cjs.map +1 -0
- package/dist/{dokploy-Ce_VTytN.cjs → dokploy-Cvgg_pID.cjs} +2 -2
- package/dist/{dokploy-Ce_VTytN.cjs.map → dokploy-Cvgg_pID.cjs.map} +1 -1
- package/dist/{dokploy-CZ1vZt21.mjs → dokploy-DZXTQX10.mjs} +2 -2
- package/dist/{dokploy-CZ1vZt21.mjs.map → dokploy-DZXTQX10.mjs.map} +1 -1
- package/dist/drivers-BdvtCKc7.cjs +200 -0
- package/dist/drivers-BdvtCKc7.cjs.map +1 -0
- package/dist/drivers-Ch1rJitn.mjs +177 -0
- package/dist/drivers-Ch1rJitn.mjs.map +1 -0
- package/dist/{encryption-CmfPICSN.mjs → encryption-JkeMPugf.mjs} +2 -2
- package/dist/{encryption-CmfPICSN.mjs.map → encryption-JkeMPugf.mjs.map} +1 -1
- package/dist/{encryption-CDGRdcMr.cjs → encryption-w-Ehh9DW.cjs} +2 -2
- package/dist/{encryption-CDGRdcMr.cjs.map → encryption-w-Ehh9DW.cjs.map} +1 -1
- package/dist/{engine-B37qBO5W.mjs → engine-CmozkHxq.mjs} +192 -4
- package/dist/engine-CmozkHxq.mjs.map +1 -0
- package/dist/{engine-DL2-PW6m.cjs → engine-zpWBMsQq.cjs} +195 -7
- package/dist/engine-zpWBMsQq.cjs.map +1 -0
- package/dist/{github-C6qF8kHg.cjs → github-BGdJ-Lnm.cjs} +2 -2
- package/dist/{github-C6qF8kHg.cjs.map → github-BGdJ-Lnm.cjs.map} +1 -1
- package/dist/{github-hjHHm7jM.mjs → github-BNngHoId.mjs} +2 -2
- package/dist/{github-hjHHm7jM.mjs.map → github-BNngHoId.mjs.map} +1 -1
- package/dist/{index-DHyTqGG3.d.mts → index-B-1lLGzy.d.mts} +2 -2
- package/dist/{index-DHyTqGG3.d.mts.map → index-B-1lLGzy.d.mts.map} +1 -1
- package/dist/{index-Dg-UZ4Hm.d.cts → index-vMidfqhU.d.cts} +2 -2
- package/dist/{index-Dg-UZ4Hm.d.cts.map → index-vMidfqhU.d.cts.map} +1 -1
- package/dist/index.cjs +89 -52
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +86 -49
- package/dist/index.mjs.map +1 -1
- package/dist/{migrate-CFFC3esE.mjs → migrate-BLL1RTAj.mjs} +2 -2
- package/dist/{migrate-CFFC3esE.mjs.map → migrate-BLL1RTAj.mjs.map} +1 -1
- package/dist/{migrate-kA_j1TPK.cjs → migrate-CgjlYeun.cjs} +2 -2
- package/dist/{migrate-kA_j1TPK.cjs.map → migrate-CgjlYeun.cjs.map} +1 -1
- package/dist/openapi.d.cts +1 -1
- package/dist/openapi.d.mts +1 -1
- package/dist/{ready-BIuMw6wH.cjs → ready-BB9xS4S9.cjs} +6 -188
- package/dist/ready-BB9xS4S9.cjs.map +1 -0
- package/dist/{ready-C0U0NYfB.mjs → ready-ZZoNskrr.mjs} +5 -169
- package/dist/ready-ZZoNskrr.mjs.map +1 -0
- package/dist/reconcile/public.d.cts +1 -1
- package/dist/reconcile/public.d.mts +1 -1
- package/dist/{resolve-CV4eQkDa.mjs → resolve-DZNx7rLy.mjs} +3 -3
- package/dist/{resolve-CV4eQkDa.mjs.map → resolve-DZNx7rLy.mjs.map} +1 -1
- package/dist/{resolve-E5RfY0bm.cjs → resolve-fevXn-wZ.cjs} +3 -3
- package/dist/{resolve-E5RfY0bm.cjs.map → resolve-fevXn-wZ.cjs.map} +1 -1
- package/dist/{rollback-C-V-Rf4a.mjs → rollback-CLi5Pln_.mjs} +4 -4
- package/dist/{rollback-C-V-Rf4a.mjs.map → rollback-CLi5Pln_.mjs.map} +1 -1
- package/dist/{rollback-QGwomD64.cjs → rollback-Dw-FW3X2.cjs} +4 -4
- package/dist/{rollback-QGwomD64.cjs.map → rollback-Dw-FW3X2.cjs.map} +1 -1
- package/dist/{runtime-CWYLIeCU.d.mts → runtime-BzZD5HHi.d.cts} +2 -2
- package/dist/{runtime-CWYLIeCU.d.mts.map → runtime-BzZD5HHi.d.cts.map} +1 -1
- package/dist/{runtime-CmuQOT6A.d.cts → runtime-Cm6J6L7n.d.mts} +2 -2
- package/dist/{runtime-CmuQOT6A.d.cts.map → runtime-Cm6J6L7n.d.mts.map} +1 -1
- package/dist/{sniffer-BFmdZpZ2.mjs → sniffer-BmCvVyhq.mjs} +59 -7
- package/dist/sniffer-BmCvVyhq.mjs.map +1 -0
- package/dist/{sniffer-C-T7dSMN.cjs → sniffer-bMzrX4cO.cjs} +75 -5
- package/dist/{sniffer-C-T7dSMN.cjs.map → sniffer-bMzrX4cO.cjs.map} +1 -1
- package/dist/{state-DtS2HEfi.mjs → state-BrWIOce_.mjs} +60 -293
- package/dist/state-BrWIOce_.mjs.map +1 -0
- package/dist/{state-DMjFPOhf.cjs → state-kA4Fkufx.cjs} +70 -321
- package/dist/state-kA4Fkufx.cjs.map +1 -0
- package/dist/target/public.cjs +1 -1
- package/dist/target/public.d.cts +2 -2
- package/dist/target/public.d.mts +2 -2
- package/dist/target/public.mjs +1 -1
- package/dist/{types-BLdCL4fB.d.mts → types-Dp9lgx_S.d.mts} +6 -2
- package/dist/{types-BLdCL4fB.d.mts.map → types-Dp9lgx_S.d.mts.map} +1 -1
- package/dist/{types-DwCQCkC-.d.cts → types-TzCChNrA.d.cts} +6 -2
- package/dist/{types-DwCQCkC-.d.cts.map → types-TzCChNrA.d.cts.map} +1 -1
- package/dist/vitest.cjs +1 -1
- package/dist/vitest.mjs +1 -1
- package/dist/workspace/index.d.cts +2 -2
- package/dist/workspace/index.d.mts +2 -2
- package/package.json +17 -17
- package/dist/SubscriberGenerator-Bixm28fN.cjs.map +0 -1
- package/dist/SubscriberGenerator-DZk_pKDh.mjs.map +0 -1
- package/dist/compose-9II-0IsK.mjs.map +0 -1
- package/dist/compose-CNnw8meq.cjs.map +0 -1
- package/dist/deploy-BitZLdnY.cjs.map +0 -1
- package/dist/deploy-nmov4j0B.mjs.map +0 -1
- package/dist/engine-B37qBO5W.mjs.map +0 -1
- package/dist/engine-DL2-PW6m.cjs.map +0 -1
- package/dist/ready-BIuMw6wH.cjs.map +0 -1
- package/dist/ready-C0U0NYfB.mjs.map +0 -1
- package/dist/sniffer-BFmdZpZ2.mjs.map +0 -1
- package/dist/state-DMjFPOhf.cjs.map +0 -1
- package/dist/state-DtS2HEfi.mjs.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const require_workspace = require("./workspace-vdrcRI-m.cjs");
|
|
2
2
|
const require_config = require("./config.cjs");
|
|
3
|
-
const require_state = require("./state-
|
|
3
|
+
const require_state = require("./state-kA4Fkufx.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-BGdJ-Lnm.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"github-C6qF8kHg.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-BGdJ-Lnm.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,6 +1,6 @@
|
|
|
1
1
|
import { k as assertDeployedStage } from "./workspace-C7NqCsQF.mjs";
|
|
2
2
|
import { loadWorkspaceConfig } from "./config.mjs";
|
|
3
|
-
import {
|
|
3
|
+
import { Ht as isRemoteStore, Kt as getKeyPath, Ut as secretsStoreFor, Yt as readKey, qt as keystoreProject } from "./state-BrWIOce_.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-BNngHoId.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"github-hjHHm7jM.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-BNngHoId.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":";;;;;;;;;;;;;;;;;;;;;;;;AAmCA,MAAM,SAAS;AAEf,MAAM,mBAAmB;AACzB,MAAM,WAAW;AAsBjB,MAAM,aAAiB,MAAM,UAC5B,aAAa,MAAM,MAAM;CACxB,UAAU;CACV,GAAI,UAAU,KAAA,IACX,CAAC,IACD;EAAE;EAAO,OAAO;GAAC;GAAQ;GAAQ;EAAM;CAAW;AACtD,CAAC,CAAC,CAAC,KAAK;;AAGT,SAAgB,SAAS,SAAiB,OAAuB;CAChE,OAAO,GAAG,QAAQ,UAAU,QAC1B,YAAY,CAAC,CACb,QAAQ,qBAAqB,GAAG,CAAC,CACjC,MAAM,GAAG,EAAE;AACd;;;;;;;AAQA,SAAgB,YACf,aACA,MACA,OACS;CACT,OAAO,KAAK,UAAU;EACrB,SAAS;EACT,WAAW,CACV;GACC,QAAQ;GACR,WAAW,EAAE,WAAW,YAAY;GACpC,QAAQ;GACR,WAAW,EACV,cAAc;KACZ,GAAG,iBAAiB,QAAQ;KAC5B,GAAG,iBAAiB,QAAQ,QAAQ,KAAK,eAAe;GAC1D,EACD;EACD,CACD;CACD,CAAC;AACF;;AAGA,eAAsB,mBAAmB,KAAiC;CACzE,MAAM,EAAE,4BAA4B,CAAC,MAAM,MAAM,IAAI,KACpD,IAAI,kCAAkC,CAAC,CAAC,CACzC;CACA,MAAM,WAAW,0BAA0B,MAAM,MAChD,EAAE,KAAK,SAAS,iBAAiB,kBAAkB,CACpD,CAAC,EAAE;CACH,IAAI,UAAU,OAAO;CAQrB,QAAO,MANe,IAAI,KACzB,IAAI,mCAAmC;EACtC,KAAK,WAAW;EAChB,cAAc,CAAC,QAAQ;CACxB,CAAC,CACF,EAAA,CACe;AAChB;;AAGA,eAAsB,WACrB,KACA,MACA,OACA,WACkB;CAClB,IAAI;CACJ,IAAI;EACH,MAAM,EAAE,SAAS,MAAM,IAAI,KAAK,IAAI,eAAe,EAAE,UAAU,KAAK,CAAC,CAAC;EACtE,MAAM,IAAI,KACT,IAAI,8BAA8B;GACjC,UAAU;GACV,gBAAgB;EACjB,CAAC,CACF;EACA,MAAM,KAAM;CACb,SAAS,OAAO;EACf,IAAK,MAA4B,SAAS,yBACzC,MAAM;EAEP,MAAM,EAAE,SAAS,MAAM,IAAI,KAC1B,IAAI,kBAAkB;GACrB,UAAU;GACV,0BAA0B;GAC1B,aAAa;GAEb,oBAAoB;EACrB,CAAC,CACF;EACA,MAAM,KAAM;CACb;CAGA,MAAM,IAAI,KACT,IAAI,wBAAwB;EAAE,UAAU;EAAM,WAAW;CAAU,CAAC,CACrE;CACA,OAAO;AACR;;;;;;;;;;;AAYA,eAAsB,OACrB,SACA,UACqB;CACrB,MAAM,SAA0B,EAAE,QAAQ,YAAY;CACtD,IAAI,SAAS;EACZ,MAAM,EAAE,YAAY,MAAM,OAAO;EACjC,OAAO,cAAc,QAAQ,EAAE,QAAQ,CAAC;CACzC;CACA,IAAI,UAAU;EACb,OAAO,WAAW;EAClB,OAAO,cAAc;GAAE,aAAa;GAAQ,iBAAiB;EAAO;CACrE;CACA,OAAO,IAAI,UAAU,MAAM;AAC5B;AAEA,eAAsB,oBACrB,SACA,OAAmD,CAAC,GACN;CAC9C,MAAM,KAAK,KAAK,MAAM;CACtB,MAAM,EAAE,cAAc,MAAM,oBAAoB,KAAK,GAAG;CACxD,oBAAoB,UAAU,QAAQ,QAAQ,KAAK;CAEnD,MAAM,OACL,QAAQ,QACR,GAAG;EAAC;EAAQ;EAAQ;EAAU;EAAiB;EAAM;CAAgB,CAAC;CACvE,MAAM,OAAO,SAAS,UAAU,MAAM,QAAQ,KAAK;CACnD,MAAM,YAAY,QAAQ,aAAA;CAG1B,MAAM,MAAM,MAAM,QAAQ,QAAQ,OAAO,gBAAgB,SAAS,CAAC;CACnE,MAAM,UAAU,WAAW,QAAQ,OAAO,gBAAgB,SAAS,CAAC;CACpE,MAAM,SAAS,QAAQ;CAGvB,MAAM,SAAS,cAAc,WAAW,QAAQ,KAAK;CAGrD,MAAM,QAAQ,MAAM,gBAAgB,WAAW,QAAQ,OAAO,EAC7D,GAAI,QAAQ,UAAU,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC,EACvD,CAAC;CACD,MAAM,aAAa,UAAW,MAAM,MAAM,KAAK,QAAQ,KAAK,MAAO;CACnE,MAAM,MAAM,QAAQ,UACjB,YAAY,QAAQ,QAAQ,KAC5B;CAEH,OAAO,IAAI,gCAAgC,QAAQ,MAAM,IAAI;CAC7D,OAAO,IAAI,mBAAmB,MAAM;CACpC,OAAO,IAAI,mBAAmB,QAAQ,WAAW,yBAAyB;CAC1E,OAAO,IAAI,mBAAmB,MAAM;CACpC,OAAO,IACN,mBAAmB,YAAY,QAAQ,YAAY,KAAK,uCACzD;CACA,OAAO,IAAI,wBAAwB,KAAK,eAAe,QAAQ,MAAM,MAAM;CAC3E,IAAI,QACH,OAAO,IACN,mBAAmB,aAAa,iBAAiB,MAAM,KAAK,eAAe,QAAQ,6DAA6D,QAAQ,SACzJ;MAEA,OAAO,IACN,mBAAmB,SAAS,UAAU,WAAW,QAAQ,kCAAkC,QAAQ,MAAM,SAC1G;CAGD,IAAI,QAAQ,QAAQ;EACnB,OAAO,IAAI,mCAAmC;EAC9C,OAAO,EAAE,KAAK;CACf;CAEA,MAAM,MAAM,KAAK,OAAQ,MAAM,OAAO,QAAQ,OAAO;CASrD,MAAM,UAAU,MAAM,WACrB,KACA,MACA,YAAY,MAXa,mBAAmB,GAAG,CAAC,CAAC,OAAO,UAAU;EAGlE,IAAI,eAAe,KAAK,OAAO,OAAO,OAAO,CAAC,KAAK,QAAQ,SAC1D,MAAM,IAAI,kBAAkB,QAAQ,OAAO;EAE5C,MAAM;CACP,CAAC,GAIyB,MAAM,QAAQ,KAAK,GAC5C,SACD;CACA,OAAO,IAAI,cAAc,SAAS;CAGlC,GAAG;EAAC;EAAO;EAAY;EAAO,SAAS,KAAK,gBAAgB,QAAQ;CAAO,CAAC;CAC5E,GAAG;EACF;EACA;EACA;EACA;EACA,QAAQ;EACR;EACA;EACA;EACA;CACD,CAAC;CACD,OAAO,IAAI,4BAA4B,QAAQ,MAAM,gBAAgB;CAErE,IAAI,QAEC;MAAA,CAAC,YACJ,OAAO,IACN,YAAY,QAAQ,MAAM,gFAAgF,QAAQ,QAAQ,QAAQ,UAAU,iBAAiB,QAAQ,QAAQ,KAAK,GAAG,EACtL;CAAA,OAEK,IAAI,QAAQ;EAElB,GACC;GACC;GACA;GACA;GACA;GACA,QAAQ;GACR;GACA;EACD,GACA,GACD;EACA,OAAO,IAAI,6BAA6B;CACzC,OACC,OAAO,IACN,2BAA2B,QAAQ,MAAM,kCAAkC,QAAQ,MAAM,mBAC1F;CAGD,OAAO,IACN,kDAAkD,QAAQ,MAAM,4CACjE;CACA,OAAO;EAAE;EAAS;CAAK;AACxB;;AAGA,IAAa,oBAAb,cAAuC,MAAM;CACvB;CAArB,YAAY,SAA0B;EACrC,MACC,gCAAgC,QAAQ,4DAA4D,SACrG;EAHoB,KAAA,UAAA;EAIpB,KAAK,OAAO;CACb;AACD"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { E as WorkspaceInput, S as NormalizedWorkspace, T as WorkspaceConfig, a as Routes, k as Framework, l as AppsRecord, r as GkmConfig, v as InferredWorkspaceConfig, y as LoadedConfig } from "./types-
|
|
1
|
+
import { E as WorkspaceInput, S as NormalizedWorkspace, T as WorkspaceConfig, a as Routes, k as Framework, l as AppsRecord, r as GkmConfig, v as InferredWorkspaceConfig, y as LoadedConfig } from "./types-Dp9lgx_S.mjs";
|
|
2
2
|
//#region src/workspace/publicEnv.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* All public env-var prefixes the toolbox understands.
|
|
@@ -166,4 +166,4 @@ declare function getEndpointForStage(dokployConfig: {
|
|
|
166
166
|
} | undefined, stage: string): string | undefined;
|
|
167
167
|
//#endregion
|
|
168
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-
|
|
169
|
+
//# sourceMappingURL=index-B-1lLGzy.d.mts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index-
|
|
1
|
+
{"version":3,"file":"index-B-1lLGzy.d.mts","names":[],"sources":["../src/workspace/publicEnv.ts","../src/workspace/index.ts"],"mappings":";;;;;;;;;cASa;;;;;;;;;;;;;;;iBAoBG,mBAAmB,YAAY;;;;;iBAoB/B,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBC6ElB,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 { E as WorkspaceInput, S as NormalizedWorkspace, T as WorkspaceConfig, a as Routes, k as Framework, l as AppsRecord, r as GkmConfig, v as InferredWorkspaceConfig, y as LoadedConfig } from "./types-
|
|
1
|
+
import { E as WorkspaceInput, S as NormalizedWorkspace, T as WorkspaceConfig, a as Routes, k as Framework, l as AppsRecord, r as GkmConfig, v as InferredWorkspaceConfig, y as LoadedConfig } from "./types-TzCChNrA.cjs";
|
|
2
2
|
//#region src/workspace/publicEnv.d.ts
|
|
3
3
|
/**
|
|
4
4
|
* All public env-var prefixes the toolbox understands.
|
|
@@ -166,4 +166,4 @@ declare function getEndpointForStage(dokployConfig: {
|
|
|
166
166
|
} | undefined, stage: string): string | undefined;
|
|
167
167
|
//#endregion
|
|
168
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-
|
|
169
|
+
//# sourceMappingURL=index-vMidfqhU.d.cts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index-
|
|
1
|
+
{"version":3,"file":"index-vMidfqhU.d.cts","names":[],"sources":["../src/workspace/publicEnv.ts","../src/workspace/index.ts"],"mappings":";;;;;;;;;cASa;;;;;;;;;;;;;;;iBAoBG,mBAAmB,YAAY;;;;;iBAoB/B,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBC6ElB,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"}
|
package/dist/index.cjs
CHANGED
|
@@ -7,19 +7,20 @@ const require_discover = require("./discover-D1It5r_h.cjs");
|
|
|
7
7
|
const require_derive = require("./derive-DAbo49sm.cjs");
|
|
8
8
|
const require_workspace = require("./workspace-vdrcRI-m.cjs");
|
|
9
9
|
const require_config = require("./config.cjs");
|
|
10
|
-
const require_state = require("./state-
|
|
10
|
+
const require_state = require("./state-kA4Fkufx.cjs");
|
|
11
11
|
const require_EndpointGenerator = require("./EndpointGenerator-CP6sBvQV.cjs");
|
|
12
|
-
const require_sniffer = require("./sniffer-
|
|
12
|
+
const require_sniffer = require("./sniffer-bMzrX4cO.cjs");
|
|
13
13
|
const require_openapi = require("./openapi.cjs");
|
|
14
|
-
const require_SubscriberGenerator = require("./SubscriberGenerator-
|
|
14
|
+
const require_SubscriberGenerator = require("./SubscriberGenerator-CajwOa5W.cjs");
|
|
15
15
|
const require_appPorts = require("./appPorts-Cu79hJF6.cjs");
|
|
16
|
-
const
|
|
16
|
+
const require_drivers = require("./drivers-BdvtCKc7.cjs");
|
|
17
17
|
const require_emit = require("./emit-Dmc1nvdS.cjs");
|
|
18
|
-
const require_deploy = require("./deploy-
|
|
19
|
-
const require_compose = require("./compose-
|
|
18
|
+
const require_deploy = require("./deploy-XUZoA1us.cjs");
|
|
19
|
+
const require_compose = require("./compose-UKyv_1yU.cjs");
|
|
20
20
|
const require_credentialSchemas = require("./credentialSchemas-DCR4Wd1W.cjs");
|
|
21
|
-
const require_LocalStateStore = require("./LocalStateStore-
|
|
21
|
+
const require_LocalStateStore = require("./LocalStateStore-zQGZ9b4J.cjs");
|
|
22
22
|
const require_utils = require("./utils-D2Nk1Nv2.cjs");
|
|
23
|
+
const require_ready = require("./ready-BB9xS4S9.cjs");
|
|
23
24
|
let node_path = require("node:path");
|
|
24
25
|
let commander = require("commander");
|
|
25
26
|
let node_process = require("node:process");
|
|
@@ -52,7 +53,7 @@ crypto = require_rolldown_runtime.__toESM(crypto, 1);
|
|
|
52
53
|
let _geekmidas_constructs_functions = require("@geekmidas/constructs/functions");
|
|
53
54
|
let _geekmidas_constructs_topic = require("@geekmidas/constructs/topic");
|
|
54
55
|
//#region package.json
|
|
55
|
-
var version = "10.0.0-alpha.
|
|
56
|
+
var version = "10.0.0-alpha.75";
|
|
56
57
|
//#endregion
|
|
57
58
|
//#region src/prompt.ts
|
|
58
59
|
/**
|
|
@@ -135,7 +136,7 @@ const logger$8 = console;
|
|
|
135
136
|
* Validate Dokploy token by making a test API call
|
|
136
137
|
*/
|
|
137
138
|
async function validateDokployToken(endpoint, token) {
|
|
138
|
-
const { DokployApi } = await Promise.resolve().then(() => require("./sniffer-
|
|
139
|
+
const { DokployApi } = await Promise.resolve().then(() => require("./sniffer-bMzrX4cO.cjs")).then((n) => n.dokploy_api_exports);
|
|
139
140
|
return new DokployApi({
|
|
140
141
|
baseUrl: endpoint,
|
|
141
142
|
token
|
|
@@ -2041,9 +2042,9 @@ async function workspaceDevCommand(configured, options) {
|
|
|
2041
2042
|
for (const { id, port } of fakes) logger$5.log(`🎭 ${id} fake: http://localhost:${port}`);
|
|
2042
2043
|
logger$5.log(options.migrate || options.seed ? "🗄️ Migrating the databases" : "🗄️ Checking for pending migrations");
|
|
2043
2044
|
if (options.migrate || options.seed) {
|
|
2044
|
-
const { prepareDevDatabases } = await Promise.resolve().then(() => require("./migrate-
|
|
2045
|
+
const { prepareDevDatabases } = await Promise.resolve().then(() => require("./migrate-CgjlYeun.cjs"));
|
|
2045
2046
|
await prepareDevDatabases(workspace, reconciled.env, { seed: !!options.seed });
|
|
2046
|
-
} else await Promise.resolve().then(() => require("./migrate-
|
|
2047
|
+
} else await Promise.resolve().then(() => require("./migrate-CgjlYeun.cjs")).then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
|
|
2047
2048
|
logger$5.log(`⚠️ Could not check for pending migrations: ${error instanceof Error ? error.message : String(error)}`);
|
|
2048
2049
|
});
|
|
2049
2050
|
const appUrls = {};
|
|
@@ -2903,6 +2904,15 @@ var UnknownBuildProvider = class extends Error {
|
|
|
2903
2904
|
this.name = "UnknownBuildProvider";
|
|
2904
2905
|
}
|
|
2905
2906
|
};
|
|
2907
|
+
/** `--cache` named something that is not a cache backend. */
|
|
2908
|
+
var UnknownCacheBackend = class extends Error {
|
|
2909
|
+
backend;
|
|
2910
|
+
constructor(backend) {
|
|
2911
|
+
super(`'${backend}' is not a cache backend. Pass --cache upstash, elasticache, db or redis, or leave it out to register the deploy target's.`);
|
|
2912
|
+
this.backend = backend;
|
|
2913
|
+
this.name = "UnknownCacheBackend";
|
|
2914
|
+
}
|
|
2915
|
+
};
|
|
2906
2916
|
async function buildCommand(options) {
|
|
2907
2917
|
const loadedConfig = await require_config.loadWorkspaceConfig();
|
|
2908
2918
|
if (loadedConfig.type === "workspace") {
|
|
@@ -2955,7 +2965,7 @@ function backendsOf(workspace) {
|
|
|
2955
2965
|
/** One app's build, from its config and the command's options. */
|
|
2956
2966
|
async function buildOneApp(input) {
|
|
2957
2967
|
const { config, options, workspace, appRoot, target } = input;
|
|
2958
|
-
const cacheBackend = require_state.cacheBackendFor(require_state.providerOf(workspace));
|
|
2968
|
+
const cacheBackend = options.cache ?? require_state.cacheBackendFor(require_state.providerOf(workspace));
|
|
2959
2969
|
const eventsBackend = require_state.eventsBackendFor(require_state.providerOf(workspace));
|
|
2960
2970
|
const production = normalizeProductionConfig(options.production ?? false);
|
|
2961
2971
|
if (production) logger$4.log(`🏭 Building for PRODUCTION`);
|
|
@@ -3042,7 +3052,7 @@ async function buildApp(input) {
|
|
|
3042
3052
|
if (production?.bundle && !input.skipBundle) {
|
|
3043
3053
|
const outputDir = (0, node_path.join)(appRoot, ".gkm", "server");
|
|
3044
3054
|
const entryPoint = await writeSurfaceServer(outputDir, production);
|
|
3045
|
-
const { bundleServer } = await Promise.resolve().then(() => require("./bundler-
|
|
3055
|
+
const { bundleServer } = await Promise.resolve().then(() => require("./bundler-_8AEZhrs.cjs"));
|
|
3046
3056
|
await bundleServer({
|
|
3047
3057
|
entryPoint,
|
|
3048
3058
|
outputDir: (0, node_path.join)(outputDir, "dist"),
|
|
@@ -3062,7 +3072,7 @@ async function buildApp(input) {
|
|
|
3062
3072
|
appRoot,
|
|
3063
3073
|
databaseApi
|
|
3064
3074
|
});
|
|
3065
|
-
const eventsBackends =
|
|
3075
|
+
const eventsBackends = require_drivers.eventsBackendsIn(declared, eventsBackend);
|
|
3066
3076
|
const buildContext = {
|
|
3067
3077
|
...derived,
|
|
3068
3078
|
telescope,
|
|
@@ -3071,9 +3081,9 @@ async function buildApp(input) {
|
|
|
3071
3081
|
constructGlobs,
|
|
3072
3082
|
cacheBackend,
|
|
3073
3083
|
emailBackend: "ses",
|
|
3074
|
-
storageDrivers:
|
|
3084
|
+
storageDrivers: require_drivers.driversFor({
|
|
3075
3085
|
appRoot,
|
|
3076
|
-
cache:
|
|
3086
|
+
cache: require_drivers.cacheBackendsIn(declared, cacheBackend),
|
|
3077
3087
|
events: eventsBackends
|
|
3078
3088
|
}),
|
|
3079
3089
|
eventsBackends,
|
|
@@ -3166,7 +3176,7 @@ async function buildWorkers(input) {
|
|
|
3166
3176
|
});
|
|
3167
3177
|
logger$4.log(`Generated worker ${worker.id}: ${crons.length} crons, ${queues.length} queues, ${subscribers.length} subscribers`);
|
|
3168
3178
|
if (!input.bundle) continue;
|
|
3169
|
-
const { bundleServer } = await Promise.resolve().then(() => require("./bundler-
|
|
3179
|
+
const { bundleServer } = await Promise.resolve().then(() => require("./bundler-_8AEZhrs.cjs"));
|
|
3170
3180
|
const outfile = require_state.workerBundleName(worker.id);
|
|
3171
3181
|
await bundleServer({
|
|
3172
3182
|
entryPoint,
|
|
@@ -3236,7 +3246,7 @@ async function buildForTarget(target, context, appRoot, workspaceRoot, endpointG
|
|
|
3236
3246
|
let masterKey;
|
|
3237
3247
|
if (context.production?.bundle && !skipBundle) {
|
|
3238
3248
|
logger$4.log(`\n📦 Bundling production server...`);
|
|
3239
|
-
const { bundleServer } = await Promise.resolve().then(() => require("./bundler-
|
|
3249
|
+
const { bundleServer } = await Promise.resolve().then(() => require("./bundler-_8AEZhrs.cjs"));
|
|
3240
3250
|
const allConstructs = [
|
|
3241
3251
|
...endpoints.map((e) => e.construct),
|
|
3242
3252
|
...functions.map((f) => f.construct),
|
|
@@ -3259,7 +3269,7 @@ async function buildForTarget(target, context, appRoot, workspaceRoot, endpointG
|
|
|
3259
3269
|
await (0, node_fs_promises.writeFile)(keyPath, `${masterKey}\n`, { mode: 384 });
|
|
3260
3270
|
await (0, node_fs_promises.chmod)(keyPath, 384);
|
|
3261
3271
|
logger$4.log(`\n🔐 Secrets encrypted for deployment`);
|
|
3262
|
-
logger$4.log(` Run with GKM_MASTER_KEY set to the key in ${(0, node_path.relative)(appRoot, keyPath)} (fingerprint ${
|
|
3272
|
+
logger$4.log(` Run with GKM_MASTER_KEY set to the key in ${(0, node_path.relative)(appRoot, keyPath)} (fingerprint ${require_sniffer.keyFingerprint(masterKey)})`);
|
|
3263
3273
|
}
|
|
3264
3274
|
}
|
|
3265
3275
|
return {
|
|
@@ -3480,9 +3490,16 @@ var ComposeModeConflict = class extends Error {
|
|
|
3480
3490
|
this.name = "ComposeModeConflict";
|
|
3481
3491
|
}
|
|
3482
3492
|
};
|
|
3493
|
+
/** Each pushed image as `<ref>@sha256:…`, by app. */
|
|
3494
|
+
function pushedDigests(run) {
|
|
3495
|
+
return Object.fromEntries(Object.entries(run.images).filter(([, image]) => image.digest).sort(([a], [b]) => a.localeCompare(b)).map(([app, image]) => [app, require_compose.pinnedRef(image.ref, image.digest)]));
|
|
3496
|
+
}
|
|
3483
3497
|
/** `gkm compose`. */
|
|
3484
3498
|
async function composeCommand(options, deps = {}) {
|
|
3485
3499
|
if (options.build && options.pull) throw new ComposeModeConflict();
|
|
3500
|
+
if (options.push && !options.build) throw new require_compose.ComposePushNeedsBuild();
|
|
3501
|
+
const pulls = !options.build && (options.pull || options.tag !== void 0);
|
|
3502
|
+
if (options.digestsFile && !options.push && !pulls) throw new require_compose.ComposePinNeedsPull();
|
|
3486
3503
|
const allowDevServices = require_state.parseDevServices(options.allowDevServices);
|
|
3487
3504
|
const cwd = (0, node_path.resolve)(options.cwd ?? process.cwd());
|
|
3488
3505
|
const { workspace } = await require_config.loadWorkspaceConfig(cwd);
|
|
@@ -3502,6 +3519,10 @@ async function composeCommand(options, deps = {}) {
|
|
|
3502
3519
|
let finished;
|
|
3503
3520
|
const mode = options.build ? "build" : options.pull ? "pull" : void 0;
|
|
3504
3521
|
const tag = options.tag ?? (options.pull ? "latest" : void 0);
|
|
3522
|
+
const pin = options.digestsFile && pulls ? {
|
|
3523
|
+
file: options.digestsFile,
|
|
3524
|
+
digests: require_compose.parseDigests(options.digestsFile, await (0, node_fs_promises.readFile)((0, node_path.resolve)(cwd, options.digestsFile), "utf-8"))
|
|
3525
|
+
} : void 0;
|
|
3505
3526
|
const result = await require_deploy.deploy({
|
|
3506
3527
|
cwd,
|
|
3507
3528
|
stage,
|
|
@@ -3509,12 +3530,15 @@ async function composeCommand(options, deps = {}) {
|
|
|
3509
3530
|
targets: { compose: require_compose.composeTarget({
|
|
3510
3531
|
...deps,
|
|
3511
3532
|
...mode ? { mode } : {},
|
|
3533
|
+
...options.push ? { push: true } : {},
|
|
3534
|
+
...pin ? { pin } : {},
|
|
3512
3535
|
report: (done) => {
|
|
3513
3536
|
finished = done;
|
|
3514
3537
|
}
|
|
3515
3538
|
}) },
|
|
3516
3539
|
...tag ? { tag } : {},
|
|
3517
3540
|
...options.dryRun ? { dryRun: true } : {},
|
|
3541
|
+
...options.push ? { buildOnly: true } : {},
|
|
3518
3542
|
...allowDevServices.length > 0 ? { allowDevServices } : {},
|
|
3519
3543
|
logger: {
|
|
3520
3544
|
info: (message) => console.log(message),
|
|
@@ -3523,6 +3547,17 @@ async function composeCommand(options, deps = {}) {
|
|
|
3523
3547
|
}
|
|
3524
3548
|
}).result;
|
|
3525
3549
|
if (!finished) return void 0;
|
|
3550
|
+
if (options.push && !options.dryRun) {
|
|
3551
|
+
const digests = pushedDigests(finished);
|
|
3552
|
+
console.log(`\n✅ Pushed ${Object.keys(digests).length} image(s):`);
|
|
3553
|
+
for (const [app, ref] of Object.entries(digests)) console.log(` ${app.padEnd(12)} ${ref}`);
|
|
3554
|
+
if (options.digestsFile) {
|
|
3555
|
+
const file = (0, node_path.resolve)(cwd, options.digestsFile);
|
|
3556
|
+
await (0, node_fs_promises.mkdir)((0, node_path.dirname)(file), { recursive: true });
|
|
3557
|
+
await (0, node_fs_promises.writeFile)(file, `${JSON.stringify(digests, null, 2)}\n`);
|
|
3558
|
+
console.log(`\n📝 Wrote the digests to ${options.digestsFile}`);
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3526
3561
|
return {
|
|
3527
3562
|
stack: finished.stack,
|
|
3528
3563
|
files: finished.files,
|
|
@@ -4935,25 +4970,25 @@ function loadPackageJson() {
|
|
|
4935
4970
|
* Run: pnpm --filter @geekmidas/cli sync-versions
|
|
4936
4971
|
*/
|
|
4937
4972
|
const GEEKMIDAS_VERSIONS = {
|
|
4938
|
-
"@geekmidas/audit": "~10.0.0-alpha.
|
|
4939
|
-
"@geekmidas/auth": "~10.0.0-alpha.
|
|
4940
|
-
"@geekmidas/cache": "~10.0.0-alpha.
|
|
4941
|
-
"@geekmidas/client": "~10.0.0-alpha.
|
|
4942
|
-
"@geekmidas/cloud": "~10.0.0-alpha.
|
|
4943
|
-
"@geekmidas/constructs": "~10.0.0-alpha.
|
|
4944
|
-
"@geekmidas/db": "~10.0.0-alpha.
|
|
4945
|
-
"@geekmidas/emailkit": "~10.0.0-alpha.
|
|
4946
|
-
"@geekmidas/envkit": "~10.0.0-alpha.
|
|
4947
|
-
"@geekmidas/errors": "~10.0.0-alpha.
|
|
4948
|
-
"@geekmidas/events": "~10.0.0-alpha.
|
|
4949
|
-
"@geekmidas/logger": "~10.0.0-alpha.
|
|
4950
|
-
"@geekmidas/manifest": "~10.0.0-alpha.
|
|
4951
|
-
"@geekmidas/rate-limit": "~10.0.0-alpha.
|
|
4952
|
-
"@geekmidas/schema": "~10.0.0-alpha.
|
|
4953
|
-
"@geekmidas/services": "~10.0.0-alpha.
|
|
4954
|
-
"@geekmidas/storage": "~10.0.0-alpha.
|
|
4955
|
-
"@geekmidas/telescope": "~10.0.0-alpha.
|
|
4956
|
-
"@geekmidas/testkit": "~10.0.0-alpha.
|
|
4973
|
+
"@geekmidas/audit": "~10.0.0-alpha.75",
|
|
4974
|
+
"@geekmidas/auth": "~10.0.0-alpha.75",
|
|
4975
|
+
"@geekmidas/cache": "~10.0.0-alpha.75",
|
|
4976
|
+
"@geekmidas/client": "~10.0.0-alpha.75",
|
|
4977
|
+
"@geekmidas/cloud": "~10.0.0-alpha.75",
|
|
4978
|
+
"@geekmidas/constructs": "~10.0.0-alpha.75",
|
|
4979
|
+
"@geekmidas/db": "~10.0.0-alpha.75",
|
|
4980
|
+
"@geekmidas/emailkit": "~10.0.0-alpha.75",
|
|
4981
|
+
"@geekmidas/envkit": "~10.0.0-alpha.75",
|
|
4982
|
+
"@geekmidas/errors": "~10.0.0-alpha.75",
|
|
4983
|
+
"@geekmidas/events": "~10.0.0-alpha.75",
|
|
4984
|
+
"@geekmidas/logger": "~10.0.0-alpha.75",
|
|
4985
|
+
"@geekmidas/manifest": "~10.0.0-alpha.75",
|
|
4986
|
+
"@geekmidas/rate-limit": "~10.0.0-alpha.75",
|
|
4987
|
+
"@geekmidas/schema": "~10.0.0-alpha.75",
|
|
4988
|
+
"@geekmidas/services": "~10.0.0-alpha.75",
|
|
4989
|
+
"@geekmidas/storage": "~10.0.0-alpha.75",
|
|
4990
|
+
"@geekmidas/telescope": "~10.0.0-alpha.75",
|
|
4991
|
+
"@geekmidas/testkit": "~10.0.0-alpha.75",
|
|
4957
4992
|
"@geekmidas/cli": `~${loadPackageJson().version}`
|
|
4958
4993
|
};
|
|
4959
4994
|
//#endregion
|
|
@@ -10744,7 +10779,7 @@ function testDatabases(reconciled) {
|
|
|
10744
10779
|
* starts without the reference data the app needs.
|
|
10745
10780
|
*/
|
|
10746
10781
|
async function migrateTestStage(workspace, env) {
|
|
10747
|
-
const { migrateAndSeed } = await Promise.resolve().then(() => require("./migrate-
|
|
10782
|
+
const { migrateAndSeed } = await Promise.resolve().then(() => require("./migrate-CgjlYeun.cjs"));
|
|
10748
10783
|
const { discover } = await Promise.resolve().then(() => require("./discover-D1It5r_h.cjs")).then((n) => n.discover_exports);
|
|
10749
10784
|
const sources = {};
|
|
10750
10785
|
const manifest = await discover({
|
|
@@ -11110,13 +11145,15 @@ program.command("setup").description("Reconcile declared constructs — containe
|
|
|
11110
11145
|
process.exit(1);
|
|
11111
11146
|
}
|
|
11112
11147
|
});
|
|
11113
|
-
program.command("build").description("Build handlers and the manifest from declared constructs").option("--provider <provider>", "Build for aws or server (default: where gkm.config.ts deploys)").option("--enable-openapi", "Enable OpenAPI documentation generation for server builds").option("--production", "Build for production (no dev tools, bundled output)").option("--skip-bundle", "Skip bundling step in production build").option("--stage <stage>", "Inject encrypted secrets for deployment stage").option("--mark-optional", "Suffix optional env vars with ? in manifest envVars field (e.g. PORT?)").action(async (options) => {
|
|
11148
|
+
program.command("build").description("Build handlers and the manifest from declared constructs").option("--provider <provider>", "Build for aws or server (default: where gkm.config.ts deploys)").option("--enable-openapi", "Enable OpenAPI documentation generation for server builds").option("--production", "Build for production (no dev tools, bundled output)").option("--skip-bundle", "Skip bundling step in production build").option("--stage <stage>", "Inject encrypted secrets for deployment stage").option("--mark-optional", "Suffix optional env vars with ? in manifest envVars field (e.g. PORT?)").addOption(new commander.Option("--cache <backend>", "Register the drivers for this cache backend rather than the target default").hideHelp()).action(async (options) => {
|
|
11114
11149
|
try {
|
|
11115
11150
|
const globalOptions = program.opts();
|
|
11116
11151
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
11117
11152
|
if (options.provider !== void 0 && !isMainProvider(options.provider)) throw new UnknownBuildProvider(options.provider);
|
|
11153
|
+
if (options.cache !== void 0 && !require_SubscriberGenerator.isCacheBackend(options.cache)) throw new UnknownCacheBackend(options.cache);
|
|
11118
11154
|
await buildCommand({
|
|
11119
11155
|
...options.provider ? { provider: options.provider } : {},
|
|
11156
|
+
...options.cache ? { cache: options.cache } : {},
|
|
11120
11157
|
enableOpenApi: options.enableOpenapi || false,
|
|
11121
11158
|
production: options.production || false,
|
|
11122
11159
|
skipBundle: options.skipBundle || false,
|
|
@@ -11175,7 +11212,7 @@ program.command("migrate").description("Apply each database construct's migratio
|
|
|
11175
11212
|
try {
|
|
11176
11213
|
const globalOptions = program.opts();
|
|
11177
11214
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
11178
|
-
const { migrateCommand } = await Promise.resolve().then(() => require("./migrate-
|
|
11215
|
+
const { migrateCommand } = await Promise.resolve().then(() => require("./migrate-CgjlYeun.cjs"));
|
|
11179
11216
|
await migrateCommand({
|
|
11180
11217
|
...options,
|
|
11181
11218
|
...construct ? { construct } : {}
|
|
@@ -11189,7 +11226,7 @@ program.command("seed").description("Migrate, then run each database construct's
|
|
|
11189
11226
|
try {
|
|
11190
11227
|
const globalOptions = program.opts();
|
|
11191
11228
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
11192
|
-
const { seedCommand } = await Promise.resolve().then(() => require("./migrate-
|
|
11229
|
+
const { seedCommand } = await Promise.resolve().then(() => require("./migrate-CgjlYeun.cjs"));
|
|
11193
11230
|
await seedCommand({
|
|
11194
11231
|
...options,
|
|
11195
11232
|
...construct ? { construct } : {}
|
|
@@ -11203,7 +11240,7 @@ program.command("migration").description("Write a construct's next migration: an
|
|
|
11203
11240
|
try {
|
|
11204
11241
|
const globalOptions = program.opts();
|
|
11205
11242
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
11206
|
-
const { migrationCommand } = await Promise.resolve().then(() => require("./migrate-
|
|
11243
|
+
const { migrationCommand } = await Promise.resolve().then(() => require("./migrate-CgjlYeun.cjs"));
|
|
11207
11244
|
await migrationCommand({
|
|
11208
11245
|
construct,
|
|
11209
11246
|
...name ? { name } : {}
|
|
@@ -11248,7 +11285,7 @@ program.command("docker").description("Generate Docker deployment files").option
|
|
|
11248
11285
|
process.exit(1);
|
|
11249
11286
|
}
|
|
11250
11287
|
});
|
|
11251
|
-
program.command("compose").description("Run the workspace's APIs and sites for a stage as one Docker Compose stack behind Caddy").requiredOption("--stage <stage>", "Stage to run — always named, so a stack is never a deployed stage by accident").option("--tag <tag>", "Run the images CI pushed at this tag (sites: <tag>-<stage>); nothing is built").option("--build", "Build images from this checkout, tagged with the commit").option("--pull", "Pull images (at --tag, or latest) rather than build them").option("--dry-run", "Write the files and print the plan; start nothing").option("--down", "Stop the stage's stack (its volumes are kept)").option("--allow-dev-services <list>", "Deployed stage: run MinIO and/or Mailpit (minio,mailpit) for mail and buckets the stage's secrets don't configure. Not production-grade").action(async (options) => {
|
|
11288
|
+
program.command("compose").description("Run the workspace's APIs and sites for a stage as one Docker Compose stack behind Caddy").requiredOption("--stage <stage>", "Stage to run — always named, so a stack is never a deployed stage by accident").option("--tag <tag>", "Run the images CI pushed at this tag (sites: <tag>-<stage>); nothing is built").option("--build", "Build images from this checkout, tagged with the commit").option("--pull", "Pull images (at --tag, or latest) rather than build them").option("--push", "With --build: push every image to deploy.registry and start nothing (for CI)").option("--digests-file <path>", "With --push: write each image as <ref>@sha256:… (JSON). With --tag: run the images at those digests").option("--dry-run", "Write the files and print the plan; start nothing").option("--down", "Stop the stage's stack (its volumes are kept)").option("--allow-dev-services <list>", "Deployed stage: run MinIO and/or Mailpit (minio,mailpit) for mail and buckets the stage's secrets don't configure. Not production-grade").action(async (options) => {
|
|
11252
11289
|
try {
|
|
11253
11290
|
const globalOptions = program.opts();
|
|
11254
11291
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
@@ -11297,7 +11334,7 @@ program.command("secrets:add").description("Build the keys a stage must be given
|
|
|
11297
11334
|
try {
|
|
11298
11335
|
const globalOptions = program.opts();
|
|
11299
11336
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
11300
|
-
const { secretsAddCommand } = await Promise.resolve().then(() => require("./add-
|
|
11337
|
+
const { secretsAddCommand } = await Promise.resolve().then(() => require("./add-CHBin8TF.cjs"));
|
|
11301
11338
|
await secretsAddCommand(options);
|
|
11302
11339
|
} catch (error) {
|
|
11303
11340
|
console.error(formatError(error));
|
|
@@ -11308,7 +11345,7 @@ program.command("encryption:rotate").description("Add a new current key to an En
|
|
|
11308
11345
|
try {
|
|
11309
11346
|
const globalOptions = program.opts();
|
|
11310
11347
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
11311
|
-
const { encryptionRotateCommand } = await Promise.resolve().then(() => require("./encryption-
|
|
11348
|
+
const { encryptionRotateCommand } = await Promise.resolve().then(() => require("./encryption-w-Ehh9DW.cjs"));
|
|
11312
11349
|
await encryptionRotateCommand(construct, options);
|
|
11313
11350
|
} catch (error) {
|
|
11314
11351
|
console.error(formatError(error));
|
|
@@ -11319,7 +11356,7 @@ program.command("encryption:retire").description("Remove an old key from an Encr
|
|
|
11319
11356
|
try {
|
|
11320
11357
|
const globalOptions = program.opts();
|
|
11321
11358
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
11322
|
-
const { encryptionRetireCommand } = await Promise.resolve().then(() => require("./encryption-
|
|
11359
|
+
const { encryptionRetireCommand } = await Promise.resolve().then(() => require("./encryption-w-Ehh9DW.cjs"));
|
|
11323
11360
|
await encryptionRetireCommand(construct, key, options);
|
|
11324
11361
|
} catch (error) {
|
|
11325
11362
|
console.error(formatError(error));
|
|
@@ -11362,7 +11399,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
|
|
|
11362
11399
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
11363
11400
|
const { loadWorkspaceConfig } = await Promise.resolve().then(() => require("./config.cjs"));
|
|
11364
11401
|
const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-CuCQBa5-.cjs"));
|
|
11365
|
-
const { secretsStoreFor } = await Promise.resolve().then(() => require("./state-
|
|
11402
|
+
const { secretsStoreFor } = await Promise.resolve().then(() => require("./state-kA4Fkufx.cjs")).then((n) => n.store_exports);
|
|
11366
11403
|
const { workspace } = await loadWorkspaceConfig();
|
|
11367
11404
|
const stage = options.stage ?? workspace.stages.local;
|
|
11368
11405
|
const store = await secretsStoreFor(workspace, stage);
|
|
@@ -11371,7 +11408,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
|
|
|
11371
11408
|
console.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
|
|
11372
11409
|
process.exit(1);
|
|
11373
11410
|
}
|
|
11374
|
-
const { derivedContainers } = await Promise.resolve().then(() => require("./state-
|
|
11411
|
+
const { derivedContainers } = await Promise.resolve().then(() => require("./state-kA4Fkufx.cjs")).then((n) => n.workspace_exports);
|
|
11375
11412
|
const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
|
|
11376
11413
|
if (!result) {
|
|
11377
11414
|
console.log(`\n✓ Secrets for stage "${stage}" are up-to-date`);
|
|
@@ -11386,7 +11423,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
|
|
|
11386
11423
|
}
|
|
11387
11424
|
});
|
|
11388
11425
|
program.command("deploy").description("Deploy a stage through its target").option("--target <name>", "Deploy target: dokploy, compose, sst, or one named in deploy.targets (default: deploy.default)").option("--provider <provider>", "[DEPRECATED] Use --target instead").requiredOption("--stage <stage>", "Deployment stage (e.g., production, staging)").option("--tag <tag>", "Image tag (default: stage-timestamp; compose: the commit). Compose pulls a given tag").option("--skip-push", "Skip pushing image to registry").option("--skip-build", "Skip build step (use existing build)").option("--json", "Write events as JSON lines instead of progress; never prompts").option("--dry-run", "Show what would be created or reused; change, build and push nothing").option("--atomic", "If the release fails, roll back every app it released, not only the failed ones").option("--allow-dev-services <list>", "Server targets: run MinIO and/or Mailpit (minio,mailpit) for mail and buckets the stage's secrets don't configure. Not production-grade").action(async (options) => {
|
|
11389
|
-
const { deployCli } = await Promise.resolve().then(() => require("./cli-
|
|
11426
|
+
const { deployCli } = await Promise.resolve().then(() => require("./cli-B4ktxAsM.cjs"));
|
|
11390
11427
|
const globalOptions = program.opts();
|
|
11391
11428
|
const code = await deployCli({
|
|
11392
11429
|
cwd: (0, node_path.resolve)(globalOptions.cwd ?? process.cwd()),
|
|
@@ -11403,7 +11440,7 @@ program.command("deploy").description("Deploy a stage through its target").optio
|
|
|
11403
11440
|
});
|
|
11404
11441
|
program.command("deploy:rollback").description("Put a stage's app back on its previous release (Dokploy): one app with --app, every app with --atomic").requiredOption("--stage <stage>", "A deployed stage").option("--app <app>", "The app to roll back").option("--atomic", "Roll back every app that has an earlier release").action(async (options) => {
|
|
11405
11442
|
try {
|
|
11406
|
-
const { rollbackStage } = await Promise.resolve().then(() => require("./rollback-
|
|
11443
|
+
const { rollbackStage } = await Promise.resolve().then(() => require("./rollback-Dw-FW3X2.cjs")).then((n) => n.rollback_exports);
|
|
11407
11444
|
const { terminalCredentials } = await Promise.resolve().then(() => require("./terminal-BL5E0xbR.cjs"));
|
|
11408
11445
|
const globalOptions = program.opts();
|
|
11409
11446
|
await rollbackStage({
|
|
@@ -11439,7 +11476,7 @@ program.command("deploy:github").description("Let GitHub Actions deploy a stage:
|
|
|
11439
11476
|
try {
|
|
11440
11477
|
const globalOptions = program.opts();
|
|
11441
11478
|
if (globalOptions.cwd) process.chdir(globalOptions.cwd);
|
|
11442
|
-
const { deployGithubCommand } = await Promise.resolve().then(() => require("./github-
|
|
11479
|
+
const { deployGithubCommand } = await Promise.resolve().then(() => require("./github-BGdJ-Lnm.cjs"));
|
|
11443
11480
|
await deployGithubCommand(options);
|
|
11444
11481
|
} catch (error) {
|
|
11445
11482
|
console.error(formatError(error));
|