@geekmidas/cli 10.0.0-alpha.79 → 10.0.0-alpha.81

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.
Files changed (81) hide show
  1. package/dist/{add-UFiIrlTj.mjs → add-Bn1bqt0c.mjs} +2 -2
  2. package/dist/{add-UFiIrlTj.mjs.map → add-Bn1bqt0c.mjs.map} +1 -1
  3. package/dist/{add-HF9POevk.cjs → add-DF9IHQov.cjs} +2 -2
  4. package/dist/{add-HF9POevk.cjs.map → add-DF9IHQov.cjs.map} +1 -1
  5. package/dist/{aws-Bja4zGSs.mjs → aws-B-6uEhgP.mjs} +8 -2
  6. package/dist/{aws-Bja4zGSs.mjs.map → aws-B-6uEhgP.mjs.map} +1 -1
  7. package/dist/{aws-CQAuluiu.cjs → aws-BTawF0F-.cjs} +19 -2
  8. package/dist/{aws-CQAuluiu.cjs.map → aws-BTawF0F-.cjs.map} +1 -1
  9. package/dist/{bundler-C45BuRdY.mjs → bundler-Ba_LoFXI.mjs} +3 -3
  10. package/dist/{bundler-C45BuRdY.mjs.map → bundler-Ba_LoFXI.mjs.map} +1 -1
  11. package/dist/{bundler-RAoSRuvp.cjs → bundler-DeIVbloM.cjs} +3 -3
  12. package/dist/{bundler-RAoSRuvp.cjs.map → bundler-DeIVbloM.cjs.map} +1 -1
  13. package/dist/{cli-C3hsEwLK.cjs → cli-BB1KOkfo.cjs} +3 -3
  14. package/dist/{cli-C3hsEwLK.cjs.map → cli-BB1KOkfo.cjs.map} +1 -1
  15. package/dist/{cli-gvc3urUl.mjs → cli-CznHL-4-.mjs} +3 -3
  16. package/dist/{cli-gvc3urUl.mjs.map → cli-CznHL-4-.mjs.map} +1 -1
  17. package/dist/{compose-CtUP07r3.cjs → compose-76ho0pUg.cjs} +99 -367
  18. package/dist/compose-76ho0pUg.cjs.map +1 -0
  19. package/dist/{compose-C40Wk9m6.mjs → compose-BxIsChn5.mjs} +81 -349
  20. package/dist/compose-BxIsChn5.mjs.map +1 -0
  21. package/dist/deploy/public.cjs +5 -5
  22. package/dist/deploy/public.mjs +5 -5
  23. package/dist/{deploy-BjsZpUQ3.mjs → deploy-BRUx9hiA.mjs} +3 -3
  24. package/dist/{deploy-BjsZpUQ3.mjs.map → deploy-BRUx9hiA.mjs.map} +1 -1
  25. package/dist/{deploy-DPzauQsm.cjs → deploy-DuHRWNeJ.cjs} +3 -3
  26. package/dist/{deploy-DPzauQsm.cjs.map → deploy-DuHRWNeJ.cjs.map} +1 -1
  27. package/dist/{dokploy-BoH0DMyS.mjs → dokploy-BTO37Co8.mjs} +2 -2
  28. package/dist/{dokploy-BoH0DMyS.mjs.map → dokploy-BTO37Co8.mjs.map} +1 -1
  29. package/dist/{dokploy-Cj1EHd4Y.cjs → dokploy-Q4iYDmls.cjs} +2 -2
  30. package/dist/{dokploy-Cj1EHd4Y.cjs.map → dokploy-Q4iYDmls.cjs.map} +1 -1
  31. package/dist/{encryption-D2z16vsS.cjs → encryption-9YA5ThNl.cjs} +2 -2
  32. package/dist/{encryption-D2z16vsS.cjs.map → encryption-9YA5ThNl.cjs.map} +1 -1
  33. package/dist/{encryption-MeDrE-rZ.mjs → encryption-BESGvjHD.mjs} +2 -2
  34. package/dist/{encryption-MeDrE-rZ.mjs.map → encryption-BESGvjHD.mjs.map} +1 -1
  35. package/dist/{engine-B-Yq0yQu.mjs → engine-CHij99aX.mjs} +2 -2
  36. package/dist/{engine-B-Yq0yQu.mjs.map → engine-CHij99aX.mjs.map} +1 -1
  37. package/dist/{engine-C3T39eTO.cjs → engine-U0xzB5i4.cjs} +2 -2
  38. package/dist/{engine-C3T39eTO.cjs.map → engine-U0xzB5i4.cjs.map} +1 -1
  39. package/dist/github-2OSRBf9G.mjs +669 -0
  40. package/dist/github-2OSRBf9G.mjs.map +1 -0
  41. package/dist/github-BM-Y9M2G.cjs +669 -0
  42. package/dist/github-BM-Y9M2G.cjs.map +1 -0
  43. package/dist/index.cjs +215 -43
  44. package/dist/index.cjs.map +1 -1
  45. package/dist/index.mjs +216 -44
  46. package/dist/index.mjs.map +1 -1
  47. package/dist/{migrate-DWUbYY5r.mjs → migrate-3oLefufw.mjs} +2 -2
  48. package/dist/{migrate-DWUbYY5r.mjs.map → migrate-3oLefufw.mjs.map} +1 -1
  49. package/dist/{migrate-CTqICj1B.cjs → migrate-BlYiMA23.cjs} +2 -2
  50. package/dist/{migrate-CTqICj1B.cjs.map → migrate-BlYiMA23.cjs.map} +1 -1
  51. package/dist/{migrate-DvIAITiG.cjs → migrate-C440QQvZ.cjs} +2 -2
  52. package/dist/{migrate-DvIAITiG.cjs.map → migrate-C440QQvZ.cjs.map} +1 -1
  53. package/dist/{migrate-A2CLFl9G.mjs → migrate-KwZsJFce.mjs} +2 -2
  54. package/dist/{migrate-A2CLFl9G.mjs.map → migrate-KwZsJFce.mjs.map} +1 -1
  55. package/dist/{resolve-CHwetPdo.mjs → resolve-BPIDcuQ3.mjs} +3 -3
  56. package/dist/{resolve-CHwetPdo.mjs.map → resolve-BPIDcuQ3.mjs.map} +1 -1
  57. package/dist/{resolve-DOXcAOcn.cjs → resolve-CBrJTyDO.cjs} +3 -3
  58. package/dist/{resolve-DOXcAOcn.cjs.map → resolve-CBrJTyDO.cjs.map} +1 -1
  59. package/dist/{rollback-Cq5s3WnD.mjs → rollback-B7uQKHEk.mjs} +3 -3
  60. package/dist/{rollback-Cq5s3WnD.mjs.map → rollback-B7uQKHEk.mjs.map} +1 -1
  61. package/dist/{rollback-DUoOaws3.cjs → rollback-P09wRaW4.cjs} +3 -3
  62. package/dist/{rollback-DUoOaws3.cjs.map → rollback-P09wRaW4.cjs.map} +1 -1
  63. package/dist/{secretsManager-Ti0oZwsS.mjs → secretsManager-Chs9VV2r.mjs} +8 -2
  64. package/dist/{secretsManager-Ti0oZwsS.mjs.map → secretsManager-Chs9VV2r.mjs.map} +1 -1
  65. package/dist/{secretsManager-DUXW3fQt.cjs → secretsManager-DcMlHnRC.cjs} +19 -2
  66. package/dist/{secretsManager-DUXW3fQt.cjs.map → secretsManager-DcMlHnRC.cjs.map} +1 -1
  67. package/dist/{seeds-DiXON7R8.mjs → seeds-B4PiO_48.mjs} +2335 -1528
  68. package/dist/seeds-B4PiO_48.mjs.map +1 -0
  69. package/dist/{seeds-qX13ubmO.cjs → seeds-BjHiy9tW.cjs} +3880 -2953
  70. package/dist/seeds-BjHiy9tW.cjs.map +1 -0
  71. package/dist/target/public.cjs +1 -1
  72. package/dist/target/public.mjs +1 -1
  73. package/package.json +15 -15
  74. package/dist/compose-C40Wk9m6.mjs.map +0 -1
  75. package/dist/compose-CtUP07r3.cjs.map +0 -1
  76. package/dist/github-B8dDa8xf.cjs +0 -213
  77. package/dist/github-B8dDa8xf.cjs.map +0 -1
  78. package/dist/github-Dyrjo0xd.mjs +0 -213
  79. package/dist/github-Dyrjo0xd.mjs.map +0 -1
  80. package/dist/seeds-DiXON7R8.mjs.map +0 -1
  81. package/dist/seeds-qX13ubmO.cjs.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-BM-Y9M2G.cjs","names":["secretsParameterName","secretsManagerSecretName","execFileSync","ListOpenIDConnectProvidersCommand","CreateOpenIDConnectProviderCommand","GetRoleCommand","UpdateAssumeRolePolicyCommand","ListRoleTagsCommand","CreateRoleCommand","DetachRolePolicyCommand","DeleteRolePolicyCommand","AttachRolePolicyCommand","TagRoleCommand","PutRolePolicyCommand","UntagRoleCommand","ListAttachedRolePoliciesCommand","IAMClient","loadWorkspaceConfig","readKey","keystoreProject","getKeyPath","isRemoteStore","secretsStoreFor"],"sources":["../src/deploy/githubOidc.ts","../src/deploy/githubPolicy.ts","../src/deploy/github.ts"],"sourcesContent":["/**\n * The `sub` GitHub puts in a deploy job's OIDC token — what the deploy role's\n * trust policy has to match exactly.\n *\n * It is not always `repo:<owner>/<name>:environment:<stage>`. A repository\n * (or its organisation) decides the format, and says which through\n * `GET /repos/{owner}/{repo}/actions/oidc/customization/sub`:\n *\n * - the default: `repo:<owner>/<name>:environment:<stage>`;\n * - the immutable subject — the default for repositories created after\n * 15 July 2026, opt-in before — puts the owner's and the repository's ids in\n * the `repo` segment: `repo:<owner>@<ownerId>/<name>@<repoId>:environment:<stage>`.\n * The API hands that segment back as `sub_claim_prefix`;\n * - a custom template (`use_default: false`) builds the `sub` from\n * `include_claim_keys`, in order, each as `<key>:<value>` joined by `:`,\n * where `context` is `environment:<stage>` in an environment job.\n *\n * A `:` inside a value is sent as `%3A`.\n *\n * See https://docs.github.com/en/actions/reference/security/oidc.\n */\n\nimport type { Gh } from './github.js';\n\n/** `GET /repos/{owner}/{repo}/actions/oidc/customization/sub`. */\nexport interface OidcSubjectCustomization {\n\tuse_default: boolean;\n\tinclude_claim_keys?: string[];\n\tuse_immutable_subject?: boolean;\n\t/** The `repo` segment of the subject, as GitHub will send it. */\n\tsub_claim_prefix?: string;\n}\n\n/** What `GET /repos/{owner}/{repo}` says that a subject may include. */\nexport interface RepoFacts {\n\townerId: number;\n\trepoId: number;\n\tvisibility: string;\n}\n\n/**\n * Template keys whose values are known for a repository's environment job\n * before the job runs. Anything else — `job_workflow_ref`, `ref`, `sha`,\n * `run_id`, `workflow`, `actor`, a custom property — depends on the run.\n */\nexport const SUPPORTED_CLAIM_KEYS = [\n\t'repo',\n\t'context',\n\t'environment',\n\t'repository',\n\t'repository_owner',\n\t'repository_owner_id',\n\t'repository_id',\n\t'repository_visibility',\n] as const;\n\nconst NEEDS_FACTS = new Set([\n\t'repository_owner_id',\n\t'repository_id',\n\t'repository_visibility',\n]);\n\n/** A `:` inside a claim value is sent as `%3A`. */\nfunction claim(value: string): string {\n\treturn value.replaceAll(':', '%3A');\n}\n\n/** Which kind of subject the repository uses, for the output. */\nexport type OidcSubjectKind = 'default' | 'immutable' | 'custom' | 'assumed';\n\n/** Whether {@link oidcSubject} needs the repository's ids or visibility. */\nexport function needsRepoFacts(\n\tcustomization: OidcSubjectCustomization,\n): boolean {\n\tconst immutableWithoutPrefix =\n\t\tcustomization.use_immutable_subject === true &&\n\t\t!customization.sub_claim_prefix;\n\tif (customization.use_default) return immutableWithoutPrefix;\n\n\tconst keys = customization.include_claim_keys ?? [];\n\treturn keys.some(\n\t\t(key) => NEEDS_FACTS.has(key) || (key === 'repo' && immutableWithoutPrefix),\n\t);\n}\n\n/**\n * The exact `sub` of a job in `repo`'s `stage` environment.\n *\n * @throws OidcSubjectNotSupported when a custom template includes a claim\n * whose value depends on the run, so no one subject can be trusted.\n */\nexport function oidcSubject(\n\trepo: string,\n\tstage: string,\n\tcustomization: OidcSubjectCustomization,\n\tfacts?: RepoFacts,\n): { subject: string; kind: OidcSubjectKind } {\n\tconst [owner = '', name = ''] = repo.split('/');\n\tconst environment = `environment:${claim(stage)}`;\n\n\tconst repoSegment = (): string => {\n\t\tif (customization.sub_claim_prefix) return customization.sub_claim_prefix;\n\t\tif (customization.use_immutable_subject) {\n\t\t\tif (!facts) throw new RepoFactsRequired(repo);\n\t\t\treturn `repo:${owner}@${facts.ownerId}/${name}@${facts.repoId}`;\n\t\t}\n\t\treturn `repo:${repo}`;\n\t};\n\tconst immutable =\n\t\tcustomization.use_immutable_subject === true ||\n\t\tcustomization.sub_claim_prefix !== undefined;\n\n\tif (customization.use_default) {\n\t\treturn {\n\t\t\tsubject: `${repoSegment()}:${environment}`,\n\t\t\tkind: immutable ? 'immutable' : 'default',\n\t\t};\n\t}\n\n\tconst keys = customization.include_claim_keys ?? [];\n\tconst unsupported = keys.filter(\n\t\t(key) => !(SUPPORTED_CLAIM_KEYS as readonly string[]).includes(key),\n\t);\n\tif (keys.length === 0 || unsupported.length > 0) {\n\t\tthrow new OidcSubjectNotSupported(repo, keys, unsupported);\n\t}\n\tif (keys.some((key) => NEEDS_FACTS.has(key)) && !facts) {\n\t\tthrow new RepoFactsRequired(repo);\n\t}\n\n\tconst parts = keys.map((key) => {\n\t\tswitch (key) {\n\t\t\tcase 'repo':\n\t\t\t\treturn repoSegment();\n\t\t\tcase 'context':\n\t\t\tcase 'environment':\n\t\t\t\treturn environment;\n\t\t\tcase 'repository':\n\t\t\t\treturn `repository:${claim(repo)}`;\n\t\t\tcase 'repository_owner':\n\t\t\t\treturn `repository_owner:${claim(owner)}`;\n\t\t\tcase 'repository_owner_id':\n\t\t\t\treturn `repository_owner_id:${facts!.ownerId}`;\n\t\t\tcase 'repository_id':\n\t\t\t\treturn `repository_id:${facts!.repoId}`;\n\t\t\tdefault:\n\t\t\t\treturn `repository_visibility:${claim(facts!.visibility)}`;\n\t\t}\n\t});\n\treturn { subject: parts.join(':'), kind: 'custom' };\n}\n\n/** What {@link resolveOidcSubject} found. */\nexport interface ResolvedOidcSubject {\n\tsubject: string;\n\tkind: OidcSubjectKind;\n\t/** Set when the format could not be read and the default was assumed. */\n\twarning?: string;\n}\n\n/** The endpoint a repository's subject format is read from. */\nexport function customizationEndpoint(repo: string): string {\n\treturn `repos/${repo}/actions/oidc/customization/sub`;\n}\n\n/**\n * The `sub` GitHub will send for `repo`'s `stage` environment, read from the\n * repository's OIDC settings through `gh`.\n *\n * A repository on a custom template that names no keys of its own uses its\n * organisation's, read from `orgs/{owner}/actions/oidc/customization/sub`.\n * When the settings cannot be read at all — no permission, or a GitHub\n * Enterprise Server without the endpoint — the default format is assumed, with\n * a warning saying where to check it.\n */\nexport function resolveOidcSubject(\n\tgh: Gh,\n\trepo: string,\n\tstage: string,\n): ResolvedOidcSubject {\n\tconst endpoint = customizationEndpoint(repo);\n\tlet customization: OidcSubjectCustomization;\n\ttry {\n\t\tcustomization = JSON.parse(gh(['api', endpoint]));\n\t} catch {\n\t\treturn {\n\t\t\tsubject: `repo:${repo}:environment:${claim(stage)}`,\n\t\t\tkind: 'assumed',\n\t\t\twarning: `Could not read ${endpoint}, so the default subject format is assumed. Check it with: gh api ${endpoint} — a repository on the immutable subject or a custom template sends a different sub, and the role would not be assumable.`,\n\t\t};\n\t}\n\n\tif (\n\t\t!customization.use_default &&\n\t\t(customization.include_claim_keys ?? []).length === 0\n\t) {\n\t\t// The repository follows its organisation's template.\n\t\tconst [owner = ''] = repo.split('/');\n\t\tconst orgEndpoint = `orgs/${owner}/actions/oidc/customization/sub`;\n\t\tlet org: { include_claim_keys?: string[]; use_immutable_subject?: boolean };\n\t\ttry {\n\t\t\torg = JSON.parse(gh(['api', orgEndpoint]));\n\t\t} catch {\n\t\t\tthrow new OrgOidcTemplateUnreadable(repo, orgEndpoint);\n\t\t}\n\t\tcustomization = {\n\t\t\t...customization,\n\t\t\tinclude_claim_keys: org.include_claim_keys ?? [],\n\t\t\tuse_immutable_subject:\n\t\t\t\tcustomization.use_immutable_subject ?? org.use_immutable_subject,\n\t\t};\n\t}\n\n\tconst facts = needsRepoFacts(customization)\n\t\t? readRepoFacts(gh, repo)\n\t\t: undefined;\n\treturn oidcSubject(repo, stage, customization, facts);\n}\n\nfunction readRepoFacts(gh: Gh, repo: string): RepoFacts {\n\tconst raw = JSON.parse(gh(['api', `repos/${repo}`])) as {\n\t\tid: number;\n\t\towner: { id: number };\n\t\tvisibility: string;\n\t};\n\treturn {\n\t\townerId: raw.owner.id,\n\t\trepoId: raw.id,\n\t\tvisibility: raw.visibility,\n\t};\n}\n\n/**\n * The repository's custom subject template includes a claim whose value\n * depends on the run — or names none — so no one subject can be trusted.\n */\nexport class OidcSubjectNotSupported extends Error {\n\tconstructor(\n\t\treadonly repo: string,\n\t\treadonly includeClaimKeys: string[],\n\t\treadonly unsupportedKeys: string[],\n\t) {\n\t\tsuper(\n\t\t\t`${repo} builds its OIDC subject from a custom template, ${JSON.stringify(includeClaimKeys)}, and ${\n\t\t\t\tunsupportedKeys.length > 0\n\t\t\t\t\t? `${unsupportedKeys.join(', ')} depend${unsupportedKeys.length === 1 ? 's' : ''} on the run`\n\t\t\t\t\t: 'it names no claims'\n\t\t\t}, so the deploy role cannot trust one exact subject. Set the repository back to the default subject — gh api --method PUT ${customizationEndpoint(repo)} -F use_default=true — (or keep the immutable one by also passing -F use_immutable_subject=true), or use a template built only from ${SUPPORTED_CLAIM_KEYS.join(', ')}; then run this again.`,\n\t\t);\n\t\tthis.name = 'OidcSubjectNotSupported';\n\t}\n}\n\n/** The subject needs the repository's ids, and none were read. */\nexport class RepoFactsRequired extends Error {\n\tconstructor(readonly repo: string) {\n\t\tsuper(\n\t\t\t`The OIDC subject of ${repo} includes its owner and repository ids; read them with gh api repos/${repo} and pass them in.`,\n\t\t);\n\t\tthis.name = 'RepoFactsRequired';\n\t}\n}\n\n/**\n * The repository uses its organisation's subject template, and that template\n * could not be read — so what GitHub sends is unknown, and is not guessed.\n */\nexport class OrgOidcTemplateUnreadable extends Error {\n\tconstructor(\n\t\treadonly repo: string,\n\t\treadonly endpoint: string,\n\t) {\n\t\tsuper(\n\t\t\t`${repo} uses its organisation's OIDC subject template, and ${endpoint} could not be read. Run this as an organisation owner (gh auth refresh -s admin:org), or set the repository back to the default subject with gh api --method PUT ${customizationEndpoint(repo)} -F use_default=true; then run this again.`,\n\t\t);\n\t\tthis.name = 'OrgOidcTemplateUnreadable';\n\t}\n}\n","/**\n * What the deploy role may do in the stage's account.\n *\n * An SST stage creates its own stack, which is most of AWS, so its role gets\n * `AdministratorAccess`. A stage deployed with the `compose` target builds and\n * runs containers on a server; from AWS its deploy job needs only the stage's\n * secrets — read, and written back when a deploy generates a new one — and,\n * when the deploy state is kept in AWS, the stage's state. So a compose stage\n * gets an inline policy naming exactly those resources and nothing else.\n *\n * `--policy-arn` replaces either.\n */\n\nimport { secretsParameterName } from '../secrets/aws.js';\nimport { secretsManagerSecretName } from '../secrets/secretsManager.js';\nimport type { NormalizedWorkspace } from '../workspace/types.js';\n\n/**\n * What SST needs to create a stack is most of AWS, so this is the default\n * outside compose — said out loud in the output, and replaceable with\n * `--policy-arn`.\n */\nexport const DEFAULT_POLICY_ARN = 'arn:aws:iam::aws:policy/AdministratorAccess';\n\n/** The name of the inline policy a compose stage's role is given. */\nexport const SCOPED_POLICY_NAME = 'gkm-deploy';\n\n/** One statement of an IAM policy document. */\nexport interface PolicyStatement {\n\tSid: string;\n\tEffect: 'Allow';\n\tAction: string[];\n\tResource: string | string[];\n\tCondition?: Record<string, Record<string, string>>;\n}\n\n/** What the role is given. */\nexport type DeployAccess =\n\t| {\n\t\t\tkind: 'managed';\n\t\t\tpolicyArn: string;\n\t\t\t/** Why, for the output. */\n\t\t\treason: string;\n\t }\n\t| {\n\t\t\tkind: 'scoped';\n\t\t\t/** The statements, in the account the role is in. */\n\t\t\tstatements(account: string): PolicyStatement[];\n\t\t\t/** What each statement is for, for the output. */\n\t\t\tdescribe: string[];\n\t };\n\n/** Every target the stage's apps deploy through. */\nexport function deployTargets(workspace: NormalizedWorkspace): string[] {\n\tconst targets = new Set<string>([workspace.deploy?.default ?? 'dokploy']);\n\tfor (const app of Object.values(workspace.apps)) {\n\t\ttargets.add(app.resolvedDeployTarget);\n\t}\n\treturn [...targets].sort();\n}\n\n/**\n * The access the stage's deploy role is given: `--policy-arn` when passed,\n * the scoped policy for a stage only the compose target deploys, and\n * `AdministratorAccess` otherwise.\n */\nexport function deployAccess(\n\tworkspace: NormalizedWorkspace,\n\tstage: string,\n\tpolicyArn?: string,\n): DeployAccess {\n\tif (policyArn) {\n\t\treturn { kind: 'managed', policyArn, reason: '--policy-arn' };\n\t}\n\n\tconst targets = deployTargets(workspace);\n\tif (targets.length !== 1 || targets[0] !== 'compose') {\n\t\treturn {\n\t\t\tkind: 'managed',\n\t\t\tpolicyArn: DEFAULT_POLICY_ARN,\n\t\t\treason: `the ${targets.join(', ')} target${targets.length === 1 ? '' : 's'} deploy${targets.length === 1 ? 's' : ''} infrastructure; --policy-arn to narrow`,\n\t\t};\n\t}\n\n\tconst project = workspace.name;\n\tconst store = workspace.secrets.store ?? 'file';\n\tconst state = workspace.state;\n\tconst custom =\n\t\t(typeof store === 'object' && typeof store.provider === 'object') ||\n\t\t(state !== undefined && typeof state.provider === 'object');\n\tif (custom) {\n\t\treturn {\n\t\t\tkind: 'managed',\n\t\t\tpolicyArn: DEFAULT_POLICY_ARN,\n\t\t\treason: `a custom ${typeof store === 'object' && typeof store.provider === 'object' ? 'secrets store' : 'state provider'} cannot be scoped; --policy-arn to narrow`,\n\t\t};\n\t}\n\n\tconst describe: string[] = [];\n\tconst build: ((account: string) => PolicyStatement[])[] = [];\n\n\tif (typeof store === 'object' && store.provider === 'ssm') {\n\t\tconst { region } = store;\n\t\tconst name = secretsParameterName(project, stage);\n\t\tdescribe.push(`read and update ${name} (SSM, ${region})`);\n\t\tbuild.push((account) => [\n\t\t\t{\n\t\t\t\tSid: 'StageSecrets',\n\t\t\t\tEffect: 'Allow',\n\t\t\t\tAction: ['ssm:GetParameter', 'ssm:PutParameter'],\n\t\t\t\tResource: `arn:aws:ssm:${region}:${account}:parameter${name}`,\n\t\t\t},\n\t\t]);\n\t} else if (\n\t\ttypeof store === 'object' &&\n\t\tstore.provider === 'secrets-manager'\n\t) {\n\t\tconst { region, kmsKeyId } = store as { region: string; kmsKeyId?: string };\n\t\tconst name = secretsManagerSecretName(project, stage);\n\t\tdescribe.push(`read and update ${name} (Secrets Manager, ${region})`);\n\t\tbuild.push((account) => {\n\t\t\tconst statements: PolicyStatement[] = [\n\t\t\t\t{\n\t\t\t\t\tSid: 'StageSecrets',\n\t\t\t\t\tEffect: 'Allow',\n\t\t\t\t\tAction: [\n\t\t\t\t\t\t'secretsmanager:GetSecretValue',\n\t\t\t\t\t\t'secretsmanager:PutSecretValue',\n\t\t\t\t\t\t'secretsmanager:CreateSecret',\n\t\t\t\t\t],\n\t\t\t\t\t// Secrets Manager appends six random characters to a secret's ARN.\n\t\t\t\t\tResource: `arn:aws:secretsmanager:${region}:${account}:secret:${name}-??????`,\n\t\t\t\t},\n\t\t\t];\n\t\t\tif (kmsKeyId) {\n\t\t\t\tstatements.push({\n\t\t\t\t\tSid: 'StageSecretsKey',\n\t\t\t\t\tEffect: 'Allow',\n\t\t\t\t\tAction: ['kms:Decrypt', 'kms:GenerateDataKey'],\n\t\t\t\t\tResource: kmsKeyId.startsWith('arn:') ? kmsKeyId : '*',\n\t\t\t\t\tCondition: {\n\t\t\t\t\t\tStringEquals: {\n\t\t\t\t\t\t\t'kms:ViaService': `secretsmanager.${region}.amazonaws.com`,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn statements;\n\t\t});\n\t}\n\n\tif (state?.provider === 'ssm') {\n\t\tconst { region } = state;\n\t\tconst path = `/gkm/${project}/${stage}/`;\n\t\tdescribe.push(\n\t\t\t`read and write the deploy state under ${path} (SSM, ${region})`,\n\t\t);\n\t\tbuild.push((account) => [\n\t\t\t{\n\t\t\t\tSid: 'StageState',\n\t\t\t\tEffect: 'Allow',\n\t\t\t\tAction: ['ssm:GetParameter', 'ssm:PutParameter', 'ssm:DeleteParameter'],\n\t\t\t\tResource: `arn:aws:ssm:${region}:${account}:parameter${path}*`,\n\t\t\t},\n\t\t]);\n\t} else if (state?.provider === 's3') {\n\t\tconst prefix = (state.prefix ?? 'gkm').replace(/\\/+$/, '');\n\t\tconst path = `${prefix ? `${prefix}/` : ''}${project}/${stage}/`;\n\t\tdescribe.push(\n\t\t\t`read and write the deploy state under s3://${state.bucket}/${path}`,\n\t\t);\n\t\tbuild.push(() => [\n\t\t\t{\n\t\t\t\tSid: 'StageState',\n\t\t\t\tEffect: 'Allow',\n\t\t\t\tAction: ['s3:GetObject', 's3:PutObject', 's3:DeleteObject'],\n\t\t\t\tResource: `arn:aws:s3:::${state.bucket}/${path}*`,\n\t\t\t},\n\t\t\t// So a state not written yet is a 404, not a 403.\n\t\t\t{\n\t\t\t\tSid: 'StageStateBucket',\n\t\t\t\tEffect: 'Allow',\n\t\t\t\tAction: ['s3:ListBucket'],\n\t\t\t\tResource: `arn:aws:s3:::${state.bucket}`,\n\t\t\t},\n\t\t]);\n\t}\n\n\treturn {\n\t\tkind: 'scoped',\n\t\tstatements: (account) => build.flatMap((statements) => statements(account)),\n\t\tdescribe,\n\t};\n}\n\n/** The scoped access as a policy document, or null when it allows nothing. */\nexport function scopedPolicyDocument(\n\tstatements: PolicyStatement[],\n): string | null {\n\tif (statements.length === 0) return null;\n\treturn JSON.stringify({ Version: '2012-10-17', Statement: statements });\n}\n\n/** The account an IAM ARN is in: `arn:aws:iam::<account>:…`. */\nexport function accountOf(arn: string): string {\n\treturn arn.split(':')[4] ?? '';\n}\n","/**\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 or Secrets Manager the deploy job reads them with the role instead, so\n * no key is handed to GitHub, and this checks the stage's store in the same\n * account, with the same profile. Re-running it converges rather than duplicating.\n *\n * The role trusts the exact `sub` GitHub sends for the environment, read from\n * the repository's OIDC subject settings (`githubOidc.ts`) — the default, the\n * immutable subject with the owner's and repository's ids, or a custom\n * template. A re-run rewrites an existing role's trust to it, so running this\n * again repairs a role that trusted the wrong format. What the role may do is\n * `githubPolicy.ts`: `AdministratorAccess` for SST, and for a compose stage\n * only its own secrets (and deploy state, when that is in AWS).\n */\n\nimport { execFileSync } from 'node:child_process';\nimport {\n\tAttachRolePolicyCommand,\n\tCreateOpenIDConnectProviderCommand,\n\tCreateRoleCommand,\n\tDeleteRolePolicyCommand,\n\tDetachRolePolicyCommand,\n\tGetRoleCommand,\n\tIAMClient,\n\ttype IAMClientConfig,\n\tListAttachedRolePoliciesCommand,\n\tListOpenIDConnectProvidersCommand,\n\tListRoleTagsCommand,\n\tPutRolePolicyCommand,\n\tTagRoleCommand,\n\tUntagRoleCommand,\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';\nimport { resolveOidcSubject } from './githubOidc.js';\nimport {\n\taccountOf,\n\tdeployAccess,\n\tSCOPED_POLICY_NAME,\n\tscopedPolicyDocument,\n} from './githubPolicy.js';\n\nexport { DEFAULT_POLICY_ARN, SCOPED_POLICY_NAME } from './githubPolicy.js';\n\nconst logger = console;\n\nconst GITHUB_OIDC_HOST = 'token.actions.githubusercontent.com';\nconst AUDIENCE = 'sts.amazonaws.com';\n\n/**\n * The tag recording which managed policy gkm attached to the role — so a\n * re-run detaches only what gkm put there, never a policy someone else did.\n */\nexport const POLICY_TAG = 'gkm:policy-arn';\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, in place of the default. */\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// stderr captured, so a read that fails (a 404) is not printed as noise.\n\t\tstdio: ['pipe', 'pipe', 'pipe'],\n\t\t...(input === undefined ? {} : { input }),\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 whose OIDC token carries exactly\n * `subject` — the repository's `<stage>` environment, in the format the\n * repository sends (see `githubOidc.ts`), and nothing else. Not another\n * branch's job, not another environment's: that is what keeps a staging\n * deploy from using the production role.\n */\nexport function trustPolicy(providerArn: string, subject: string): 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`]: subject,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t},\n\t\t],\n\t});\n}\n\n/**\n * The subject a trust policy trusts, or undefined when it names none. IAM\n * hands documents back URL-encoded.\n */\nexport function trustedSubject(\n\tdocument: string | undefined,\n): string | undefined {\n\tif (!document) return undefined;\n\tlet parsed: {\n\t\tStatement?: { Condition?: Record<string, Record<string, unknown>> }[];\n\t};\n\ttry {\n\t\tparsed = JSON.parse(safeDecode(document));\n\t} catch {\n\t\treturn undefined;\n\t}\n\tfor (const statement of parsed.Statement ?? []) {\n\t\tfor (const operator of Object.values(statement.Condition ?? {})) {\n\t\t\tconst value = operator[`${GITHUB_OIDC_HOST}:sub`];\n\t\t\tif (typeof value === 'string') return value;\n\t\t\tif (Array.isArray(value)) return value.join(', ');\n\t\t}\n\t}\n\treturn undefined;\n}\n\nfunction safeDecode(value: string): string {\n\ttry {\n\t\treturn decodeURIComponent(value);\n\t} catch {\n\t\treturn value;\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/**\n * What the role is given: a managed policy, attached; or an inline policy\n * document — null for none at all.\n */\nexport type RoleAccess = { managed: string } | { inline: string | null };\n\n/** What {@link ensureRole} did. */\nexport interface RoleOutcome {\n\tarn: string;\n\tcreated: boolean;\n\t/** The subject the role trusted before, when it existed. */\n\tpreviousSubject?: string;\n\t/** Managed policies gkm had attached and detached now. */\n\tdetached: string[];\n\t/**\n\t * Managed policies still attached that the role no longer needs, which gkm\n\t * did not record attaching — left alone, for a person to detach.\n\t */\n\tleftAttached: string[];\n}\n\nfunction isNoSuchEntity(error: unknown): boolean {\n\treturn (error as { name?: string }).name === 'NoSuchEntityException';\n}\n\n/**\n * The deploy role, created or brought back to this trust and access.\n *\n * Converges: re-running rewrites the trust, and moves the role from one access\n * to another — detaching a managed policy only when the role's\n * {@link POLICY_TAG} says gkm attached it.\n */\nexport async function ensureRole(\n\tiam: IAMClient,\n\tname: string,\n\ttrust: string,\n\taccess: RoleAccess,\n): Promise<RoleOutcome> {\n\tlet arn: string;\n\tlet created = false;\n\tlet previousSubject: string | undefined;\n\tlet recorded: string | undefined;\n\ttry {\n\t\tconst { Role } = await iam.send(new GetRoleCommand({ RoleName: name }));\n\t\tpreviousSubject = trustedSubject(Role!.AssumeRolePolicyDocument);\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\tconst { Tags = [] } = await iam.send(\n\t\t\tnew ListRoleTagsCommand({ RoleName: name }),\n\t\t);\n\t\trecorded = Tags.find((tag) => tag.Key === POLICY_TAG)?.Value;\n\t} catch (error) {\n\t\tif (!isNoSuchEntity(error)) throw error;\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\tcreated = true;\n\t}\n\n\tconst detached: string[] = [];\n\tconst leftAttached: string[] = [];\n\tconst detach = async (policyArn: string) => {\n\t\ttry {\n\t\t\tawait iam.send(\n\t\t\t\tnew DetachRolePolicyCommand({ RoleName: name, PolicyArn: policyArn }),\n\t\t\t);\n\t\t\tdetached.push(policyArn);\n\t\t} catch (error) {\n\t\t\t// Already gone — by hand, or a run that stopped halfway.\n\t\t\tif (!isNoSuchEntity(error)) throw error;\n\t\t}\n\t};\n\tconst deleteInline = async () => {\n\t\ttry {\n\t\t\tawait iam.send(\n\t\t\t\tnew DeleteRolePolicyCommand({\n\t\t\t\t\tRoleName: name,\n\t\t\t\t\tPolicyName: SCOPED_POLICY_NAME,\n\t\t\t\t}),\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tif (!isNoSuchEntity(error)) throw error;\n\t\t}\n\t};\n\n\tif ('managed' in access) {\n\t\t// Attaching an attached policy is a no-op, so this converges too.\n\t\tawait iam.send(\n\t\t\tnew AttachRolePolicyCommand({\n\t\t\t\tRoleName: name,\n\t\t\t\tPolicyArn: access.managed,\n\t\t\t}),\n\t\t);\n\t\tif (recorded && recorded !== access.managed) await detach(recorded);\n\t\tif (recorded !== access.managed) {\n\t\t\tawait iam.send(\n\t\t\t\tnew TagRoleCommand({\n\t\t\t\t\tRoleName: name,\n\t\t\t\t\tTags: [{ Key: POLICY_TAG, Value: access.managed }],\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\t\tif (!created) await deleteInline();\n\t\treturn { arn, created, previousSubject, detached, leftAttached };\n\t}\n\n\tif (access.inline) {\n\t\tawait iam.send(\n\t\t\tnew PutRolePolicyCommand({\n\t\t\t\tRoleName: name,\n\t\t\t\tPolicyName: SCOPED_POLICY_NAME,\n\t\t\t\tPolicyDocument: access.inline,\n\t\t\t}),\n\t\t);\n\t} else if (!created) {\n\t\tawait deleteInline();\n\t}\n\tif (created) return { arn, created, previousSubject, detached, leftAttached };\n\n\tif (recorded) {\n\t\tawait detach(recorded);\n\t\tawait iam.send(\n\t\t\tnew UntagRoleCommand({ RoleName: name, TagKeys: [POLICY_TAG] }),\n\t\t);\n\t}\n\t// A role from before gkm recorded what it attached: what is attached may\n\t// be gkm's or someone's own, so it is named, not detached.\n\tconst { AttachedPolicies = [] } = await iam.send(\n\t\tnew ListAttachedRolePoliciesCommand({ RoleName: name }),\n\t);\n\tfor (const policy of AttachedPolicies) {\n\t\tif (policy.PolicyArn && !detached.includes(policy.PolicyArn)) {\n\t\t\tleftAttached.push(policy.PolicyArn);\n\t\t}\n\t}\n\treturn { arn, created, previousSubject, detached, leftAttached };\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; subject: 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\t// The exact subject GitHub will send — read from the repository's OIDC\n\t// settings, before anything is changed, so a format no role can trust\n\t// fails here.\n\tconst oidc = resolveOidcSubject(gh, repo, options.stage);\n\tconst access = deployAccess(workspace, options.stage, options.policyArn);\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\tif (access.kind === 'managed') {\n\t\tlogger.log(` Policy: ${access.policyArn} (${access.reason})`);\n\t} else if (access.describe.length > 0) {\n\t\tlogger.log(\n\t\t\t` Policy: inline \"${SCOPED_POLICY_NAME}\", scoped to the compose deploy — only:`,\n\t\t);\n\t\tfor (const line of access.describe) logger.log(` ${line}`);\n\t} else {\n\t\tlogger.log(\n\t\t\t' Policy: none — a compose deploy with file secrets and local state reads nothing from AWS',\n\t\t);\n\t}\n\tlogger.log(\n\t\t` Trusted by: ${oidc.subject} only (${SUBJECT_KINDS[oidc.kind]})`,\n\t);\n\tif (oidc.warning) logger.log(` ⚠ ${oidc.warning}`);\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\tif (access.kind === 'scoped') {\n\t\t\tconst document = scopedPolicyDocument(access.statements('<account>'));\n\t\t\tif (document) {\n\t\t\t\tlogger.log(\n\t\t\t\t\t`\\n Inline policy:\\n${JSON.stringify(JSON.parse(document), null, 2).replace(/^/gm, ' ')}`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t\tlogger.log('\\n --dry-run: nothing changed.\\n');\n\t\treturn { repo, subject: oidc.subject };\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 outcome = await ensureRole(\n\t\tiam,\n\t\trole,\n\t\ttrustPolicy(providerArn, oidc.subject),\n\t\taccess.kind === 'managed'\n\t\t\t? { managed: access.policyArn }\n\t\t\t: {\n\t\t\t\t\tinline: scopedPolicyDocument(\n\t\t\t\t\t\taccess.statements(accountOf(providerArn)),\n\t\t\t\t\t),\n\t\t\t\t},\n\t);\n\tconst roleArn = outcome.arn;\n\tlogger.log(`\\n ✓ AWS: ${roleArn}${outcome.created ? ' (created)' : ''}`);\n\tif (!outcome.created) {\n\t\tlogger.log(\n\t\t\toutcome.previousSubject === oidc.subject\n\t\t\t\t? ` ✓ Trust: unchanged, ${oidc.subject}`\n\t\t\t\t: ` ✓ Trust: ${outcome.previousSubject ?? '(no subject)'} → ${oidc.subject}`,\n\t\t);\n\t}\n\tfor (const detached of outcome.detached) {\n\t\tlogger.log(` ✓ Detached ${detached} (attached by gkm, no longer needed)`);\n\t}\n\tif (access.kind === 'scoped') {\n\t\tfor (const attached of outcome.leftAttached) {\n\t\t\tlogger.log(\n\t\t\t\t` ⚠ ${attached} is still attached to ${role}. gkm has no record of attaching it, so it is left; the compose deploy does not need it. Detach it with: aws iam detach-role-policy --role-name ${role} --policy-arn ${attached}${options.profile ? ` --profile ${options.profile}` : ''}`,\n\t\t\t);\n\t\t}\n\t}\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, subject: oidc.subject };\n}\n\nconst SUBJECT_KINDS = {\n\tdefault: \"the repository's default subject\",\n\timmutable: \"the repository's immutable subject\",\n\tcustom: \"the repository's custom subject template\",\n\tassumed: 'assumed: the default format',\n} as const;\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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"}
package/dist/index.cjs CHANGED
@@ -7,7 +7,7 @@ const require_discover = require("./discover-D1It5r_h.cjs");
7
7
  const require_derive = require("./derive-DAbo49sm.cjs");
8
8
  const require_workspace = require("./workspace-Bp8FEkF-.cjs");
9
9
  const require_config = require("./config.cjs");
10
- const require_seeds = require("./seeds-qX13ubmO.cjs");
10
+ const require_seeds = require("./seeds-BjHiy9tW.cjs");
11
11
  const require_EndpointGenerator = require("./EndpointGenerator-CP6sBvQV.cjs");
12
12
  const require_openapi = require("./openapi.cjs");
13
13
  const require_SubscriberGenerator = require("./SubscriberGenerator-CajwOa5W.cjs");
@@ -15,8 +15,8 @@ const require_appPorts = require("./appPorts-Cu79hJF6.cjs");
15
15
  const require_drivers = require("./drivers-BdvtCKc7.cjs");
16
16
  const require_emit = require("./emit-Dmc1nvdS.cjs");
17
17
  const require_sniffer = require("./sniffer-BjG-1mlD.cjs");
18
- const require_deploy = require("./deploy-DPzauQsm.cjs");
19
- const require_compose = require("./compose-CtUP07r3.cjs");
18
+ const require_deploy = require("./deploy-DuHRWNeJ.cjs");
19
+ const require_compose = require("./compose-76ho0pUg.cjs");
20
20
  const require_credentialSchemas = require("./credentialSchemas-DCR4Wd1W.cjs");
21
21
  const require_LocalStateStore = require("./LocalStateStore-Bocb8dmZ.cjs");
22
22
  const require_utils = require("./utils-D2Nk1Nv2.cjs");
@@ -53,7 +53,7 @@ crypto = require_rolldown_runtime.__toESM(crypto, 1);
53
53
  let _geekmidas_constructs_functions = require("@geekmidas/constructs/functions");
54
54
  let _geekmidas_constructs_topic = require("@geekmidas/constructs/topic");
55
55
  //#region package.json
56
- var version = "10.0.0-alpha.79";
56
+ var version = "10.0.0-alpha.81";
57
57
  //#endregion
58
58
  //#region src/prompt.ts
59
59
  /**
@@ -240,6 +240,91 @@ function maskToken(token) {
240
240
  return `${token.slice(0, 4)}...${token.slice(-4)}`;
241
241
  }
242
242
  //#endregion
243
+ //#region src/reconcile/serviceLogins.ts
244
+ /**
245
+ * Where to sign in to each local service, and as whom — what `gkm dev`
246
+ * prints with its other addresses and `gkm dev:credentials` prints alone.
247
+ *
248
+ * Only the services a person opens or connects a client to: Postgres, the
249
+ * MinIO console, Mailpit's inbox, Redis and its HTTP proxy, RabbitMQ's
250
+ * console, the AWS emulator — and OpenObserve, where `gkm compose` runs it
251
+ * for the local stage.
252
+ */
253
+ /** Each running service's address and login, in a stable order. */
254
+ function serviceLogins(options) {
255
+ const { containers, ports, credentials } = options;
256
+ const has = (container, key) => containers.includes(container) && ports[key] !== void 0;
257
+ const logins = [];
258
+ if (has("postgres", "postgres")) {
259
+ const { user, password } = credentials.postgres;
260
+ logins.push({
261
+ service: "postgres",
262
+ label: "Postgres",
263
+ url: `postgres://${user}:${encodeURIComponent(password)}@localhost:${ports.postgres}/${require_seeds.CLUSTER_DATABASE}`,
264
+ user,
265
+ password
266
+ });
267
+ }
268
+ if (has("minio", "minio-console")) logins.push({
269
+ service: "minio",
270
+ label: "MinIO console",
271
+ url: `http://localhost:${ports["minio-console"]}`,
272
+ ...credentials.minio,
273
+ ...ports.minio !== void 0 ? { note: `S3 API on http://localhost:${ports.minio}` } : {}
274
+ });
275
+ if (has("mailpit", "mailpit-web")) logins.push({
276
+ service: "mailpit",
277
+ label: "Mailpit inbox",
278
+ url: `http://localhost:${ports["mailpit-web"]}`,
279
+ note: "no login"
280
+ });
281
+ if (has("redis", "redis")) logins.push({
282
+ service: "redis",
283
+ label: "Redis",
284
+ url: `redis://:${encodeURIComponent(credentials.redis.password)}@localhost:${ports.redis}`,
285
+ password: credentials.redis.password
286
+ });
287
+ if (has("redis-http", "redis-http")) logins.push({
288
+ service: "redis-http",
289
+ label: "Cache (HTTP)",
290
+ url: `http://localhost:${ports["redis-http"]}`,
291
+ password: credentials.cacheToken,
292
+ note: "the password is its bearer token"
293
+ });
294
+ if (has("rabbitmq", "rabbitmq-management")) logins.push({
295
+ service: "rabbitmq",
296
+ label: "RabbitMQ console",
297
+ url: `http://localhost:${ports["rabbitmq-management"]}`,
298
+ ...credentials.rabbitmq
299
+ });
300
+ if (has("localstack", "localstack")) logins.push({
301
+ service: "localstack",
302
+ label: "AWS emulator",
303
+ url: `http://localhost:${ports.localstack}`,
304
+ user: credentials.emulator.accessKeyId,
305
+ password: credentials.emulator.secretAccessKey,
306
+ note: "access key id / secret access key"
307
+ });
308
+ if (options.logs) logins.push({
309
+ service: "openobserve",
310
+ label: "OpenObserve",
311
+ url: options.logs.url,
312
+ user: credentials.logs.email,
313
+ password: credentials.logs.password,
314
+ note: `when running: ${options.logs.via}`
315
+ });
316
+ return logins;
317
+ }
318
+ /** The lines `gkm dev` and `gkm dev:credentials` print, one per service. */
319
+ function describeLogins(logins) {
320
+ const width = Math.max(0, ...logins.map(({ label }) => label.length));
321
+ return logins.flatMap((login) => {
322
+ const head = ` ${login.label.padEnd(width)} ${login.url}`;
323
+ const detail = [login.user !== void 0 && login.password !== void 0 ? `user ${login.user}, password ${login.password}` : login.password !== void 0 ? `password ${login.password}` : void 0, login.note].filter(Boolean).join(" — ");
324
+ return detail ? [head, ` ${" ".repeat(width)} ${detail}`] : [head];
325
+ });
326
+ }
327
+ //#endregion
243
328
  //#region src/trust/index.ts
244
329
  /**
245
330
  * Installing the local edge's certificate authority into the OS trust store.
@@ -413,6 +498,74 @@ var UnsupportedPlatform = class extends Error {
413
498
  this.name = "UnsupportedPlatform";
414
499
  }
415
500
  };
501
+ //#endregion
502
+ //#region src/dev/credentials.ts
503
+ /**
504
+ * `gkm dev:credentials` — where to sign in to each local service, and as
505
+ * whom, without starting anything.
506
+ *
507
+ * Reads the workspace's generated local logins (see
508
+ * `reconcile/localCredentials.ts`) and the ports the last `gkm dev` or
509
+ * `gkm test` published them on (`.gkm/ports.json`). `--json` prints the same
510
+ * for a script.
511
+ */
512
+ var credentials_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
513
+ NoLocalCredentials: () => NoLocalCredentials,
514
+ devCredentials: () => devCredentials,
515
+ devCredentialsCommand: () => devCredentialsCommand
516
+ });
517
+ /** The containers a person may sign in to, in the order they print. */
518
+ const SIGNED_IN = [
519
+ "postgres",
520
+ "minio",
521
+ "mailpit",
522
+ "redis",
523
+ "redis-http",
524
+ "rabbitmq",
525
+ "localstack"
526
+ ];
527
+ /** Nothing has generated this workspace's local logins yet. */
528
+ var NoLocalCredentials = class extends Error {
529
+ workspace;
530
+ constructor(workspace) {
531
+ super(`'${workspace}' has no local logins on this machine yet: they are generated the first time gkm dev, gkm test or gkm setup starts its services. Run one of them, then this again.`);
532
+ this.workspace = workspace;
533
+ this.name = "NoLocalCredentials";
534
+ }
535
+ };
536
+ /** The logins of every service the workspace last published. */
537
+ function devCredentials(workspace, credentials, ports) {
538
+ const containers = SIGNED_IN.filter((container) => require_seeds.portsOf(container).some((port) => ports[port.key] !== void 0));
539
+ const logs = require_workspace.resolveLogs(workspace.deploy?.compose?.logs);
540
+ const local = logs ? {
541
+ url: logs.public ? `https://logs.${workspace.name}.localhost` : `http://localhost:${logs.port}`,
542
+ via: `gkm compose --stage ${workspace.stages.local}`
543
+ } : void 0;
544
+ return {
545
+ workspace: workspace.name,
546
+ stage: workspace.stages.local,
547
+ services: serviceLogins({
548
+ containers,
549
+ ports,
550
+ credentials,
551
+ ...local ? { logs: local } : {}
552
+ })
553
+ };
554
+ }
555
+ async function devCredentialsCommand(options = {}, print = (line) => console.log(line)) {
556
+ const { workspace } = await require_config.loadWorkspaceConfig(process.cwd());
557
+ const credentials = await require_seeds.readLocalCredentials(workspace);
558
+ if (!credentials) throw new NoLocalCredentials(workspace.name);
559
+ const output = devCredentials(workspace, credentials, await require_seeds.loadPortState(workspace.root));
560
+ if (options.json) {
561
+ print(JSON.stringify(output, null, 2));
562
+ return output;
563
+ }
564
+ print(`🔑 Local logins for ${output.workspace} (${output.stage})`);
565
+ if (output.services.length === 0) print(" No service has been published yet — start them with gkm dev, then run this again.");
566
+ for (const line of describeLogins(output.services)) print(line);
567
+ return output;
568
+ }
416
569
  /** Overrides `dev.discoveryPort` — `0` serves on a port of its own. */
417
570
  const DISCOVERY_PORT_ENV = "GKM_DISCOVERY_PORT";
418
571
  /**
@@ -2051,6 +2204,11 @@ async function workspaceDevCommand(configured, options) {
2051
2204
  logger$5.log("🐳 Services");
2052
2205
  for (const line of require_seeds.describeServices(reconciled.services)) logger$5.log(line);
2053
2206
  }
2207
+ const { services: logins } = devCredentials(workspace, reconciled.credentials, reconciled.ports);
2208
+ if (logins.length > 0) {
2209
+ logger$5.log("🔑 Logins (again any time: gkm dev:credentials)");
2210
+ for (const line of describeLogins(logins)) logger$5.log(line);
2211
+ }
2054
2212
  const secretsEnv = {
2055
2213
  ...rawSecrets,
2056
2214
  ...reconciled.env
@@ -2059,9 +2217,9 @@ async function workspaceDevCommand(configured, options) {
2059
2217
  for (const { id, port } of fakes) logger$5.log(`🎭 ${id} fake: http://localhost:${port}`);
2060
2218
  logger$5.log(options.migrate || options.seed ? "🗄️ Migrating the databases" : "🗄️ Checking for pending migrations");
2061
2219
  if (options.migrate || options.seed) {
2062
- const { prepareDevDatabases } = await Promise.resolve().then(() => require("./migrate-CTqICj1B.cjs"));
2220
+ const { prepareDevDatabases } = await Promise.resolve().then(() => require("./migrate-BlYiMA23.cjs"));
2063
2221
  await prepareDevDatabases(workspace, reconciled.env, { seed: !!options.seed });
2064
- } else await Promise.resolve().then(() => require("./migrate-CTqICj1B.cjs")).then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
2222
+ } else await Promise.resolve().then(() => require("./migrate-BlYiMA23.cjs")).then(({ reportPendingMigrations }) => reportPendingMigrations(workspace, reconciled.env)).catch((error) => {
2065
2223
  logger$5.log(`⚠️ Could not check for pending migrations: ${error instanceof Error ? error.message : String(error)}`);
2066
2224
  });
2067
2225
  const appUrls = {};
@@ -3069,7 +3227,7 @@ async function buildApp(input) {
3069
3227
  if (production?.bundle && !input.skipBundle) {
3070
3228
  const outputDir = (0, node_path.join)(appRoot, ".gkm", "server");
3071
3229
  const entryPoint = await writeSurfaceServer(outputDir, production);
3072
- const { bundleServer } = await Promise.resolve().then(() => require("./bundler-RAoSRuvp.cjs"));
3230
+ const { bundleServer } = await Promise.resolve().then(() => require("./bundler-DeIVbloM.cjs"));
3073
3231
  await bundleServer({
3074
3232
  entryPoint,
3075
3233
  outputDir: (0, node_path.join)(outputDir, "dist"),
@@ -3193,7 +3351,7 @@ async function buildWorkers(input) {
3193
3351
  });
3194
3352
  logger$4.log(`Generated worker ${worker.id}: ${crons.length} crons, ${queues.length} queues, ${subscribers.length} subscribers`);
3195
3353
  if (!input.bundle) continue;
3196
- const { bundleServer } = await Promise.resolve().then(() => require("./bundler-RAoSRuvp.cjs"));
3354
+ const { bundleServer } = await Promise.resolve().then(() => require("./bundler-DeIVbloM.cjs"));
3197
3355
  const outfile = require_seeds.workerBundleName(worker.id);
3198
3356
  await bundleServer({
3199
3357
  entryPoint,
@@ -3263,7 +3421,7 @@ async function buildForTarget(target, context, appRoot, workspaceRoot, endpointG
3263
3421
  let masterKey;
3264
3422
  if (context.production?.bundle && !skipBundle) {
3265
3423
  logger$4.log(`\n📦 Bundling production server...`);
3266
- const { bundleServer } = await Promise.resolve().then(() => require("./bundler-RAoSRuvp.cjs"));
3424
+ const { bundleServer } = await Promise.resolve().then(() => require("./bundler-DeIVbloM.cjs"));
3267
3425
  const allConstructs = [
3268
3426
  ...endpoints.map((e) => e.construct),
3269
3427
  ...functions.map((f) => f.construct),
@@ -4965,25 +5123,25 @@ function loadPackageJson() {
4965
5123
  * Run: pnpm --filter @geekmidas/cli sync-versions
4966
5124
  */
4967
5125
  const GEEKMIDAS_VERSIONS = {
4968
- "@geekmidas/audit": "~10.0.0-alpha.79",
4969
- "@geekmidas/auth": "~10.0.0-alpha.79",
4970
- "@geekmidas/cache": "~10.0.0-alpha.79",
4971
- "@geekmidas/client": "~10.0.0-alpha.79",
4972
- "@geekmidas/cloud": "~10.0.0-alpha.79",
4973
- "@geekmidas/constructs": "~10.0.0-alpha.79",
4974
- "@geekmidas/db": "~10.0.0-alpha.79",
4975
- "@geekmidas/emailkit": "~10.0.0-alpha.79",
4976
- "@geekmidas/envkit": "~10.0.0-alpha.79",
4977
- "@geekmidas/errors": "~10.0.0-alpha.79",
4978
- "@geekmidas/events": "~10.0.0-alpha.79",
4979
- "@geekmidas/logger": "~10.0.0-alpha.79",
4980
- "@geekmidas/manifest": "~10.0.0-alpha.79",
4981
- "@geekmidas/rate-limit": "~10.0.0-alpha.79",
4982
- "@geekmidas/schema": "~10.0.0-alpha.79",
4983
- "@geekmidas/services": "~10.0.0-alpha.79",
4984
- "@geekmidas/storage": "~10.0.0-alpha.79",
4985
- "@geekmidas/telescope": "~10.0.0-alpha.79",
4986
- "@geekmidas/testkit": "~10.0.0-alpha.79",
5126
+ "@geekmidas/audit": "~10.0.0-alpha.81",
5127
+ "@geekmidas/auth": "~10.0.0-alpha.81",
5128
+ "@geekmidas/cache": "~10.0.0-alpha.81",
5129
+ "@geekmidas/client": "~10.0.0-alpha.81",
5130
+ "@geekmidas/cloud": "~10.0.0-alpha.81",
5131
+ "@geekmidas/constructs": "~10.0.0-alpha.81",
5132
+ "@geekmidas/db": "~10.0.0-alpha.81",
5133
+ "@geekmidas/emailkit": "~10.0.0-alpha.81",
5134
+ "@geekmidas/envkit": "~10.0.0-alpha.81",
5135
+ "@geekmidas/errors": "~10.0.0-alpha.81",
5136
+ "@geekmidas/events": "~10.0.0-alpha.81",
5137
+ "@geekmidas/logger": "~10.0.0-alpha.81",
5138
+ "@geekmidas/manifest": "~10.0.0-alpha.81",
5139
+ "@geekmidas/rate-limit": "~10.0.0-alpha.81",
5140
+ "@geekmidas/schema": "~10.0.0-alpha.81",
5141
+ "@geekmidas/services": "~10.0.0-alpha.81",
5142
+ "@geekmidas/storage": "~10.0.0-alpha.81",
5143
+ "@geekmidas/telescope": "~10.0.0-alpha.81",
5144
+ "@geekmidas/testkit": "~10.0.0-alpha.81",
4987
5145
  "@geekmidas/cli": `~${loadPackageJson().version}`
4988
5146
  };
4989
5147
  //#endregion
@@ -10592,7 +10750,7 @@ function printSummary(workspace, stage) {
10592
10750
  * A no-op when nothing is recorded — no run yet, or one already torn down —
10593
10751
  * so running it twice, or before a setup, is harmless.
10594
10752
  */
10595
- async function dropTestDatabases(cwd, root, sql = require_seeds.pgClient) {
10753
+ async function dropTestDatabases(cwd, root, sql) {
10596
10754
  const readyPath = (0, node_path.join)(cwd, require_ready.TEST_READY_FILE);
10597
10755
  const ready = await readReady(readyPath);
10598
10756
  const recorded = ready?.databases;
@@ -10728,7 +10886,10 @@ async function testCommand(options = {}) {
10728
10886
  }
10729
10887
  /** Drop what the last setup created — see `dropTestDatabases`. */
10730
10888
  async function teardown(cwd) {
10731
- const dropped = await dropTestDatabases(cwd, await require_config.loadWorkspaceConfig(cwd).then((loaded) => loaded.workspace.root).catch(() => cwd));
10889
+ const workspace = await require_config.loadWorkspaceConfig(cwd).then((loaded) => loaded.workspace).catch(() => void 0);
10890
+ const credentials = workspace ? await require_seeds.readLocalCredentials(workspace) : null;
10891
+ if (!workspace || !credentials) return;
10892
+ const dropped = await dropTestDatabases(cwd, workspace.root, (port) => require_seeds.pgClient(port, credentials.postgres));
10732
10893
  if (dropped.length > 0) console.log(` 🧹 Dropped ${dropped.join(", ")}`);
10733
10894
  }
10734
10895
  /**
@@ -10748,7 +10909,7 @@ function testDatabases(reconciled) {
10748
10909
  * starts without the reference data the app needs.
10749
10910
  */
10750
10911
  async function migrateTestStage(workspace, env) {
10751
- const { migrateAndSeed } = await Promise.resolve().then(() => require("./migrate-CTqICj1B.cjs"));
10912
+ const { migrateAndSeed } = await Promise.resolve().then(() => require("./migrate-BlYiMA23.cjs"));
10752
10913
  const { discover } = await Promise.resolve().then(() => require("./discover-D1It5r_h.cjs")).then((n) => n.discover_exports);
10753
10914
  const sources = {};
10754
10915
  const manifest = await discover({
@@ -11154,6 +11315,17 @@ program.command("dev").description("Reconcile constructs and start the dev serve
11154
11315
  process.exit(1);
11155
11316
  }
11156
11317
  });
11318
+ program.command("dev:credentials").description("Print each local service's address and login — Postgres, MinIO, Mailpit, Redis, … — without starting anything").option("--json", "Print them as JSON, for a script").action(async (options) => {
11319
+ try {
11320
+ const globalOptions = program.opts();
11321
+ if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11322
+ const { devCredentialsCommand } = await Promise.resolve().then(() => credentials_exports);
11323
+ await devCredentialsCommand(options);
11324
+ } catch (error) {
11325
+ console.error(formatError(error));
11326
+ process.exit(1);
11327
+ }
11328
+ });
11157
11329
  program.command("exec").description("Run a command with secrets injected into Credentials").argument("<command...>", "Command to run (use -- before command)").action(async (commandArgs) => {
11158
11330
  try {
11159
11331
  const globalOptions = program.opts();
@@ -11181,7 +11353,7 @@ program.command("migrate").description("Apply each database construct's migratio
11181
11353
  try {
11182
11354
  const globalOptions = program.opts();
11183
11355
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11184
- const { migrateCommand } = await Promise.resolve().then(() => require("./migrate-CTqICj1B.cjs"));
11356
+ const { migrateCommand } = await Promise.resolve().then(() => require("./migrate-BlYiMA23.cjs"));
11185
11357
  await migrateCommand({
11186
11358
  ...options,
11187
11359
  ...construct ? { construct } : {}
@@ -11195,7 +11367,7 @@ program.command("seed").description("Migrate, then run each database construct's
11195
11367
  try {
11196
11368
  const globalOptions = program.opts();
11197
11369
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11198
- const { seedCommand } = await Promise.resolve().then(() => require("./migrate-CTqICj1B.cjs"));
11370
+ const { seedCommand } = await Promise.resolve().then(() => require("./migrate-BlYiMA23.cjs"));
11199
11371
  await seedCommand({
11200
11372
  ...options,
11201
11373
  ...construct ? { construct } : {}
@@ -11209,7 +11381,7 @@ program.command("migration").description("Write a construct's next migration: an
11209
11381
  try {
11210
11382
  const globalOptions = program.opts();
11211
11383
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11212
- const { migrationCommand } = await Promise.resolve().then(() => require("./migrate-CTqICj1B.cjs"));
11384
+ const { migrationCommand } = await Promise.resolve().then(() => require("./migrate-BlYiMA23.cjs"));
11213
11385
  await migrationCommand({
11214
11386
  construct,
11215
11387
  ...name ? { name } : {}
@@ -11313,7 +11485,7 @@ program.command("secrets:add").description("Build the keys a stage must be given
11313
11485
  try {
11314
11486
  const globalOptions = program.opts();
11315
11487
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11316
- const { secretsAddCommand } = await Promise.resolve().then(() => require("./add-HF9POevk.cjs"));
11488
+ const { secretsAddCommand } = await Promise.resolve().then(() => require("./add-DF9IHQov.cjs"));
11317
11489
  await secretsAddCommand(options);
11318
11490
  } catch (error) {
11319
11491
  console.error(formatError(error));
@@ -11324,7 +11496,7 @@ program.command("encryption:rotate").description("Add a new current key to an En
11324
11496
  try {
11325
11497
  const globalOptions = program.opts();
11326
11498
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11327
- const { encryptionRotateCommand } = await Promise.resolve().then(() => require("./encryption-D2z16vsS.cjs"));
11499
+ const { encryptionRotateCommand } = await Promise.resolve().then(() => require("./encryption-9YA5ThNl.cjs"));
11328
11500
  await encryptionRotateCommand(construct, options);
11329
11501
  } catch (error) {
11330
11502
  console.error(formatError(error));
@@ -11335,7 +11507,7 @@ program.command("encryption:retire").description("Remove an old key from an Encr
11335
11507
  try {
11336
11508
  const globalOptions = program.opts();
11337
11509
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11338
- const { encryptionRetireCommand } = await Promise.resolve().then(() => require("./encryption-D2z16vsS.cjs"));
11510
+ const { encryptionRetireCommand } = await Promise.resolve().then(() => require("./encryption-9YA5ThNl.cjs"));
11339
11511
  await encryptionRetireCommand(construct, key, options);
11340
11512
  } catch (error) {
11341
11513
  console.error(formatError(error));
@@ -11378,7 +11550,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
11378
11550
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11379
11551
  const { loadWorkspaceConfig } = await Promise.resolve().then(() => require("./config.cjs"));
11380
11552
  const { reconcileMissingSecrets } = await Promise.resolve().then(() => require("./reconcile-CuCQBa5-.cjs"));
11381
- const { secretsStoreFor } = await Promise.resolve().then(() => require("./seeds-qX13ubmO.cjs")).then((n) => n.store_exports);
11553
+ const { secretsStoreFor } = await Promise.resolve().then(() => require("./seeds-BjHiy9tW.cjs")).then((n) => n.store_exports);
11382
11554
  const { workspace } = await loadWorkspaceConfig();
11383
11555
  const stage = options.stage ?? workspace.stages.local;
11384
11556
  const store = await secretsStoreFor(workspace, stage);
@@ -11387,7 +11559,7 @@ program.command("secrets:reconcile").description("Backfill missing custom secret
11387
11559
  console.error(`No secrets found for stage "${stage}". Run "gkm secrets:init --stage ${stage}" first.`);
11388
11560
  process.exit(1);
11389
11561
  }
11390
- const { derivedContainers } = await Promise.resolve().then(() => require("./seeds-qX13ubmO.cjs")).then((n) => n.workspace_exports);
11562
+ const { derivedContainers } = await Promise.resolve().then(() => require("./seeds-BjHiy9tW.cjs")).then((n) => n.workspace_exports);
11391
11563
  const result = reconcileMissingSecrets(secrets, workspace, await derivedContainers(workspace, stage));
11392
11564
  if (!result) {
11393
11565
  console.log(`\n✓ Secrets for stage "${stage}" are up-to-date`);
@@ -11405,7 +11577,7 @@ program.command("secrets:migrate").description("Copy a deployed stage's secrets
11405
11577
  try {
11406
11578
  const globalOptions = program.opts();
11407
11579
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11408
- const { secretsMigrateCommand } = await Promise.resolve().then(() => require("./migrate-DvIAITiG.cjs"));
11580
+ const { secretsMigrateCommand } = await Promise.resolve().then(() => require("./migrate-C440QQvZ.cjs"));
11409
11581
  await secretsMigrateCommand(options);
11410
11582
  } catch (error) {
11411
11583
  console.error(formatError(error));
@@ -11413,7 +11585,7 @@ program.command("secrets:migrate").description("Copy a deployed stage's secrets
11413
11585
  }
11414
11586
  });
11415
11587
  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) => {
11416
- const { deployCli } = await Promise.resolve().then(() => require("./cli-C3hsEwLK.cjs"));
11588
+ const { deployCli } = await Promise.resolve().then(() => require("./cli-BB1KOkfo.cjs"));
11417
11589
  const globalOptions = program.opts();
11418
11590
  const code = await deployCli({
11419
11591
  cwd: (0, node_path.resolve)(globalOptions.cwd ?? process.cwd()),
@@ -11430,7 +11602,7 @@ program.command("deploy").description("Deploy a stage through its target").optio
11430
11602
  });
11431
11603
  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) => {
11432
11604
  try {
11433
- const { rollbackStage } = await Promise.resolve().then(() => require("./rollback-DUoOaws3.cjs")).then((n) => n.rollback_exports);
11605
+ const { rollbackStage } = await Promise.resolve().then(() => require("./rollback-P09wRaW4.cjs")).then((n) => n.rollback_exports);
11434
11606
  const { terminalCredentials } = await Promise.resolve().then(() => require("./terminal-BL5E0xbR.cjs"));
11435
11607
  const globalOptions = program.opts();
11436
11608
  await rollbackStage({
@@ -11466,7 +11638,7 @@ program.command("deploy:github").description("Let GitHub Actions deploy a stage:
11466
11638
  try {
11467
11639
  const globalOptions = program.opts();
11468
11640
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
11469
- const { deployGithubCommand } = await Promise.resolve().then(() => require("./github-B8dDa8xf.cjs"));
11641
+ const { deployGithubCommand } = await Promise.resolve().then(() => require("./github-BM-Y9M2G.cjs"));
11470
11642
  await deployGithubCommand(options);
11471
11643
  } catch (error) {
11472
11644
  console.error(formatError(error));