@geekmidas/cli 10.0.0-alpha.63 → 10.0.0-alpha.64

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 (193) hide show
  1. package/dist/{HostingerProvider-mJ7YruxH.cjs → HostingerProvider-Bx2lHHvu.cjs} +3 -3
  2. package/dist/{HostingerProvider-mJ7YruxH.cjs.map → HostingerProvider-Bx2lHHvu.cjs.map} +1 -1
  3. package/dist/{HostingerProvider-DBBwpDjn.mjs → HostingerProvider-Cd4aS5Kj.mjs} +2 -2
  4. package/dist/{HostingerProvider-DBBwpDjn.mjs.map → HostingerProvider-Cd4aS5Kj.mjs.map} +1 -1
  5. package/dist/{LegacyStateStore-CP7SZwTP.cjs → LegacyStateStore-C2abV49N.cjs} +2 -2
  6. package/dist/{LegacyStateStore-CP7SZwTP.cjs.map → LegacyStateStore-C2abV49N.cjs.map} +1 -1
  7. package/dist/{LegacyStateStore-Q3GmKOdw.mjs → LegacyStateStore-DIgkGaUP.mjs} +2 -2
  8. package/dist/{LegacyStateStore-Q3GmKOdw.mjs.map → LegacyStateStore-DIgkGaUP.mjs.map} +1 -1
  9. package/dist/{LocalStateStore-BGIXcrvX.mjs → LocalStateStore-Cy1ZBW7U.mjs} +2 -2
  10. package/dist/{LocalStateStore-BGIXcrvX.mjs.map → LocalStateStore-Cy1ZBW7U.mjs.map} +1 -1
  11. package/dist/{LocalStateStore-sS8mvLeq.cjs → LocalStateStore-DiJqKyPy.cjs} +2 -2
  12. package/dist/{LocalStateStore-sS8mvLeq.cjs.map → LocalStateStore-DiJqKyPy.cjs.map} +1 -1
  13. package/dist/{OpenApiTsGenerator-CjHWFRDd.cjs → OpenApiTsGenerator-BWuDRHe9.cjs} +2 -2
  14. package/dist/{OpenApiTsGenerator-CjHWFRDd.cjs.map → OpenApiTsGenerator-BWuDRHe9.cjs.map} +1 -1
  15. package/dist/{OpenApiTsGenerator-CnAwhGxi.mjs → OpenApiTsGenerator-C6v-IeHt.mjs} +2 -2
  16. package/dist/{OpenApiTsGenerator-CnAwhGxi.mjs.map → OpenApiTsGenerator-C6v-IeHt.mjs.map} +1 -1
  17. package/dist/{S3StateStore-D7l_lWJ3.mjs → S3StateStore-D3x6ivBL.mjs} +2 -2
  18. package/dist/{S3StateStore-D7l_lWJ3.mjs.map → S3StateStore-D3x6ivBL.mjs.map} +1 -1
  19. package/dist/{S3StateStore-6jqITH2F.cjs → S3StateStore-Ds0V1Lgm.cjs} +2 -2
  20. package/dist/{S3StateStore-6jqITH2F.cjs.map → S3StateStore-Ds0V1Lgm.cjs.map} +1 -1
  21. package/dist/{SSMStateStore-B__F7IJa.cjs → SSMStateStore-CLipvx47.cjs} +2 -2
  22. package/dist/{SSMStateStore-B__F7IJa.cjs.map → SSMStateStore-CLipvx47.cjs.map} +1 -1
  23. package/dist/{SSMStateStore-CAQDVpoc.mjs → SSMStateStore-DfF174xQ.mjs} +2 -2
  24. package/dist/{SSMStateStore-CAQDVpoc.mjs.map → SSMStateStore-DfF174xQ.mjs.map} +1 -1
  25. package/dist/{bundler-DLfOn0ls.cjs → bundler-C8uJf324.cjs} +3 -3
  26. package/dist/{bundler-DLfOn0ls.cjs.map → bundler-C8uJf324.cjs.map} +1 -1
  27. package/dist/{bundler-DyWI41Iy.mjs → bundler-DYgm9xsd.mjs} +3 -3
  28. package/dist/{bundler-DyWI41Iy.mjs.map → bundler-DYgm9xsd.mjs.map} +1 -1
  29. package/dist/{cli-PkVF28LM.mjs → cli-Bogz-zEj.mjs} +5 -4
  30. package/dist/{cli-PkVF28LM.mjs.map → cli-Bogz-zEj.mjs.map} +1 -1
  31. package/dist/{cli-D-ZM-X2s.cjs → cli-D3jofJ6j.cjs} +7 -6
  32. package/dist/{cli-D-ZM-X2s.cjs.map → cli-D3jofJ6j.cjs.map} +1 -1
  33. package/dist/{config-CemHVcIr.d.cts → config-Be-4_I6H.d.cts} +3 -3
  34. package/dist/{config-C2BUosLm.d.mts.map → config-Be-4_I6H.d.cts.map} +1 -1
  35. package/dist/{config-C2BUosLm.d.mts → config-DqNRiQOp.d.mts} +3 -3
  36. package/dist/{config-CemHVcIr.d.cts.map → config-DqNRiQOp.d.mts.map} +1 -1
  37. package/dist/config.cjs +416 -17
  38. package/dist/config.cjs.map +1 -0
  39. package/dist/config.d.cts +3 -3
  40. package/dist/config.d.mts +3 -3
  41. package/dist/config.mjs +401 -2
  42. package/dist/config.mjs.map +1 -0
  43. package/dist/credentials-B70vmLcm.cjs +125 -0
  44. package/dist/credentials-B70vmLcm.cjs.map +1 -0
  45. package/dist/credentials-s0wMeZBU.mjs +108 -0
  46. package/dist/credentials-s0wMeZBU.mjs.map +1 -0
  47. package/dist/{define-CHBIEc5j.mjs → define-CYs0PEfZ.mjs} +2 -3
  48. package/dist/define-CYs0PEfZ.mjs.map +1 -0
  49. package/dist/{define-Cv_QvDrg.cjs → define-zUsP72ym.cjs} +2 -3
  50. package/dist/define-zUsP72ym.cjs.map +1 -0
  51. package/dist/deploy/public.cjs +32 -26
  52. package/dist/deploy/public.cjs.map +1 -1
  53. package/dist/deploy/public.d.cts +40 -4
  54. package/dist/deploy/public.d.cts.map +1 -1
  55. package/dist/deploy/public.d.mts +40 -4
  56. package/dist/deploy/public.d.mts.map +1 -1
  57. package/dist/deploy/public.mjs +15 -12
  58. package/dist/deploy/public.mjs.map +1 -1
  59. package/dist/{deploy-ABBI0lf1.cjs → deploy-55rJkdKz.cjs} +17 -14
  60. package/dist/{deploy-ABBI0lf1.cjs.map → deploy-55rJkdKz.cjs.map} +1 -1
  61. package/dist/{deploy-CGW-QY9D.mjs → deploy-Dc2LBJaK.mjs} +8 -7
  62. package/dist/{deploy-CGW-QY9D.mjs.map → deploy-Dc2LBJaK.mjs.map} +1 -1
  63. package/dist/{deploy-CG8bfFij.mjs → deploy-ZRXIPp1c.mjs} +11 -8
  64. package/dist/{deploy-CG8bfFij.mjs.map → deploy-ZRXIPp1c.mjs.map} +1 -1
  65. package/dist/{deploy-BiMrP9i7.cjs → deploy-yUcR2OlW.cjs} +12 -11
  66. package/dist/{deploy-BiMrP9i7.cjs.map → deploy-yUcR2OlW.cjs.map} +1 -1
  67. package/dist/derive-B4vDpzgP.mjs +150 -0
  68. package/dist/derive-B4vDpzgP.mjs.map +1 -0
  69. package/dist/derive-DAbo49sm.cjs +161 -0
  70. package/dist/derive-DAbo49sm.cjs.map +1 -0
  71. package/dist/{discover-C_6COHEv.cjs → discover-DHdECueV.cjs} +6 -157
  72. package/dist/discover-DHdECueV.cjs.map +1 -0
  73. package/dist/{discover-BaT6bpnA.mjs → discover-LMrqsWSU.mjs} +8 -117
  74. package/dist/discover-LMrqsWSU.mjs.map +1 -0
  75. package/dist/{dokploy-CwdmlyQL.mjs → dokploy-CepdXuMl.mjs} +3 -3
  76. package/dist/{dokploy-CwdmlyQL.mjs.map → dokploy-CepdXuMl.mjs.map} +1 -1
  77. package/dist/{dokploy-QJxMhxdf.cjs → dokploy-D1d37rvM.cjs} +3 -3
  78. package/dist/{dokploy-QJxMhxdf.cjs.map → dokploy-D1d37rvM.cjs.map} +1 -1
  79. package/dist/{encryption-DXw3jj0c.cjs → encryption-C9X98u2F.cjs} +5 -5
  80. package/dist/{encryption-DXw3jj0c.cjs.map → encryption-C9X98u2F.cjs.map} +1 -1
  81. package/dist/{encryption-CbQG_ayW.mjs → encryption-CKHjMpCr.mjs} +5 -5
  82. package/dist/{encryption-CbQG_ayW.mjs.map → encryption-CKHjMpCr.mjs.map} +1 -1
  83. package/dist/{github-ciwbIwVa.mjs → github-D-byNI7T.mjs} +4 -4
  84. package/dist/{github-ciwbIwVa.mjs.map → github-D-byNI7T.mjs.map} +1 -1
  85. package/dist/{github-BH_mETav.cjs → github-Dd3tMa1u.cjs} +4 -4
  86. package/dist/{github-BH_mETav.cjs.map → github-Dd3tMa1u.cjs.map} +1 -1
  87. package/dist/{index-DrKzpHl-.d.mts → index-CJ2qsSpp.d.mts} +2 -2
  88. package/dist/{index-BtXDZQuF.d.cts.map → index-CJ2qsSpp.d.mts.map} +1 -1
  89. package/dist/{index-BtXDZQuF.d.cts → index-CfE9Y885.d.cts} +2 -2
  90. package/dist/{index-DrKzpHl-.d.mts.map → index-CfE9Y885.d.cts.map} +1 -1
  91. package/dist/index.cjs +100 -235
  92. package/dist/index.cjs.map +1 -1
  93. package/dist/index.mjs +71 -206
  94. package/dist/index.mjs.map +1 -1
  95. package/dist/local-C7wnrcYK.cjs +586 -0
  96. package/dist/local-C7wnrcYK.cjs.map +1 -0
  97. package/dist/local-Cd8ZYQG3.mjs +473 -0
  98. package/dist/local-Cd8ZYQG3.mjs.map +1 -0
  99. package/dist/{migrate-ImBKTBcW.mjs → migrate-B4gngTLe.mjs} +6 -6
  100. package/dist/{migrate-ImBKTBcW.mjs.map → migrate-B4gngTLe.mjs.map} +1 -1
  101. package/dist/{migrate-6oa9y_f2.cjs → migrate-tx9-P9uz.cjs} +6 -6
  102. package/dist/{migrate-6oa9y_f2.cjs.map → migrate-tx9-P9uz.cjs.map} +1 -1
  103. package/dist/openapi.cjs +2 -2
  104. package/dist/openapi.d.cts +1 -1
  105. package/dist/openapi.d.mts +1 -1
  106. package/dist/openapi.mjs +2 -2
  107. package/dist/{ready-Bpdnn2ON.mjs → ready-bQfu9GES.mjs} +3 -3
  108. package/dist/{ready-Bpdnn2ON.mjs.map → ready-bQfu9GES.mjs.map} +1 -1
  109. package/dist/{ready-BiG0rt4h.cjs → ready-brEhh68f.cjs} +3 -3
  110. package/dist/{ready-BiG0rt4h.cjs.map → ready-brEhh68f.cjs.map} +1 -1
  111. package/dist/reconcile/public.cjs +1 -1
  112. package/dist/reconcile/public.d.cts +1 -1
  113. package/dist/reconcile/public.d.mts +1 -1
  114. package/dist/reconcile/public.mjs +1 -1
  115. package/dist/{resolve-xytBTtG4.cjs → resolve-BXsNQjMR.cjs} +9 -6
  116. package/dist/resolve-BXsNQjMR.cjs.map +1 -0
  117. package/dist/{resolve-B1T0hUaR.mjs → resolve-DZ2IJ0qY.mjs} +9 -6
  118. package/dist/resolve-DZ2IJ0qY.mjs.map +1 -0
  119. package/dist/{runtime-CMwtq0AL.cjs → runtime-BGNDrHzh.cjs} +2 -2
  120. package/dist/{runtime-CMwtq0AL.cjs.map → runtime-BGNDrHzh.cjs.map} +1 -1
  121. package/dist/{runtime-Dn_n-29S.d.cts → runtime-CJEYK4Gz.d.mts} +2 -2
  122. package/dist/{runtime-CXPlkZzs.d.mts.map → runtime-CJEYK4Gz.d.mts.map} +1 -1
  123. package/dist/{runtime-CXPlkZzs.d.mts → runtime-Cjq1jlVX.d.cts} +2 -2
  124. package/dist/{runtime-Dn_n-29S.d.cts.map → runtime-Cjq1jlVX.d.cts.map} +1 -1
  125. package/dist/{runtime-DbakJS8u.mjs → runtime-Cmn-FeYO.mjs} +2 -2
  126. package/dist/{runtime-DbakJS8u.mjs.map → runtime-Cmn-FeYO.mjs.map} +1 -1
  127. package/dist/sandbox/discover-worker.cjs +1 -1
  128. package/dist/sandbox/discover-worker.mjs +1 -1
  129. package/dist/sandbox-CL--ZDui.cjs +164 -0
  130. package/dist/sandbox-CL--ZDui.cjs.map +1 -0
  131. package/dist/sandbox-Dm247NGx.mjs +117 -0
  132. package/dist/sandbox-Dm247NGx.mjs.map +1 -0
  133. package/dist/{sniffer-aO_-s5vq.mjs → sniffer-49KPbDgq.mjs} +25 -470
  134. package/dist/sniffer-49KPbDgq.mjs.map +1 -0
  135. package/dist/{sniffer-D_SYc_pO.cjs → sniffer-ByhbEW5A.cjs} +38 -567
  136. package/dist/sniffer-ByhbEW5A.cjs.map +1 -0
  137. package/dist/sst-BdALKEas.cjs +453 -0
  138. package/dist/sst-BdALKEas.cjs.map +1 -0
  139. package/dist/sst-Cper040I.mjs +430 -0
  140. package/dist/sst-Cper040I.mjs.map +1 -0
  141. package/dist/{store-BS8A7NcA.mjs → store-BaaonhkJ.mjs} +2 -2
  142. package/dist/{store-BS8A7NcA.mjs.map → store-BaaonhkJ.mjs.map} +1 -1
  143. package/dist/{store-KvNQwYaB.cjs → store-D_2ByRtW.cjs} +4 -4
  144. package/dist/{store-KvNQwYaB.cjs.map → store-D_2ByRtW.cjs.map} +1 -1
  145. package/dist/target/public.cjs +3 -3
  146. package/dist/target/public.d.cts +3 -3
  147. package/dist/target/public.d.mts +3 -3
  148. package/dist/target/public.mjs +3 -3
  149. package/dist/{terminal-D0f1u0LG.cjs → terminal-Bdv--jhY.cjs} +16 -111
  150. package/dist/terminal-Bdv--jhY.cjs.map +1 -0
  151. package/dist/{terminal-gMxAo9Dj.mjs → terminal-D7Ntz6WE.mjs} +13 -90
  152. package/dist/terminal-D7Ntz6WE.mjs.map +1 -0
  153. package/dist/{types-DIA7Li-J.d.cts → types-B_bEQvb9.d.mts} +42 -7
  154. package/dist/types-B_bEQvb9.d.mts.map +1 -0
  155. package/dist/{types-CfLKGfC_.d.mts → types-DkrDOMxw.d.cts} +43 -6
  156. package/dist/types-DkrDOMxw.d.cts.map +1 -0
  157. package/dist/utils-D2Nk1Nv2.cjs +205 -0
  158. package/dist/utils-D2Nk1Nv2.cjs.map +1 -0
  159. package/dist/utils-D_fxUqvU.mjs +156 -0
  160. package/dist/utils-D_fxUqvU.mjs.map +1 -0
  161. package/dist/vitest.cjs +1 -1
  162. package/dist/vitest.mjs +1 -1
  163. package/dist/workspace/index.cjs +1 -1
  164. package/dist/workspace/index.d.cts +2 -2
  165. package/dist/workspace/index.d.mts +2 -2
  166. package/dist/workspace/index.mjs +1 -1
  167. package/dist/{workspace-Kl4NG9IH.cjs → workspace-BwgWJK7h.cjs} +5 -5
  168. package/dist/workspace-BwgWJK7h.cjs.map +1 -0
  169. package/dist/{workspace-Dh7rr7l4.mjs → workspace-Bwit4q9O.mjs} +5 -5
  170. package/dist/workspace-Bwit4q9O.mjs.map +1 -0
  171. package/package.json +13 -13
  172. package/dist/config-BEJYKd4s.cjs +0 -648
  173. package/dist/config-BEJYKd4s.cjs.map +0 -1
  174. package/dist/config-LKKCVCnC.mjs +0 -547
  175. package/dist/config-LKKCVCnC.mjs.map +0 -1
  176. package/dist/define-CHBIEc5j.mjs.map +0 -1
  177. package/dist/define-Cv_QvDrg.cjs.map +0 -1
  178. package/dist/discover-BaT6bpnA.mjs.map +0 -1
  179. package/dist/discover-C_6COHEv.cjs.map +0 -1
  180. package/dist/home-DOzBaWaL.cjs +0 -33
  181. package/dist/home-DOzBaWaL.cjs.map +0 -1
  182. package/dist/home-GI7pJl1M.mjs +0 -22
  183. package/dist/home-GI7pJl1M.mjs.map +0 -1
  184. package/dist/resolve-B1T0hUaR.mjs.map +0 -1
  185. package/dist/resolve-xytBTtG4.cjs.map +0 -1
  186. package/dist/sniffer-D_SYc_pO.cjs.map +0 -1
  187. package/dist/sniffer-aO_-s5vq.mjs.map +0 -1
  188. package/dist/terminal-D0f1u0LG.cjs.map +0 -1
  189. package/dist/terminal-gMxAo9Dj.mjs.map +0 -1
  190. package/dist/types-CfLKGfC_.d.mts.map +0 -1
  191. package/dist/types-DIA7Li-J.d.cts.map +0 -1
  192. package/dist/workspace-Dh7rr7l4.mjs.map +0 -1
  193. package/dist/workspace-Kl4NG9IH.cjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"store-BS8A7NcA.mjs","names":[],"sources":["../src/deploy/identity.ts","../src/secrets/keystore.ts","../src/secrets/file.ts","../src/secrets/store.ts"],"sourcesContent":["/**\n * Who a deploy is, on a target it shares with other workspaces.\n *\n * A Dokploy server is one flat namespace of projects, images and Docker\n * service names. Deploys used to identify everything they made by the\n * workspace name alone, so two workspaces called `shop` — or `Shop` and\n * `shop` — deploying to one server landed in the same project, pushed over\n * each other's images and redeployed each other's applications. A\n * `DeployIdentity` is what a deploy names and claims its resources by instead.\n */\n\nimport { kebabCase, scopedName } from '@geekmidas/manifest';\n\nexport interface DeployIdentity {\n\t/**\n\t * Whose deploy this is — an organisation, a team, a repository. Two\n\t * workspaces with the same name are told apart by it. Defaults to the\n\t * kebab-cased workspace name; set `deploy.namespace` to choose one.\n\t */\n\treadonly namespace: string;\n\t/** The workspace name as the target spells it: lowercase, `[a-z0-9-]`. */\n\treadonly project: string;\n\treadonly stage: string;\n\t/**\n\t * `<namespace>/<project>`: what a Dokploy project is claimed by.\n\t *\n\t * The stage is not part of it because one Dokploy project holds every stage\n\t * of a workspace, each as an environment — the key has to be the same from\n\t * every stage that deploys into it.\n\t */\n\treadonly key: string;\n\t/**\n\t * The prefix every name on the target carries: the project alone in the\n\t * default namespace, `<namespace>-<project>` in any other.\n\t *\n\t * The default adds nothing so that a workspace that never set a namespace\n\t * keeps the names it was already deployed under — a Postgres is found by\n\t * its name, and a new name would be a new, empty database.\n\t */\n\treadonly scope: string;\n}\n\n/** A namespace that cannot be part of a Dokploy name or an image path. */\nexport class DeployNamespaceInvalid extends Error {\n\tconstructor(readonly namespace: string) {\n\t\tsuper(\n\t\t\t`deploy.namespace '${namespace}' is not usable as a name: use lowercase letters, digits and single '-' between them (e.g. 'acme' or 'acme-platform') in gkm.config.ts.`,\n\t\t);\n\t\tthis.name = 'DeployNamespaceInvalid';\n\t}\n}\n\n/** A workspace name with nothing a target can name a resource by. */\nexport class DeployProjectNameInvalid extends Error {\n\tconstructor(readonly workspaceName: string) {\n\t\tsuper(\n\t\t\t`The workspace name '${workspaceName}' has no letters or digits to name deployed resources by. Set \"name\" in gkm.config.ts.`,\n\t\t);\n\t\tthis.name = 'DeployProjectNameInvalid';\n\t}\n}\n\nconst NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\n\n/**\n * Lowercase `[a-z0-9-]`, runs of anything else collapsed to one `-`: valid as\n * a Docker image path component, a Docker service name and a Dokploy name.\n */\nfunction slug(value: string): string {\n\treturn value\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9]+/g, '-')\n\t\t.replace(/^-+|-+$/g, '');\n}\n\n/** The namespace a workspace is in when it does not name one. */\nexport function defaultNamespace(workspaceName: string): string {\n\treturn slug(kebabCase(workspaceName));\n}\n\n/** What an identity is derived from: a workspace's name and namespace. */\nexport interface IdentitySource {\n\tname: string;\n\tdeploy?: { namespace?: string };\n}\n\nexport function deployIdentity(\n\tworkspace: IdentitySource,\n\tstage: string,\n): DeployIdentity {\n\t// Lowercased rather than kebab-cased: the names a workspace was already\n\t// deployed under were `${stage}-${name}`.toLowerCase(), and this has to\n\t// reproduce them for the databases it finds by name.\n\tconst project = slug(workspace.name);\n\tif (!project) throw new DeployProjectNameInvalid(workspace.name);\n\n\tconst fallback = defaultNamespace(workspace.name);\n\tconst namespace = workspace.deploy?.namespace ?? fallback;\n\tif (!NAME.test(namespace)) throw new DeployNamespaceInvalid(namespace);\n\n\treturn {\n\t\tnamespace,\n\t\tproject,\n\t\tstage,\n\t\tkey: `${namespace}/${project}`,\n\t\tscope: namespace === fallback ? project : `${namespace}-${project}`,\n\t};\n}\n\n/**\n * The token a Dokploy project's description carries to say which identity\n * created it. A project with the right name and no marker belongs to someone\n * else, and a deploy never adopts it.\n */\nexport function ownershipMarker(identity: DeployIdentity): string {\n\treturn `gkm:${identity.key}`;\n}\n\n/** Whether a project description carries `identity`'s marker. */\nexport function isOwnedBy(\n\tdescription: string | null | undefined,\n\tidentity: DeployIdentity,\n): boolean {\n\tconst marker = ownershipMarker(identity);\n\treturn (description ?? '').split(/\\s+/).includes(marker);\n}\n\n/** Any `gkm:` marker a description carries — whoever's it is. */\nexport function markerOf(\n\tdescription: string | null | undefined,\n): string | undefined {\n\treturn (description ?? '').split(/\\s+/).find((t) => t.startsWith('gkm:'));\n}\n\n/** What the Dokploy project is called. */\nexport function projectName(identity: DeployIdentity): string {\n\treturn identity.scope;\n}\n\n/**\n * The repository an app's image is pushed to, below the registry:\n * `<namespace>/<project>-<app>`.\n *\n * The namespace is a path segment of its own so that a registry that maps\n * paths to owners (an organisation's packages, a Harbor project) can grant it\n * as one; the app is joined to the project so the two read as one name.\n */\nexport function imageName(identity: DeployIdentity, app: string): string {\n\treturn `${identity.namespace}/${identity.project}-${slug(kebabCase(app))}`;\n}\n\n/** The full image ref an app is built, pushed and deployed as. */\nexport function imageRef(\n\tidentity: DeployIdentity,\n\tapp: string,\n\tregistry: string | undefined,\n\ttag: string,\n): string {\n\tconst name = imageName(identity, app);\n\treturn registry\n\t\t? `${registry.replace(/\\/+$/, '')}/${name}:${tag}`\n\t\t: `${name}:${tag}`;\n}\n\n/**\n * What one application is called on the target.\n *\n * The same rule the constructs use, through the same `scopedName`, scoped by\n * the stage and the identity: the application beside\n * `production-kitchen-sink-database` is `production-kitchen-sink-api`. A\n * Dokploy application's name is also its Docker service name, which is unique\n * on the whole server, so the namespace has to be in it too — `acme`'s and\n * `globex`'s `shop` would otherwise both run `production-shop-api`.\n *\n * The app id is dropped when it repeats the project, so a project named for its\n * one application is `production-kitchen-sink` rather than\n * `production-kitchen-sink-kitchen-sink`.\n */\nexport function applicationName(identity: DeployIdentity, app: string): string {\n\treturn slug(app) === identity.project\n\t\t? `${identity.stage}-${identity.scope}`\n\t\t: scopedName([identity.stage, identity.scope], app);\n}\n","/**\n * The keys that open each stage's encrypted secrets file, kept outside the\n * project in the CLI's home.\n *\n * Keys were kept at `~/.gkm/<folder>/<stage>.key`, named after the project\n * directory — so two checkouts in folders with the same name (`~/work/api`\n * and `~/oss/api`) shared one key, and the second `gkm secrets:init`\n * overwrote the first project's. They are now kept under the project's\n * identity, `<home>/keys/<namespace>/<project>/<stage>.key`, the same\n * `<namespace>/<project>` a deploy claims its Dokploy project by. A key still\n * at the old place is copied to the new one the first time it is read; the\n * old file stays, so an older CLI on the same machine keeps working.\n */\n\nimport { randomBytes } from 'node:crypto';\nimport { constants, existsSync } from 'node:fs';\nimport {\n\tchmod,\n\tcopyFile,\n\tmkdir,\n\treadFile,\n\trm,\n\twriteFile,\n} from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { deployIdentity, type IdentitySource } from '../deploy/identity';\nimport { gkmHome } from '../home';\n\n/** Key length for AES-256 encryption */\nconst KEY_LENGTH = 32; // 256 bits\n\n/** Whose keys, and where the CLI's home is. */\nexport interface KeystoreProject {\n\t/** `<namespace>/<project>`, as `DeployIdentity.key` spells it. */\n\tkey: string;\n\t/**\n\t * The names the project's keys were kept under, `~/.gkm/<name>/`, before\n\t * they were keyed by identity: its directory's basename, which the secrets\n\t * file used, and its workspace name, which `gkm init` and the generated CI\n\t * workflow wrote to. Looked in, and copied from, when the project has no\n\t * key of its own yet.\n\t */\n\tlegacy?: readonly string[];\n\t/**\n\t * Other project keys this project's keys may be copied from when it has\n\t * none: the default namespace's, for a workspace that has just set\n\t * `deploy.namespace` — the secrets file in the checkout is the same one.\n\t */\n\taliases?: string[];\n\t/** The CLI's home. Defaults to `GKM_HOME`, else `~/.gkm`. */\n\thome?: string;\n}\n\n/** A keystore project key that is not `<namespace>/<project>`. */\nexport class KeystoreProjectInvalid extends Error {\n\tconstructor(readonly key: string) {\n\t\tsuper(\n\t\t\t`'${key}' is not a keystore project: expected '<namespace>/<project>' in lowercase letters, digits and '-', as deployIdentity() produces. Pass the workspace's identity key.`,\n\t\t);\n\t\tthis.name = 'KeystoreProjectInvalid';\n\t}\n}\n\n/** A stage has no key, here or at the place keys used to be kept. */\nexport class KeyNotFound extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly project: string,\n\t\treadonly path: string,\n\t) {\n\t\tsuper(\n\t\t\t`Encryption key not found for stage \"${stage}\" in project \"${project}\". ` +\n\t\t\t\t`Expected key at: ${path}. Run \\`gkm secrets:init --stage ${stage}\\` to create one, or copy the stage's key there.`,\n\t\t);\n\t\tthis.name = 'KeyNotFound';\n\t}\n}\n\nconst PROJECT_KEY = /^[a-z0-9]+(?:-[a-z0-9]+)*\\/[a-z0-9]+(?:-[a-z0-9]+)*$/;\n\n/**\n * The keystore key of a workspace: its deploy identity without the stage, so\n * a project's keys and its Dokploy project are named by the same thing.\n */\nexport function projectKey(workspace: IdentitySource): string {\n\treturn deployIdentity(workspace, '').key;\n}\n\n/** The keystore project of a loaded workspace. */\nexport function keystoreProject(\n\tworkspace: IdentitySource & { root: string },\n\thome?: string,\n): KeystoreProject {\n\tconst key = projectKey(workspace);\n\tconst unscoped = projectKey({ name: workspace.name });\n\tconst legacy = [\n\t\t...new Set([folderName(workspace.root), workspace.name]),\n\t].filter((name): name is string => !!name);\n\treturn {\n\t\tkey,\n\t\tlegacy,\n\t\t...(unscoped !== key ? { aliases: [unscoped] } : {}),\n\t\t...(home ? { home } : {}),\n\t};\n}\n\n/**\n * Whether `name` stays inside the home it is joined to. A workspace name was\n * joined as is — `@acme/shop` is `~/.gkm/@acme/shop` — but never upwards.\n */\nfunction isLegacyName(name: string): boolean {\n\treturn name.split(/[\\\\/]/).every((part) => part !== '..' && part !== '');\n}\n\n/** The last segment of a path, as the old keystore named projects. */\nfunction folderName(path: string): string | undefined {\n\tconst name = path.split(/[\\\\/]/).filter(Boolean).at(-1);\n\treturn name && name !== '.' && name !== '..' ? name : undefined;\n}\n\n/**\n * Get the keystore directory for a project:\n * `<home>/keys/<namespace>/<project>`.\n */\nexport function getKeystoreDir(project: KeystoreProject): string {\n\tif (!PROJECT_KEY.test(project.key)) {\n\t\tthrow new KeystoreProjectInvalid(project.key);\n\t}\n\tconst [namespace, name] = project.key.split('/') as [string, string];\n\treturn join(project.home ?? gkmHome(), 'keys', namespace, name);\n}\n\n/** Get the path to a stage's encryption key. */\nexport function getKeyPath(stage: string, project: KeystoreProject): string {\n\treturn join(getKeystoreDir(project), `${stage}.key`);\n}\n\n/**\n * Where else a stage's key may be, most likely first: under an alias of the\n * project, then where keys were kept before they were keyed by identity —\n * under the configured home, then under `~/.gkm`, which is where every CLI\n * before `GKM_HOME` wrote.\n */\nfunction legacyKeyPaths(stage: string, project: KeystoreProject): string[] {\n\tconst aliases = (project.aliases ?? []).map((key) =>\n\t\tgetKeyPath(stage, { key, home: project.home }),\n\t);\n\tconst homes = [\n\t\t...new Set([project.home ?? gkmHome(), join(homedir(), '.gkm')]),\n\t];\n\tconst names = (project.legacy ?? []).filter(isLegacyName);\n\treturn [\n\t\t...aliases,\n\t\t...names.flatMap((name) =>\n\t\t\thomes.map((home) => join(home, name, `${stage}.key`)),\n\t\t),\n\t];\n}\n\n/** Check if a key exists for a stage, here or where it used to be kept. */\nexport function keyExists(stage: string, project: KeystoreProject): boolean {\n\treturn (\n\t\texistsSync(getKeyPath(stage, project)) ||\n\t\tlegacyKeyPaths(stage, project).some((path) => existsSync(path))\n\t);\n}\n\n/**\n * Generate a new encryption key for a stage, stored owner-only at\n * `<home>/keys/<namespace>/<project>/<stage>.key`.\n */\nexport async function generateKey(\n\tstage: string,\n\tproject: KeystoreProject,\n\t/** `wx` to refuse replacing a key that is already there. */\n\tflag: 'w' | 'wx' = 'w',\n): Promise<string> {\n\tconst keyDir = getKeystoreDir(project);\n\tconst keyPath = getKeyPath(stage, project);\n\n\t// Ensure keystore directory exists with restricted permissions\n\tawait mkdir(keyDir, { recursive: true, mode: 0o700 });\n\n\t// Generate random key\n\tconst key = randomBytes(KEY_LENGTH).toString('hex');\n\n\t// Write key with restricted permissions (owner read/write only)\n\tawait writeFile(keyPath, key, { mode: 0o600, encoding: 'utf-8', flag });\n\n\t// Ensure permissions are set correctly (in case file existed)\n\tawait chmod(keyPath, 0o600);\n\n\treturn key;\n}\n\n/**\n * Copy a key from where it used to be kept to where it is kept now.\n *\n * Copied, not moved: an older CLI on the same machine still reads the old\n * place. Exclusive, so two processes migrating at once cannot overwrite a key\n * one of them already put there.\n */\nasync function migrateLegacyKey(\n\tstage: string,\n\tproject: KeystoreProject,\n): Promise<boolean> {\n\tconst legacy = legacyKeyPaths(stage, project).find((path) =>\n\t\texistsSync(path),\n\t);\n\tif (!legacy) return false;\n\n\tconst keyPath = getKeyPath(stage, project);\n\tawait mkdir(getKeystoreDir(project), { recursive: true, mode: 0o700 });\n\ttry {\n\t\tawait copyFile(legacy, keyPath, constants.COPYFILE_EXCL);\n\t} catch (error) {\n\t\tif ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;\n\t}\n\tawait chmod(keyPath, 0o600);\n\treturn true;\n}\n\n/**\n * Read an encryption key for a stage, or `null` when there is none — after\n * copying it over from where keys used to be kept, if it is still there.\n */\nexport async function readKey(\n\tstage: string,\n\tproject: KeystoreProject,\n): Promise<string | null> {\n\tconst keyPath = getKeyPath(stage, project);\n\n\tif (!existsSync(keyPath) && !(await migrateLegacyKey(stage, project))) {\n\t\treturn null;\n\t}\n\n\tconst key = await readFile(keyPath, 'utf-8');\n\treturn key.trim();\n}\n\n/**\n * Read an encryption key for a stage, throwing {@link KeyNotFound} if there\n * is none.\n */\nexport async function requireKey(\n\tstage: string,\n\tproject: KeystoreProject,\n): Promise<string> {\n\tconst key = await readKey(stage, project);\n\n\tif (!key) {\n\t\tthrow new KeyNotFound(stage, project.key, getKeyPath(stage, project));\n\t}\n\n\treturn key;\n}\n\n/**\n * Delete a key for a stage. Only the current one: a key left at the old place\n * belongs to whichever older CLI still reads it.\n */\nexport async function deleteKey(\n\tstage: string,\n\tproject: KeystoreProject,\n): Promise<void> {\n\tawait rm(getKeyPath(stage, project), { force: true });\n}\n\n/**\n * Get or create a key for a stage.\n * If the key already exists, it is returned. Otherwise, a new key is generated.\n */\nexport async function getOrCreateKey(\n\tstage: string,\n\tproject: KeystoreProject,\n): Promise<string> {\n\tconst existingKey = await readKey(stage, project);\n\n\tif (existingKey) {\n\t\treturn existingKey;\n\t}\n\n\t// Exclusively: a key is now shared by every checkout of one project, and\n\t// two commands creating it at once must not each keep a different one.\n\ttry {\n\t\treturn await generateKey(stage, project, 'wx');\n\t} catch (error) {\n\t\tif ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;\n\t\treturn requireKey(stage, project);\n\t}\n}\n","import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { basename, join } from 'node:path';\nimport {\n\tgetKeyPath,\n\tgetOrCreateKey,\n\ttype KeystoreProject,\n\tprojectKey,\n\treadKey,\n} from './keystore';\nimport type { SecretsStore } from './store.js';\nimport type { StageSecrets } from './types';\n\n/** Where the files live, relative to the project root. */\nconst SECRETS_DIR = '.gkm/secrets';\n\n/** AES-256-GCM configuration */\nconst ALGORITHM = 'aes-256-gcm';\nconst IV_LENGTH = 12; // 96 bits for GCM\nconst AUTH_TAG_LENGTH = 16; // 128 bits\n\n/** Encrypted secrets file structure */\ninterface EncryptedSecretsFile {\n\t/** Version for future format changes */\n\tversion: 1;\n\t/** Base64 encoded encrypted data (ciphertext + auth tag) */\n\tencrypted: string;\n\t/** Hex encoded IV */\n\tiv: string;\n}\n\n/**\n * A stage's secrets as an encrypted file on this machine:\n * `.gkm/secrets/<stage>.json`, with its key in the CLI's home at\n * `keys/<namespace>/<project>/<stage>.key`.\n *\n * The local stage's store, always — its secrets belong to the machine running\n * `gkm dev` — and a deployed stage's when `secrets.store` is `'file'`.\n */\nexport class FileSecretsStore implements SecretsStore {\n\treadonly name = 'file';\n\tprivate readonly project: KeystoreProject;\n\n\t/**\n\t * @param project - Whose keys: a workspace's `keystoreProject()`. Outside a\n\t * workspace there is no config to name the project, so it is named the\n\t * way a workspace without `name` is — its package name, else its folder.\n\t */\n\tconstructor(\n\t\tprivate readonly root: string,\n\t\tproject?: KeystoreProject,\n\t) {\n\t\tthis.project = project ?? {\n\t\t\tkey: projectKey({ name: packageName(root) ?? basename(root) }),\n\t\t\tlegacy: [basename(root)],\n\t\t};\n\t}\n\n\t/** The file a stage's secrets are kept in. */\n\tpath(stage: string): string {\n\t\treturn join(this.root, SECRETS_DIR, `${stage}.json`);\n\t}\n\n\tasync read(stage: string): Promise<StageSecrets | null> {\n\t\tconst path = this.path(stage);\n\t\tif (!existsSync(path)) return null;\n\n\t\tconst data = JSON.parse(await readFile(path, 'utf-8'));\n\n\t\t// Legacy: unencrypted format (for backwards compatibility)\n\t\tif (!(data.version === 1 && data.encrypted && data.iv)) {\n\t\t\treturn data as StageSecrets;\n\t\t}\n\n\t\tconst key = await readKey(stage, this.project);\n\t\tif (!key) {\n\t\t\tthrow new MissingSecretsKey(\n\t\t\t\tstage,\n\t\t\t\tthis.project.key,\n\t\t\t\tgetKeyPath(stage, this.project),\n\t\t\t);\n\t\t}\n\n\t\treturn decrypt(data as EncryptedSecretsFile, key);\n\t}\n\n\tasync write(stage: string, secrets: StageSecrets): Promise<void> {\n\t\tawait mkdir(join(this.root, SECRETS_DIR), { recursive: true });\n\n\t\tconst key = await getOrCreateKey(stage, this.project);\n\t\tawait writeFile(\n\t\t\tthis.path(stage),\n\t\t\tJSON.stringify(encrypt(secrets, key), null, 2),\n\t\t\t'utf-8',\n\t\t);\n\t}\n}\n\n/** A stage's secrets file exists, and the key that opens it does not. */\nexport class MissingSecretsKey extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly project: string,\n\t\treadonly path: string,\n\t) {\n\t\tsuper(\n\t\t\t`Decryption key not found for stage \"${stage}\". ` +\n\t\t\t\t`Expected key at: ${path}. Copy the stage's key there from whoever created it, or set secrets.store to a store this machine can reach.`,\n\t\t);\n\t\tthis.name = 'MissingSecretsKey';\n\t}\n}\n\n/** A project's package name without its scope, as a workspace name reads it. */\nfunction packageName(root: string): string | undefined {\n\ttry {\n\t\tconst pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf-8'));\n\t\treturn typeof pkg.name === 'string'\n\t\t\t? pkg.name.replace(/^@[^/]+\\//, '')\n\t\t\t: undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction encrypt(secrets: StageSecrets, keyHex: string): EncryptedSecretsFile {\n\tconst key = Buffer.from(keyHex, 'hex');\n\tconst iv = randomBytes(IV_LENGTH);\n\n\tconst cipher = createCipheriv(ALGORITHM, key, iv);\n\tconst ciphertext = Buffer.concat([\n\t\tcipher.update(JSON.stringify(secrets), 'utf-8'),\n\t\tcipher.final(),\n\t]);\n\n\t// Ciphertext and auth tag, together\n\tconst combined = Buffer.concat([ciphertext, cipher.getAuthTag()]);\n\n\treturn {\n\t\tversion: 1,\n\t\tencrypted: combined.toString('base64'),\n\t\tiv: iv.toString('hex'),\n\t};\n}\n\nfunction decrypt(data: EncryptedSecretsFile, keyHex: string): StageSecrets {\n\tconst key = Buffer.from(keyHex, 'hex');\n\tconst combined = Buffer.from(data.encrypted, 'base64');\n\n\tconst decipher = createDecipheriv(\n\t\tALGORITHM,\n\t\tkey,\n\t\tBuffer.from(data.iv, 'hex'),\n\t);\n\tdecipher.setAuthTag(combined.subarray(-AUTH_TAG_LENGTH));\n\n\tconst plaintext = Buffer.concat([\n\t\tdecipher.update(combined.subarray(0, -AUTH_TAG_LENGTH)),\n\t\tdecipher.final(),\n\t]);\n\n\treturn JSON.parse(plaintext.toString('utf-8')) as StageSecrets;\n}\n","/**\n * Where a stage's secrets are kept.\n *\n * Every command that needs a stage's secrets — `gkm dev`, `test`, `deploy`,\n * `build`, `setup`, `secrets:*` — asks {@link secretsStoreFor} for that\n * stage's store and reads or writes it. None of them knows whether that is a\n * file on this machine or SSM in the stage's account: the local stage is\n * always the file, and a deployed stage is whatever `secrets.store` names.\n *\n * The same shape deploy state has (`StateProvider`): one interface, a backend\n * per place, and a custom object for any place this package does not ship.\n */\n\nimport type { NormalizedWorkspace } from '../workspace/types.js';\nimport { FileSecretsStore } from './file.js';\nimport { keystoreProject } from './keystore.js';\nimport type { StageSecrets } from './types.js';\n\nexport interface SecretsStore {\n\t/** Which kind of store this is — `'file'`, `'ssm'`, or a custom one's. */\n\treadonly name: string;\n\t/** The stage's secrets, or null when it has none here. */\n\tread(stage: string): Promise<StageSecrets | null>;\n\t/** Replace the stage's secrets. */\n\twrite(stage: string, secrets: StageSecrets): Promise<void>;\n}\n\n/** Secrets in SSM Parameter Store, in the account of the active credentials. */\nexport interface SsmSecretsStoreConfig {\n\tprovider: 'ssm';\n\tregion: string;\n}\n\n/** Any other backend: an object implementing {@link SecretsStore}. */\nexport interface CustomSecretsStoreConfig {\n\tprovider: SecretsStore;\n}\n\n/**\n * `secrets.store` in gkm.config.ts.\n *\n * `'file'` — the default — keeps each stage's secrets in the encrypted\n * `.gkm/secrets/<stage>.json`, its key in the CLI's home (`GKM_HOME`, else\n * `~/.gkm`). It cannot serve a\n * deploy from CI while `.gkm/` is gitignored; `ssm` or a custom store can.\n */\nexport type SecretsStoreConfig =\n\t| 'file'\n\t| SsmSecretsStoreConfig\n\t| CustomSecretsStoreConfig;\n\nexport interface SecretsStoreOptions {\n\t/**\n\t * The AWS profile for the stage's account. Only that profile is used —\n\t * never AWS_* from the environment — so a store cannot quietly land in\n\t * whichever account the shell was exported for.\n\t */\n\tprofile?: string;\n\t/**\n\t * The CLI's home, where a file store's keys are kept. Defaults to\n\t * `GKM_HOME`, else `~/.gkm`.\n\t */\n\thome?: string;\n}\n\n/**\n * The store a stage's secrets are kept in — what every command reads and\n * writes, for the stage it acts on.\n *\n * The local stage is always the file: its secrets belong to the machine\n * running `gkm dev`. A deployed stage uses `secrets.store`.\n */\nexport async function secretsStoreFor(\n\tworkspace: NormalizedWorkspace,\n\tstage: string,\n\toptions: SecretsStoreOptions = {},\n): Promise<SecretsStore> {\n\tconst configured = workspace.secrets.store ?? 'file';\n\n\tif (stage === workspace.stages.local || configured === 'file') {\n\t\treturn new FileSecretsStore(\n\t\t\tworkspace.root,\n\t\t\tkeystoreProject(workspace, options.home),\n\t\t);\n\t}\n\n\tif (configured.provider !== 'ssm') {\n\t\treturn configured.provider;\n\t}\n\n\tconst { AwsSecretsStore } = await import('./aws.js');\n\treturn new AwsSecretsStore({\n\t\tproject: workspace.name,\n\t\tregion: configured.region,\n\t\t...(options.profile ? { profile: options.profile } : {}),\n\t});\n}\n\n/** Whether a stage's secrets live somewhere other than this machine. */\nexport function isRemoteStore(\n\tworkspace: NormalizedWorkspace,\n\tstage: string,\n): boolean {\n\tconst configured = workspace.secrets.store ?? 'file';\n\treturn stage !== workspace.stages.local && configured !== 'file';\n}\n\nexport { FileSecretsStore };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA2CA,IAAa,yBAAb,cAA4C,MAAM;CAC5B;CAArB,YAAY,WAA4B;EACvC,MACC,qBAAqB,UAAU,wIAChC;EAHoB,KAAA,YAAA;EAIpB,KAAK,OAAO;CACb;AACD;;AAGA,IAAa,2BAAb,cAA8C,MAAM;CAC9B;CAArB,YAAY,eAAgC;EAC3C,MACC,uBAAuB,cAAc,uFACtC;EAHoB,KAAA,gBAAA;EAIpB,KAAK,OAAO;CACb;AACD;AAEA,MAAM,OAAO;;;;;AAMb,SAAS,KAAK,OAAuB;CACpC,OAAO,MACL,YAAY,CAAC,CACb,QAAQ,eAAe,GAAG,CAAC,CAC3B,QAAQ,YAAY,EAAE;AACzB;;AAGA,SAAgB,iBAAiB,eAA+B;CAC/D,OAAO,KAAK,UAAU,aAAa,CAAC;AACrC;AAQA,SAAgB,eACf,WACA,OACiB;CAIjB,MAAM,UAAU,KAAK,UAAU,IAAI;CACnC,IAAI,CAAC,SAAS,MAAM,IAAI,yBAAyB,UAAU,IAAI;CAE/D,MAAM,WAAW,iBAAiB,UAAU,IAAI;CAChD,MAAM,YAAY,UAAU,QAAQ,aAAa;CACjD,IAAI,CAAC,KAAK,KAAK,SAAS,GAAG,MAAM,IAAI,uBAAuB,SAAS;CAErE,OAAO;EACN;EACA;EACA;EACA,KAAK,GAAG,UAAU,GAAG;EACrB,OAAO,cAAc,WAAW,UAAU,GAAG,UAAU,GAAG;CAC3D;AACD;;;;;;AAOA,SAAgB,gBAAgB,UAAkC;CACjE,OAAO,OAAO,SAAS;AACxB;;AAGA,SAAgB,UACf,aACA,UACU;CACV,MAAM,SAAS,gBAAgB,QAAQ;CACvC,QAAQ,eAAe,GAAA,CAAI,MAAM,KAAK,CAAC,CAAC,SAAS,MAAM;AACxD;;AAGA,SAAgB,SACf,aACqB;CACrB,QAAQ,eAAe,GAAA,CAAI,MAAM,KAAK,CAAC,CAAC,MAAM,MAAM,EAAE,WAAW,MAAM,CAAC;AACzE;;AAGA,SAAgB,YAAY,UAAkC;CAC7D,OAAO,SAAS;AACjB;;;;;;;;;AAUA,SAAgB,UAAU,UAA0B,KAAqB;CACxE,OAAO,GAAG,SAAS,UAAU,GAAG,SAAS,QAAQ,GAAG,KAAK,UAAU,GAAG,CAAC;AACxE;;AAGA,SAAgB,SACf,UACA,KACA,UACA,KACS;CACT,MAAM,OAAO,UAAU,UAAU,GAAG;CACpC,OAAO,WACJ,GAAG,SAAS,QAAQ,QAAQ,EAAE,EAAE,GAAG,KAAK,GAAG,QAC3C,GAAG,KAAK,GAAG;AACf;;;;;;;;;;;;;;;AAgBA,SAAgB,gBAAgB,UAA0B,KAAqB;CAC9E,OAAO,KAAK,GAAG,MAAM,SAAS,UAC3B,GAAG,SAAS,MAAM,GAAG,SAAS,UAC9B,WAAW,CAAC,SAAS,OAAO,SAAS,KAAK,GAAG,GAAG;AACpD;;;;;;;;;;;;;;;;;ACxJA,MAAM,aAAa;;AAyBnB,IAAa,yBAAb,cAA4C,MAAM;CAC5B;CAArB,YAAY,KAAsB;EACjC,MACC,IAAI,IAAI,qKACT;EAHoB,KAAA,MAAA;EAIpB,KAAK,OAAO;CACb;AACD;;AAGA,IAAa,cAAb,cAAiC,MAAM;CAE5B;CACA;CACA;CAHV,YACC,OACA,SACA,MACC;EACD,MACC,uCAAuC,MAAM,gBAAgB,QAAQ,sBAChD,KAAK,mCAAmC,MAAM,iDACpE;EAPS,KAAA,QAAA;EACA,KAAA,UAAA;EACA,KAAA,OAAA;EAMT,KAAK,OAAO;CACb;AACD;AAEA,MAAM,cAAc;;;;;AAMpB,SAAgB,WAAW,WAAmC;CAC7D,OAAO,eAAe,WAAW,EAAE,CAAC,CAAC;AACtC;;AAGA,SAAgB,gBACf,WACA,MACkB;CAClB,MAAM,MAAM,WAAW,SAAS;CAChC,MAAM,WAAW,WAAW,EAAE,MAAM,UAAU,KAAK,CAAC;CAIpD,OAAO;EACN;EACA,QALc,CACd,mBAAG,IAAI,IAAI,CAAC,WAAW,UAAU,IAAI,GAAG,UAAU,IAAI,CAAC,CACxD,CAAC,CAAC,QAAQ,SAAyB,CAAC,CAAC,IAG/B;EACL,GAAI,aAAa,MAAM,EAAE,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC;EAClD,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;CACxB;AACD;;;;;AAMA,SAAS,aAAa,MAAuB;CAC5C,OAAO,KAAK,MAAM,OAAO,CAAC,CAAC,OAAO,SAAS,SAAS,QAAQ,SAAS,EAAE;AACxE;;AAGA,SAAS,WAAW,MAAkC;CACrD,MAAM,OAAO,KAAK,MAAM,OAAO,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,GAAG,EAAE;CACtD,OAAO,QAAQ,SAAS,OAAO,SAAS,OAAO,OAAO,KAAA;AACvD;;;;;AAMA,SAAgB,eAAe,SAAkC;CAChE,IAAI,CAAC,YAAY,KAAK,QAAQ,GAAG,GAChC,MAAM,IAAI,uBAAuB,QAAQ,GAAG;CAE7C,MAAM,CAAC,WAAW,QAAQ,QAAQ,IAAI,MAAM,GAAG;CAC/C,OAAO,KAAK,QAAQ,QAAQ,QAAQ,GAAG,QAAQ,WAAW,IAAI;AAC/D;;AAGA,SAAgB,WAAW,OAAe,SAAkC;CAC3E,OAAO,KAAK,eAAe,OAAO,GAAG,GAAG,MAAM,KAAK;AACpD;;;;;;;AAQA,SAAS,eAAe,OAAe,SAAoC;CAC1E,MAAM,WAAW,QAAQ,WAAW,CAAC,EAAA,CAAG,KAAK,QAC5C,WAAW,OAAO;EAAE;EAAK,MAAM,QAAQ;CAAK,CAAC,CAC9C;CACA,MAAM,QAAQ,CACb,mBAAG,IAAI,IAAI,CAAC,QAAQ,QAAQ,QAAQ,GAAG,KAAK,QAAQ,GAAG,MAAM,CAAC,CAAC,CAChE;CACA,MAAM,SAAS,QAAQ,UAAU,CAAC,EAAA,CAAG,OAAO,YAAY;CACxD,OAAO,CACN,GAAG,SACH,GAAG,MAAM,SAAS,SACjB,MAAM,KAAK,SAAS,KAAK,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,CACrD,CACD;AACD;;;;;AAcA,eAAsB,YACrB,OACA,SAEA,OAAmB,KACD;CAClB,MAAM,SAAS,eAAe,OAAO;CACrC,MAAM,UAAU,WAAW,OAAO,OAAO;CAGzC,MAAM,MAAM,QAAQ;EAAE,WAAW;EAAM,MAAM;CAAM,CAAC;CAGpD,MAAM,MAAM,YAAY,UAAU,CAAC,CAAC,SAAS,KAAK;CAGlD,MAAM,UAAU,SAAS,KAAK;EAAE,MAAM;EAAO,UAAU;EAAS;CAAK,CAAC;CAGtE,MAAM,MAAM,SAAS,GAAK;CAE1B,OAAO;AACR;;;;;;;;AASA,eAAe,iBACd,OACA,SACmB;CACnB,MAAM,SAAS,eAAe,OAAO,OAAO,CAAC,CAAC,MAAM,SACnD,WAAW,IAAI,CAChB;CACA,IAAI,CAAC,QAAQ,OAAO;CAEpB,MAAM,UAAU,WAAW,OAAO,OAAO;CACzC,MAAM,MAAM,eAAe,OAAO,GAAG;EAAE,WAAW;EAAM,MAAM;CAAM,CAAC;CACrE,IAAI;EACH,MAAM,SAAS,QAAQ,SAAS,UAAU,aAAa;CACxD,SAAS,OAAO;EACf,IAAK,MAAgC,SAAS,UAAU,MAAM;CAC/D;CACA,MAAM,MAAM,SAAS,GAAK;CAC1B,OAAO;AACR;;;;;AAMA,eAAsB,QACrB,OACA,SACyB;CACzB,MAAM,UAAU,WAAW,OAAO,OAAO;CAEzC,IAAI,CAAC,WAAW,OAAO,KAAK,CAAE,MAAM,iBAAiB,OAAO,OAAO,GAClE,OAAO;CAIR,QAAO,MADW,SAAS,SAAS,OAAO,EAAA,CAChC,KAAK;AACjB;;;;;AAMA,eAAsB,WACrB,OACA,SACkB;CAClB,MAAM,MAAM,MAAM,QAAQ,OAAO,OAAO;CAExC,IAAI,CAAC,KACJ,MAAM,IAAI,YAAY,OAAO,QAAQ,KAAK,WAAW,OAAO,OAAO,CAAC;CAGrE,OAAO;AACR;;;;;AAiBA,eAAsB,eACrB,OACA,SACkB;CAClB,MAAM,cAAc,MAAM,QAAQ,OAAO,OAAO;CAEhD,IAAI,aACH,OAAO;CAKR,IAAI;EACH,OAAO,MAAM,YAAY,OAAO,SAAS,IAAI;CAC9C,SAAS,OAAO;EACf,IAAK,MAAgC,SAAS,UAAU,MAAM;EAC9D,OAAO,WAAW,OAAO,OAAO;CACjC;AACD;;;;ACpRA,MAAM,cAAc;;AAGpB,MAAM,YAAY;AAClB,MAAM,YAAY;;;;;;;;;AAqBlB,IAAa,mBAAb,MAAsD;CAUnC;CATlB,OAAgB;CAChB;;;;;;CAOA,YACC,MACA,SACC;EAFgB,KAAA,OAAA;EAGjB,KAAK,UAAU,WAAW;GACzB,KAAK,WAAW,EAAE,MAAM,YAAY,IAAI,KAAK,SAAS,IAAI,EAAE,CAAC;GAC7D,QAAQ,CAAC,SAAS,IAAI,CAAC;EACxB;CACD;;CAGA,KAAK,OAAuB;EAC3B,OAAO,KAAK,KAAK,MAAM,aAAa,GAAG,MAAM,MAAM;CACpD;CAEA,MAAM,KAAK,OAA6C;EACvD,MAAM,OAAO,KAAK,KAAK,KAAK;EAC5B,IAAI,CAAC,WAAW,IAAI,GAAG,OAAO;EAE9B,MAAM,OAAO,KAAK,MAAM,MAAM,SAAS,MAAM,OAAO,CAAC;EAGrD,IAAI,EAAE,KAAK,YAAY,KAAK,KAAK,aAAa,KAAK,KAClD,OAAO;EAGR,MAAM,MAAM,MAAM,QAAQ,OAAO,KAAK,OAAO;EAC7C,IAAI,CAAC,KACJ,MAAM,IAAI,kBACT,OACA,KAAK,QAAQ,KACb,WAAW,OAAO,KAAK,OAAO,CAC/B;EAGD,OAAO,QAAQ,MAA8B,GAAG;CACjD;CAEA,MAAM,MAAM,OAAe,SAAsC;EAChE,MAAM,MAAM,KAAK,KAAK,MAAM,WAAW,GAAG,EAAE,WAAW,KAAK,CAAC;EAE7D,MAAM,MAAM,MAAM,eAAe,OAAO,KAAK,OAAO;EACpD,MAAM,UACL,KAAK,KAAK,KAAK,GACf,KAAK,UAAU,QAAQ,SAAS,GAAG,GAAG,MAAM,CAAC,GAC7C,OACD;CACD;AACD;;AAGA,IAAa,oBAAb,cAAuC,MAAM;CAElC;CACA;CACA;CAHV,YACC,OACA,SACA,MACC;EACD,MACC,uCAAuC,MAAM,sBACxB,KAAK,8GAC3B;EAPS,KAAA,QAAA;EACA,KAAA,UAAA;EACA,KAAA,OAAA;EAMT,KAAK,OAAO;CACb;AACD;;AAGA,SAAS,YAAY,MAAkC;CACtD,IAAI;EACH,MAAM,MAAM,KAAK,MAAM,aAAa,KAAK,MAAM,cAAc,GAAG,OAAO,CAAC;EACxE,OAAO,OAAO,IAAI,SAAS,WACxB,IAAI,KAAK,QAAQ,aAAa,EAAE,IAChC,KAAA;CACJ,QAAQ;EACP;CACD;AACD;AAEA,SAAS,QAAQ,SAAuB,QAAsC;CAC7E,MAAM,MAAM,OAAO,KAAK,QAAQ,KAAK;CACrC,MAAM,KAAK,YAAY,SAAS;CAEhC,MAAM,SAAS,eAAe,WAAW,KAAK,EAAE;CAChD,MAAM,aAAa,OAAO,OAAO,CAChC,OAAO,OAAO,KAAK,UAAU,OAAO,GAAG,OAAO,GAC9C,OAAO,MAAM,CACd,CAAC;CAKD,OAAO;EACN,SAAS;EACT,WAJgB,OAAO,OAAO,CAAC,YAAY,OAAO,WAAW,CAAC,CAI5C,CAAC,CAAC,SAAS,QAAQ;EACrC,IAAI,GAAG,SAAS,KAAK;CACtB;AACD;AAEA,SAAS,QAAQ,MAA4B,QAA8B;CAC1E,MAAM,MAAM,OAAO,KAAK,QAAQ,KAAK;CACrC,MAAM,WAAW,OAAO,KAAK,KAAK,WAAW,QAAQ;CAErD,MAAM,WAAW,iBAChB,WACA,KACA,OAAO,KAAK,KAAK,IAAI,KAAK,CAC3B;CACA,SAAS,WAAW,SAAS,SAAS,GAAgB,CAAC;CAEvD,MAAM,YAAY,OAAO,OAAO,CAC/B,SAAS,OAAO,SAAS,SAAS,GAAG,GAAgB,CAAC,GACtD,SAAS,MAAM,CAChB,CAAC;CAED,OAAO,KAAK,MAAM,UAAU,SAAS,OAAO,CAAC;AAC9C;;;;;;;;;;;;;;;AC3FA,eAAsB,gBACrB,WACA,OACA,UAA+B,CAAC,GACR;CACxB,MAAM,aAAa,UAAU,QAAQ,SAAS;CAE9C,IAAI,UAAU,UAAU,OAAO,SAAS,eAAe,QACtD,OAAO,IAAI,iBACV,UAAU,MACV,gBAAgB,WAAW,QAAQ,IAAI,CACxC;CAGD,IAAI,WAAW,aAAa,OAC3B,OAAO,WAAW;CAGnB,MAAM,EAAE,oBAAoB,MAAM,OAAO;CACzC,OAAO,IAAI,gBAAgB;EAC1B,SAAS,UAAU;EACnB,QAAQ,WAAW;EACnB,GAAI,QAAQ,UAAU,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;CACvD,CAAC;AACF;;AAGA,SAAgB,cACf,WACA,OACU;CACV,MAAM,aAAa,UAAU,QAAQ,SAAS;CAC9C,OAAO,UAAU,UAAU,OAAO,SAAS,eAAe;AAC3D"}
1
+ {"version":3,"file":"store-BaaonhkJ.mjs","names":[],"sources":["../src/deploy/identity.ts","../src/secrets/keystore.ts","../src/secrets/file.ts","../src/secrets/store.ts"],"sourcesContent":["/**\n * Who a deploy is, on a target it shares with other workspaces.\n *\n * A Dokploy server is one flat namespace of projects, images and Docker\n * service names. Deploys used to identify everything they made by the\n * workspace name alone, so two workspaces called `shop` — or `Shop` and\n * `shop` — deploying to one server landed in the same project, pushed over\n * each other's images and redeployed each other's applications. A\n * `DeployIdentity` is what a deploy names and claims its resources by instead.\n */\n\nimport { kebabCase, scopedName } from '@geekmidas/manifest';\n\nexport interface DeployIdentity {\n\t/**\n\t * Whose deploy this is — an organisation, a team, a repository. Two\n\t * workspaces with the same name are told apart by it. Defaults to the\n\t * kebab-cased workspace name; set `deploy.namespace` to choose one.\n\t */\n\treadonly namespace: string;\n\t/** The workspace name as the target spells it: lowercase, `[a-z0-9-]`. */\n\treadonly project: string;\n\treadonly stage: string;\n\t/**\n\t * `<namespace>/<project>`: what a Dokploy project is claimed by.\n\t *\n\t * The stage is not part of it because one Dokploy project holds every stage\n\t * of a workspace, each as an environment — the key has to be the same from\n\t * every stage that deploys into it.\n\t */\n\treadonly key: string;\n\t/**\n\t * The prefix every name on the target carries: the project alone in the\n\t * default namespace, `<namespace>-<project>` in any other.\n\t *\n\t * The default adds nothing so that a workspace that never set a namespace\n\t * keeps the names it was already deployed under — a Postgres is found by\n\t * its name, and a new name would be a new, empty database.\n\t */\n\treadonly scope: string;\n}\n\n/** A namespace that cannot be part of a Dokploy name or an image path. */\nexport class DeployNamespaceInvalid extends Error {\n\tconstructor(readonly namespace: string) {\n\t\tsuper(\n\t\t\t`deploy.namespace '${namespace}' is not usable as a name: use lowercase letters, digits and single '-' between them (e.g. 'acme' or 'acme-platform') in gkm.config.ts.`,\n\t\t);\n\t\tthis.name = 'DeployNamespaceInvalid';\n\t}\n}\n\n/** A workspace name with nothing a target can name a resource by. */\nexport class DeployProjectNameInvalid extends Error {\n\tconstructor(readonly workspaceName: string) {\n\t\tsuper(\n\t\t\t`The workspace name '${workspaceName}' has no letters or digits to name deployed resources by. Set \"name\" in gkm.config.ts.`,\n\t\t);\n\t\tthis.name = 'DeployProjectNameInvalid';\n\t}\n}\n\nconst NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\n\n/**\n * Lowercase `[a-z0-9-]`, runs of anything else collapsed to one `-`: valid as\n * a Docker image path component, a Docker service name and a Dokploy name.\n */\nfunction slug(value: string): string {\n\treturn value\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9]+/g, '-')\n\t\t.replace(/^-+|-+$/g, '');\n}\n\n/** The namespace a workspace is in when it does not name one. */\nexport function defaultNamespace(workspaceName: string): string {\n\treturn slug(kebabCase(workspaceName));\n}\n\n/** What an identity is derived from: a workspace's name and namespace. */\nexport interface IdentitySource {\n\tname: string;\n\tdeploy?: { namespace?: string };\n}\n\nexport function deployIdentity(\n\tworkspace: IdentitySource,\n\tstage: string,\n): DeployIdentity {\n\t// Lowercased rather than kebab-cased: the names a workspace was already\n\t// deployed under were `${stage}-${name}`.toLowerCase(), and this has to\n\t// reproduce them for the databases it finds by name.\n\tconst project = slug(workspace.name);\n\tif (!project) throw new DeployProjectNameInvalid(workspace.name);\n\n\tconst fallback = defaultNamespace(workspace.name);\n\tconst namespace = workspace.deploy?.namespace ?? fallback;\n\tif (!NAME.test(namespace)) throw new DeployNamespaceInvalid(namespace);\n\n\treturn {\n\t\tnamespace,\n\t\tproject,\n\t\tstage,\n\t\tkey: `${namespace}/${project}`,\n\t\tscope: namespace === fallback ? project : `${namespace}-${project}`,\n\t};\n}\n\n/**\n * The token a Dokploy project's description carries to say which identity\n * created it. A project with the right name and no marker belongs to someone\n * else, and a deploy never adopts it.\n */\nexport function ownershipMarker(identity: DeployIdentity): string {\n\treturn `gkm:${identity.key}`;\n}\n\n/** Whether a project description carries `identity`'s marker. */\nexport function isOwnedBy(\n\tdescription: string | null | undefined,\n\tidentity: DeployIdentity,\n): boolean {\n\tconst marker = ownershipMarker(identity);\n\treturn (description ?? '').split(/\\s+/).includes(marker);\n}\n\n/** Any `gkm:` marker a description carries — whoever's it is. */\nexport function markerOf(\n\tdescription: string | null | undefined,\n): string | undefined {\n\treturn (description ?? '').split(/\\s+/).find((t) => t.startsWith('gkm:'));\n}\n\n/** What the Dokploy project is called. */\nexport function projectName(identity: DeployIdentity): string {\n\treturn identity.scope;\n}\n\n/**\n * The repository an app's image is pushed to, below the registry:\n * `<namespace>/<project>-<app>`.\n *\n * The namespace is a path segment of its own so that a registry that maps\n * paths to owners (an organisation's packages, a Harbor project) can grant it\n * as one; the app is joined to the project so the two read as one name.\n */\nexport function imageName(identity: DeployIdentity, app: string): string {\n\treturn `${identity.namespace}/${identity.project}-${slug(kebabCase(app))}`;\n}\n\n/** The full image ref an app is built, pushed and deployed as. */\nexport function imageRef(\n\tidentity: DeployIdentity,\n\tapp: string,\n\tregistry: string | undefined,\n\ttag: string,\n): string {\n\tconst name = imageName(identity, app);\n\treturn registry\n\t\t? `${registry.replace(/\\/+$/, '')}/${name}:${tag}`\n\t\t: `${name}:${tag}`;\n}\n\n/**\n * What one application is called on the target.\n *\n * The same rule the constructs use, through the same `scopedName`, scoped by\n * the stage and the identity: the application beside\n * `production-kitchen-sink-database` is `production-kitchen-sink-api`. A\n * Dokploy application's name is also its Docker service name, which is unique\n * on the whole server, so the namespace has to be in it too — `acme`'s and\n * `globex`'s `shop` would otherwise both run `production-shop-api`.\n *\n * The app id is dropped when it repeats the project, so a project named for its\n * one application is `production-kitchen-sink` rather than\n * `production-kitchen-sink-kitchen-sink`.\n */\nexport function applicationName(identity: DeployIdentity, app: string): string {\n\treturn slug(app) === identity.project\n\t\t? `${identity.stage}-${identity.scope}`\n\t\t: scopedName([identity.stage, identity.scope], app);\n}\n","/**\n * The keys that open each stage's encrypted secrets file, kept outside the\n * project in the CLI's home.\n *\n * Keys were kept at `~/.gkm/<folder>/<stage>.key`, named after the project\n * directory — so two checkouts in folders with the same name (`~/work/api`\n * and `~/oss/api`) shared one key, and the second `gkm secrets:init`\n * overwrote the first project's. They are now kept under the project's\n * identity, `<home>/keys/<namespace>/<project>/<stage>.key`, the same\n * `<namespace>/<project>` a deploy claims its Dokploy project by. A key still\n * at the old place is copied to the new one the first time it is read; the\n * old file stays, so an older CLI on the same machine keeps working.\n */\n\nimport { randomBytes } from 'node:crypto';\nimport { constants, existsSync } from 'node:fs';\nimport {\n\tchmod,\n\tcopyFile,\n\tmkdir,\n\treadFile,\n\trm,\n\twriteFile,\n} from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { deployIdentity, type IdentitySource } from '../deploy/identity';\nimport { gkmHome } from '../home';\n\n/** Key length for AES-256 encryption */\nconst KEY_LENGTH = 32; // 256 bits\n\n/** Whose keys, and where the CLI's home is. */\nexport interface KeystoreProject {\n\t/** `<namespace>/<project>`, as `DeployIdentity.key` spells it. */\n\tkey: string;\n\t/**\n\t * The names the project's keys were kept under, `~/.gkm/<name>/`, before\n\t * they were keyed by identity: its directory's basename, which the secrets\n\t * file used, and its workspace name, which `gkm init` and the generated CI\n\t * workflow wrote to. Looked in, and copied from, when the project has no\n\t * key of its own yet.\n\t */\n\tlegacy?: readonly string[];\n\t/**\n\t * Other project keys this project's keys may be copied from when it has\n\t * none: the default namespace's, for a workspace that has just set\n\t * `deploy.namespace` — the secrets file in the checkout is the same one.\n\t */\n\taliases?: string[];\n\t/** The CLI's home. Defaults to `GKM_HOME`, else `~/.gkm`. */\n\thome?: string;\n}\n\n/** A keystore project key that is not `<namespace>/<project>`. */\nexport class KeystoreProjectInvalid extends Error {\n\tconstructor(readonly key: string) {\n\t\tsuper(\n\t\t\t`'${key}' is not a keystore project: expected '<namespace>/<project>' in lowercase letters, digits and '-', as deployIdentity() produces. Pass the workspace's identity key.`,\n\t\t);\n\t\tthis.name = 'KeystoreProjectInvalid';\n\t}\n}\n\n/** A stage has no key, here or at the place keys used to be kept. */\nexport class KeyNotFound extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly project: string,\n\t\treadonly path: string,\n\t) {\n\t\tsuper(\n\t\t\t`Encryption key not found for stage \"${stage}\" in project \"${project}\". ` +\n\t\t\t\t`Expected key at: ${path}. Run \\`gkm secrets:init --stage ${stage}\\` to create one, or copy the stage's key there.`,\n\t\t);\n\t\tthis.name = 'KeyNotFound';\n\t}\n}\n\nconst PROJECT_KEY = /^[a-z0-9]+(?:-[a-z0-9]+)*\\/[a-z0-9]+(?:-[a-z0-9]+)*$/;\n\n/**\n * The keystore key of a workspace: its deploy identity without the stage, so\n * a project's keys and its Dokploy project are named by the same thing.\n */\nexport function projectKey(workspace: IdentitySource): string {\n\treturn deployIdentity(workspace, '').key;\n}\n\n/** The keystore project of a loaded workspace. */\nexport function keystoreProject(\n\tworkspace: IdentitySource & { root: string },\n\thome?: string,\n): KeystoreProject {\n\tconst key = projectKey(workspace);\n\tconst unscoped = projectKey({ name: workspace.name });\n\tconst legacy = [\n\t\t...new Set([folderName(workspace.root), workspace.name]),\n\t].filter((name): name is string => !!name);\n\treturn {\n\t\tkey,\n\t\tlegacy,\n\t\t...(unscoped !== key ? { aliases: [unscoped] } : {}),\n\t\t...(home ? { home } : {}),\n\t};\n}\n\n/**\n * Whether `name` stays inside the home it is joined to. A workspace name was\n * joined as is — `@acme/shop` is `~/.gkm/@acme/shop` — but never upwards.\n */\nfunction isLegacyName(name: string): boolean {\n\treturn name.split(/[\\\\/]/).every((part) => part !== '..' && part !== '');\n}\n\n/** The last segment of a path, as the old keystore named projects. */\nfunction folderName(path: string): string | undefined {\n\tconst name = path.split(/[\\\\/]/).filter(Boolean).at(-1);\n\treturn name && name !== '.' && name !== '..' ? name : undefined;\n}\n\n/**\n * Get the keystore directory for a project:\n * `<home>/keys/<namespace>/<project>`.\n */\nexport function getKeystoreDir(project: KeystoreProject): string {\n\tif (!PROJECT_KEY.test(project.key)) {\n\t\tthrow new KeystoreProjectInvalid(project.key);\n\t}\n\tconst [namespace, name] = project.key.split('/') as [string, string];\n\treturn join(project.home ?? gkmHome(), 'keys', namespace, name);\n}\n\n/** Get the path to a stage's encryption key. */\nexport function getKeyPath(stage: string, project: KeystoreProject): string {\n\treturn join(getKeystoreDir(project), `${stage}.key`);\n}\n\n/**\n * Where else a stage's key may be, most likely first: under an alias of the\n * project, then where keys were kept before they were keyed by identity —\n * under the configured home, then under `~/.gkm`, which is where every CLI\n * before `GKM_HOME` wrote.\n */\nfunction legacyKeyPaths(stage: string, project: KeystoreProject): string[] {\n\tconst aliases = (project.aliases ?? []).map((key) =>\n\t\tgetKeyPath(stage, { key, home: project.home }),\n\t);\n\tconst homes = [\n\t\t...new Set([project.home ?? gkmHome(), join(homedir(), '.gkm')]),\n\t];\n\tconst names = (project.legacy ?? []).filter(isLegacyName);\n\treturn [\n\t\t...aliases,\n\t\t...names.flatMap((name) =>\n\t\t\thomes.map((home) => join(home, name, `${stage}.key`)),\n\t\t),\n\t];\n}\n\n/** Check if a key exists for a stage, here or where it used to be kept. */\nexport function keyExists(stage: string, project: KeystoreProject): boolean {\n\treturn (\n\t\texistsSync(getKeyPath(stage, project)) ||\n\t\tlegacyKeyPaths(stage, project).some((path) => existsSync(path))\n\t);\n}\n\n/**\n * Generate a new encryption key for a stage, stored owner-only at\n * `<home>/keys/<namespace>/<project>/<stage>.key`.\n */\nexport async function generateKey(\n\tstage: string,\n\tproject: KeystoreProject,\n\t/** `wx` to refuse replacing a key that is already there. */\n\tflag: 'w' | 'wx' = 'w',\n): Promise<string> {\n\tconst keyDir = getKeystoreDir(project);\n\tconst keyPath = getKeyPath(stage, project);\n\n\t// Ensure keystore directory exists with restricted permissions\n\tawait mkdir(keyDir, { recursive: true, mode: 0o700 });\n\n\t// Generate random key\n\tconst key = randomBytes(KEY_LENGTH).toString('hex');\n\n\t// Write key with restricted permissions (owner read/write only)\n\tawait writeFile(keyPath, key, { mode: 0o600, encoding: 'utf-8', flag });\n\n\t// Ensure permissions are set correctly (in case file existed)\n\tawait chmod(keyPath, 0o600);\n\n\treturn key;\n}\n\n/**\n * Copy a key from where it used to be kept to where it is kept now.\n *\n * Copied, not moved: an older CLI on the same machine still reads the old\n * place. Exclusive, so two processes migrating at once cannot overwrite a key\n * one of them already put there.\n */\nasync function migrateLegacyKey(\n\tstage: string,\n\tproject: KeystoreProject,\n): Promise<boolean> {\n\tconst legacy = legacyKeyPaths(stage, project).find((path) =>\n\t\texistsSync(path),\n\t);\n\tif (!legacy) return false;\n\n\tconst keyPath = getKeyPath(stage, project);\n\tawait mkdir(getKeystoreDir(project), { recursive: true, mode: 0o700 });\n\ttry {\n\t\tawait copyFile(legacy, keyPath, constants.COPYFILE_EXCL);\n\t} catch (error) {\n\t\tif ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;\n\t}\n\tawait chmod(keyPath, 0o600);\n\treturn true;\n}\n\n/**\n * Read an encryption key for a stage, or `null` when there is none — after\n * copying it over from where keys used to be kept, if it is still there.\n */\nexport async function readKey(\n\tstage: string,\n\tproject: KeystoreProject,\n): Promise<string | null> {\n\tconst keyPath = getKeyPath(stage, project);\n\n\tif (!existsSync(keyPath) && !(await migrateLegacyKey(stage, project))) {\n\t\treturn null;\n\t}\n\n\tconst key = await readFile(keyPath, 'utf-8');\n\treturn key.trim();\n}\n\n/**\n * Read an encryption key for a stage, throwing {@link KeyNotFound} if there\n * is none.\n */\nexport async function requireKey(\n\tstage: string,\n\tproject: KeystoreProject,\n): Promise<string> {\n\tconst key = await readKey(stage, project);\n\n\tif (!key) {\n\t\tthrow new KeyNotFound(stage, project.key, getKeyPath(stage, project));\n\t}\n\n\treturn key;\n}\n\n/**\n * Delete a key for a stage. Only the current one: a key left at the old place\n * belongs to whichever older CLI still reads it.\n */\nexport async function deleteKey(\n\tstage: string,\n\tproject: KeystoreProject,\n): Promise<void> {\n\tawait rm(getKeyPath(stage, project), { force: true });\n}\n\n/**\n * Get or create a key for a stage.\n * If the key already exists, it is returned. Otherwise, a new key is generated.\n */\nexport async function getOrCreateKey(\n\tstage: string,\n\tproject: KeystoreProject,\n): Promise<string> {\n\tconst existingKey = await readKey(stage, project);\n\n\tif (existingKey) {\n\t\treturn existingKey;\n\t}\n\n\t// Exclusively: a key is now shared by every checkout of one project, and\n\t// two commands creating it at once must not each keep a different one.\n\ttry {\n\t\treturn await generateKey(stage, project, 'wx');\n\t} catch (error) {\n\t\tif ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;\n\t\treturn requireKey(stage, project);\n\t}\n}\n","import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { basename, join } from 'node:path';\nimport {\n\tgetKeyPath,\n\tgetOrCreateKey,\n\ttype KeystoreProject,\n\tprojectKey,\n\treadKey,\n} from './keystore';\nimport type { SecretsStore } from './store.js';\nimport type { StageSecrets } from './types';\n\n/** Where the files live, relative to the project root. */\nconst SECRETS_DIR = '.gkm/secrets';\n\n/** AES-256-GCM configuration */\nconst ALGORITHM = 'aes-256-gcm';\nconst IV_LENGTH = 12; // 96 bits for GCM\nconst AUTH_TAG_LENGTH = 16; // 128 bits\n\n/** Encrypted secrets file structure */\ninterface EncryptedSecretsFile {\n\t/** Version for future format changes */\n\tversion: 1;\n\t/** Base64 encoded encrypted data (ciphertext + auth tag) */\n\tencrypted: string;\n\t/** Hex encoded IV */\n\tiv: string;\n}\n\n/**\n * A stage's secrets as an encrypted file on this machine:\n * `.gkm/secrets/<stage>.json`, with its key in the CLI's home at\n * `keys/<namespace>/<project>/<stage>.key`.\n *\n * The local stage's store, always — its secrets belong to the machine running\n * `gkm dev` — and a deployed stage's when `secrets.store` is `'file'`.\n */\nexport class FileSecretsStore implements SecretsStore {\n\treadonly name = 'file';\n\tprivate readonly project: KeystoreProject;\n\n\t/**\n\t * @param project - Whose keys: a workspace's `keystoreProject()`. Outside a\n\t * workspace there is no config to name the project, so it is named the\n\t * way a workspace without `name` is — its package name, else its folder.\n\t */\n\tconstructor(\n\t\tprivate readonly root: string,\n\t\tproject?: KeystoreProject,\n\t) {\n\t\tthis.project = project ?? {\n\t\t\tkey: projectKey({ name: packageName(root) ?? basename(root) }),\n\t\t\tlegacy: [basename(root)],\n\t\t};\n\t}\n\n\t/** The file a stage's secrets are kept in. */\n\tpath(stage: string): string {\n\t\treturn join(this.root, SECRETS_DIR, `${stage}.json`);\n\t}\n\n\tasync read(stage: string): Promise<StageSecrets | null> {\n\t\tconst path = this.path(stage);\n\t\tif (!existsSync(path)) return null;\n\n\t\tconst data = JSON.parse(await readFile(path, 'utf-8'));\n\n\t\t// Legacy: unencrypted format (for backwards compatibility)\n\t\tif (!(data.version === 1 && data.encrypted && data.iv)) {\n\t\t\treturn data as StageSecrets;\n\t\t}\n\n\t\tconst key = await readKey(stage, this.project);\n\t\tif (!key) {\n\t\t\tthrow new MissingSecretsKey(\n\t\t\t\tstage,\n\t\t\t\tthis.project.key,\n\t\t\t\tgetKeyPath(stage, this.project),\n\t\t\t);\n\t\t}\n\n\t\treturn decrypt(data as EncryptedSecretsFile, key);\n\t}\n\n\tasync write(stage: string, secrets: StageSecrets): Promise<void> {\n\t\tawait mkdir(join(this.root, SECRETS_DIR), { recursive: true });\n\n\t\tconst key = await getOrCreateKey(stage, this.project);\n\t\tawait writeFile(\n\t\t\tthis.path(stage),\n\t\t\tJSON.stringify(encrypt(secrets, key), null, 2),\n\t\t\t'utf-8',\n\t\t);\n\t}\n}\n\n/** A stage's secrets file exists, and the key that opens it does not. */\nexport class MissingSecretsKey extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly project: string,\n\t\treadonly path: string,\n\t) {\n\t\tsuper(\n\t\t\t`Decryption key not found for stage \"${stage}\". ` +\n\t\t\t\t`Expected key at: ${path}. Copy the stage's key there from whoever created it, or set secrets.store to a store this machine can reach.`,\n\t\t);\n\t\tthis.name = 'MissingSecretsKey';\n\t}\n}\n\n/** A project's package name without its scope, as a workspace name reads it. */\nfunction packageName(root: string): string | undefined {\n\ttry {\n\t\tconst pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf-8'));\n\t\treturn typeof pkg.name === 'string'\n\t\t\t? pkg.name.replace(/^@[^/]+\\//, '')\n\t\t\t: undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction encrypt(secrets: StageSecrets, keyHex: string): EncryptedSecretsFile {\n\tconst key = Buffer.from(keyHex, 'hex');\n\tconst iv = randomBytes(IV_LENGTH);\n\n\tconst cipher = createCipheriv(ALGORITHM, key, iv);\n\tconst ciphertext = Buffer.concat([\n\t\tcipher.update(JSON.stringify(secrets), 'utf-8'),\n\t\tcipher.final(),\n\t]);\n\n\t// Ciphertext and auth tag, together\n\tconst combined = Buffer.concat([ciphertext, cipher.getAuthTag()]);\n\n\treturn {\n\t\tversion: 1,\n\t\tencrypted: combined.toString('base64'),\n\t\tiv: iv.toString('hex'),\n\t};\n}\n\nfunction decrypt(data: EncryptedSecretsFile, keyHex: string): StageSecrets {\n\tconst key = Buffer.from(keyHex, 'hex');\n\tconst combined = Buffer.from(data.encrypted, 'base64');\n\n\tconst decipher = createDecipheriv(\n\t\tALGORITHM,\n\t\tkey,\n\t\tBuffer.from(data.iv, 'hex'),\n\t);\n\tdecipher.setAuthTag(combined.subarray(-AUTH_TAG_LENGTH));\n\n\tconst plaintext = Buffer.concat([\n\t\tdecipher.update(combined.subarray(0, -AUTH_TAG_LENGTH)),\n\t\tdecipher.final(),\n\t]);\n\n\treturn JSON.parse(plaintext.toString('utf-8')) as StageSecrets;\n}\n","/**\n * Where a stage's secrets are kept.\n *\n * Every command that needs a stage's secrets — `gkm dev`, `test`, `deploy`,\n * `build`, `setup`, `secrets:*` — asks {@link secretsStoreFor} for that\n * stage's store and reads or writes it. None of them knows whether that is a\n * file on this machine or SSM in the stage's account: the local stage is\n * always the file, and a deployed stage is whatever `secrets.store` names.\n *\n * The same shape deploy state has (`StateProvider`): one interface, a backend\n * per place, and a custom object for any place this package does not ship.\n */\n\nimport type { NormalizedWorkspace } from '../workspace/types.js';\nimport { FileSecretsStore } from './file.js';\nimport { keystoreProject } from './keystore.js';\nimport type { StageSecrets } from './types.js';\n\nexport interface SecretsStore {\n\t/** Which kind of store this is — `'file'`, `'ssm'`, or a custom one's. */\n\treadonly name: string;\n\t/** The stage's secrets, or null when it has none here. */\n\tread(stage: string): Promise<StageSecrets | null>;\n\t/** Replace the stage's secrets. */\n\twrite(stage: string, secrets: StageSecrets): Promise<void>;\n}\n\n/** Secrets in SSM Parameter Store, in the account of the active credentials. */\nexport interface SsmSecretsStoreConfig {\n\tprovider: 'ssm';\n\tregion: string;\n}\n\n/** Any other backend: an object implementing {@link SecretsStore}. */\nexport interface CustomSecretsStoreConfig {\n\tprovider: SecretsStore;\n}\n\n/**\n * `secrets.store` in gkm.config.ts.\n *\n * `'file'` — the default — keeps each stage's secrets in the encrypted\n * `.gkm/secrets/<stage>.json`, its key in the CLI's home (`GKM_HOME`, else\n * `~/.gkm`). It cannot serve a\n * deploy from CI while `.gkm/` is gitignored; `ssm` or a custom store can.\n */\nexport type SecretsStoreConfig =\n\t| 'file'\n\t| SsmSecretsStoreConfig\n\t| CustomSecretsStoreConfig;\n\nexport interface SecretsStoreOptions {\n\t/**\n\t * The AWS profile for the stage's account. Only that profile is used —\n\t * never AWS_* from the environment — so a store cannot quietly land in\n\t * whichever account the shell was exported for.\n\t */\n\tprofile?: string;\n\t/**\n\t * The CLI's home, where a file store's keys are kept. Defaults to\n\t * `GKM_HOME`, else `~/.gkm`.\n\t */\n\thome?: string;\n}\n\n/**\n * The store a stage's secrets are kept in — what every command reads and\n * writes, for the stage it acts on.\n *\n * The local stage is always the file: its secrets belong to the machine\n * running `gkm dev`. A deployed stage uses `secrets.store`.\n */\nexport async function secretsStoreFor(\n\tworkspace: NormalizedWorkspace,\n\tstage: string,\n\toptions: SecretsStoreOptions = {},\n): Promise<SecretsStore> {\n\tconst configured = workspace.secrets.store ?? 'file';\n\n\tif (stage === workspace.stages.local || configured === 'file') {\n\t\treturn new FileSecretsStore(\n\t\t\tworkspace.root,\n\t\t\tkeystoreProject(workspace, options.home),\n\t\t);\n\t}\n\n\tif (configured.provider !== 'ssm') {\n\t\treturn configured.provider;\n\t}\n\n\tconst { AwsSecretsStore } = await import('./aws.js');\n\treturn new AwsSecretsStore({\n\t\tproject: workspace.name,\n\t\tregion: configured.region,\n\t\t...(options.profile ? { profile: options.profile } : {}),\n\t});\n}\n\n/** Whether a stage's secrets live somewhere other than this machine. */\nexport function isRemoteStore(\n\tworkspace: NormalizedWorkspace,\n\tstage: string,\n): boolean {\n\tconst configured = workspace.secrets.store ?? 'file';\n\treturn stage !== workspace.stages.local && configured !== 'file';\n}\n\nexport { FileSecretsStore };\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  const require_rolldown_runtime = require("./rolldown-runtime-C0BPl7ul.cjs");
2
- const require_home = require("./home-DOzBaWaL.cjs");
2
+ const require_local = require("./local-C7wnrcYK.cjs");
3
3
  let node_path = require("node:path");
4
4
  let node_fs = require("node:fs");
5
5
  let node_fs_promises = require("node:fs/promises");
@@ -192,7 +192,7 @@ function folderName(path) {
192
192
  function getKeystoreDir(project) {
193
193
  if (!PROJECT_KEY.test(project.key)) throw new KeystoreProjectInvalid(project.key);
194
194
  const [namespace, name] = project.key.split("/");
195
- return (0, node_path.join)(project.home ?? require_home.gkmHome(), "keys", namespace, name);
195
+ return (0, node_path.join)(project.home ?? require_local.gkmHome(), "keys", namespace, name);
196
196
  }
197
197
  /** Get the path to a stage's encryption key. */
198
198
  function getKeyPath(stage, project) {
@@ -209,7 +209,7 @@ function legacyKeyPaths(stage, project) {
209
209
  key,
210
210
  home: project.home
211
211
  }));
212
- const homes = [.../* @__PURE__ */ new Set([project.home ?? require_home.gkmHome(), (0, node_path.join)((0, node_os.homedir)(), ".gkm")])];
212
+ const homes = [.../* @__PURE__ */ new Set([project.home ?? require_local.gkmHome(), (0, node_path.join)((0, node_os.homedir)(), ".gkm")])];
213
213
  const names = (project.legacy ?? []).filter(isLegacyName);
214
214
  return [...aliases, ...names.flatMap((name) => homes.map((home) => (0, node_path.join)(home, name, `${stage}.key`)))];
215
215
  }
@@ -507,4 +507,4 @@ Object.defineProperty(exports, "store_exports", {
507
507
  }
508
508
  });
509
509
 
510
- //# sourceMappingURL=store-KvNQwYaB.cjs.map
510
+ //# sourceMappingURL=store-D_2ByRtW.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"store-KvNQwYaB.cjs","names":["kebabCase","scopedName","join","gkmHome","homedir","mkdir","randomBytes","writeFile","chmod","existsSync","copyFile","constants","readFile","basename","join","existsSync","readFile","mkdir","writeFile","readFileSync","randomBytes","createCipheriv","createDecipheriv"],"sources":["../src/deploy/identity.ts","../src/secrets/keystore.ts","../src/secrets/file.ts","../src/secrets/store.ts"],"sourcesContent":["/**\n * Who a deploy is, on a target it shares with other workspaces.\n *\n * A Dokploy server is one flat namespace of projects, images and Docker\n * service names. Deploys used to identify everything they made by the\n * workspace name alone, so two workspaces called `shop` — or `Shop` and\n * `shop` — deploying to one server landed in the same project, pushed over\n * each other's images and redeployed each other's applications. A\n * `DeployIdentity` is what a deploy names and claims its resources by instead.\n */\n\nimport { kebabCase, scopedName } from '@geekmidas/manifest';\n\nexport interface DeployIdentity {\n\t/**\n\t * Whose deploy this is — an organisation, a team, a repository. Two\n\t * workspaces with the same name are told apart by it. Defaults to the\n\t * kebab-cased workspace name; set `deploy.namespace` to choose one.\n\t */\n\treadonly namespace: string;\n\t/** The workspace name as the target spells it: lowercase, `[a-z0-9-]`. */\n\treadonly project: string;\n\treadonly stage: string;\n\t/**\n\t * `<namespace>/<project>`: what a Dokploy project is claimed by.\n\t *\n\t * The stage is not part of it because one Dokploy project holds every stage\n\t * of a workspace, each as an environment — the key has to be the same from\n\t * every stage that deploys into it.\n\t */\n\treadonly key: string;\n\t/**\n\t * The prefix every name on the target carries: the project alone in the\n\t * default namespace, `<namespace>-<project>` in any other.\n\t *\n\t * The default adds nothing so that a workspace that never set a namespace\n\t * keeps the names it was already deployed under — a Postgres is found by\n\t * its name, and a new name would be a new, empty database.\n\t */\n\treadonly scope: string;\n}\n\n/** A namespace that cannot be part of a Dokploy name or an image path. */\nexport class DeployNamespaceInvalid extends Error {\n\tconstructor(readonly namespace: string) {\n\t\tsuper(\n\t\t\t`deploy.namespace '${namespace}' is not usable as a name: use lowercase letters, digits and single '-' between them (e.g. 'acme' or 'acme-platform') in gkm.config.ts.`,\n\t\t);\n\t\tthis.name = 'DeployNamespaceInvalid';\n\t}\n}\n\n/** A workspace name with nothing a target can name a resource by. */\nexport class DeployProjectNameInvalid extends Error {\n\tconstructor(readonly workspaceName: string) {\n\t\tsuper(\n\t\t\t`The workspace name '${workspaceName}' has no letters or digits to name deployed resources by. Set \"name\" in gkm.config.ts.`,\n\t\t);\n\t\tthis.name = 'DeployProjectNameInvalid';\n\t}\n}\n\nconst NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\n\n/**\n * Lowercase `[a-z0-9-]`, runs of anything else collapsed to one `-`: valid as\n * a Docker image path component, a Docker service name and a Dokploy name.\n */\nfunction slug(value: string): string {\n\treturn value\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9]+/g, '-')\n\t\t.replace(/^-+|-+$/g, '');\n}\n\n/** The namespace a workspace is in when it does not name one. */\nexport function defaultNamespace(workspaceName: string): string {\n\treturn slug(kebabCase(workspaceName));\n}\n\n/** What an identity is derived from: a workspace's name and namespace. */\nexport interface IdentitySource {\n\tname: string;\n\tdeploy?: { namespace?: string };\n}\n\nexport function deployIdentity(\n\tworkspace: IdentitySource,\n\tstage: string,\n): DeployIdentity {\n\t// Lowercased rather than kebab-cased: the names a workspace was already\n\t// deployed under were `${stage}-${name}`.toLowerCase(), and this has to\n\t// reproduce them for the databases it finds by name.\n\tconst project = slug(workspace.name);\n\tif (!project) throw new DeployProjectNameInvalid(workspace.name);\n\n\tconst fallback = defaultNamespace(workspace.name);\n\tconst namespace = workspace.deploy?.namespace ?? fallback;\n\tif (!NAME.test(namespace)) throw new DeployNamespaceInvalid(namespace);\n\n\treturn {\n\t\tnamespace,\n\t\tproject,\n\t\tstage,\n\t\tkey: `${namespace}/${project}`,\n\t\tscope: namespace === fallback ? project : `${namespace}-${project}`,\n\t};\n}\n\n/**\n * The token a Dokploy project's description carries to say which identity\n * created it. A project with the right name and no marker belongs to someone\n * else, and a deploy never adopts it.\n */\nexport function ownershipMarker(identity: DeployIdentity): string {\n\treturn `gkm:${identity.key}`;\n}\n\n/** Whether a project description carries `identity`'s marker. */\nexport function isOwnedBy(\n\tdescription: string | null | undefined,\n\tidentity: DeployIdentity,\n): boolean {\n\tconst marker = ownershipMarker(identity);\n\treturn (description ?? '').split(/\\s+/).includes(marker);\n}\n\n/** Any `gkm:` marker a description carries — whoever's it is. */\nexport function markerOf(\n\tdescription: string | null | undefined,\n): string | undefined {\n\treturn (description ?? '').split(/\\s+/).find((t) => t.startsWith('gkm:'));\n}\n\n/** What the Dokploy project is called. */\nexport function projectName(identity: DeployIdentity): string {\n\treturn identity.scope;\n}\n\n/**\n * The repository an app's image is pushed to, below the registry:\n * `<namespace>/<project>-<app>`.\n *\n * The namespace is a path segment of its own so that a registry that maps\n * paths to owners (an organisation's packages, a Harbor project) can grant it\n * as one; the app is joined to the project so the two read as one name.\n */\nexport function imageName(identity: DeployIdentity, app: string): string {\n\treturn `${identity.namespace}/${identity.project}-${slug(kebabCase(app))}`;\n}\n\n/** The full image ref an app is built, pushed and deployed as. */\nexport function imageRef(\n\tidentity: DeployIdentity,\n\tapp: string,\n\tregistry: string | undefined,\n\ttag: string,\n): string {\n\tconst name = imageName(identity, app);\n\treturn registry\n\t\t? `${registry.replace(/\\/+$/, '')}/${name}:${tag}`\n\t\t: `${name}:${tag}`;\n}\n\n/**\n * What one application is called on the target.\n *\n * The same rule the constructs use, through the same `scopedName`, scoped by\n * the stage and the identity: the application beside\n * `production-kitchen-sink-database` is `production-kitchen-sink-api`. A\n * Dokploy application's name is also its Docker service name, which is unique\n * on the whole server, so the namespace has to be in it too — `acme`'s and\n * `globex`'s `shop` would otherwise both run `production-shop-api`.\n *\n * The app id is dropped when it repeats the project, so a project named for its\n * one application is `production-kitchen-sink` rather than\n * `production-kitchen-sink-kitchen-sink`.\n */\nexport function applicationName(identity: DeployIdentity, app: string): string {\n\treturn slug(app) === identity.project\n\t\t? `${identity.stage}-${identity.scope}`\n\t\t: scopedName([identity.stage, identity.scope], app);\n}\n","/**\n * The keys that open each stage's encrypted secrets file, kept outside the\n * project in the CLI's home.\n *\n * Keys were kept at `~/.gkm/<folder>/<stage>.key`, named after the project\n * directory — so two checkouts in folders with the same name (`~/work/api`\n * and `~/oss/api`) shared one key, and the second `gkm secrets:init`\n * overwrote the first project's. They are now kept under the project's\n * identity, `<home>/keys/<namespace>/<project>/<stage>.key`, the same\n * `<namespace>/<project>` a deploy claims its Dokploy project by. A key still\n * at the old place is copied to the new one the first time it is read; the\n * old file stays, so an older CLI on the same machine keeps working.\n */\n\nimport { randomBytes } from 'node:crypto';\nimport { constants, existsSync } from 'node:fs';\nimport {\n\tchmod,\n\tcopyFile,\n\tmkdir,\n\treadFile,\n\trm,\n\twriteFile,\n} from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { deployIdentity, type IdentitySource } from '../deploy/identity';\nimport { gkmHome } from '../home';\n\n/** Key length for AES-256 encryption */\nconst KEY_LENGTH = 32; // 256 bits\n\n/** Whose keys, and where the CLI's home is. */\nexport interface KeystoreProject {\n\t/** `<namespace>/<project>`, as `DeployIdentity.key` spells it. */\n\tkey: string;\n\t/**\n\t * The names the project's keys were kept under, `~/.gkm/<name>/`, before\n\t * they were keyed by identity: its directory's basename, which the secrets\n\t * file used, and its workspace name, which `gkm init` and the generated CI\n\t * workflow wrote to. Looked in, and copied from, when the project has no\n\t * key of its own yet.\n\t */\n\tlegacy?: readonly string[];\n\t/**\n\t * Other project keys this project's keys may be copied from when it has\n\t * none: the default namespace's, for a workspace that has just set\n\t * `deploy.namespace` — the secrets file in the checkout is the same one.\n\t */\n\taliases?: string[];\n\t/** The CLI's home. Defaults to `GKM_HOME`, else `~/.gkm`. */\n\thome?: string;\n}\n\n/** A keystore project key that is not `<namespace>/<project>`. */\nexport class KeystoreProjectInvalid extends Error {\n\tconstructor(readonly key: string) {\n\t\tsuper(\n\t\t\t`'${key}' is not a keystore project: expected '<namespace>/<project>' in lowercase letters, digits and '-', as deployIdentity() produces. Pass the workspace's identity key.`,\n\t\t);\n\t\tthis.name = 'KeystoreProjectInvalid';\n\t}\n}\n\n/** A stage has no key, here or at the place keys used to be kept. */\nexport class KeyNotFound extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly project: string,\n\t\treadonly path: string,\n\t) {\n\t\tsuper(\n\t\t\t`Encryption key not found for stage \"${stage}\" in project \"${project}\". ` +\n\t\t\t\t`Expected key at: ${path}. Run \\`gkm secrets:init --stage ${stage}\\` to create one, or copy the stage's key there.`,\n\t\t);\n\t\tthis.name = 'KeyNotFound';\n\t}\n}\n\nconst PROJECT_KEY = /^[a-z0-9]+(?:-[a-z0-9]+)*\\/[a-z0-9]+(?:-[a-z0-9]+)*$/;\n\n/**\n * The keystore key of a workspace: its deploy identity without the stage, so\n * a project's keys and its Dokploy project are named by the same thing.\n */\nexport function projectKey(workspace: IdentitySource): string {\n\treturn deployIdentity(workspace, '').key;\n}\n\n/** The keystore project of a loaded workspace. */\nexport function keystoreProject(\n\tworkspace: IdentitySource & { root: string },\n\thome?: string,\n): KeystoreProject {\n\tconst key = projectKey(workspace);\n\tconst unscoped = projectKey({ name: workspace.name });\n\tconst legacy = [\n\t\t...new Set([folderName(workspace.root), workspace.name]),\n\t].filter((name): name is string => !!name);\n\treturn {\n\t\tkey,\n\t\tlegacy,\n\t\t...(unscoped !== key ? { aliases: [unscoped] } : {}),\n\t\t...(home ? { home } : {}),\n\t};\n}\n\n/**\n * Whether `name` stays inside the home it is joined to. A workspace name was\n * joined as is — `@acme/shop` is `~/.gkm/@acme/shop` — but never upwards.\n */\nfunction isLegacyName(name: string): boolean {\n\treturn name.split(/[\\\\/]/).every((part) => part !== '..' && part !== '');\n}\n\n/** The last segment of a path, as the old keystore named projects. */\nfunction folderName(path: string): string | undefined {\n\tconst name = path.split(/[\\\\/]/).filter(Boolean).at(-1);\n\treturn name && name !== '.' && name !== '..' ? name : undefined;\n}\n\n/**\n * Get the keystore directory for a project:\n * `<home>/keys/<namespace>/<project>`.\n */\nexport function getKeystoreDir(project: KeystoreProject): string {\n\tif (!PROJECT_KEY.test(project.key)) {\n\t\tthrow new KeystoreProjectInvalid(project.key);\n\t}\n\tconst [namespace, name] = project.key.split('/') as [string, string];\n\treturn join(project.home ?? gkmHome(), 'keys', namespace, name);\n}\n\n/** Get the path to a stage's encryption key. */\nexport function getKeyPath(stage: string, project: KeystoreProject): string {\n\treturn join(getKeystoreDir(project), `${stage}.key`);\n}\n\n/**\n * Where else a stage's key may be, most likely first: under an alias of the\n * project, then where keys were kept before they were keyed by identity —\n * under the configured home, then under `~/.gkm`, which is where every CLI\n * before `GKM_HOME` wrote.\n */\nfunction legacyKeyPaths(stage: string, project: KeystoreProject): string[] {\n\tconst aliases = (project.aliases ?? []).map((key) =>\n\t\tgetKeyPath(stage, { key, home: project.home }),\n\t);\n\tconst homes = [\n\t\t...new Set([project.home ?? gkmHome(), join(homedir(), '.gkm')]),\n\t];\n\tconst names = (project.legacy ?? []).filter(isLegacyName);\n\treturn [\n\t\t...aliases,\n\t\t...names.flatMap((name) =>\n\t\t\thomes.map((home) => join(home, name, `${stage}.key`)),\n\t\t),\n\t];\n}\n\n/** Check if a key exists for a stage, here or where it used to be kept. */\nexport function keyExists(stage: string, project: KeystoreProject): boolean {\n\treturn (\n\t\texistsSync(getKeyPath(stage, project)) ||\n\t\tlegacyKeyPaths(stage, project).some((path) => existsSync(path))\n\t);\n}\n\n/**\n * Generate a new encryption key for a stage, stored owner-only at\n * `<home>/keys/<namespace>/<project>/<stage>.key`.\n */\nexport async function generateKey(\n\tstage: string,\n\tproject: KeystoreProject,\n\t/** `wx` to refuse replacing a key that is already there. */\n\tflag: 'w' | 'wx' = 'w',\n): Promise<string> {\n\tconst keyDir = getKeystoreDir(project);\n\tconst keyPath = getKeyPath(stage, project);\n\n\t// Ensure keystore directory exists with restricted permissions\n\tawait mkdir(keyDir, { recursive: true, mode: 0o700 });\n\n\t// Generate random key\n\tconst key = randomBytes(KEY_LENGTH).toString('hex');\n\n\t// Write key with restricted permissions (owner read/write only)\n\tawait writeFile(keyPath, key, { mode: 0o600, encoding: 'utf-8', flag });\n\n\t// Ensure permissions are set correctly (in case file existed)\n\tawait chmod(keyPath, 0o600);\n\n\treturn key;\n}\n\n/**\n * Copy a key from where it used to be kept to where it is kept now.\n *\n * Copied, not moved: an older CLI on the same machine still reads the old\n * place. Exclusive, so two processes migrating at once cannot overwrite a key\n * one of them already put there.\n */\nasync function migrateLegacyKey(\n\tstage: string,\n\tproject: KeystoreProject,\n): Promise<boolean> {\n\tconst legacy = legacyKeyPaths(stage, project).find((path) =>\n\t\texistsSync(path),\n\t);\n\tif (!legacy) return false;\n\n\tconst keyPath = getKeyPath(stage, project);\n\tawait mkdir(getKeystoreDir(project), { recursive: true, mode: 0o700 });\n\ttry {\n\t\tawait copyFile(legacy, keyPath, constants.COPYFILE_EXCL);\n\t} catch (error) {\n\t\tif ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;\n\t}\n\tawait chmod(keyPath, 0o600);\n\treturn true;\n}\n\n/**\n * Read an encryption key for a stage, or `null` when there is none — after\n * copying it over from where keys used to be kept, if it is still there.\n */\nexport async function readKey(\n\tstage: string,\n\tproject: KeystoreProject,\n): Promise<string | null> {\n\tconst keyPath = getKeyPath(stage, project);\n\n\tif (!existsSync(keyPath) && !(await migrateLegacyKey(stage, project))) {\n\t\treturn null;\n\t}\n\n\tconst key = await readFile(keyPath, 'utf-8');\n\treturn key.trim();\n}\n\n/**\n * Read an encryption key for a stage, throwing {@link KeyNotFound} if there\n * is none.\n */\nexport async function requireKey(\n\tstage: string,\n\tproject: KeystoreProject,\n): Promise<string> {\n\tconst key = await readKey(stage, project);\n\n\tif (!key) {\n\t\tthrow new KeyNotFound(stage, project.key, getKeyPath(stage, project));\n\t}\n\n\treturn key;\n}\n\n/**\n * Delete a key for a stage. Only the current one: a key left at the old place\n * belongs to whichever older CLI still reads it.\n */\nexport async function deleteKey(\n\tstage: string,\n\tproject: KeystoreProject,\n): Promise<void> {\n\tawait rm(getKeyPath(stage, project), { force: true });\n}\n\n/**\n * Get or create a key for a stage.\n * If the key already exists, it is returned. Otherwise, a new key is generated.\n */\nexport async function getOrCreateKey(\n\tstage: string,\n\tproject: KeystoreProject,\n): Promise<string> {\n\tconst existingKey = await readKey(stage, project);\n\n\tif (existingKey) {\n\t\treturn existingKey;\n\t}\n\n\t// Exclusively: a key is now shared by every checkout of one project, and\n\t// two commands creating it at once must not each keep a different one.\n\ttry {\n\t\treturn await generateKey(stage, project, 'wx');\n\t} catch (error) {\n\t\tif ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;\n\t\treturn requireKey(stage, project);\n\t}\n}\n","import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { basename, join } from 'node:path';\nimport {\n\tgetKeyPath,\n\tgetOrCreateKey,\n\ttype KeystoreProject,\n\tprojectKey,\n\treadKey,\n} from './keystore';\nimport type { SecretsStore } from './store.js';\nimport type { StageSecrets } from './types';\n\n/** Where the files live, relative to the project root. */\nconst SECRETS_DIR = '.gkm/secrets';\n\n/** AES-256-GCM configuration */\nconst ALGORITHM = 'aes-256-gcm';\nconst IV_LENGTH = 12; // 96 bits for GCM\nconst AUTH_TAG_LENGTH = 16; // 128 bits\n\n/** Encrypted secrets file structure */\ninterface EncryptedSecretsFile {\n\t/** Version for future format changes */\n\tversion: 1;\n\t/** Base64 encoded encrypted data (ciphertext + auth tag) */\n\tencrypted: string;\n\t/** Hex encoded IV */\n\tiv: string;\n}\n\n/**\n * A stage's secrets as an encrypted file on this machine:\n * `.gkm/secrets/<stage>.json`, with its key in the CLI's home at\n * `keys/<namespace>/<project>/<stage>.key`.\n *\n * The local stage's store, always — its secrets belong to the machine running\n * `gkm dev` — and a deployed stage's when `secrets.store` is `'file'`.\n */\nexport class FileSecretsStore implements SecretsStore {\n\treadonly name = 'file';\n\tprivate readonly project: KeystoreProject;\n\n\t/**\n\t * @param project - Whose keys: a workspace's `keystoreProject()`. Outside a\n\t * workspace there is no config to name the project, so it is named the\n\t * way a workspace without `name` is — its package name, else its folder.\n\t */\n\tconstructor(\n\t\tprivate readonly root: string,\n\t\tproject?: KeystoreProject,\n\t) {\n\t\tthis.project = project ?? {\n\t\t\tkey: projectKey({ name: packageName(root) ?? basename(root) }),\n\t\t\tlegacy: [basename(root)],\n\t\t};\n\t}\n\n\t/** The file a stage's secrets are kept in. */\n\tpath(stage: string): string {\n\t\treturn join(this.root, SECRETS_DIR, `${stage}.json`);\n\t}\n\n\tasync read(stage: string): Promise<StageSecrets | null> {\n\t\tconst path = this.path(stage);\n\t\tif (!existsSync(path)) return null;\n\n\t\tconst data = JSON.parse(await readFile(path, 'utf-8'));\n\n\t\t// Legacy: unencrypted format (for backwards compatibility)\n\t\tif (!(data.version === 1 && data.encrypted && data.iv)) {\n\t\t\treturn data as StageSecrets;\n\t\t}\n\n\t\tconst key = await readKey(stage, this.project);\n\t\tif (!key) {\n\t\t\tthrow new MissingSecretsKey(\n\t\t\t\tstage,\n\t\t\t\tthis.project.key,\n\t\t\t\tgetKeyPath(stage, this.project),\n\t\t\t);\n\t\t}\n\n\t\treturn decrypt(data as EncryptedSecretsFile, key);\n\t}\n\n\tasync write(stage: string, secrets: StageSecrets): Promise<void> {\n\t\tawait mkdir(join(this.root, SECRETS_DIR), { recursive: true });\n\n\t\tconst key = await getOrCreateKey(stage, this.project);\n\t\tawait writeFile(\n\t\t\tthis.path(stage),\n\t\t\tJSON.stringify(encrypt(secrets, key), null, 2),\n\t\t\t'utf-8',\n\t\t);\n\t}\n}\n\n/** A stage's secrets file exists, and the key that opens it does not. */\nexport class MissingSecretsKey extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly project: string,\n\t\treadonly path: string,\n\t) {\n\t\tsuper(\n\t\t\t`Decryption key not found for stage \"${stage}\". ` +\n\t\t\t\t`Expected key at: ${path}. Copy the stage's key there from whoever created it, or set secrets.store to a store this machine can reach.`,\n\t\t);\n\t\tthis.name = 'MissingSecretsKey';\n\t}\n}\n\n/** A project's package name without its scope, as a workspace name reads it. */\nfunction packageName(root: string): string | undefined {\n\ttry {\n\t\tconst pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf-8'));\n\t\treturn typeof pkg.name === 'string'\n\t\t\t? pkg.name.replace(/^@[^/]+\\//, '')\n\t\t\t: undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction encrypt(secrets: StageSecrets, keyHex: string): EncryptedSecretsFile {\n\tconst key = Buffer.from(keyHex, 'hex');\n\tconst iv = randomBytes(IV_LENGTH);\n\n\tconst cipher = createCipheriv(ALGORITHM, key, iv);\n\tconst ciphertext = Buffer.concat([\n\t\tcipher.update(JSON.stringify(secrets), 'utf-8'),\n\t\tcipher.final(),\n\t]);\n\n\t// Ciphertext and auth tag, together\n\tconst combined = Buffer.concat([ciphertext, cipher.getAuthTag()]);\n\n\treturn {\n\t\tversion: 1,\n\t\tencrypted: combined.toString('base64'),\n\t\tiv: iv.toString('hex'),\n\t};\n}\n\nfunction decrypt(data: EncryptedSecretsFile, keyHex: string): StageSecrets {\n\tconst key = Buffer.from(keyHex, 'hex');\n\tconst combined = Buffer.from(data.encrypted, 'base64');\n\n\tconst decipher = createDecipheriv(\n\t\tALGORITHM,\n\t\tkey,\n\t\tBuffer.from(data.iv, 'hex'),\n\t);\n\tdecipher.setAuthTag(combined.subarray(-AUTH_TAG_LENGTH));\n\n\tconst plaintext = Buffer.concat([\n\t\tdecipher.update(combined.subarray(0, -AUTH_TAG_LENGTH)),\n\t\tdecipher.final(),\n\t]);\n\n\treturn JSON.parse(plaintext.toString('utf-8')) as StageSecrets;\n}\n","/**\n * Where a stage's secrets are kept.\n *\n * Every command that needs a stage's secrets — `gkm dev`, `test`, `deploy`,\n * `build`, `setup`, `secrets:*` — asks {@link secretsStoreFor} for that\n * stage's store and reads or writes it. None of them knows whether that is a\n * file on this machine or SSM in the stage's account: the local stage is\n * always the file, and a deployed stage is whatever `secrets.store` names.\n *\n * The same shape deploy state has (`StateProvider`): one interface, a backend\n * per place, and a custom object for any place this package does not ship.\n */\n\nimport type { NormalizedWorkspace } from '../workspace/types.js';\nimport { FileSecretsStore } from './file.js';\nimport { keystoreProject } from './keystore.js';\nimport type { StageSecrets } from './types.js';\n\nexport interface SecretsStore {\n\t/** Which kind of store this is — `'file'`, `'ssm'`, or a custom one's. */\n\treadonly name: string;\n\t/** The stage's secrets, or null when it has none here. */\n\tread(stage: string): Promise<StageSecrets | null>;\n\t/** Replace the stage's secrets. */\n\twrite(stage: string, secrets: StageSecrets): Promise<void>;\n}\n\n/** Secrets in SSM Parameter Store, in the account of the active credentials. */\nexport interface SsmSecretsStoreConfig {\n\tprovider: 'ssm';\n\tregion: string;\n}\n\n/** Any other backend: an object implementing {@link SecretsStore}. */\nexport interface CustomSecretsStoreConfig {\n\tprovider: SecretsStore;\n}\n\n/**\n * `secrets.store` in gkm.config.ts.\n *\n * `'file'` — the default — keeps each stage's secrets in the encrypted\n * `.gkm/secrets/<stage>.json`, its key in the CLI's home (`GKM_HOME`, else\n * `~/.gkm`). It cannot serve a\n * deploy from CI while `.gkm/` is gitignored; `ssm` or a custom store can.\n */\nexport type SecretsStoreConfig =\n\t| 'file'\n\t| SsmSecretsStoreConfig\n\t| CustomSecretsStoreConfig;\n\nexport interface SecretsStoreOptions {\n\t/**\n\t * The AWS profile for the stage's account. Only that profile is used —\n\t * never AWS_* from the environment — so a store cannot quietly land in\n\t * whichever account the shell was exported for.\n\t */\n\tprofile?: string;\n\t/**\n\t * The CLI's home, where a file store's keys are kept. Defaults to\n\t * `GKM_HOME`, else `~/.gkm`.\n\t */\n\thome?: string;\n}\n\n/**\n * The store a stage's secrets are kept in — what every command reads and\n * writes, for the stage it acts on.\n *\n * The local stage is always the file: its secrets belong to the machine\n * running `gkm dev`. A deployed stage uses `secrets.store`.\n */\nexport async function secretsStoreFor(\n\tworkspace: NormalizedWorkspace,\n\tstage: string,\n\toptions: SecretsStoreOptions = {},\n): Promise<SecretsStore> {\n\tconst configured = workspace.secrets.store ?? 'file';\n\n\tif (stage === workspace.stages.local || configured === 'file') {\n\t\treturn new FileSecretsStore(\n\t\t\tworkspace.root,\n\t\t\tkeystoreProject(workspace, options.home),\n\t\t);\n\t}\n\n\tif (configured.provider !== 'ssm') {\n\t\treturn configured.provider;\n\t}\n\n\tconst { AwsSecretsStore } = await import('./aws.js');\n\treturn new AwsSecretsStore({\n\t\tproject: workspace.name,\n\t\tregion: configured.region,\n\t\t...(options.profile ? { profile: options.profile } : {}),\n\t});\n}\n\n/** Whether a stage's secrets live somewhere other than this machine. */\nexport function isRemoteStore(\n\tworkspace: NormalizedWorkspace,\n\tstage: string,\n): boolean {\n\tconst configured = workspace.secrets.store ?? 'file';\n\treturn stage !== workspace.stages.local && configured !== 'file';\n}\n\nexport { FileSecretsStore };\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AA2CA,IAAa,yBAAb,cAA4C,MAAM;CAC5B;CAArB,YAAY,WAA4B;EACvC,MACC,qBAAqB,UAAU,wIAChC;EAHoB,KAAA,YAAA;EAIpB,KAAK,OAAO;CACb;AACD;;AAGA,IAAa,2BAAb,cAA8C,MAAM;CAC9B;CAArB,YAAY,eAAgC;EAC3C,MACC,uBAAuB,cAAc,uFACtC;EAHoB,KAAA,gBAAA;EAIpB,KAAK,OAAO;CACb;AACD;AAEA,MAAM,OAAO;;;;;AAMb,SAAS,KAAK,OAAuB;CACpC,OAAO,MACL,YAAY,CAAC,CACb,QAAQ,eAAe,GAAG,CAAC,CAC3B,QAAQ,YAAY,EAAE;AACzB;;AAGA,SAAgB,iBAAiB,eAA+B;CAC/D,OAAO,MAAA,GAAKA,oBAAAA,UAAAA,CAAU,aAAa,CAAC;AACrC;AAQA,SAAgB,eACf,WACA,OACiB;CAIjB,MAAM,UAAU,KAAK,UAAU,IAAI;CACnC,IAAI,CAAC,SAAS,MAAM,IAAI,yBAAyB,UAAU,IAAI;CAE/D,MAAM,WAAW,iBAAiB,UAAU,IAAI;CAChD,MAAM,YAAY,UAAU,QAAQ,aAAa;CACjD,IAAI,CAAC,KAAK,KAAK,SAAS,GAAG,MAAM,IAAI,uBAAuB,SAAS;CAErE,OAAO;EACN;EACA;EACA;EACA,KAAK,GAAG,UAAU,GAAG;EACrB,OAAO,cAAc,WAAW,UAAU,GAAG,UAAU,GAAG;CAC3D;AACD;;;;;;AAOA,SAAgB,gBAAgB,UAAkC;CACjE,OAAO,OAAO,SAAS;AACxB;;AAGA,SAAgB,UACf,aACA,UACU;CACV,MAAM,SAAS,gBAAgB,QAAQ;CACvC,QAAQ,eAAe,GAAA,CAAI,MAAM,KAAK,CAAC,CAAC,SAAS,MAAM;AACxD;;AAGA,SAAgB,SACf,aACqB;CACrB,QAAQ,eAAe,GAAA,CAAI,MAAM,KAAK,CAAC,CAAC,MAAM,MAAM,EAAE,WAAW,MAAM,CAAC;AACzE;;AAGA,SAAgB,YAAY,UAAkC;CAC7D,OAAO,SAAS;AACjB;;;;;;;;;AAUA,SAAgB,UAAU,UAA0B,KAAqB;CACxE,OAAO,GAAG,SAAS,UAAU,GAAG,SAAS,QAAQ,GAAG,MAAA,GAAKA,oBAAAA,UAAAA,CAAU,GAAG,CAAC;AACxE;;AAGA,SAAgB,SACf,UACA,KACA,UACA,KACS;CACT,MAAM,OAAO,UAAU,UAAU,GAAG;CACpC,OAAO,WACJ,GAAG,SAAS,QAAQ,QAAQ,EAAE,EAAE,GAAG,KAAK,GAAG,QAC3C,GAAG,KAAK,GAAG;AACf;;;;;;;;;;;;;;;AAgBA,SAAgB,gBAAgB,UAA0B,KAAqB;CAC9E,OAAO,KAAK,GAAG,MAAM,SAAS,UAC3B,GAAG,SAAS,MAAM,GAAG,SAAS,WAAA,GAC9BC,oBAAAA,WAAAA,CAAW,CAAC,SAAS,OAAO,SAAS,KAAK,GAAG,GAAG;AACpD;;;;;;;;;;;;;;;;;ACxJA,MAAM,aAAa;;AAyBnB,IAAa,yBAAb,cAA4C,MAAM;CAC5B;CAArB,YAAY,KAAsB;EACjC,MACC,IAAI,IAAI,qKACT;EAHoB,KAAA,MAAA;EAIpB,KAAK,OAAO;CACb;AACD;;AAGA,IAAa,cAAb,cAAiC,MAAM;CAE5B;CACA;CACA;CAHV,YACC,OACA,SACA,MACC;EACD,MACC,uCAAuC,MAAM,gBAAgB,QAAQ,sBAChD,KAAK,mCAAmC,MAAM,iDACpE;EAPS,KAAA,QAAA;EACA,KAAA,UAAA;EACA,KAAA,OAAA;EAMT,KAAK,OAAO;CACb;AACD;AAEA,MAAM,cAAc;;;;;AAMpB,SAAgB,WAAW,WAAmC;CAC7D,OAAO,eAAe,WAAW,EAAE,CAAC,CAAC;AACtC;;AAGA,SAAgB,gBACf,WACA,MACkB;CAClB,MAAM,MAAM,WAAW,SAAS;CAChC,MAAM,WAAW,WAAW,EAAE,MAAM,UAAU,KAAK,CAAC;CAIpD,OAAO;EACN;EACA,QALc,CACd,mBAAG,IAAI,IAAI,CAAC,WAAW,UAAU,IAAI,GAAG,UAAU,IAAI,CAAC,CACxD,CAAC,CAAC,QAAQ,SAAyB,CAAC,CAAC,IAG/B;EACL,GAAI,aAAa,MAAM,EAAE,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC;EAClD,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;CACxB;AACD;;;;;AAMA,SAAS,aAAa,MAAuB;CAC5C,OAAO,KAAK,MAAM,OAAO,CAAC,CAAC,OAAO,SAAS,SAAS,QAAQ,SAAS,EAAE;AACxE;;AAGA,SAAS,WAAW,MAAkC;CACrD,MAAM,OAAO,KAAK,MAAM,OAAO,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,GAAG,EAAE;CACtD,OAAO,QAAQ,SAAS,OAAO,SAAS,OAAO,OAAO,KAAA;AACvD;;;;;AAMA,SAAgB,eAAe,SAAkC;CAChE,IAAI,CAAC,YAAY,KAAK,QAAQ,GAAG,GAChC,MAAM,IAAI,uBAAuB,QAAQ,GAAG;CAE7C,MAAM,CAAC,WAAW,QAAQ,QAAQ,IAAI,MAAM,GAAG;CAC/C,QAAA,GAAOC,UAAAA,KAAAA,CAAK,QAAQ,QAAQC,aAAAA,QAAQ,GAAG,QAAQ,WAAW,IAAI;AAC/D;;AAGA,SAAgB,WAAW,OAAe,SAAkC;CAC3E,QAAA,GAAOD,UAAAA,KAAAA,CAAK,eAAe,OAAO,GAAG,GAAG,MAAM,KAAK;AACpD;;;;;;;AAQA,SAAS,eAAe,OAAe,SAAoC;CAC1E,MAAM,WAAW,QAAQ,WAAW,CAAC,EAAA,CAAG,KAAK,QAC5C,WAAW,OAAO;EAAE;EAAK,MAAM,QAAQ;CAAK,CAAC,CAC9C;CACA,MAAM,QAAQ,CACb,mBAAG,IAAI,IAAI,CAAC,QAAQ,QAAQC,aAAAA,QAAQ,IAAA,GAAGD,UAAAA,KAAAA,EAAAA,GAAKE,QAAAA,QAAAA,CAAQ,GAAG,MAAM,CAAC,CAAC,CAChE;CACA,MAAM,SAAS,QAAQ,UAAU,CAAC,EAAA,CAAG,OAAO,YAAY;CACxD,OAAO,CACN,GAAG,SACH,GAAG,MAAM,SAAS,SACjB,MAAM,KAAK,UAAA,GAASF,UAAAA,KAAAA,CAAK,MAAM,MAAM,GAAG,MAAM,KAAK,CAAC,CACrD,CACD;AACD;;;;;AAcA,eAAsB,YACrB,OACA,SAEA,OAAmB,KACD;CAClB,MAAM,SAAS,eAAe,OAAO;CACrC,MAAM,UAAU,WAAW,OAAO,OAAO;CAGzC,OAAA,GAAMG,iBAAAA,MAAAA,CAAM,QAAQ;EAAE,WAAW;EAAM,MAAM;CAAM,CAAC;CAGpD,MAAM,OAAA,GAAMC,YAAAA,YAAAA,CAAY,UAAU,CAAC,CAAC,SAAS,KAAK;CAGlD,OAAA,GAAMC,iBAAAA,UAAAA,CAAU,SAAS,KAAK;EAAE,MAAM;EAAO,UAAU;EAAS;CAAK,CAAC;CAGtE,OAAA,GAAMC,iBAAAA,MAAAA,CAAM,SAAS,GAAK;CAE1B,OAAO;AACR;;;;;;;;AASA,eAAe,iBACd,OACA,SACmB;CACnB,MAAM,SAAS,eAAe,OAAO,OAAO,CAAC,CAAC,MAAM,UAAA,GACnDC,QAAAA,WAAAA,CAAW,IAAI,CAChB;CACA,IAAI,CAAC,QAAQ,OAAO;CAEpB,MAAM,UAAU,WAAW,OAAO,OAAO;CACzC,OAAA,GAAMJ,iBAAAA,MAAAA,CAAM,eAAe,OAAO,GAAG;EAAE,WAAW;EAAM,MAAM;CAAM,CAAC;CACrE,IAAI;EACH,OAAA,GAAMK,iBAAAA,SAAAA,CAAS,QAAQ,SAASC,QAAAA,UAAU,aAAa;CACxD,SAAS,OAAO;EACf,IAAK,MAAgC,SAAS,UAAU,MAAM;CAC/D;CACA,OAAA,GAAMH,iBAAAA,MAAAA,CAAM,SAAS,GAAK;CAC1B,OAAO;AACR;;;;;AAMA,eAAsB,QACrB,OACA,SACyB;CACzB,MAAM,UAAU,WAAW,OAAO,OAAO;CAEzC,IAAI,EAAA,GAACC,QAAAA,WAAAA,CAAW,OAAO,KAAK,CAAE,MAAM,iBAAiB,OAAO,OAAO,GAClE,OAAO;CAIR,QAAO,OAAA,GADWG,iBAAAA,SAAAA,CAAS,SAAS,OAAO,EAAA,CAChC,KAAK;AACjB;;;;;AAMA,eAAsB,WACrB,OACA,SACkB;CAClB,MAAM,MAAM,MAAM,QAAQ,OAAO,OAAO;CAExC,IAAI,CAAC,KACJ,MAAM,IAAI,YAAY,OAAO,QAAQ,KAAK,WAAW,OAAO,OAAO,CAAC;CAGrE,OAAO;AACR;;;;;AAiBA,eAAsB,eACrB,OACA,SACkB;CAClB,MAAM,cAAc,MAAM,QAAQ,OAAO,OAAO;CAEhD,IAAI,aACH,OAAO;CAKR,IAAI;EACH,OAAO,MAAM,YAAY,OAAO,SAAS,IAAI;CAC9C,SAAS,OAAO;EACf,IAAK,MAAgC,SAAS,UAAU,MAAM;EAC9D,OAAO,WAAW,OAAO,OAAO;CACjC;AACD;;;;ACpRA,MAAM,cAAc;;AAGpB,MAAM,YAAY;AAClB,MAAM,YAAY;;;;;;;;;AAqBlB,IAAa,mBAAb,MAAsD;CAUnC;CATlB,OAAgB;CAChB;;;;;;CAOA,YACC,MACA,SACC;EAFgB,KAAA,OAAA;EAGjB,KAAK,UAAU,WAAW;GACzB,KAAK,WAAW,EAAE,MAAM,YAAY,IAAI,MAAA,GAAKC,UAAAA,SAAAA,CAAS,IAAI,EAAE,CAAC;GAC7D,QAAQ,EAAA,GAACA,UAAAA,SAAAA,CAAS,IAAI,CAAC;EACxB;CACD;;CAGA,KAAK,OAAuB;EAC3B,QAAA,GAAOC,UAAAA,KAAAA,CAAK,KAAK,MAAM,aAAa,GAAG,MAAM,MAAM;CACpD;CAEA,MAAM,KAAK,OAA6C;EACvD,MAAM,OAAO,KAAK,KAAK,KAAK;EAC5B,IAAI,EAAA,GAACC,QAAAA,WAAAA,CAAW,IAAI,GAAG,OAAO;EAE9B,MAAM,OAAO,KAAK,MAAM,OAAA,GAAMC,iBAAAA,SAAAA,CAAS,MAAM,OAAO,CAAC;EAGrD,IAAI,EAAE,KAAK,YAAY,KAAK,KAAK,aAAa,KAAK,KAClD,OAAO;EAGR,MAAM,MAAM,MAAM,QAAQ,OAAO,KAAK,OAAO;EAC7C,IAAI,CAAC,KACJ,MAAM,IAAI,kBACT,OACA,KAAK,QAAQ,KACb,WAAW,OAAO,KAAK,OAAO,CAC/B;EAGD,OAAO,QAAQ,MAA8B,GAAG;CACjD;CAEA,MAAM,MAAM,OAAe,SAAsC;EAChE,OAAA,GAAMC,iBAAAA,MAAAA,EAAAA,GAAMH,UAAAA,KAAAA,CAAK,KAAK,MAAM,WAAW,GAAG,EAAE,WAAW,KAAK,CAAC;EAE7D,MAAM,MAAM,MAAM,eAAe,OAAO,KAAK,OAAO;EACpD,OAAA,GAAMI,iBAAAA,UAAAA,CACL,KAAK,KAAK,KAAK,GACf,KAAK,UAAU,QAAQ,SAAS,GAAG,GAAG,MAAM,CAAC,GAC7C,OACD;CACD;AACD;;AAGA,IAAa,oBAAb,cAAuC,MAAM;CAElC;CACA;CACA;CAHV,YACC,OACA,SACA,MACC;EACD,MACC,uCAAuC,MAAM,sBACxB,KAAK,8GAC3B;EAPS,KAAA,QAAA;EACA,KAAA,UAAA;EACA,KAAA,OAAA;EAMT,KAAK,OAAO;CACb;AACD;;AAGA,SAAS,YAAY,MAAkC;CACtD,IAAI;EACH,MAAM,MAAM,KAAK,OAAA,GAAMC,QAAAA,aAAAA,EAAAA,GAAaL,UAAAA,KAAAA,CAAK,MAAM,cAAc,GAAG,OAAO,CAAC;EACxE,OAAO,OAAO,IAAI,SAAS,WACxB,IAAI,KAAK,QAAQ,aAAa,EAAE,IAChC,KAAA;CACJ,QAAQ;EACP;CACD;AACD;AAEA,SAAS,QAAQ,SAAuB,QAAsC;CAC7E,MAAM,MAAM,OAAO,KAAK,QAAQ,KAAK;CACrC,MAAM,MAAA,GAAKM,YAAAA,YAAAA,CAAY,SAAS;CAEhC,MAAM,UAAA,GAASC,YAAAA,eAAAA,CAAe,WAAW,KAAK,EAAE;CAChD,MAAM,aAAa,OAAO,OAAO,CAChC,OAAO,OAAO,KAAK,UAAU,OAAO,GAAG,OAAO,GAC9C,OAAO,MAAM,CACd,CAAC;CAKD,OAAO;EACN,SAAS;EACT,WAJgB,OAAO,OAAO,CAAC,YAAY,OAAO,WAAW,CAAC,CAI5C,CAAC,CAAC,SAAS,QAAQ;EACrC,IAAI,GAAG,SAAS,KAAK;CACtB;AACD;AAEA,SAAS,QAAQ,MAA4B,QAA8B;CAC1E,MAAM,MAAM,OAAO,KAAK,QAAQ,KAAK;CACrC,MAAM,WAAW,OAAO,KAAK,KAAK,WAAW,QAAQ;CAErD,MAAM,YAAA,GAAWC,YAAAA,iBAAAA,CAChB,WACA,KACA,OAAO,KAAK,KAAK,IAAI,KAAK,CAC3B;CACA,SAAS,WAAW,SAAS,SAAS,GAAgB,CAAC;CAEvD,MAAM,YAAY,OAAO,OAAO,CAC/B,SAAS,OAAO,SAAS,SAAS,GAAG,GAAgB,CAAC,GACtD,SAAS,MAAM,CAChB,CAAC;CAED,OAAO,KAAK,MAAM,UAAU,SAAS,OAAO,CAAC;AAC9C;;;;;;;;;;;;;;;AC3FA,eAAsB,gBACrB,WACA,OACA,UAA+B,CAAC,GACR;CACxB,MAAM,aAAa,UAAU,QAAQ,SAAS;CAE9C,IAAI,UAAU,UAAU,OAAO,SAAS,eAAe,QACtD,OAAO,IAAI,iBACV,UAAU,MACV,gBAAgB,WAAW,QAAQ,IAAI,CACxC;CAGD,IAAI,WAAW,aAAa,OAC3B,OAAO,WAAW;CAGnB,MAAM,EAAE,oBAAoB,MAAA,QAAA,QAAA,CAAA,CAAA,WAAA,QAAM,oBAAA,CAAA;CAClC,OAAO,IAAI,gBAAgB;EAC1B,SAAS,UAAU;EACnB,QAAQ,WAAW;EACnB,GAAI,QAAQ,UAAU,EAAE,SAAS,QAAQ,QAAQ,IAAI,CAAC;CACvD,CAAC;AACF;;AAGA,SAAgB,cACf,WACA,OACU;CACV,MAAM,aAAa,UAAU,QAAQ,SAAS;CAC9C,OAAO,UAAU,UAAU,OAAO,SAAS,eAAe;AAC3D"}
1
+ {"version":3,"file":"store-D_2ByRtW.cjs","names":["kebabCase","scopedName","join","gkmHome","homedir","mkdir","randomBytes","writeFile","chmod","existsSync","copyFile","constants","readFile","basename","join","existsSync","readFile","mkdir","writeFile","readFileSync","randomBytes","createCipheriv","createDecipheriv"],"sources":["../src/deploy/identity.ts","../src/secrets/keystore.ts","../src/secrets/file.ts","../src/secrets/store.ts"],"sourcesContent":["/**\n * Who a deploy is, on a target it shares with other workspaces.\n *\n * A Dokploy server is one flat namespace of projects, images and Docker\n * service names. Deploys used to identify everything they made by the\n * workspace name alone, so two workspaces called `shop` — or `Shop` and\n * `shop` — deploying to one server landed in the same project, pushed over\n * each other's images and redeployed each other's applications. A\n * `DeployIdentity` is what a deploy names and claims its resources by instead.\n */\n\nimport { kebabCase, scopedName } from '@geekmidas/manifest';\n\nexport interface DeployIdentity {\n\t/**\n\t * Whose deploy this is — an organisation, a team, a repository. Two\n\t * workspaces with the same name are told apart by it. Defaults to the\n\t * kebab-cased workspace name; set `deploy.namespace` to choose one.\n\t */\n\treadonly namespace: string;\n\t/** The workspace name as the target spells it: lowercase, `[a-z0-9-]`. */\n\treadonly project: string;\n\treadonly stage: string;\n\t/**\n\t * `<namespace>/<project>`: what a Dokploy project is claimed by.\n\t *\n\t * The stage is not part of it because one Dokploy project holds every stage\n\t * of a workspace, each as an environment — the key has to be the same from\n\t * every stage that deploys into it.\n\t */\n\treadonly key: string;\n\t/**\n\t * The prefix every name on the target carries: the project alone in the\n\t * default namespace, `<namespace>-<project>` in any other.\n\t *\n\t * The default adds nothing so that a workspace that never set a namespace\n\t * keeps the names it was already deployed under — a Postgres is found by\n\t * its name, and a new name would be a new, empty database.\n\t */\n\treadonly scope: string;\n}\n\n/** A namespace that cannot be part of a Dokploy name or an image path. */\nexport class DeployNamespaceInvalid extends Error {\n\tconstructor(readonly namespace: string) {\n\t\tsuper(\n\t\t\t`deploy.namespace '${namespace}' is not usable as a name: use lowercase letters, digits and single '-' between them (e.g. 'acme' or 'acme-platform') in gkm.config.ts.`,\n\t\t);\n\t\tthis.name = 'DeployNamespaceInvalid';\n\t}\n}\n\n/** A workspace name with nothing a target can name a resource by. */\nexport class DeployProjectNameInvalid extends Error {\n\tconstructor(readonly workspaceName: string) {\n\t\tsuper(\n\t\t\t`The workspace name '${workspaceName}' has no letters or digits to name deployed resources by. Set \"name\" in gkm.config.ts.`,\n\t\t);\n\t\tthis.name = 'DeployProjectNameInvalid';\n\t}\n}\n\nconst NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;\n\n/**\n * Lowercase `[a-z0-9-]`, runs of anything else collapsed to one `-`: valid as\n * a Docker image path component, a Docker service name and a Dokploy name.\n */\nfunction slug(value: string): string {\n\treturn value\n\t\t.toLowerCase()\n\t\t.replace(/[^a-z0-9]+/g, '-')\n\t\t.replace(/^-+|-+$/g, '');\n}\n\n/** The namespace a workspace is in when it does not name one. */\nexport function defaultNamespace(workspaceName: string): string {\n\treturn slug(kebabCase(workspaceName));\n}\n\n/** What an identity is derived from: a workspace's name and namespace. */\nexport interface IdentitySource {\n\tname: string;\n\tdeploy?: { namespace?: string };\n}\n\nexport function deployIdentity(\n\tworkspace: IdentitySource,\n\tstage: string,\n): DeployIdentity {\n\t// Lowercased rather than kebab-cased: the names a workspace was already\n\t// deployed under were `${stage}-${name}`.toLowerCase(), and this has to\n\t// reproduce them for the databases it finds by name.\n\tconst project = slug(workspace.name);\n\tif (!project) throw new DeployProjectNameInvalid(workspace.name);\n\n\tconst fallback = defaultNamespace(workspace.name);\n\tconst namespace = workspace.deploy?.namespace ?? fallback;\n\tif (!NAME.test(namespace)) throw new DeployNamespaceInvalid(namespace);\n\n\treturn {\n\t\tnamespace,\n\t\tproject,\n\t\tstage,\n\t\tkey: `${namespace}/${project}`,\n\t\tscope: namespace === fallback ? project : `${namespace}-${project}`,\n\t};\n}\n\n/**\n * The token a Dokploy project's description carries to say which identity\n * created it. A project with the right name and no marker belongs to someone\n * else, and a deploy never adopts it.\n */\nexport function ownershipMarker(identity: DeployIdentity): string {\n\treturn `gkm:${identity.key}`;\n}\n\n/** Whether a project description carries `identity`'s marker. */\nexport function isOwnedBy(\n\tdescription: string | null | undefined,\n\tidentity: DeployIdentity,\n): boolean {\n\tconst marker = ownershipMarker(identity);\n\treturn (description ?? '').split(/\\s+/).includes(marker);\n}\n\n/** Any `gkm:` marker a description carries — whoever's it is. */\nexport function markerOf(\n\tdescription: string | null | undefined,\n): string | undefined {\n\treturn (description ?? '').split(/\\s+/).find((t) => t.startsWith('gkm:'));\n}\n\n/** What the Dokploy project is called. */\nexport function projectName(identity: DeployIdentity): string {\n\treturn identity.scope;\n}\n\n/**\n * The repository an app's image is pushed to, below the registry:\n * `<namespace>/<project>-<app>`.\n *\n * The namespace is a path segment of its own so that a registry that maps\n * paths to owners (an organisation's packages, a Harbor project) can grant it\n * as one; the app is joined to the project so the two read as one name.\n */\nexport function imageName(identity: DeployIdentity, app: string): string {\n\treturn `${identity.namespace}/${identity.project}-${slug(kebabCase(app))}`;\n}\n\n/** The full image ref an app is built, pushed and deployed as. */\nexport function imageRef(\n\tidentity: DeployIdentity,\n\tapp: string,\n\tregistry: string | undefined,\n\ttag: string,\n): string {\n\tconst name = imageName(identity, app);\n\treturn registry\n\t\t? `${registry.replace(/\\/+$/, '')}/${name}:${tag}`\n\t\t: `${name}:${tag}`;\n}\n\n/**\n * What one application is called on the target.\n *\n * The same rule the constructs use, through the same `scopedName`, scoped by\n * the stage and the identity: the application beside\n * `production-kitchen-sink-database` is `production-kitchen-sink-api`. A\n * Dokploy application's name is also its Docker service name, which is unique\n * on the whole server, so the namespace has to be in it too — `acme`'s and\n * `globex`'s `shop` would otherwise both run `production-shop-api`.\n *\n * The app id is dropped when it repeats the project, so a project named for its\n * one application is `production-kitchen-sink` rather than\n * `production-kitchen-sink-kitchen-sink`.\n */\nexport function applicationName(identity: DeployIdentity, app: string): string {\n\treturn slug(app) === identity.project\n\t\t? `${identity.stage}-${identity.scope}`\n\t\t: scopedName([identity.stage, identity.scope], app);\n}\n","/**\n * The keys that open each stage's encrypted secrets file, kept outside the\n * project in the CLI's home.\n *\n * Keys were kept at `~/.gkm/<folder>/<stage>.key`, named after the project\n * directory — so two checkouts in folders with the same name (`~/work/api`\n * and `~/oss/api`) shared one key, and the second `gkm secrets:init`\n * overwrote the first project's. They are now kept under the project's\n * identity, `<home>/keys/<namespace>/<project>/<stage>.key`, the same\n * `<namespace>/<project>` a deploy claims its Dokploy project by. A key still\n * at the old place is copied to the new one the first time it is read; the\n * old file stays, so an older CLI on the same machine keeps working.\n */\n\nimport { randomBytes } from 'node:crypto';\nimport { constants, existsSync } from 'node:fs';\nimport {\n\tchmod,\n\tcopyFile,\n\tmkdir,\n\treadFile,\n\trm,\n\twriteFile,\n} from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\nimport { deployIdentity, type IdentitySource } from '../deploy/identity';\nimport { gkmHome } from '../home';\n\n/** Key length for AES-256 encryption */\nconst KEY_LENGTH = 32; // 256 bits\n\n/** Whose keys, and where the CLI's home is. */\nexport interface KeystoreProject {\n\t/** `<namespace>/<project>`, as `DeployIdentity.key` spells it. */\n\tkey: string;\n\t/**\n\t * The names the project's keys were kept under, `~/.gkm/<name>/`, before\n\t * they were keyed by identity: its directory's basename, which the secrets\n\t * file used, and its workspace name, which `gkm init` and the generated CI\n\t * workflow wrote to. Looked in, and copied from, when the project has no\n\t * key of its own yet.\n\t */\n\tlegacy?: readonly string[];\n\t/**\n\t * Other project keys this project's keys may be copied from when it has\n\t * none: the default namespace's, for a workspace that has just set\n\t * `deploy.namespace` — the secrets file in the checkout is the same one.\n\t */\n\taliases?: string[];\n\t/** The CLI's home. Defaults to `GKM_HOME`, else `~/.gkm`. */\n\thome?: string;\n}\n\n/** A keystore project key that is not `<namespace>/<project>`. */\nexport class KeystoreProjectInvalid extends Error {\n\tconstructor(readonly key: string) {\n\t\tsuper(\n\t\t\t`'${key}' is not a keystore project: expected '<namespace>/<project>' in lowercase letters, digits and '-', as deployIdentity() produces. Pass the workspace's identity key.`,\n\t\t);\n\t\tthis.name = 'KeystoreProjectInvalid';\n\t}\n}\n\n/** A stage has no key, here or at the place keys used to be kept. */\nexport class KeyNotFound extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly project: string,\n\t\treadonly path: string,\n\t) {\n\t\tsuper(\n\t\t\t`Encryption key not found for stage \"${stage}\" in project \"${project}\". ` +\n\t\t\t\t`Expected key at: ${path}. Run \\`gkm secrets:init --stage ${stage}\\` to create one, or copy the stage's key there.`,\n\t\t);\n\t\tthis.name = 'KeyNotFound';\n\t}\n}\n\nconst PROJECT_KEY = /^[a-z0-9]+(?:-[a-z0-9]+)*\\/[a-z0-9]+(?:-[a-z0-9]+)*$/;\n\n/**\n * The keystore key of a workspace: its deploy identity without the stage, so\n * a project's keys and its Dokploy project are named by the same thing.\n */\nexport function projectKey(workspace: IdentitySource): string {\n\treturn deployIdentity(workspace, '').key;\n}\n\n/** The keystore project of a loaded workspace. */\nexport function keystoreProject(\n\tworkspace: IdentitySource & { root: string },\n\thome?: string,\n): KeystoreProject {\n\tconst key = projectKey(workspace);\n\tconst unscoped = projectKey({ name: workspace.name });\n\tconst legacy = [\n\t\t...new Set([folderName(workspace.root), workspace.name]),\n\t].filter((name): name is string => !!name);\n\treturn {\n\t\tkey,\n\t\tlegacy,\n\t\t...(unscoped !== key ? { aliases: [unscoped] } : {}),\n\t\t...(home ? { home } : {}),\n\t};\n}\n\n/**\n * Whether `name` stays inside the home it is joined to. A workspace name was\n * joined as is — `@acme/shop` is `~/.gkm/@acme/shop` — but never upwards.\n */\nfunction isLegacyName(name: string): boolean {\n\treturn name.split(/[\\\\/]/).every((part) => part !== '..' && part !== '');\n}\n\n/** The last segment of a path, as the old keystore named projects. */\nfunction folderName(path: string): string | undefined {\n\tconst name = path.split(/[\\\\/]/).filter(Boolean).at(-1);\n\treturn name && name !== '.' && name !== '..' ? name : undefined;\n}\n\n/**\n * Get the keystore directory for a project:\n * `<home>/keys/<namespace>/<project>`.\n */\nexport function getKeystoreDir(project: KeystoreProject): string {\n\tif (!PROJECT_KEY.test(project.key)) {\n\t\tthrow new KeystoreProjectInvalid(project.key);\n\t}\n\tconst [namespace, name] = project.key.split('/') as [string, string];\n\treturn join(project.home ?? gkmHome(), 'keys', namespace, name);\n}\n\n/** Get the path to a stage's encryption key. */\nexport function getKeyPath(stage: string, project: KeystoreProject): string {\n\treturn join(getKeystoreDir(project), `${stage}.key`);\n}\n\n/**\n * Where else a stage's key may be, most likely first: under an alias of the\n * project, then where keys were kept before they were keyed by identity —\n * under the configured home, then under `~/.gkm`, which is where every CLI\n * before `GKM_HOME` wrote.\n */\nfunction legacyKeyPaths(stage: string, project: KeystoreProject): string[] {\n\tconst aliases = (project.aliases ?? []).map((key) =>\n\t\tgetKeyPath(stage, { key, home: project.home }),\n\t);\n\tconst homes = [\n\t\t...new Set([project.home ?? gkmHome(), join(homedir(), '.gkm')]),\n\t];\n\tconst names = (project.legacy ?? []).filter(isLegacyName);\n\treturn [\n\t\t...aliases,\n\t\t...names.flatMap((name) =>\n\t\t\thomes.map((home) => join(home, name, `${stage}.key`)),\n\t\t),\n\t];\n}\n\n/** Check if a key exists for a stage, here or where it used to be kept. */\nexport function keyExists(stage: string, project: KeystoreProject): boolean {\n\treturn (\n\t\texistsSync(getKeyPath(stage, project)) ||\n\t\tlegacyKeyPaths(stage, project).some((path) => existsSync(path))\n\t);\n}\n\n/**\n * Generate a new encryption key for a stage, stored owner-only at\n * `<home>/keys/<namespace>/<project>/<stage>.key`.\n */\nexport async function generateKey(\n\tstage: string,\n\tproject: KeystoreProject,\n\t/** `wx` to refuse replacing a key that is already there. */\n\tflag: 'w' | 'wx' = 'w',\n): Promise<string> {\n\tconst keyDir = getKeystoreDir(project);\n\tconst keyPath = getKeyPath(stage, project);\n\n\t// Ensure keystore directory exists with restricted permissions\n\tawait mkdir(keyDir, { recursive: true, mode: 0o700 });\n\n\t// Generate random key\n\tconst key = randomBytes(KEY_LENGTH).toString('hex');\n\n\t// Write key with restricted permissions (owner read/write only)\n\tawait writeFile(keyPath, key, { mode: 0o600, encoding: 'utf-8', flag });\n\n\t// Ensure permissions are set correctly (in case file existed)\n\tawait chmod(keyPath, 0o600);\n\n\treturn key;\n}\n\n/**\n * Copy a key from where it used to be kept to where it is kept now.\n *\n * Copied, not moved: an older CLI on the same machine still reads the old\n * place. Exclusive, so two processes migrating at once cannot overwrite a key\n * one of them already put there.\n */\nasync function migrateLegacyKey(\n\tstage: string,\n\tproject: KeystoreProject,\n): Promise<boolean> {\n\tconst legacy = legacyKeyPaths(stage, project).find((path) =>\n\t\texistsSync(path),\n\t);\n\tif (!legacy) return false;\n\n\tconst keyPath = getKeyPath(stage, project);\n\tawait mkdir(getKeystoreDir(project), { recursive: true, mode: 0o700 });\n\ttry {\n\t\tawait copyFile(legacy, keyPath, constants.COPYFILE_EXCL);\n\t} catch (error) {\n\t\tif ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;\n\t}\n\tawait chmod(keyPath, 0o600);\n\treturn true;\n}\n\n/**\n * Read an encryption key for a stage, or `null` when there is none — after\n * copying it over from where keys used to be kept, if it is still there.\n */\nexport async function readKey(\n\tstage: string,\n\tproject: KeystoreProject,\n): Promise<string | null> {\n\tconst keyPath = getKeyPath(stage, project);\n\n\tif (!existsSync(keyPath) && !(await migrateLegacyKey(stage, project))) {\n\t\treturn null;\n\t}\n\n\tconst key = await readFile(keyPath, 'utf-8');\n\treturn key.trim();\n}\n\n/**\n * Read an encryption key for a stage, throwing {@link KeyNotFound} if there\n * is none.\n */\nexport async function requireKey(\n\tstage: string,\n\tproject: KeystoreProject,\n): Promise<string> {\n\tconst key = await readKey(stage, project);\n\n\tif (!key) {\n\t\tthrow new KeyNotFound(stage, project.key, getKeyPath(stage, project));\n\t}\n\n\treturn key;\n}\n\n/**\n * Delete a key for a stage. Only the current one: a key left at the old place\n * belongs to whichever older CLI still reads it.\n */\nexport async function deleteKey(\n\tstage: string,\n\tproject: KeystoreProject,\n): Promise<void> {\n\tawait rm(getKeyPath(stage, project), { force: true });\n}\n\n/**\n * Get or create a key for a stage.\n * If the key already exists, it is returned. Otherwise, a new key is generated.\n */\nexport async function getOrCreateKey(\n\tstage: string,\n\tproject: KeystoreProject,\n): Promise<string> {\n\tconst existingKey = await readKey(stage, project);\n\n\tif (existingKey) {\n\t\treturn existingKey;\n\t}\n\n\t// Exclusively: a key is now shared by every checkout of one project, and\n\t// two commands creating it at once must not each keep a different one.\n\ttry {\n\t\treturn await generateKey(stage, project, 'wx');\n\t} catch (error) {\n\t\tif ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;\n\t\treturn requireKey(stage, project);\n\t}\n}\n","import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { basename, join } from 'node:path';\nimport {\n\tgetKeyPath,\n\tgetOrCreateKey,\n\ttype KeystoreProject,\n\tprojectKey,\n\treadKey,\n} from './keystore';\nimport type { SecretsStore } from './store.js';\nimport type { StageSecrets } from './types';\n\n/** Where the files live, relative to the project root. */\nconst SECRETS_DIR = '.gkm/secrets';\n\n/** AES-256-GCM configuration */\nconst ALGORITHM = 'aes-256-gcm';\nconst IV_LENGTH = 12; // 96 bits for GCM\nconst AUTH_TAG_LENGTH = 16; // 128 bits\n\n/** Encrypted secrets file structure */\ninterface EncryptedSecretsFile {\n\t/** Version for future format changes */\n\tversion: 1;\n\t/** Base64 encoded encrypted data (ciphertext + auth tag) */\n\tencrypted: string;\n\t/** Hex encoded IV */\n\tiv: string;\n}\n\n/**\n * A stage's secrets as an encrypted file on this machine:\n * `.gkm/secrets/<stage>.json`, with its key in the CLI's home at\n * `keys/<namespace>/<project>/<stage>.key`.\n *\n * The local stage's store, always — its secrets belong to the machine running\n * `gkm dev` — and a deployed stage's when `secrets.store` is `'file'`.\n */\nexport class FileSecretsStore implements SecretsStore {\n\treadonly name = 'file';\n\tprivate readonly project: KeystoreProject;\n\n\t/**\n\t * @param project - Whose keys: a workspace's `keystoreProject()`. Outside a\n\t * workspace there is no config to name the project, so it is named the\n\t * way a workspace without `name` is — its package name, else its folder.\n\t */\n\tconstructor(\n\t\tprivate readonly root: string,\n\t\tproject?: KeystoreProject,\n\t) {\n\t\tthis.project = project ?? {\n\t\t\tkey: projectKey({ name: packageName(root) ?? basename(root) }),\n\t\t\tlegacy: [basename(root)],\n\t\t};\n\t}\n\n\t/** The file a stage's secrets are kept in. */\n\tpath(stage: string): string {\n\t\treturn join(this.root, SECRETS_DIR, `${stage}.json`);\n\t}\n\n\tasync read(stage: string): Promise<StageSecrets | null> {\n\t\tconst path = this.path(stage);\n\t\tif (!existsSync(path)) return null;\n\n\t\tconst data = JSON.parse(await readFile(path, 'utf-8'));\n\n\t\t// Legacy: unencrypted format (for backwards compatibility)\n\t\tif (!(data.version === 1 && data.encrypted && data.iv)) {\n\t\t\treturn data as StageSecrets;\n\t\t}\n\n\t\tconst key = await readKey(stage, this.project);\n\t\tif (!key) {\n\t\t\tthrow new MissingSecretsKey(\n\t\t\t\tstage,\n\t\t\t\tthis.project.key,\n\t\t\t\tgetKeyPath(stage, this.project),\n\t\t\t);\n\t\t}\n\n\t\treturn decrypt(data as EncryptedSecretsFile, key);\n\t}\n\n\tasync write(stage: string, secrets: StageSecrets): Promise<void> {\n\t\tawait mkdir(join(this.root, SECRETS_DIR), { recursive: true });\n\n\t\tconst key = await getOrCreateKey(stage, this.project);\n\t\tawait writeFile(\n\t\t\tthis.path(stage),\n\t\t\tJSON.stringify(encrypt(secrets, key), null, 2),\n\t\t\t'utf-8',\n\t\t);\n\t}\n}\n\n/** A stage's secrets file exists, and the key that opens it does not. */\nexport class MissingSecretsKey extends Error {\n\tconstructor(\n\t\treadonly stage: string,\n\t\treadonly project: string,\n\t\treadonly path: string,\n\t) {\n\t\tsuper(\n\t\t\t`Decryption key not found for stage \"${stage}\". ` +\n\t\t\t\t`Expected key at: ${path}. Copy the stage's key there from whoever created it, or set secrets.store to a store this machine can reach.`,\n\t\t);\n\t\tthis.name = 'MissingSecretsKey';\n\t}\n}\n\n/** A project's package name without its scope, as a workspace name reads it. */\nfunction packageName(root: string): string | undefined {\n\ttry {\n\t\tconst pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf-8'));\n\t\treturn typeof pkg.name === 'string'\n\t\t\t? pkg.name.replace(/^@[^/]+\\//, '')\n\t\t\t: undefined;\n\t} catch {\n\t\treturn undefined;\n\t}\n}\n\nfunction encrypt(secrets: StageSecrets, keyHex: string): EncryptedSecretsFile {\n\tconst key = Buffer.from(keyHex, 'hex');\n\tconst iv = randomBytes(IV_LENGTH);\n\n\tconst cipher = createCipheriv(ALGORITHM, key, iv);\n\tconst ciphertext = Buffer.concat([\n\t\tcipher.update(JSON.stringify(secrets), 'utf-8'),\n\t\tcipher.final(),\n\t]);\n\n\t// Ciphertext and auth tag, together\n\tconst combined = Buffer.concat([ciphertext, cipher.getAuthTag()]);\n\n\treturn {\n\t\tversion: 1,\n\t\tencrypted: combined.toString('base64'),\n\t\tiv: iv.toString('hex'),\n\t};\n}\n\nfunction decrypt(data: EncryptedSecretsFile, keyHex: string): StageSecrets {\n\tconst key = Buffer.from(keyHex, 'hex');\n\tconst combined = Buffer.from(data.encrypted, 'base64');\n\n\tconst decipher = createDecipheriv(\n\t\tALGORITHM,\n\t\tkey,\n\t\tBuffer.from(data.iv, 'hex'),\n\t);\n\tdecipher.setAuthTag(combined.subarray(-AUTH_TAG_LENGTH));\n\n\tconst plaintext = Buffer.concat([\n\t\tdecipher.update(combined.subarray(0, -AUTH_TAG_LENGTH)),\n\t\tdecipher.final(),\n\t]);\n\n\treturn JSON.parse(plaintext.toString('utf-8')) as StageSecrets;\n}\n","/**\n * Where a stage's secrets are kept.\n *\n * Every command that needs a stage's secrets — `gkm dev`, `test`, `deploy`,\n * `build`, `setup`, `secrets:*` — asks {@link secretsStoreFor} for that\n * stage's store and reads or writes it. None of them knows whether that is a\n * file on this machine or SSM in the stage's account: the local stage is\n * always the file, and a deployed stage is whatever `secrets.store` names.\n *\n * The same shape deploy state has (`StateProvider`): one interface, a backend\n * per place, and a custom object for any place this package does not ship.\n */\n\nimport type { NormalizedWorkspace } from '../workspace/types.js';\nimport { FileSecretsStore } from './file.js';\nimport { keystoreProject } from './keystore.js';\nimport type { StageSecrets } from './types.js';\n\nexport interface SecretsStore {\n\t/** Which kind of store this is — `'file'`, `'ssm'`, or a custom one's. */\n\treadonly name: string;\n\t/** The stage's secrets, or null when it has none here. */\n\tread(stage: string): Promise<StageSecrets | null>;\n\t/** Replace the stage's secrets. */\n\twrite(stage: string, secrets: StageSecrets): Promise<void>;\n}\n\n/** Secrets in SSM Parameter Store, in the account of the active credentials. */\nexport interface SsmSecretsStoreConfig {\n\tprovider: 'ssm';\n\tregion: string;\n}\n\n/** Any other backend: an object implementing {@link SecretsStore}. */\nexport interface CustomSecretsStoreConfig {\n\tprovider: SecretsStore;\n}\n\n/**\n * `secrets.store` in gkm.config.ts.\n *\n * `'file'` — the default — keeps each stage's secrets in the encrypted\n * `.gkm/secrets/<stage>.json`, its key in the CLI's home (`GKM_HOME`, else\n * `~/.gkm`). It cannot serve a\n * deploy from CI while `.gkm/` is gitignored; `ssm` or a custom store can.\n */\nexport type SecretsStoreConfig =\n\t| 'file'\n\t| SsmSecretsStoreConfig\n\t| CustomSecretsStoreConfig;\n\nexport interface SecretsStoreOptions {\n\t/**\n\t * The AWS profile for the stage's account. Only that profile is used —\n\t * never AWS_* from the environment — so a store cannot quietly land in\n\t * whichever account the shell was exported for.\n\t */\n\tprofile?: string;\n\t/**\n\t * The CLI's home, where a file store's keys are kept. Defaults to\n\t * `GKM_HOME`, else `~/.gkm`.\n\t */\n\thome?: string;\n}\n\n/**\n * The store a stage's secrets are kept in — what every command reads and\n * writes, for the stage it acts on.\n *\n * The local stage is always the file: its secrets belong to the machine\n * running `gkm dev`. A deployed stage uses `secrets.store`.\n */\nexport async function secretsStoreFor(\n\tworkspace: NormalizedWorkspace,\n\tstage: string,\n\toptions: SecretsStoreOptions = {},\n): Promise<SecretsStore> {\n\tconst configured = workspace.secrets.store ?? 'file';\n\n\tif (stage === workspace.stages.local || configured === 'file') {\n\t\treturn new FileSecretsStore(\n\t\t\tworkspace.root,\n\t\t\tkeystoreProject(workspace, options.home),\n\t\t);\n\t}\n\n\tif (configured.provider !== 'ssm') {\n\t\treturn configured.provider;\n\t}\n\n\tconst { AwsSecretsStore } = await import('./aws.js');\n\treturn new AwsSecretsStore({\n\t\tproject: workspace.name,\n\t\tregion: configured.region,\n\t\t...(options.profile ? { profile: options.profile } : {}),\n\t});\n}\n\n/** Whether a stage's secrets live somewhere other than this machine. */\nexport function isRemoteStore(\n\tworkspace: NormalizedWorkspace,\n\tstage: string,\n): boolean {\n\tconst configured = workspace.secrets.store ?? 'file';\n\treturn stage !== workspace.stages.local && configured !== 'file';\n}\n\nexport { FileSecretsStore };\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_define = require("../define-Cv_QvDrg.cjs");
3
- const require_runtime = require("../runtime-CMwtq0AL.cjs");
4
- const require_resolve = require("../resolve-xytBTtG4.cjs");
2
+ const require_define = require("../define-zUsP72ym.cjs");
3
+ const require_runtime = require("../runtime-BGNDrHzh.cjs");
4
+ const require_resolve = require("../resolve-BXsNQjMR.cjs");
5
5
  exports.DeployTargetNotYetSupported = require_define.DeployTargetNotYetSupported;
6
6
  exports.InvalidTargetOptions = require_resolve.InvalidTargetOptions;
7
7
  exports.ProviderRemoved = require_resolve.ProviderRemoved;
@@ -1,5 +1,5 @@
1
- import { $ as DeployEventError, B as DeployFailure, G as DeployTargetCapabilities, H as DeployRuntime, J as TargetEvent, K as DeployTargetEntry, Q as DeployEvent, U as DeploySecrets, V as DeployPhaseContext, W as DeployTarget, X as TargetOptionsIssue, Y as TargetOptions, Z as TargetOptionsSchema, at as DeployResult, ct as CredentialKinds, et as DeployEventType, gt as StateStore, it as AppDeployResult, lt as CredentialProvider, nt as ResourceChange, ot as Credential, q as DeployTargetLogger, rt as ResourceVia, st as CredentialKind, tt as DeployPhase, ut as CredentialRequest, vt as StageSecrets, yt as DeployIdentity, z as AnyDeployTarget } from "../types-DIA7Li-J.cjs";
2
- import { a as TargetRuntimeMismatch, c as DeployTargetNotYetSupported, i as TargetPackageInvalid, l as UnknownDeployTarget, n as InvalidTargetOptions, o as ProviderRemoved, r as TargetEntryInvalid, s as TargetPackageNotFound, t as TargetRuntimeUndeclared } from "../runtime-Dn_n-29S.cjs";
1
+ import { $ as DeployEventError, B as DeployFailure, G as DeployTargetCapabilities, H as DeployRuntime, J as TargetEvent, K as DeployTargetEntry, Q as DeployEvent, U as DeploySecrets, V as DeployPhaseContext, W as DeployTarget, X as TargetOptionsIssue, Y as TargetOptions, Z as TargetOptionsSchema, _t as StateStore, at as DeployResult, bt as DeployIdentity, ct as CredentialKind, dt as CredentialRequest, et as DeployEventType, it as AppDeployResult, lt as CredentialKinds, nt as ResourceChange, ot as AwsCredential, q as DeployTargetLogger, rt as ResourceVia, st as Credential, tt as DeployPhase, ut as CredentialProvider, yt as StageSecrets, z as AnyDeployTarget } from "../types-DkrDOMxw.cjs";
2
+ import { a as TargetRuntimeMismatch, c as DeployTargetNotYetSupported, i as TargetPackageInvalid, l as UnknownDeployTarget, n as InvalidTargetOptions, o as ProviderRemoved, r as TargetEntryInvalid, s as TargetPackageNotFound, t as TargetRuntimeUndeclared } from "../runtime-Cjq1jlVX.cjs";
3
3
  //#region src/target/define.d.ts
4
4
  /**
5
5
  * A deploy target, typed from what it is written with: the options from its
@@ -24,5 +24,5 @@ export declare function defineTarget<Options = undefined, Run = void>(target: De
24
24
  /** Whether `value` has the shape of a target. */
25
25
  export declare function isDeployTarget(value: unknown): value is AnyDeployTarget;
26
26
  //#endregion
27
- export { type AnyDeployTarget, type AppDeployResult, type Credential, type CredentialKind, type CredentialKinds, type CredentialProvider, type CredentialRequest, type DeployEvent, type DeployEventError, type DeployEventType, type DeployFailure, type DeployIdentity, type DeployPhase, type DeployPhaseContext, type DeployResult, type DeployRuntime, type DeploySecrets, type DeployTarget, type DeployTargetCapabilities, type DeployTargetEntry, type DeployTargetLogger, DeployTargetNotYetSupported, InvalidTargetOptions, ProviderRemoved, type ResourceChange, type ResourceVia, type StageSecrets, type StateStore, TargetEntryInvalid, type TargetEvent, type TargetOptions, type TargetOptionsIssue, type TargetOptionsSchema, TargetPackageInvalid, TargetPackageNotFound, TargetRuntimeMismatch, TargetRuntimeUndeclared, UnknownDeployTarget };
27
+ export { type AnyDeployTarget, type AppDeployResult, type AwsCredential, type Credential, type CredentialKind, type CredentialKinds, type CredentialProvider, type CredentialRequest, type DeployEvent, type DeployEventError, type DeployEventType, type DeployFailure, type DeployIdentity, type DeployPhase, type DeployPhaseContext, type DeployResult, type DeployRuntime, type DeploySecrets, type DeployTarget, type DeployTargetCapabilities, type DeployTargetEntry, type DeployTargetLogger, DeployTargetNotYetSupported, InvalidTargetOptions, ProviderRemoved, type ResourceChange, type ResourceVia, type StageSecrets, type StateStore, TargetEntryInvalid, type TargetEvent, type TargetOptions, type TargetOptionsIssue, type TargetOptionsSchema, TargetPackageInvalid, TargetPackageNotFound, TargetRuntimeMismatch, TargetRuntimeUndeclared, UnknownDeployTarget };
28
28
  //# sourceMappingURL=public.d.cts.map
@@ -1,5 +1,5 @@
1
- import { $ as DeployEventError, B as DeployFailure, G as DeployTargetCapabilities, H as DeployRuntime, J as TargetEvent, K as DeployTargetEntry, Q as DeployEvent, U as DeploySecrets, V as DeployPhaseContext, W as DeployTarget, X as TargetOptionsIssue, Y as TargetOptions, Z as TargetOptionsSchema, at as DeployResult, ct as CredentialKinds, et as DeployEventType, gt as StateStore, it as AppDeployResult, lt as CredentialProvider, nt as ResourceChange, ot as Credential, q as DeployTargetLogger, rt as ResourceVia, st as CredentialKind, tt as DeployPhase, ut as CredentialRequest, vt as StageSecrets, yt as DeployIdentity, z as AnyDeployTarget } from "../types-CfLKGfC_.mjs";
2
- import { a as TargetRuntimeMismatch, c as DeployTargetNotYetSupported, i as TargetPackageInvalid, l as UnknownDeployTarget, n as InvalidTargetOptions, o as ProviderRemoved, r as TargetEntryInvalid, s as TargetPackageNotFound, t as TargetRuntimeUndeclared } from "../runtime-CXPlkZzs.mjs";
1
+ import { $ as DeployEventError, B as DeployFailure, G as DeployTargetCapabilities, H as DeployRuntime, J as TargetEvent, K as DeployTargetEntry, Q as DeployEvent, U as DeploySecrets, V as DeployPhaseContext, W as DeployTarget, X as TargetOptionsIssue, Y as TargetOptions, Z as TargetOptionsSchema, _t as StateStore, at as DeployResult, bt as DeployIdentity, ct as CredentialKind, dt as CredentialRequest, et as DeployEventType, it as AppDeployResult, lt as CredentialKinds, nt as ResourceChange, ot as AwsCredential, q as DeployTargetLogger, rt as ResourceVia, st as Credential, tt as DeployPhase, ut as CredentialProvider, yt as StageSecrets, z as AnyDeployTarget } from "../types-B_bEQvb9.mjs";
2
+ import { a as TargetRuntimeMismatch, c as DeployTargetNotYetSupported, i as TargetPackageInvalid, l as UnknownDeployTarget, n as InvalidTargetOptions, o as ProviderRemoved, r as TargetEntryInvalid, s as TargetPackageNotFound, t as TargetRuntimeUndeclared } from "../runtime-CJEYK4Gz.mjs";
3
3
  //#region src/target/define.d.ts
4
4
  /**
5
5
  * A deploy target, typed from what it is written with: the options from its
@@ -24,5 +24,5 @@ export declare function defineTarget<Options = undefined, Run = void>(target: De
24
24
  /** Whether `value` has the shape of a target. */
25
25
  export declare function isDeployTarget(value: unknown): value is AnyDeployTarget;
26
26
  //#endregion
27
- export { type AnyDeployTarget, type AppDeployResult, type Credential, type CredentialKind, type CredentialKinds, type CredentialProvider, type CredentialRequest, type DeployEvent, type DeployEventError, type DeployEventType, type DeployFailure, type DeployIdentity, type DeployPhase, type DeployPhaseContext, type DeployResult, type DeployRuntime, type DeploySecrets, type DeployTarget, type DeployTargetCapabilities, type DeployTargetEntry, type DeployTargetLogger, DeployTargetNotYetSupported, InvalidTargetOptions, ProviderRemoved, type ResourceChange, type ResourceVia, type StageSecrets, type StateStore, TargetEntryInvalid, type TargetEvent, type TargetOptions, type TargetOptionsIssue, type TargetOptionsSchema, TargetPackageInvalid, TargetPackageNotFound, TargetRuntimeMismatch, TargetRuntimeUndeclared, UnknownDeployTarget };
27
+ export { type AnyDeployTarget, type AppDeployResult, type AwsCredential, type Credential, type CredentialKind, type CredentialKinds, type CredentialProvider, type CredentialRequest, type DeployEvent, type DeployEventError, type DeployEventType, type DeployFailure, type DeployIdentity, type DeployPhase, type DeployPhaseContext, type DeployResult, type DeployRuntime, type DeploySecrets, type DeployTarget, type DeployTargetCapabilities, type DeployTargetEntry, type DeployTargetLogger, DeployTargetNotYetSupported, InvalidTargetOptions, ProviderRemoved, type ResourceChange, type ResourceVia, type StageSecrets, type StateStore, TargetEntryInvalid, type TargetEvent, type TargetOptions, type TargetOptionsIssue, type TargetOptionsSchema, TargetPackageInvalid, TargetPackageNotFound, TargetRuntimeMismatch, TargetRuntimeUndeclared, UnknownDeployTarget };
28
28
  //# sourceMappingURL=public.d.mts.map
@@ -1,4 +1,4 @@
1
- import { a as UnknownDeployTarget, i as DeployTargetNotYetSupported, n as isDeployTarget, t as defineTarget } from "../define-CHBIEc5j.mjs";
2
- import { i as TargetPackageNotFound, t as TargetRuntimeUndeclared } from "../runtime-DbakJS8u.mjs";
3
- import { i as TargetRuntimeMismatch, n as TargetEntryInvalid, r as TargetPackageInvalid, s as ProviderRemoved, t as InvalidTargetOptions } from "../resolve-B1T0hUaR.mjs";
1
+ import { a as UnknownDeployTarget, i as DeployTargetNotYetSupported, n as isDeployTarget, t as defineTarget } from "../define-CYs0PEfZ.mjs";
2
+ import { i as TargetPackageNotFound, t as TargetRuntimeUndeclared } from "../runtime-Cmn-FeYO.mjs";
3
+ import { i as TargetRuntimeMismatch, n as TargetEntryInvalid, r as TargetPackageInvalid, s as ProviderRemoved, t as InvalidTargetOptions } from "../resolve-DZ2IJ0qY.mjs";
4
4
  export { DeployTargetNotYetSupported, InvalidTargetOptions, ProviderRemoved, TargetEntryInvalid, TargetPackageInvalid, TargetPackageNotFound, TargetRuntimeMismatch, TargetRuntimeUndeclared, UnknownDeployTarget, defineTarget, isDeployTarget };
@@ -1,90 +1,12 @@
1
- const require_sniffer = require("./sniffer-D_SYc_pO.cjs");
2
- const require_discover = require("./discover-C_6COHEv.cjs");
3
- const require_config = require("./config-BEJYKd4s.cjs");
4
- const require_define = require("./define-Cv_QvDrg.cjs");
5
- const require_workspace = require("./workspace-Kl4NG9IH.cjs");
6
- const require_store = require("./store-KvNQwYaB.cjs");
7
- const require_resolve = require("./resolve-xytBTtG4.cjs");
8
- //#region src/deploy/credentials.ts
9
- /**
10
- * How a deploy gets the credentials it acts with.
11
- *
12
- * `deploy()` never prompts and never reads a terminal: it asks a
13
- * `CredentialProvider` and, when the provider has nothing, stops with
14
- * `MissingCredential` naming what it needed and how to supply it. Whether to
15
- * ask a person, read a vault or fail is the caller's decision — `gkm deploy`
16
- * prompts at a terminal and stores what was typed; a CI job sets the
17
- * environment; a host passes its own provider.
18
- */
19
- /**
20
- * How to supply each kind the CLI knows, for the error that says it is
21
- * missing. A target's own kinds are its to explain.
22
- */
23
- const HOW_TO_PROVIDE = {
24
- dokploy: "Set DOKPLOY_API_TOKEN (and DOKPLOY_ENDPOINT, or deploy.dokploy.endpoint in gkm.config.ts), run `gkm login --provider dokploy`, or pass them through the deploy's CredentialProvider.",
25
- registry: "Add the registry in Dokploy (Settings → Docker Registry) and set deploy.dokploy.registryId, set DOCKER_REGISTRY_USERNAME and DOCKER_REGISTRY_PASSWORD, or run `gkm deploy` at a terminal to be asked for them."
26
- };
27
- /** A deploy needed a credential, and its provider had none. */
28
- var MissingCredential = class extends Error {
29
- kind;
30
- target;
31
- howToProvide;
32
- constructor(kind, target, howToProvide = HOW_TO_PROVIDE[kind] ?? "Pass it through the deploy's CredentialProvider.") {
33
- const what = kind === "dokploy" ? `No Dokploy credentials${target ? ` for ${target}` : ""}.` : kind === "registry" ? `Dokploy has no registry for ${target ?? "the configured registry"}, and there are no credentials to create one with.` : `No "${String(kind)}" credentials${target ? ` for ${target}` : ""}.`;
34
- super(`${what} ${howToProvide}`);
35
- this.kind = kind;
36
- this.target = target;
37
- this.howToProvide = howToProvide;
38
- this.name = "MissingCredential";
39
- }
40
- };
41
- /** Whatever was asked for, from the first provider that has it. */
42
- function chainCredentials(...providers) {
43
- return { async get(request, options) {
44
- for (const provider of providers) {
45
- const found = await provider.get(request, options);
46
- if (found) return found;
47
- }
48
- } };
49
- }
50
- /**
51
- * Credentials from the environment, then from what `gkm login` stored. What
52
- * `deploy()` uses when it is handed no provider.
53
- *
54
- * - Dokploy: `DOKPLOY_API_TOKEN` / `DOKPLOY_ENDPOINT`, each falling back to
55
- * the stored login, and the endpoint finally to `deploy.dokploy.endpoint`.
56
- * Each half is read on its own: a token is a secret and belongs in the
57
- * environment, an endpoint is configuration and belongs in the config.
58
- * - Registry: `DOCKER_REGISTRY_USERNAME` / `DOCKER_REGISTRY_PASSWORD`.
59
- */
60
- function storedCredentials(options = {}) {
61
- const env = options.env ?? process.env;
62
- return { async get(asked) {
63
- const request = asked;
64
- switch (request.kind) {
65
- case "dokploy": {
66
- const stored = (await require_sniffer.readCredentials(options.home ? { home: options.home } : void 0)).dokploy;
67
- const token = env.DOKPLOY_API_TOKEN ?? stored?.token;
68
- const endpoint = env.DOKPLOY_ENDPOINT ?? stored?.endpoint ?? request.endpoint;
69
- if (!token || !endpoint) return void 0;
70
- return {
71
- endpoint: endpoint.replace(/\/$/, ""),
72
- token
73
- };
74
- }
75
- case "registry": {
76
- const username = env.DOCKER_REGISTRY_USERNAME;
77
- const password = env.DOCKER_REGISTRY_PASSWORD;
78
- if (!username || !password) return void 0;
79
- return {
80
- username,
81
- password
82
- };
83
- }
84
- }
85
- } };
86
- }
87
- //#endregion
1
+ const require_sniffer = require("./sniffer-ByhbEW5A.cjs");
2
+ const require_local = require("./local-C7wnrcYK.cjs");
3
+ const require_discover = require("./discover-DHdECueV.cjs");
4
+ const require_derive = require("./derive-DAbo49sm.cjs");
5
+ const require_define = require("./define-zUsP72ym.cjs");
6
+ const require_workspace = require("./workspace-BwgWJK7h.cjs");
7
+ const require_store = require("./store-D_2ByRtW.cjs");
8
+ const require_resolve = require("./resolve-BXsNQjMR.cjs");
9
+ const require_credentials = require("./credentials-B70vmLcm.cjs");
88
10
  //#region src/target/secrets.ts
89
11
  /**
90
12
  * A stage's secrets as a target sees them: read and written through the
@@ -210,7 +132,7 @@ function generateTag(stage) {
210
132
  /** Why an app that deploys through another target is left out. */
211
133
  function elsewhere(target, stage) {
212
134
  const instead = require_define.builtinTarget(target)?.instead?.(stage);
213
- if (target === "sst" && instead) return `it deploys with SST — ${instead}`;
135
+ if (instead) return `it deploys through "${target}" — ${instead}`;
214
136
  return `it deploys through "${target}" — run \`gkm deploy --target ${target} --stage ${stage}\``;
215
137
  }
216
138
  /**
@@ -356,7 +278,7 @@ async function prepare(source, request, ctx, emit, redactor) {
356
278
  patterns: require_sniffer.constructGlobs(configured),
357
279
  cwd: configured.root
358
280
  });
359
- const units = require_config.derivedApps(manifest, configured);
281
+ const units = require_derive.derivedApps(manifest, configured);
360
282
  const workspace = Object.keys(units).length > 0 ? {
361
283
  ...configured,
362
284
  apps: units
@@ -487,13 +409,14 @@ function asking() {
487
409
  });
488
410
  logger.log("\nValidating credentials...");
489
411
  if (!await require_sniffer.validateDokployToken(endpoint, token)) throw new DokployTokenRejected(endpoint);
490
- await require_sniffer.storeDokployCredentials(token, endpoint);
412
+ await require_local.storeDokployCredentials(token, endpoint);
491
413
  logger.log("✓ Credentials saved");
492
414
  return {
493
415
  endpoint,
494
416
  token
495
417
  };
496
418
  }
419
+ if (request.kind !== "registry") return void 0;
497
420
  logger.log(` Dokploy has no registry for ${request.url}. Let's create one.`);
498
421
  return {
499
422
  username: await require_sniffer.prompt("Registry username: ", { instead: INSTEAD }),
@@ -508,16 +431,10 @@ function asking() {
508
431
  * The environment, then the stored login, then — at a terminal — the person
509
432
  * running the command.
510
433
  */
511
- function terminalCredentials(base = storedCredentials()) {
512
- return chainCredentials(base, asking());
434
+ function terminalCredentials(base = require_credentials.storedCredentials()) {
435
+ return require_credentials.chainCredentials(base, asking());
513
436
  }
514
437
  //#endregion
515
- Object.defineProperty(exports, "MissingCredential", {
516
- enumerable: true,
517
- get: function() {
518
- return MissingCredential;
519
- }
520
- });
521
438
  Object.defineProperty(exports, "NoDeployableApps", {
522
439
  enumerable: true,
523
440
  get: function() {
@@ -542,12 +459,6 @@ Object.defineProperty(exports, "UnknownDeployApps", {
542
459
  return UnknownDeployApps;
543
460
  }
544
461
  });
545
- Object.defineProperty(exports, "chainCredentials", {
546
- enumerable: true,
547
- get: function() {
548
- return chainCredentials;
549
- }
550
- });
551
462
  Object.defineProperty(exports, "eventError", {
552
463
  enumerable: true,
553
464
  get: function() {
@@ -560,12 +471,6 @@ Object.defineProperty(exports, "runDeploy", {
560
471
  return runDeploy;
561
472
  }
562
473
  });
563
- Object.defineProperty(exports, "storedCredentials", {
564
- enumerable: true,
565
- get: function() {
566
- return storedCredentials;
567
- }
568
- });
569
474
  Object.defineProperty(exports, "terminalCredentials", {
570
475
  enumerable: true,
571
476
  get: function() {
@@ -573,4 +478,4 @@ Object.defineProperty(exports, "terminalCredentials", {
573
478
  }
574
479
  });
575
480
 
576
- //# sourceMappingURL=terminal-D0f1u0LG.cjs.map
481
+ //# sourceMappingURL=terminal-Bdv--jhY.cjs.map