@geekmidas/cli 10.0.0-alpha.37 → 10.0.0-alpha.39

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 (114) hide show
  1. package/README.md +27 -26
  2. package/dist/{HostingerProvider-GF6NAZAK.mjs → HostingerProvider-Bh6K7QYz.mjs} +2 -2
  3. package/dist/{HostingerProvider-GF6NAZAK.mjs.map → HostingerProvider-Bh6K7QYz.mjs.map} +1 -1
  4. package/dist/{OpenApiTsGenerator-D1nhN98m.cjs → OpenApiTsGenerator-CeEI4ll9.cjs} +3 -3
  5. package/dist/OpenApiTsGenerator-CeEI4ll9.cjs.map +1 -0
  6. package/dist/{OpenApiTsGenerator-hDINScnz.mjs → OpenApiTsGenerator-DQqOuSW8.mjs} +3 -3
  7. package/dist/OpenApiTsGenerator-DQqOuSW8.mjs.map +1 -0
  8. package/dist/{ssm-FxeSAt35.cjs → aws-DkHdTV-p.cjs} +7 -6
  9. package/dist/aws-DkHdTV-p.cjs.map +1 -0
  10. package/dist/{ssm-BKN1Cg4z.mjs → aws-DojzQnLd.mjs} +7 -6
  11. package/dist/aws-DojzQnLd.mjs.map +1 -0
  12. package/dist/{bundler-D9IPNvDt.mjs → bundler--fyybf1N.mjs} +13 -7
  13. package/dist/bundler--fyybf1N.mjs.map +1 -0
  14. package/dist/{bundler-CYF5mWi9.cjs → bundler-By6_sXiL.cjs} +12 -6
  15. package/dist/bundler-By6_sXiL.cjs.map +1 -0
  16. package/dist/{config-Dss1WwtH.mjs → config-5watB1D0.mjs} +95 -71
  17. package/dist/config-5watB1D0.mjs.map +1 -0
  18. package/dist/{config-DgAHy8nY.cjs → config-CJ-PAb7M.cjs} +116 -74
  19. package/dist/config-CJ-PAb7M.cjs.map +1 -0
  20. package/dist/config.cjs +6 -3
  21. package/dist/config.d.cts +40 -19
  22. package/dist/config.d.cts.map +1 -1
  23. package/dist/config.d.mts +40 -19
  24. package/dist/config.d.mts.map +1 -1
  25. package/dist/config.mjs +3 -3
  26. package/dist/{discover-CroTIm0K.cjs → discover-B0f_kuNN.cjs} +2 -1
  27. package/dist/{discover-CroTIm0K.cjs.map → discover-B0f_kuNN.cjs.map} +1 -1
  28. package/dist/{discover-CMxesskG.mjs → discover-BLhf7V_J.mjs} +2 -1
  29. package/dist/{discover-CMxesskG.mjs.map → discover-BLhf7V_J.mjs.map} +1 -1
  30. package/dist/{github-tZmMZ7td.cjs → github-BAr8WGkR.cjs} +10 -13
  31. package/dist/github-BAr8WGkR.cjs.map +1 -0
  32. package/dist/{github-C99BMxf0.mjs → github-D5MthUlR.mjs} +8 -11
  33. package/dist/github-D5MthUlR.mjs.map +1 -0
  34. package/dist/{index-CAp22ul5.d.cts → index-CLH1DgsO.d.cts} +2 -2
  35. package/dist/{index-CAp22ul5.d.cts.map → index-CLH1DgsO.d.cts.map} +1 -1
  36. package/dist/{index-DxXMtQNT.d.mts → index-CST2jOPH.d.mts} +2 -2
  37. package/dist/{index-DxXMtQNT.d.mts.map → index-CST2jOPH.d.mts.map} +1 -1
  38. package/dist/index.cjs +1015 -353
  39. package/dist/index.cjs.map +1 -1
  40. package/dist/index.mjs +1003 -341
  41. package/dist/index.mjs.map +1 -1
  42. package/dist/{migrate-_2J3lFT5.mjs → migrate-BJR912RE.mjs} +9 -7
  43. package/dist/migrate-BJR912RE.mjs.map +1 -0
  44. package/dist/{migrate-Cgga-OGi.cjs → migrate-Bu8POqsR.cjs} +8 -6
  45. package/dist/migrate-Bu8POqsR.cjs.map +1 -0
  46. package/dist/openapi.cjs +2 -2
  47. package/dist/openapi.d.cts +1 -1
  48. package/dist/openapi.d.mts +1 -1
  49. package/dist/openapi.mjs +2 -2
  50. package/dist/{ready-CXhoutix.cjs → ready-B0WVtkjR.cjs} +96 -6
  51. package/dist/ready-B0WVtkjR.cjs.map +1 -0
  52. package/dist/{ready-D1BCiaYU.mjs → ready-BXmLS4VB.mjs} +92 -8
  53. package/dist/ready-BXmLS4VB.mjs.map +1 -0
  54. package/dist/reconcile/public.cjs +1 -1
  55. package/dist/reconcile/public.d.cts +1 -1
  56. package/dist/reconcile/public.d.mts +1 -1
  57. package/dist/reconcile/public.mjs +1 -1
  58. package/dist/reconcile--I7V4Klj.mjs +34 -0
  59. package/dist/reconcile--I7V4Klj.mjs.map +1 -0
  60. package/dist/reconcile-CuCQBa5-.cjs +34 -0
  61. package/dist/reconcile-CuCQBa5-.cjs.map +1 -0
  62. package/dist/storage-BzaUxmqe.mjs +124 -0
  63. package/dist/storage-BzaUxmqe.mjs.map +1 -0
  64. package/dist/storage-CSGoC3bK.cjs +152 -0
  65. package/dist/storage-CSGoC3bK.cjs.map +1 -0
  66. package/dist/store-DQgxT_WR.cjs +211 -0
  67. package/dist/store-DQgxT_WR.cjs.map +1 -0
  68. package/dist/store-DeGATCS-.mjs +182 -0
  69. package/dist/store-DeGATCS-.mjs.map +1 -0
  70. package/dist/{types-BTiu5kwo.d.mts → types-B0EVAF6U.d.mts} +24 -10
  71. package/dist/types-B0EVAF6U.d.mts.map +1 -0
  72. package/dist/{types-Bj_FpeTc.d.cts → types-CHKCUKM0.d.cts} +24 -10
  73. package/dist/types-CHKCUKM0.d.cts.map +1 -0
  74. package/dist/vitest.cjs +1 -1
  75. package/dist/vitest.mjs +1 -1
  76. package/dist/workspace/index.cjs +1 -1
  77. package/dist/workspace/index.d.cts +2 -2
  78. package/dist/workspace/index.d.mts +2 -2
  79. package/dist/workspace/index.mjs +1 -1
  80. package/dist/{workspace-BtXCL1C8.mjs → workspace-CrFbItPz.mjs} +111 -39
  81. package/dist/workspace-CrFbItPz.mjs.map +1 -0
  82. package/dist/{workspace-CsL76tpc.mjs → workspace-DOmIhiau.mjs} +2 -2
  83. package/dist/{workspace-CsL76tpc.mjs.map → workspace-DOmIhiau.mjs.map} +1 -1
  84. package/dist/{workspace-DUhLejv4.cjs → workspace-DZ1G4zow.cjs} +121 -37
  85. package/dist/workspace-DZ1G4zow.cjs.map +1 -0
  86. package/dist/{workspace-CAJk6UmC.cjs → workspace-RYSYtHP7.cjs} +2 -2
  87. package/dist/{workspace-CAJk6UmC.cjs.map → workspace-RYSYtHP7.cjs.map} +1 -1
  88. package/package.json +14 -14
  89. package/dist/OpenApiTsGenerator-D1nhN98m.cjs.map +0 -1
  90. package/dist/OpenApiTsGenerator-hDINScnz.mjs.map +0 -1
  91. package/dist/bundler-CYF5mWi9.cjs.map +0 -1
  92. package/dist/bundler-D9IPNvDt.mjs.map +0 -1
  93. package/dist/config-DgAHy8nY.cjs.map +0 -1
  94. package/dist/config-Dss1WwtH.mjs.map +0 -1
  95. package/dist/github-C99BMxf0.mjs.map +0 -1
  96. package/dist/github-tZmMZ7td.cjs.map +0 -1
  97. package/dist/migrate-Cgga-OGi.cjs.map +0 -1
  98. package/dist/migrate-_2J3lFT5.mjs.map +0 -1
  99. package/dist/ready-CXhoutix.cjs.map +0 -1
  100. package/dist/ready-D1BCiaYU.mjs.map +0 -1
  101. package/dist/reconcile-BxZTV2vl.cjs +0 -332
  102. package/dist/reconcile-BxZTV2vl.cjs.map +0 -1
  103. package/dist/reconcile-yNq6Shau.mjs +0 -279
  104. package/dist/reconcile-yNq6Shau.mjs.map +0 -1
  105. package/dist/ssm-BKN1Cg4z.mjs.map +0 -1
  106. package/dist/ssm-FxeSAt35.cjs.map +0 -1
  107. package/dist/storage-BjTLjDDp.mjs +0 -297
  108. package/dist/storage-BjTLjDDp.mjs.map +0 -1
  109. package/dist/storage-C2JC31V9.cjs +0 -344
  110. package/dist/storage-C2JC31V9.cjs.map +0 -1
  111. package/dist/types-BTiu5kwo.d.mts.map +0 -1
  112. package/dist/types-Bj_FpeTc.d.cts.map +0 -1
  113. package/dist/workspace-BtXCL1C8.mjs.map +0 -1
  114. package/dist/workspace-DUhLejv4.cjs.map +0 -1
@@ -0,0 +1,152 @@
1
+ //#region src/secrets/storage.ts
2
+ var storage_exports = /* @__PURE__ */ require("./rolldown-runtime-C0BPl7ul.cjs").__exportAll({
3
+ initStageSecrets: () => initStageSecrets,
4
+ maskPassword: () => maskPassword,
5
+ toEmbeddableSecrets: () => toEmbeddableSecrets,
6
+ validateEnvironmentVariables: () => validateEnvironmentVariables,
7
+ withCustomSecret: () => withCustomSecret
8
+ });
9
+ /**
10
+ * Initialize an empty StageSecrets object for a stage.
11
+ */
12
+ function initStageSecrets(stage) {
13
+ const now = (/* @__PURE__ */ new Date()).toISOString();
14
+ return {
15
+ stage,
16
+ createdAt: now,
17
+ updatedAt: now,
18
+ services: {},
19
+ urls: {},
20
+ custom: {}
21
+ };
22
+ }
23
+ /**
24
+ * Convert StageSecrets to embeddable format (flat key-value pairs).
25
+ * This is what gets encrypted and embedded in the bundle.
26
+ */
27
+ function toEmbeddableSecrets(secrets) {
28
+ return {
29
+ ...secrets.urls,
30
+ ...secrets.custom,
31
+ ...secrets.services.postgres && {
32
+ POSTGRES_USER: secrets.services.postgres.username,
33
+ POSTGRES_PASSWORD: secrets.services.postgres.password,
34
+ POSTGRES_DB: secrets.services.postgres.database ?? "app",
35
+ POSTGRES_HOST: secrets.services.postgres.host,
36
+ POSTGRES_PORT: String(secrets.services.postgres.port)
37
+ },
38
+ ...secrets.services.redis && {
39
+ REDIS_PASSWORD: secrets.services.redis.password,
40
+ REDIS_HOST: secrets.services.redis.host,
41
+ REDIS_PORT: String(secrets.services.redis.port)
42
+ },
43
+ ...secrets.services.minio && {
44
+ STORAGE_ACCESS_KEY_ID: secrets.services.minio.username,
45
+ STORAGE_SECRET_ACCESS_KEY: secrets.services.minio.password,
46
+ STORAGE_BUCKET: secrets.services.minio.bucket ?? "app",
47
+ STORAGE_REGION: "eu-west-1",
48
+ STORAGE_FORCE_PATH_STYLE: "true"
49
+ },
50
+ ...secrets.services.mailpit && {
51
+ SMTP_HOST: secrets.services.mailpit.host,
52
+ SMTP_PORT: String(secrets.services.mailpit.port),
53
+ SMTP_USER: secrets.services.mailpit.username,
54
+ SMTP_PASS: secrets.services.mailpit.password,
55
+ SMTP_SECURE: "false",
56
+ MAIL_FROM: "noreply@localhost"
57
+ },
58
+ ...secrets.services.localstack && {
59
+ AWS_ACCESS_KEY_ID: secrets.services.localstack.accessKeyId ?? secrets.services.localstack.username,
60
+ AWS_SECRET_ACCESS_KEY: secrets.services.localstack.password,
61
+ AWS_REGION: secrets.services.localstack.region ?? "us-east-1",
62
+ AWS_ENDPOINT_URL: `http://${secrets.services.localstack.host}:${secrets.services.localstack.port}`
63
+ },
64
+ ...secrets.services.pgboss && {
65
+ PGBOSS_DB_USER: secrets.services.pgboss.username,
66
+ PGBOSS_DB_PASSWORD: secrets.services.pgboss.password
67
+ }
68
+ };
69
+ }
70
+ /** The same secrets with one custom value set, and `updatedAt` moved on. */
71
+ function withCustomSecret(secrets, key, value) {
72
+ return {
73
+ ...secrets,
74
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
75
+ custom: {
76
+ ...secrets.custom,
77
+ [key]: value
78
+ }
79
+ };
80
+ }
81
+ /**
82
+ * Mask a password for display (show first 4 and last 2 chars).
83
+ */
84
+ function maskPassword(password) {
85
+ if (password.length <= 8) return "********";
86
+ return `${password.slice(0, 4)}${"*".repeat(password.length - 6)}${password.slice(-2)}`;
87
+ }
88
+ /**
89
+ * Validate that all required environment variables are present in secrets.
90
+ *
91
+ * @param requiredVars - Array of environment variable names required by the application
92
+ * @param secrets - Stage secrets to validate against
93
+ * @returns Validation result with missing and provided variables
94
+ *
95
+ * @example
96
+ * ```typescript
97
+ * const required = ['DATABASE_URL', 'API_KEY', 'JWT_SECRET'];
98
+ * const secrets = await secretsStoreFor(workspace, 'production').then((s) => s.read('production'));
99
+ * const result = validateEnvironmentVariables(required, secrets);
100
+ *
101
+ * if (!result.valid) {
102
+ * console.error(`Missing environment variables: ${result.missing.join(', ')}`);
103
+ * }
104
+ * ```
105
+ */
106
+ function validateEnvironmentVariables(requiredVars, secrets) {
107
+ const embeddable = toEmbeddableSecrets(secrets);
108
+ const availableVars = new Set(Object.keys(embeddable));
109
+ const missing = [];
110
+ const provided = [];
111
+ for (const varName of requiredVars) if (availableVars.has(varName)) provided.push(varName);
112
+ else missing.push(varName);
113
+ return {
114
+ valid: missing.length === 0,
115
+ missing: missing.sort(),
116
+ provided: provided.sort(),
117
+ required: [...requiredVars].sort()
118
+ };
119
+ }
120
+ //#endregion
121
+ Object.defineProperty(exports, "initStageSecrets", {
122
+ enumerable: true,
123
+ get: function() {
124
+ return initStageSecrets;
125
+ }
126
+ });
127
+ Object.defineProperty(exports, "maskPassword", {
128
+ enumerable: true,
129
+ get: function() {
130
+ return maskPassword;
131
+ }
132
+ });
133
+ Object.defineProperty(exports, "storage_exports", {
134
+ enumerable: true,
135
+ get: function() {
136
+ return storage_exports;
137
+ }
138
+ });
139
+ Object.defineProperty(exports, "toEmbeddableSecrets", {
140
+ enumerable: true,
141
+ get: function() {
142
+ return toEmbeddableSecrets;
143
+ }
144
+ });
145
+ Object.defineProperty(exports, "withCustomSecret", {
146
+ enumerable: true,
147
+ get: function() {
148
+ return withCustomSecret;
149
+ }
150
+ });
151
+
152
+ //# sourceMappingURL=storage-CSGoC3bK.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage-CSGoC3bK.cjs","names":[],"sources":["../src/secrets/storage.ts"],"sourcesContent":["/**\n * What a stage's secrets are, independent of where they are kept: building\n * them, changing them, and handing them to a process. Where they are kept is a\n * {@link SecretsStore}'s business — see `secretsStoreFor`.\n */\n\nimport type { SecretsStore } from './store.js';\nimport type { EmbeddableSecrets, StageSecrets } from './types';\n\n/**\n * Initialize an empty StageSecrets object for a stage.\n */\nexport function initStageSecrets(stage: string): StageSecrets {\n\tconst now = new Date().toISOString();\n\treturn {\n\t\tstage,\n\t\tcreatedAt: now,\n\t\tupdatedAt: now,\n\t\tservices: {},\n\t\turls: {},\n\t\tcustom: {},\n\t};\n}\n\n/**\n * Convert StageSecrets to embeddable format (flat key-value pairs).\n * This is what gets encrypted and embedded in the bundle.\n */\nexport function toEmbeddableSecrets(secrets: StageSecrets): EmbeddableSecrets {\n\treturn {\n\t\t...secrets.urls,\n\t\t...secrets.custom,\n\t\t// Also include individual service credentials if needed\n\t\t...(secrets.services.postgres && {\n\t\t\tPOSTGRES_USER: secrets.services.postgres.username,\n\t\t\tPOSTGRES_PASSWORD: secrets.services.postgres.password,\n\t\t\tPOSTGRES_DB: secrets.services.postgres.database ?? 'app',\n\t\t\tPOSTGRES_HOST: secrets.services.postgres.host,\n\t\t\tPOSTGRES_PORT: String(secrets.services.postgres.port),\n\t\t}),\n\t\t...(secrets.services.redis && {\n\t\t\tREDIS_PASSWORD: secrets.services.redis.password,\n\t\t\tREDIS_HOST: secrets.services.redis.host,\n\t\t\tREDIS_PORT: String(secrets.services.redis.port),\n\t\t}),\n\t\t...(secrets.services.minio && {\n\t\t\tSTORAGE_ACCESS_KEY_ID: secrets.services.minio.username,\n\t\t\tSTORAGE_SECRET_ACCESS_KEY: secrets.services.minio.password,\n\t\t\tSTORAGE_BUCKET: secrets.services.minio.bucket ?? 'app',\n\t\t\tSTORAGE_REGION: 'eu-west-1',\n\t\t\tSTORAGE_FORCE_PATH_STYLE: 'true',\n\t\t}),\n\t\t...(secrets.services.mailpit && {\n\t\t\tSMTP_HOST: secrets.services.mailpit.host,\n\t\t\tSMTP_PORT: String(secrets.services.mailpit.port),\n\t\t\tSMTP_USER: secrets.services.mailpit.username,\n\t\t\tSMTP_PASS: secrets.services.mailpit.password,\n\t\t\tSMTP_SECURE: 'false',\n\t\t\tMAIL_FROM: 'noreply@localhost',\n\t\t}),\n\t\t...(secrets.services.localstack && {\n\t\t\tAWS_ACCESS_KEY_ID:\n\t\t\t\tsecrets.services.localstack.accessKeyId ??\n\t\t\t\tsecrets.services.localstack.username,\n\t\t\tAWS_SECRET_ACCESS_KEY: secrets.services.localstack.password,\n\t\t\tAWS_REGION: secrets.services.localstack.region ?? 'us-east-1',\n\t\t\tAWS_ENDPOINT_URL: `http://${secrets.services.localstack.host}:${secrets.services.localstack.port}`,\n\t\t}),\n\t\t...(secrets.services.pgboss && {\n\t\t\tPGBOSS_DB_USER: secrets.services.pgboss.username,\n\t\t\tPGBOSS_DB_PASSWORD: secrets.services.pgboss.password,\n\t\t}),\n\t};\n}\n\n/** The same secrets with one custom value set, and `updatedAt` moved on. */\nexport function withCustomSecret(\n\tsecrets: StageSecrets,\n\tkey: string,\n\tvalue: string,\n): StageSecrets {\n\treturn {\n\t\t...secrets,\n\t\tupdatedAt: new Date().toISOString(),\n\t\tcustom: { ...secrets.custom, [key]: value },\n\t};\n}\n\n/**\n * Mask a password for display (show first 4 and last 2 chars).\n */\nexport function maskPassword(password: string): string {\n\tif (password.length <= 8) {\n\t\treturn '********';\n\t}\n\treturn `${password.slice(0, 4)}${'*'.repeat(password.length - 6)}${password.slice(-2)}`;\n}\n\n/**\n * Result of environment variable validation.\n */\nexport interface EnvValidationResult {\n\t/** Whether all required environment variables are present */\n\tvalid: boolean;\n\t/** List of missing environment variable names */\n\tmissing: string[];\n\t/** List of environment variables that are provided */\n\tprovided: string[];\n\t/** List of environment variables that were required */\n\trequired: string[];\n}\n\n/**\n * Validate that all required environment variables are present in secrets.\n *\n * @param requiredVars - Array of environment variable names required by the application\n * @param secrets - Stage secrets to validate against\n * @returns Validation result with missing and provided variables\n *\n * @example\n * ```typescript\n * const required = ['DATABASE_URL', 'API_KEY', 'JWT_SECRET'];\n * const secrets = await secretsStoreFor(workspace, 'production').then((s) => s.read('production'));\n * const result = validateEnvironmentVariables(required, secrets);\n *\n * if (!result.valid) {\n * console.error(`Missing environment variables: ${result.missing.join(', ')}`);\n * }\n * ```\n */\nexport function validateEnvironmentVariables(\n\trequiredVars: string[],\n\tsecrets: StageSecrets,\n): EnvValidationResult {\n\tconst embeddable = toEmbeddableSecrets(secrets);\n\tconst availableVars = new Set(Object.keys(embeddable));\n\n\tconst missing: string[] = [];\n\tconst provided: string[] = [];\n\n\tfor (const varName of requiredVars) {\n\t\tif (availableVars.has(varName)) {\n\t\t\tprovided.push(varName);\n\t\t} else {\n\t\t\tmissing.push(varName);\n\t\t}\n\t}\n\n\treturn {\n\t\tvalid: missing.length === 0,\n\t\tmissing: missing.sort(),\n\t\tprovided: provided.sort(),\n\t\trequired: [...requiredVars].sort(),\n\t};\n}\n"],"mappings":";;;;;;;;;;;AAYA,SAAgB,iBAAiB,OAA6B;CAC7D,MAAM,uBAAM,IAAI,KAAK,EAAA,CAAE,YAAY;CACnC,OAAO;EACN;EACA,WAAW;EACX,WAAW;EACX,UAAU,CAAC;EACX,MAAM,CAAC;EACP,QAAQ,CAAC;CACV;AACD;;;;;AAMA,SAAgB,oBAAoB,SAA0C;CAC7E,OAAO;EACN,GAAG,QAAQ;EACX,GAAG,QAAQ;EAEX,GAAI,QAAQ,SAAS,YAAY;GAChC,eAAe,QAAQ,SAAS,SAAS;GACzC,mBAAmB,QAAQ,SAAS,SAAS;GAC7C,aAAa,QAAQ,SAAS,SAAS,YAAY;GACnD,eAAe,QAAQ,SAAS,SAAS;GACzC,eAAe,OAAO,QAAQ,SAAS,SAAS,IAAI;EACrD;EACA,GAAI,QAAQ,SAAS,SAAS;GAC7B,gBAAgB,QAAQ,SAAS,MAAM;GACvC,YAAY,QAAQ,SAAS,MAAM;GACnC,YAAY,OAAO,QAAQ,SAAS,MAAM,IAAI;EAC/C;EACA,GAAI,QAAQ,SAAS,SAAS;GAC7B,uBAAuB,QAAQ,SAAS,MAAM;GAC9C,2BAA2B,QAAQ,SAAS,MAAM;GAClD,gBAAgB,QAAQ,SAAS,MAAM,UAAU;GACjD,gBAAgB;GAChB,0BAA0B;EAC3B;EACA,GAAI,QAAQ,SAAS,WAAW;GAC/B,WAAW,QAAQ,SAAS,QAAQ;GACpC,WAAW,OAAO,QAAQ,SAAS,QAAQ,IAAI;GAC/C,WAAW,QAAQ,SAAS,QAAQ;GACpC,WAAW,QAAQ,SAAS,QAAQ;GACpC,aAAa;GACb,WAAW;EACZ;EACA,GAAI,QAAQ,SAAS,cAAc;GAClC,mBACC,QAAQ,SAAS,WAAW,eAC5B,QAAQ,SAAS,WAAW;GAC7B,uBAAuB,QAAQ,SAAS,WAAW;GACnD,YAAY,QAAQ,SAAS,WAAW,UAAU;GAClD,kBAAkB,UAAU,QAAQ,SAAS,WAAW,KAAK,GAAG,QAAQ,SAAS,WAAW;EAC7F;EACA,GAAI,QAAQ,SAAS,UAAU;GAC9B,gBAAgB,QAAQ,SAAS,OAAO;GACxC,oBAAoB,QAAQ,SAAS,OAAO;EAC7C;CACD;AACD;;AAGA,SAAgB,iBACf,SACA,KACA,OACe;CACf,OAAO;EACN,GAAG;EACH,4BAAW,IAAI,KAAK,EAAA,CAAE,YAAY;EAClC,QAAQ;GAAE,GAAG,QAAQ;IAAS,MAAM;EAAM;CAC3C;AACD;;;;AAKA,SAAgB,aAAa,UAA0B;CACtD,IAAI,SAAS,UAAU,GACtB,OAAO;CAER,OAAO,GAAG,SAAS,MAAM,GAAG,CAAC,IAAI,IAAI,OAAO,SAAS,SAAS,CAAC,IAAI,SAAS,MAAM,EAAE;AACrF;;;;;;;;;;;;;;;;;;;AAkCA,SAAgB,6BACf,cACA,SACsB;CACtB,MAAM,aAAa,oBAAoB,OAAO;CAC9C,MAAM,gBAAgB,IAAI,IAAI,OAAO,KAAK,UAAU,CAAC;CAErD,MAAM,UAAoB,CAAC;CAC3B,MAAM,WAAqB,CAAC;CAE5B,KAAK,MAAM,WAAW,cACrB,IAAI,cAAc,IAAI,OAAO,GAC5B,SAAS,KAAK,OAAO;MAErB,QAAQ,KAAK,OAAO;CAItB,OAAO;EACN,OAAO,QAAQ,WAAW;EAC1B,SAAS,QAAQ,KAAK;EACtB,UAAU,SAAS,KAAK;EACxB,UAAU,CAAC,GAAG,YAAY,CAAC,CAAC,KAAK;CAClC;AACD"}
@@ -0,0 +1,211 @@
1
+ const require_rolldown_runtime = require("./rolldown-runtime-C0BPl7ul.cjs");
2
+ let node_fs = require("node:fs");
3
+ let node_fs_promises = require("node:fs/promises");
4
+ let node_os = require("node:os");
5
+ let node_path = require("node:path");
6
+ let node_crypto = require("node:crypto");
7
+ //#region src/secrets/keystore.ts
8
+ /** Key length for AES-256 encryption */
9
+ const KEY_LENGTH = 32;
10
+ /**
11
+ * Get the keystore directory for a project.
12
+ * Keys are stored at ~/.gkm/{project-name}/
13
+ *
14
+ * @param projectName - Name of the project (defaults to current directory name)
15
+ * @returns Path to the keystore directory
16
+ */
17
+ function getKeystoreDir(projectName) {
18
+ const name = projectName ?? (0, node_path.basename)(process.cwd());
19
+ return (0, node_path.join)((0, node_os.homedir)(), ".gkm", name);
20
+ }
21
+ /**
22
+ * Get the path to a stage's encryption key.
23
+ *
24
+ * @param stage - Stage name (e.g., 'development', 'production')
25
+ * @param projectName - Name of the project (defaults to current directory name)
26
+ * @returns Path to the key file
27
+ */
28
+ function getKeyPath(stage, projectName) {
29
+ return (0, node_path.join)(getKeystoreDir(projectName), `${stage}.key`);
30
+ }
31
+ /**
32
+ * Generate a new encryption key for a stage.
33
+ * The key is stored at ~/.gkm/{project-name}/{stage}.key with restricted permissions.
34
+ *
35
+ * @param stage - Stage name
36
+ * @param projectName - Project name (defaults to current directory name)
37
+ * @returns The generated key as a hex string
38
+ */
39
+ async function generateKey(stage, projectName) {
40
+ const keyDir = getKeystoreDir(projectName);
41
+ const keyPath = getKeyPath(stage, projectName);
42
+ await (0, node_fs_promises.mkdir)(keyDir, {
43
+ recursive: true,
44
+ mode: 448
45
+ });
46
+ const key = (0, node_crypto.randomBytes)(KEY_LENGTH).toString("hex");
47
+ await (0, node_fs_promises.writeFile)(keyPath, key, {
48
+ mode: 384,
49
+ encoding: "utf-8"
50
+ });
51
+ await (0, node_fs_promises.chmod)(keyPath, 384);
52
+ return key;
53
+ }
54
+ /**
55
+ * Read an encryption key for a stage.
56
+ *
57
+ * @param stage - Stage name
58
+ * @param projectName - Project name (defaults to current directory name)
59
+ * @returns The key as a hex string, or null if not found
60
+ */
61
+ async function readKey(stage, projectName) {
62
+ const keyPath = getKeyPath(stage, projectName);
63
+ if (!(0, node_fs.existsSync)(keyPath)) return null;
64
+ return (await (0, node_fs_promises.readFile)(keyPath, "utf-8")).trim();
65
+ }
66
+ /**
67
+ * Get or create a key for a stage.
68
+ * If the key already exists, it is returned. Otherwise, a new key is generated.
69
+ *
70
+ * @param stage - Stage name
71
+ * @param projectName - Project name (defaults to current directory name)
72
+ * @returns The key as a hex string
73
+ */
74
+ async function getOrCreateKey(stage, projectName) {
75
+ const existingKey = await readKey(stage, projectName);
76
+ if (existingKey) return existingKey;
77
+ return generateKey(stage, projectName);
78
+ }
79
+ //#endregion
80
+ //#region src/secrets/file.ts
81
+ /** Where the files live, relative to the project root. */
82
+ const SECRETS_DIR = ".gkm/secrets";
83
+ /** AES-256-GCM configuration */
84
+ const ALGORITHM = "aes-256-gcm";
85
+ const IV_LENGTH = 12;
86
+ /**
87
+ * A stage's secrets as an encrypted file on this machine:
88
+ * `.gkm/secrets/<stage>.json`, with its key at `~/.gkm/<project>/<stage>.key`.
89
+ *
90
+ * The local stage's store, always — its secrets belong to the machine running
91
+ * `gkm dev` — and a deployed stage's when `secrets.store` is `'file'`.
92
+ */
93
+ var FileSecretsStore = class {
94
+ root;
95
+ name = "file";
96
+ constructor(root) {
97
+ this.root = root;
98
+ }
99
+ /** The file a stage's secrets are kept in. */
100
+ path(stage) {
101
+ return (0, node_path.join)(this.root, SECRETS_DIR, `${stage}.json`);
102
+ }
103
+ async read(stage) {
104
+ const path = this.path(stage);
105
+ if (!(0, node_fs.existsSync)(path)) return null;
106
+ const data = JSON.parse(await (0, node_fs_promises.readFile)(path, "utf-8"));
107
+ if (!(data.version === 1 && data.encrypted && data.iv)) return data;
108
+ const project = (0, node_path.basename)(this.root);
109
+ const key = await readKey(stage, project);
110
+ if (!key) throw new MissingSecretsKey(stage, project);
111
+ return decrypt(data, key);
112
+ }
113
+ async write(stage, secrets) {
114
+ await (0, node_fs_promises.mkdir)((0, node_path.join)(this.root, SECRETS_DIR), { recursive: true });
115
+ const key = await getOrCreateKey(stage, (0, node_path.basename)(this.root));
116
+ await (0, node_fs_promises.writeFile)(this.path(stage), JSON.stringify(encrypt(secrets, key), null, 2), "utf-8");
117
+ }
118
+ };
119
+ /** A stage's secrets file exists, and the key that opens it does not. */
120
+ var MissingSecretsKey = class extends Error {
121
+ stage;
122
+ project;
123
+ constructor(stage, project) {
124
+ super(`Decryption key not found for stage "${stage}". Expected key at: ~/.gkm/${project}/${stage}.key`);
125
+ this.stage = stage;
126
+ this.project = project;
127
+ this.name = "MissingSecretsKey";
128
+ }
129
+ };
130
+ function encrypt(secrets, keyHex) {
131
+ const key = Buffer.from(keyHex, "hex");
132
+ const iv = (0, node_crypto.randomBytes)(IV_LENGTH);
133
+ const cipher = (0, node_crypto.createCipheriv)(ALGORITHM, key, iv);
134
+ const ciphertext = Buffer.concat([cipher.update(JSON.stringify(secrets), "utf-8"), cipher.final()]);
135
+ return {
136
+ version: 1,
137
+ encrypted: Buffer.concat([ciphertext, cipher.getAuthTag()]).toString("base64"),
138
+ iv: iv.toString("hex")
139
+ };
140
+ }
141
+ function decrypt(data, keyHex) {
142
+ const key = Buffer.from(keyHex, "hex");
143
+ const combined = Buffer.from(data.encrypted, "base64");
144
+ const decipher = (0, node_crypto.createDecipheriv)(ALGORITHM, key, Buffer.from(data.iv, "hex"));
145
+ decipher.setAuthTag(combined.subarray(-16));
146
+ const plaintext = Buffer.concat([decipher.update(combined.subarray(0, -16)), decipher.final()]);
147
+ return JSON.parse(plaintext.toString("utf-8"));
148
+ }
149
+ //#endregion
150
+ //#region src/secrets/store.ts
151
+ var store_exports = /* @__PURE__ */ require_rolldown_runtime.__exportAll({
152
+ FileSecretsStore: () => FileSecretsStore,
153
+ isRemoteStore: () => isRemoteStore,
154
+ secretsStoreFor: () => secretsStoreFor
155
+ });
156
+ /**
157
+ * The store a stage's secrets are kept in — what every command reads and
158
+ * writes, for the stage it acts on.
159
+ *
160
+ * The local stage is always the file: its secrets belong to the machine
161
+ * running `gkm dev`. A deployed stage uses `secrets.store`.
162
+ */
163
+ async function secretsStoreFor(workspace, stage, options = {}) {
164
+ const configured = workspace.secrets.store ?? "file";
165
+ if (stage === workspace.stages.local || configured === "file") return new FileSecretsStore(workspace.root);
166
+ if (configured.provider !== "ssm") return configured.provider;
167
+ const { AwsSecretsStore } = await Promise.resolve().then(() => require("./aws-DkHdTV-p.cjs"));
168
+ return new AwsSecretsStore({
169
+ project: workspace.name,
170
+ region: configured.region,
171
+ ...options.profile ? { profile: options.profile } : {}
172
+ });
173
+ }
174
+ /** Whether a stage's secrets live somewhere other than this machine. */
175
+ function isRemoteStore(workspace, stage) {
176
+ const configured = workspace.secrets.store ?? "file";
177
+ return stage !== workspace.stages.local && configured !== "file";
178
+ }
179
+ //#endregion
180
+ Object.defineProperty(exports, "FileSecretsStore", {
181
+ enumerable: true,
182
+ get: function() {
183
+ return FileSecretsStore;
184
+ }
185
+ });
186
+ Object.defineProperty(exports, "getKeyPath", {
187
+ enumerable: true,
188
+ get: function() {
189
+ return getKeyPath;
190
+ }
191
+ });
192
+ Object.defineProperty(exports, "isRemoteStore", {
193
+ enumerable: true,
194
+ get: function() {
195
+ return isRemoteStore;
196
+ }
197
+ });
198
+ Object.defineProperty(exports, "secretsStoreFor", {
199
+ enumerable: true,
200
+ get: function() {
201
+ return secretsStoreFor;
202
+ }
203
+ });
204
+ Object.defineProperty(exports, "store_exports", {
205
+ enumerable: true,
206
+ get: function() {
207
+ return store_exports;
208
+ }
209
+ });
210
+
211
+ //# sourceMappingURL=store-DQgxT_WR.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-DQgxT_WR.cjs","names":["basename","join","homedir","mkdir","randomBytes","writeFile","chmod","existsSync","readFile","join","existsSync","readFile","basename","mkdir","writeFile","randomBytes","createCipheriv","createDecipheriv"],"sources":["../src/secrets/keystore.ts","../src/secrets/file.ts","../src/secrets/store.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport { existsSync } from 'node:fs';\nimport { chmod, mkdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { basename, join } from 'node:path';\n\n/** Key length for AES-256 encryption */\nconst KEY_LENGTH = 32; // 256 bits\n\n/**\n * Get the keystore directory for a project.\n * Keys are stored at ~/.gkm/{project-name}/\n *\n * @param projectName - Name of the project (defaults to current directory name)\n * @returns Path to the keystore directory\n */\nexport function getKeystoreDir(projectName?: string): string {\n\tconst name = projectName ?? basename(process.cwd());\n\treturn join(homedir(), '.gkm', name);\n}\n\n/**\n * Get the path to a stage's encryption key.\n *\n * @param stage - Stage name (e.g., 'development', 'production')\n * @param projectName - Name of the project (defaults to current directory name)\n * @returns Path to the key file\n */\nexport function getKeyPath(stage: string, projectName?: string): string {\n\treturn join(getKeystoreDir(projectName), `${stage}.key`);\n}\n\n/**\n * Check if a key exists for a stage.\n */\nexport function keyExists(stage: string, projectName?: string): boolean {\n\treturn existsSync(getKeyPath(stage, projectName));\n}\n\n/**\n * Generate a new encryption key for a stage.\n * The key is stored at ~/.gkm/{project-name}/{stage}.key with restricted permissions.\n *\n * @param stage - Stage name\n * @param projectName - Project name (defaults to current directory name)\n * @returns The generated key as a hex string\n */\nexport async function generateKey(\n\tstage: string,\n\tprojectName?: string,\n): Promise<string> {\n\tconst keyDir = getKeystoreDir(projectName);\n\tconst keyPath = getKeyPath(stage, projectName);\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' });\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 * Read an encryption key for a stage.\n *\n * @param stage - Stage name\n * @param projectName - Project name (defaults to current directory name)\n * @returns The key as a hex string, or null if not found\n */\nexport async function readKey(\n\tstage: string,\n\tprojectName?: string,\n): Promise<string | null> {\n\tconst keyPath = getKeyPath(stage, projectName);\n\n\tif (!existsSync(keyPath)) {\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 if not found.\n */\nexport async function requireKey(\n\tstage: string,\n\tprojectName?: string,\n): Promise<string> {\n\tconst key = await readKey(stage, projectName);\n\n\tif (!key) {\n\t\tconst name = projectName ?? basename(process.cwd());\n\t\tthrow new Error(\n\t\t\t`Encryption key not found for stage \"${stage}\" in project \"${name}\". ` +\n\t\t\t\t`Expected key at: ${getKeyPath(stage, projectName)}`,\n\t\t);\n\t}\n\n\treturn key;\n}\n\n/**\n * Delete a key for a stage.\n */\nexport async function deleteKey(\n\tstage: string,\n\tprojectName?: string,\n): Promise<void> {\n\tconst keyPath = getKeyPath(stage, projectName);\n\n\tif (existsSync(keyPath)) {\n\t\tawait rm(keyPath);\n\t}\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 *\n * @param stage - Stage name\n * @param projectName - Project name (defaults to current directory name)\n * @returns The key as a hex string\n */\nexport async function getOrCreateKey(\n\tstage: string,\n\tprojectName?: string,\n): Promise<string> {\n\tconst existingKey = await readKey(stage, projectName);\n\n\tif (existingKey) {\n\t\treturn existingKey;\n\t}\n\n\treturn generateKey(stage, projectName);\n}\n","import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';\nimport { existsSync } from 'node:fs';\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { basename, join } from 'node:path';\nimport { getOrCreateKey, readKey } 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 at `~/.gkm/<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\n\tconstructor(private readonly root: string) {}\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 project = basename(this.root);\n\t\tconst key = await readKey(stage, project);\n\t\tif (!key) throw new MissingSecretsKey(stage, project);\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, basename(this.root));\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) {\n\t\tsuper(\n\t\t\t`Decryption key not found for stage \"${stage}\". ` +\n\t\t\t\t`Expected key at: ~/.gkm/${project}/${stage}.key`,\n\t\t);\n\t\tthis.name = 'MissingSecretsKey';\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 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` beside its key in `~/.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}\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(workspace.root);\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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"}
@@ -0,0 +1,182 @@
1
+ import { t as __exportAll } from "./rolldown-runtime-BhDjJH2R.mjs";
2
+ import { existsSync } from "node:fs";
3
+ import { chmod, mkdir, readFile, writeFile } from "node:fs/promises";
4
+ import { homedir } from "node:os";
5
+ import { basename, join } from "node:path";
6
+ import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
7
+ //#region src/secrets/keystore.ts
8
+ /** Key length for AES-256 encryption */
9
+ const KEY_LENGTH = 32;
10
+ /**
11
+ * Get the keystore directory for a project.
12
+ * Keys are stored at ~/.gkm/{project-name}/
13
+ *
14
+ * @param projectName - Name of the project (defaults to current directory name)
15
+ * @returns Path to the keystore directory
16
+ */
17
+ function getKeystoreDir(projectName) {
18
+ const name = projectName ?? basename(process.cwd());
19
+ return join(homedir(), ".gkm", name);
20
+ }
21
+ /**
22
+ * Get the path to a stage's encryption key.
23
+ *
24
+ * @param stage - Stage name (e.g., 'development', 'production')
25
+ * @param projectName - Name of the project (defaults to current directory name)
26
+ * @returns Path to the key file
27
+ */
28
+ function getKeyPath(stage, projectName) {
29
+ return join(getKeystoreDir(projectName), `${stage}.key`);
30
+ }
31
+ /**
32
+ * Generate a new encryption key for a stage.
33
+ * The key is stored at ~/.gkm/{project-name}/{stage}.key with restricted permissions.
34
+ *
35
+ * @param stage - Stage name
36
+ * @param projectName - Project name (defaults to current directory name)
37
+ * @returns The generated key as a hex string
38
+ */
39
+ async function generateKey(stage, projectName) {
40
+ const keyDir = getKeystoreDir(projectName);
41
+ const keyPath = getKeyPath(stage, projectName);
42
+ await mkdir(keyDir, {
43
+ recursive: true,
44
+ mode: 448
45
+ });
46
+ const key = randomBytes(KEY_LENGTH).toString("hex");
47
+ await writeFile(keyPath, key, {
48
+ mode: 384,
49
+ encoding: "utf-8"
50
+ });
51
+ await chmod(keyPath, 384);
52
+ return key;
53
+ }
54
+ /**
55
+ * Read an encryption key for a stage.
56
+ *
57
+ * @param stage - Stage name
58
+ * @param projectName - Project name (defaults to current directory name)
59
+ * @returns The key as a hex string, or null if not found
60
+ */
61
+ async function readKey(stage, projectName) {
62
+ const keyPath = getKeyPath(stage, projectName);
63
+ if (!existsSync(keyPath)) return null;
64
+ return (await readFile(keyPath, "utf-8")).trim();
65
+ }
66
+ /**
67
+ * Get or create a key for a stage.
68
+ * If the key already exists, it is returned. Otherwise, a new key is generated.
69
+ *
70
+ * @param stage - Stage name
71
+ * @param projectName - Project name (defaults to current directory name)
72
+ * @returns The key as a hex string
73
+ */
74
+ async function getOrCreateKey(stage, projectName) {
75
+ const existingKey = await readKey(stage, projectName);
76
+ if (existingKey) return existingKey;
77
+ return generateKey(stage, projectName);
78
+ }
79
+ //#endregion
80
+ //#region src/secrets/file.ts
81
+ /** Where the files live, relative to the project root. */
82
+ const SECRETS_DIR = ".gkm/secrets";
83
+ /** AES-256-GCM configuration */
84
+ const ALGORITHM = "aes-256-gcm";
85
+ const IV_LENGTH = 12;
86
+ /**
87
+ * A stage's secrets as an encrypted file on this machine:
88
+ * `.gkm/secrets/<stage>.json`, with its key at `~/.gkm/<project>/<stage>.key`.
89
+ *
90
+ * The local stage's store, always — its secrets belong to the machine running
91
+ * `gkm dev` — and a deployed stage's when `secrets.store` is `'file'`.
92
+ */
93
+ var FileSecretsStore = class {
94
+ root;
95
+ name = "file";
96
+ constructor(root) {
97
+ this.root = root;
98
+ }
99
+ /** The file a stage's secrets are kept in. */
100
+ path(stage) {
101
+ return join(this.root, SECRETS_DIR, `${stage}.json`);
102
+ }
103
+ async read(stage) {
104
+ const path = this.path(stage);
105
+ if (!existsSync(path)) return null;
106
+ const data = JSON.parse(await readFile(path, "utf-8"));
107
+ if (!(data.version === 1 && data.encrypted && data.iv)) return data;
108
+ const project = basename(this.root);
109
+ const key = await readKey(stage, project);
110
+ if (!key) throw new MissingSecretsKey(stage, project);
111
+ return decrypt(data, key);
112
+ }
113
+ async write(stage, secrets) {
114
+ await mkdir(join(this.root, SECRETS_DIR), { recursive: true });
115
+ const key = await getOrCreateKey(stage, basename(this.root));
116
+ await writeFile(this.path(stage), JSON.stringify(encrypt(secrets, key), null, 2), "utf-8");
117
+ }
118
+ };
119
+ /** A stage's secrets file exists, and the key that opens it does not. */
120
+ var MissingSecretsKey = class extends Error {
121
+ stage;
122
+ project;
123
+ constructor(stage, project) {
124
+ super(`Decryption key not found for stage "${stage}". Expected key at: ~/.gkm/${project}/${stage}.key`);
125
+ this.stage = stage;
126
+ this.project = project;
127
+ this.name = "MissingSecretsKey";
128
+ }
129
+ };
130
+ function encrypt(secrets, keyHex) {
131
+ const key = Buffer.from(keyHex, "hex");
132
+ const iv = randomBytes(IV_LENGTH);
133
+ const cipher = createCipheriv(ALGORITHM, key, iv);
134
+ const ciphertext = Buffer.concat([cipher.update(JSON.stringify(secrets), "utf-8"), cipher.final()]);
135
+ return {
136
+ version: 1,
137
+ encrypted: Buffer.concat([ciphertext, cipher.getAuthTag()]).toString("base64"),
138
+ iv: iv.toString("hex")
139
+ };
140
+ }
141
+ function decrypt(data, keyHex) {
142
+ const key = Buffer.from(keyHex, "hex");
143
+ const combined = Buffer.from(data.encrypted, "base64");
144
+ const decipher = createDecipheriv(ALGORITHM, key, Buffer.from(data.iv, "hex"));
145
+ decipher.setAuthTag(combined.subarray(-16));
146
+ const plaintext = Buffer.concat([decipher.update(combined.subarray(0, -16)), decipher.final()]);
147
+ return JSON.parse(plaintext.toString("utf-8"));
148
+ }
149
+ //#endregion
150
+ //#region src/secrets/store.ts
151
+ var store_exports = /* @__PURE__ */ __exportAll({
152
+ FileSecretsStore: () => FileSecretsStore,
153
+ isRemoteStore: () => isRemoteStore,
154
+ secretsStoreFor: () => secretsStoreFor
155
+ });
156
+ /**
157
+ * The store a stage's secrets are kept in — what every command reads and
158
+ * writes, for the stage it acts on.
159
+ *
160
+ * The local stage is always the file: its secrets belong to the machine
161
+ * running `gkm dev`. A deployed stage uses `secrets.store`.
162
+ */
163
+ async function secretsStoreFor(workspace, stage, options = {}) {
164
+ const configured = workspace.secrets.store ?? "file";
165
+ if (stage === workspace.stages.local || configured === "file") return new FileSecretsStore(workspace.root);
166
+ if (configured.provider !== "ssm") return configured.provider;
167
+ const { AwsSecretsStore } = await import("./aws-DojzQnLd.mjs");
168
+ return new AwsSecretsStore({
169
+ project: workspace.name,
170
+ region: configured.region,
171
+ ...options.profile ? { profile: options.profile } : {}
172
+ });
173
+ }
174
+ /** Whether a stage's secrets live somewhere other than this machine. */
175
+ function isRemoteStore(workspace, stage) {
176
+ const configured = workspace.secrets.store ?? "file";
177
+ return stage !== workspace.stages.local && configured !== "file";
178
+ }
179
+ //#endregion
180
+ export { getKeyPath as a, FileSecretsStore as i, secretsStoreFor as n, store_exports as r, isRemoteStore as t };
181
+
182
+ //# sourceMappingURL=store-DeGATCS-.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-DeGATCS-.mjs","names":[],"sources":["../src/secrets/keystore.ts","../src/secrets/file.ts","../src/secrets/store.ts"],"sourcesContent":["import { randomBytes } from 'node:crypto';\nimport { existsSync } from 'node:fs';\nimport { chmod, mkdir, readFile, rm, writeFile } from 'node:fs/promises';\nimport { homedir } from 'node:os';\nimport { basename, join } from 'node:path';\n\n/** Key length for AES-256 encryption */\nconst KEY_LENGTH = 32; // 256 bits\n\n/**\n * Get the keystore directory for a project.\n * Keys are stored at ~/.gkm/{project-name}/\n *\n * @param projectName - Name of the project (defaults to current directory name)\n * @returns Path to the keystore directory\n */\nexport function getKeystoreDir(projectName?: string): string {\n\tconst name = projectName ?? basename(process.cwd());\n\treturn join(homedir(), '.gkm', name);\n}\n\n/**\n * Get the path to a stage's encryption key.\n *\n * @param stage - Stage name (e.g., 'development', 'production')\n * @param projectName - Name of the project (defaults to current directory name)\n * @returns Path to the key file\n */\nexport function getKeyPath(stage: string, projectName?: string): string {\n\treturn join(getKeystoreDir(projectName), `${stage}.key`);\n}\n\n/**\n * Check if a key exists for a stage.\n */\nexport function keyExists(stage: string, projectName?: string): boolean {\n\treturn existsSync(getKeyPath(stage, projectName));\n}\n\n/**\n * Generate a new encryption key for a stage.\n * The key is stored at ~/.gkm/{project-name}/{stage}.key with restricted permissions.\n *\n * @param stage - Stage name\n * @param projectName - Project name (defaults to current directory name)\n * @returns The generated key as a hex string\n */\nexport async function generateKey(\n\tstage: string,\n\tprojectName?: string,\n): Promise<string> {\n\tconst keyDir = getKeystoreDir(projectName);\n\tconst keyPath = getKeyPath(stage, projectName);\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' });\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 * Read an encryption key for a stage.\n *\n * @param stage - Stage name\n * @param projectName - Project name (defaults to current directory name)\n * @returns The key as a hex string, or null if not found\n */\nexport async function readKey(\n\tstage: string,\n\tprojectName?: string,\n): Promise<string | null> {\n\tconst keyPath = getKeyPath(stage, projectName);\n\n\tif (!existsSync(keyPath)) {\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 if not found.\n */\nexport async function requireKey(\n\tstage: string,\n\tprojectName?: string,\n): Promise<string> {\n\tconst key = await readKey(stage, projectName);\n\n\tif (!key) {\n\t\tconst name = projectName ?? basename(process.cwd());\n\t\tthrow new Error(\n\t\t\t`Encryption key not found for stage \"${stage}\" in project \"${name}\". ` +\n\t\t\t\t`Expected key at: ${getKeyPath(stage, projectName)}`,\n\t\t);\n\t}\n\n\treturn key;\n}\n\n/**\n * Delete a key for a stage.\n */\nexport async function deleteKey(\n\tstage: string,\n\tprojectName?: string,\n): Promise<void> {\n\tconst keyPath = getKeyPath(stage, projectName);\n\n\tif (existsSync(keyPath)) {\n\t\tawait rm(keyPath);\n\t}\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 *\n * @param stage - Stage name\n * @param projectName - Project name (defaults to current directory name)\n * @returns The key as a hex string\n */\nexport async function getOrCreateKey(\n\tstage: string,\n\tprojectName?: string,\n): Promise<string> {\n\tconst existingKey = await readKey(stage, projectName);\n\n\tif (existingKey) {\n\t\treturn existingKey;\n\t}\n\n\treturn generateKey(stage, projectName);\n}\n","import { createCipheriv, createDecipheriv, randomBytes } from 'node:crypto';\nimport { existsSync } from 'node:fs';\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport { basename, join } from 'node:path';\nimport { getOrCreateKey, readKey } 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 at `~/.gkm/<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\n\tconstructor(private readonly root: string) {}\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 project = basename(this.root);\n\t\tconst key = await readKey(stage, project);\n\t\tif (!key) throw new MissingSecretsKey(stage, project);\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, basename(this.root));\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) {\n\t\tsuper(\n\t\t\t`Decryption key not found for stage \"${stage}\". ` +\n\t\t\t\t`Expected key at: ~/.gkm/${project}/${stage}.key`,\n\t\t);\n\t\tthis.name = 'MissingSecretsKey';\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 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` beside its key in `~/.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}\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(workspace.root);\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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"}