@geekmidas/cli 10.0.0-alpha.90 → 10.0.0-alpha.91

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.
@@ -113,6 +113,13 @@ function workspaceStageKeys(input) {
113
113
  * construct's schema — checked, and saved through the stage's own store, the
114
114
  * one `gkm secrets:set` writes. No value is ever printed.
115
115
  *
116
+ * The keys are walked one at a time — unset ones first — and each is a
117
+ * checkpoint: set it now, skip it, or stop here. A key is saved the moment it
118
+ * is built, so stopping, Ctrl-C or a failure keeps every key before it, and
119
+ * running the command again picks up what is still missing. A key a provider
120
+ * on the stage creates (a bucket under `deploy.objects.<stage>`, written by
121
+ * `gkm setup`) is not a checkpoint: it is listed, with what creates it.
122
+ *
116
123
  * `--json` asks nothing: it prints the keys, for a script or an agent to set
117
124
  * with `gkm secrets:set`.
118
125
  */
@@ -125,11 +132,14 @@ var SecretsAddNeedsTerminal = class extends Error {
125
132
  this.name = "SecretsAddNeedsTerminal";
126
133
  }
127
134
  };
128
- /** The person stopped the builder; nothing was saved. */
135
+ /**
136
+ * The person stopped the builder at a prompt (Ctrl-C). Every key saved
137
+ * before it is kept; the run ends with what was saved and what is missing.
138
+ */
129
139
  var SecretsAddCancelled = class extends Error {
130
140
  stage;
131
141
  constructor(stage) {
132
- super(`Stopped. Nothing was saved to the stage '${stage}'.`);
142
+ super(`Stopped. Every key saved to the stage '${stage}' before it is kept.`);
133
143
  this.stage = stage;
134
144
  this.name = "SecretsAddCancelled";
135
145
  }
@@ -166,98 +176,142 @@ async function secretsAddCommand(options, io = defaultIo()) {
166
176
  providers: require_seeds.stageProviderNotes(workspace, stage),
167
177
  ...!local && domain && require_index.deploysWithCompose(workspace) ? { server: true } : {}
168
178
  });
169
- const keys = options.missing ? all.filter((k) => !k.set) : all;
179
+ const scope = [...all.filter((k) => !k.set), ...all.filter((k) => k.set)];
180
+ const keys = options.missing ? scope.filter((k) => !k.set) : scope;
181
+ const objects = require_workspace.stageProvider(workspace, "objects", stage);
182
+ const provisioned = (k) => objects.mode === "provider" && k.hint !== void 0 && (k.kind === "bucket" || k.kind === "file-server");
170
183
  if (options.json) {
171
184
  const listed = keys.map((k) => ({
172
185
  key: k.key,
173
186
  kind: k.kind,
174
187
  construct: k.id,
175
188
  apps: [...k.apps ?? []],
176
- set: k.set
189
+ set: k.set,
190
+ ...provisioned(k) ? { provisioned: true } : {}
177
191
  }));
178
192
  io.write(`${JSON.stringify(listed, null, 2)}\n`);
179
193
  return {
180
194
  keys,
181
- saved: []
195
+ saved: [],
196
+ skipped: [],
197
+ missing: []
182
198
  };
183
199
  }
184
200
  if (!io.interactive) throw new SecretsAddNeedsTerminal(stage);
185
- if (keys.length === 0) {
186
- io.log(all.length === 0 ? `Nothing to add: no construct on '${stage}' needs a key only the stage can supply.` : `Nothing to add: the stage '${stage}' has every key it needs.`);
201
+ const offered = keys.filter((k) => !provisioned(k));
202
+ const created = keys.filter(provisioned);
203
+ if (created.length > 0 && objects.mode === "provider") {
204
+ io.log("Not offered — a provider on the stage creates them:");
205
+ for (const k of created) io.log(` ${k.key} — created by ${require_seeds.provisionCommand(stage)} (deploy.objects.${stage} is ${objects.name})`);
206
+ }
207
+ if (offered.length === 0) {
208
+ io.log(all.every(provisioned) ? `Nothing to add: no construct on '${stage}' needs a key only the stage can supply.` : `Nothing to add: the stage '${stage}' has every key it needs.`);
187
209
  return {
188
- keys,
189
- saved: []
210
+ keys: offered,
211
+ saved: [],
212
+ skipped: [],
213
+ missing: []
190
214
  };
191
215
  }
192
216
  const ask = asker(stage);
193
- const chosen = await ask({
194
- type: "multiselect",
195
- message: `Which keys for '${stage}'? (${store.name} store)`,
196
- choices: keys.map((k) => ({
197
- title: `${k.key} ${KIND_LABEL[k.kind]} '${k.id}'${k.apps?.length ? ` · ${k.apps.join(", ")}` : ""}${k.set ? " · set" : ""}`,
198
- value: k.key,
199
- selected: !k.set
200
- })),
201
- instructions: false,
202
- hint: "space to toggle, enter to continue"
203
- });
204
- const selected = keys.filter((k) => (chosen ?? []).includes(k.key));
205
- const values = {};
206
- const has = (key) => values[key] !== void 0 || stored?.custom?.[key] !== void 0;
217
+ let current = stored ?? require_seeds.initStageSecrets(stage);
218
+ const saved = [];
219
+ const skipped = [];
220
+ const has = (key) => current.custom?.[key] !== void 0;
207
221
  let schemas;
208
- for (const entry of selected) {
209
- if (entry.set) {
210
- if (!await ask({
211
- type: "confirm",
212
- message: `${entry.key} is set. Replace it?`,
213
- initial: false
214
- })) continue;
215
- }
216
- io.log(`\n${entry.key} — ${entry.what}`);
217
- if (entry.hint) io.log(` ${entry.hint}`);
218
- switch (entry.kind) {
219
- case "bucket":
220
- Object.assign(values, await buildBucket(ask, io, entry, has));
221
- break;
222
- case "email":
223
- values[entry.key] = entry.key.endsWith("_FROM") ? await askFrom(ask, io) : await buildSmtpUrl(ask, io);
224
- break;
225
- case "file-server":
226
- values[entry.key] = await askHttpUrl(ask, io, "Its public URL");
227
- break;
228
- case "server":
229
- Object.assign(values, await buildServer(ask, io, has));
230
- break;
231
- case "external-api":
232
- case "credential":
233
- schemas ??= await require_credentialSchemas.loadCredentialSchemas({
234
- root: workspace.root,
235
- patterns
236
- });
237
- values[entry.key] = await buildCredentials(ask, io, entry, schemas);
222
+ const save = async (values) => {
223
+ current = {
224
+ ...current,
225
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
226
+ custom: {
227
+ ...current.custom,
228
+ ...values
229
+ }
230
+ };
231
+ await store.write(stage, current);
232
+ saved.push(...Object.keys(values));
233
+ io.log(` ✓ Saved ${Object.keys(values).join(", ")}.`);
234
+ };
235
+ const walk = async () => {
236
+ for (const [index, entry] of offered.entries()) {
237
+ io.log(`\n[${index + 1}/${offered.length}] ${entry.key} — ${entry.what}`);
238
+ if (entry.hint) io.log(` ${entry.hint}`);
239
+ const step = await ask({
240
+ type: "select",
241
+ message: `${entry.key} ${KIND_LABEL[entry.kind]} '${entry.id}'${entry.apps?.length ? ` · ${entry.apps.join(", ")}` : ""}${entry.set ? " · set" : ""}`,
242
+ choices: [
243
+ {
244
+ title: "Set it now",
245
+ value: "set"
246
+ },
247
+ {
248
+ title: "Skip",
249
+ value: "skip"
250
+ },
251
+ {
252
+ title: "Stop here",
253
+ value: "stop"
254
+ }
255
+ ],
256
+ initial: 0
257
+ });
258
+ if (step === "stop") return;
259
+ if (step !== "set") {
260
+ skipped.push(entry.key);
261
+ continue;
262
+ }
263
+ if (entry.set) {
264
+ if (!await ask({
265
+ type: "confirm",
266
+ message: `${entry.key} is set. Replace it?`,
267
+ initial: false
268
+ })) {
269
+ skipped.push(entry.key);
270
+ continue;
271
+ }
272
+ }
273
+ switch (entry.kind) {
274
+ case "bucket":
275
+ await save(await buildBucket(ask, io, entry, has));
276
+ break;
277
+ case "email":
278
+ await save({ [entry.key]: entry.key.endsWith("_FROM") ? await askFrom(ask, io) : await buildSmtpUrl(ask, io) });
279
+ break;
280
+ case "file-server":
281
+ await save({ [entry.key]: await askHttpUrl(ask, io, "Its public URL") });
282
+ break;
283
+ case "server":
284
+ await save(await buildServer(ask, io, has));
285
+ break;
286
+ case "external-api":
287
+ case "credential":
288
+ schemas ??= await require_credentialSchemas.loadCredentialSchemas({
289
+ root: workspace.root,
290
+ patterns
291
+ });
292
+ await save({ [entry.key]: await buildCredentials(ask, io, entry, schemas) });
293
+ }
238
294
  }
295
+ };
296
+ try {
297
+ await walk();
298
+ } catch (error) {
299
+ if (!(error instanceof SecretsAddCancelled)) throw error;
300
+ io.log(`\n${error.message}`);
239
301
  }
240
- const saved = Object.keys(values);
241
- if (saved.length === 0) {
242
- io.log("\nNothing was saved.");
243
- return {
244
- keys,
245
- saved
246
- };
302
+ const missing = all.filter((k) => !provisioned(k) && !has(k.key)).map((k) => k.key);
303
+ io.log("");
304
+ io.log(saved.length > 0 ? `✓ Saved to the stage '${stage}' (${store.name} store): ${saved.join(", ")}` : "Nothing was saved.");
305
+ if (skipped.length > 0) io.log(` Skipped: ${skipped.join(", ")}`);
306
+ if (missing.length > 0) {
307
+ io.log(` Still missing: ${missing.join(", ")}`);
308
+ io.log(` Run gkm secrets:add --stage ${stage} --missing again to pick up where this left off.`);
247
309
  }
248
- const base = stored ?? require_seeds.initStageSecrets(stage);
249
- await store.write(stage, {
250
- ...base,
251
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
252
- custom: {
253
- ...base.custom,
254
- ...values
255
- }
256
- });
257
- io.log(`\n✓ Saved ${saved.join(", ")} to the stage '${stage}' (${store.name} store).`);
258
310
  return {
259
- keys,
260
- saved
311
+ keys: offered,
312
+ saved,
313
+ skipped,
314
+ missing
261
315
  };
262
316
  }
263
317
  const KIND_LABEL = {
@@ -322,8 +376,101 @@ async function askFrom(ask, io) {
322
376
  * An SMTP server as the URL `@geekmidas/emailkit` reads: `smtp://` for
323
377
  * STARTTLS, `smtps://` for TLS from the first byte, with the user and
324
378
  * password percent-encoded.
379
+ *
380
+ * A known service fills in its own host, port and user; each from its docs:
381
+ * - Resend: smtp.resend.com, 465 implicit TLS, user `resend`, password the
382
+ * API key — https://resend.com/docs/send-with-smtp
383
+ * - Amazon SES: email-smtp.<region>.amazonaws.com, 587 STARTTLS, SMTP
384
+ * credentials — https://docs.aws.amazon.com/ses/latest/dg/smtp-connect.html,
385
+ * https://docs.aws.amazon.com/general/latest/gr/ses.html
386
+ * - Postmark: smtp.postmarkapp.com, 587 STARTTLS, the server API token as
387
+ * user and password —
388
+ * https://postmarkapp.com/developer/user-guide/send-email-with-smtp
389
+ * - Mailgun: smtp.mailgun.org (EU: smtp.eu.mailgun.org), 587 STARTTLS, the
390
+ * domain's SMTP credentials —
391
+ * https://documentation.mailgun.com/docs/mailgun/user-manual/sending-messages/send-smtp,
392
+ * https://documentation.mailgun.com/docs/mailgun/api-reference/api-overview
325
393
  */
326
394
  async function buildSmtpUrl(ask, io) {
395
+ const service = await ask({
396
+ type: "select",
397
+ message: "Which mail service?",
398
+ choices: [
399
+ {
400
+ title: "Resend",
401
+ value: "resend"
402
+ },
403
+ {
404
+ title: "Amazon SES",
405
+ value: "ses"
406
+ },
407
+ {
408
+ title: "Postmark",
409
+ value: "postmark"
410
+ },
411
+ {
412
+ title: "Mailgun",
413
+ value: "mailgun"
414
+ },
415
+ {
416
+ title: "Other SMTP server",
417
+ value: "other"
418
+ }
419
+ ],
420
+ initial: 0
421
+ });
422
+ const secret = async (message, what) => text(await askUntil(ask, io, {
423
+ type: "password",
424
+ message
425
+ }, required(what)));
426
+ const login = async (message, what) => text(await askUntil(ask, io, {
427
+ type: "text",
428
+ message
429
+ }, required(what)));
430
+ switch (service) {
431
+ case "resend": return smtpUrl({
432
+ tls: "tls",
433
+ host: "smtp.resend.com",
434
+ port: 465,
435
+ user: "resend",
436
+ password: await secret("API key (re_…)", "The API key")
437
+ });
438
+ case "ses": return smtpUrl({
439
+ tls: "starttls",
440
+ host: `email-smtp.${await login("Region", "The region")}.amazonaws.com`,
441
+ port: 587,
442
+ user: await login("SMTP user name", "The SMTP user name"),
443
+ password: await secret("SMTP password", "The SMTP password")
444
+ });
445
+ case "postmark": {
446
+ const token = await secret("Server API token", "The server API token");
447
+ return smtpUrl({
448
+ tls: "starttls",
449
+ host: "smtp.postmarkapp.com",
450
+ port: 587,
451
+ user: token,
452
+ password: token
453
+ });
454
+ }
455
+ case "mailgun": return smtpUrl({
456
+ tls: "starttls",
457
+ host: await ask({
458
+ type: "select",
459
+ message: "Region",
460
+ choices: [{
461
+ title: "US (smtp.mailgun.org)",
462
+ value: "us"
463
+ }, {
464
+ title: "EU (smtp.eu.mailgun.org)",
465
+ value: "eu"
466
+ }],
467
+ initial: 0
468
+ }) === "eu" ? "smtp.eu.mailgun.org" : "smtp.mailgun.org",
469
+ port: 587,
470
+ user: await login("SMTP login (e.g. postmaster@mg.example.com)", "The SMTP login"),
471
+ password: await secret("SMTP password", "The SMTP password")
472
+ });
473
+ }
327
474
  const host = text(await askUntil(ask, io, {
328
475
  type: "text",
329
476
  message: "SMTP host"
@@ -344,23 +491,31 @@ async function buildSmtpUrl(ask, io) {
344
491
  type: "password",
345
492
  message: "Password"
346
493
  }) : "";
347
- const tls = await ask({
348
- type: "select",
349
- message: "TLS",
350
- choices: [{
351
- title: "STARTTLS (smtp://, usually port 587)",
352
- value: "starttls"
353
- }, {
354
- title: "TLS on connect (smtps://, usually port 465)",
355
- value: "tls"
356
- }],
357
- initial: 0
494
+ return smtpUrl({
495
+ tls: await ask({
496
+ type: "select",
497
+ message: "TLS",
498
+ choices: [{
499
+ title: "STARTTLS (smtp://, usually port 587)",
500
+ value: "starttls"
501
+ }, {
502
+ title: "TLS on connect (smtps://, usually port 465)",
503
+ value: "tls"
504
+ }],
505
+ initial: 0
506
+ }),
507
+ host,
508
+ port: Number(port),
509
+ user,
510
+ password
358
511
  });
359
- const url = new URL(`${tls === "tls" ? "smtps" : "smtp"}://${host}`);
360
- url.port = String(Number(port));
361
- if (user) {
362
- url.username = encodeURIComponent(user);
363
- url.password = encodeURIComponent(password ?? "");
512
+ }
513
+ function smtpUrl(server) {
514
+ const url = new URL(`${server.tls === "tls" ? "smtps" : "smtp"}://${server.host}`);
515
+ url.port = String(server.port);
516
+ if (server.user) {
517
+ url.username = encodeURIComponent(server.user);
518
+ url.password = encodeURIComponent(server.password ?? "");
364
519
  }
365
520
  return url.toString().replace(/\/$/, "");
366
521
  }
@@ -591,4 +746,4 @@ async function askFields(ask, io, shape) {
591
746
  //#endregion
592
747
  exports.secretsAddCommand = secretsAddCommand;
593
748
 
594
- //# sourceMappingURL=add-CNMF0Q3m.cjs.map
749
+ //# sourceMappingURL=add-DmmBDV2e.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add-DmmBDV2e.cjs","names":["provideKey","appKey","appEnvKeys","requiredStageKeys","loadWorkspaceSettings","constructGlobs","discover","secretsStoreFor","stageProviderNotes","deploysWithCompose","stageProvider","provisionCommand","initStageSecrets","loadCredentialSchemas","isIPv4","SERVER_IPV4_KEY","isIPv6","SERVER_IPV6_KEY","s3Parse","s3Build","issueLine"],"sources":["../src/secrets/stageKeys.ts","../src/secrets/add.ts"],"sourcesContent":["/**\n * The keys a workspace's stage must be given — across every app, each once,\n * with who reads it and whether the stage holds it.\n *\n * The keys themselves are `requiredStageKeys`'s, the list a deploy refuses a\n * stage by. What this adds is the workspace: which constructs its apps read\n * (`appEnvKeys`, the keys each app's environment is built from) and which of\n * the keys the stage has set.\n */\n\nimport { type ConstructManifest, provideKey } from '@geekmidas/manifest';\nimport {\n\ttype CredentialDeclaration,\n\trequiredStageKeys,\n\ttype ServiceDeclaration,\n\ttype StageKey,\n\ttype StageProviderNotes,\n} from '../deploy/devServices';\nimport { appEnvKeys } from '../reconcile/apps';\nimport { appKey } from '../workspace/derive';\n\n/** A stage key, and whether the stage's secrets hold it. */\nexport interface WorkspaceStageKey extends StageKey {\n\tset: boolean;\n}\n\nexport interface WorkspaceStageKeysInput {\n\tmanifest: ConstructManifest;\n\t/** Each owner's runnables' edges, from discovery. */\n\trunnables?: Readonly<Record<string, readonly string[]>>;\n\t/** Whether the stage is the workspace's local one. */\n\tlocal: boolean;\n\t/** What the stage's secrets hold, by key. */\n\tsupplied: Readonly<Record<string, string>>;\n\t/** The stage's base domain, for the examples. */\n\tdomain?: string;\n\t/** What the stage's providers say — a missing key's hint. */\n\tproviders?: StageProviderNotes;\n\t/** The stage serves a domain from its own server: `GKM_SERVER_IPV4`. */\n\tserver?: boolean;\n}\n\n/** The keys of each kind of construct an app reads, that the stage supplies. */\nfunction keysOf(id: string, kind: string): string[] {\n\tswitch (kind) {\n\t\tcase 'email':\n\t\t\treturn [provideKey(id, 'url'), provideKey(id, 'from')];\n\t\tcase 'objects':\n\t\tcase 'file-server':\n\t\t\treturn [provideKey(id, 'url')];\n\t\tcase 'external-api':\n\t\tcase 'credential':\n\t\t\treturn [provideKey(id, 'credentials')];\n\t\tdefault:\n\t\t\treturn [];\n\t}\n}\n\n/**\n * Every key the stage must be given for the constructs its apps read — a\n * file server's whenever its bucket is read, since its client reads both —\n * and every file server's bucket, which serves it whether or not an app\n * reads it directly.\n */\nexport function workspaceStageKeys(\n\tinput: WorkspaceStageKeysInput,\n): WorkspaceStageKey[] {\n\tconst { manifest } = input;\n\n\t// What each app's environment holds.\n\tconst reads = new Map<string, Set<string>>();\n\tfor (const [id, declaration] of Object.entries(manifest)) {\n\t\tconst { kind } = declaration;\n\t\tif (kind !== 'rest-api' && kind !== 'site' && kind !== 'mobile-app') {\n\t\t\tcontinue;\n\t\t}\n\t\tconst name = appKey(id);\n\t\tconst keys = appEnvKeys(manifest, name, input.runnables);\n\t\tif (keys) reads.set(name, keys);\n\t}\n\n\tconst readers = (id: string, kind: string) => {\n\t\tconst keys = keysOf(id, kind);\n\t\treturn [...reads]\n\t\t\t.filter(([, env]) => keys.some((key) => env.has(key)))\n\t\t\t.map(([app]) => app)\n\t\t\t.sort();\n\t};\n\n\tconst services: ServiceDeclaration[] = [];\n\tconst credentials: CredentialDeclaration[] = [];\n\tconst buckets = new Set<string>();\n\tfor (const [id, declaration] of Object.entries(manifest)) {\n\t\tconst { kind } = declaration;\n\t\t// A file server's client reads its bucket's URL and its own, and an\n\t\t// app's edge is to the bucket: whoever reads the bucket reads both.\n\t\tconst apps =\n\t\t\tdeclaration.kind === 'file-server' && declaration.of\n\t\t\t\t? [\n\t\t\t\t\t\t...new Set([\n\t\t\t\t\t\t\t...readers(id, kind),\n\t\t\t\t\t\t\t...readers(declaration.of, 'objects'),\n\t\t\t\t\t\t]),\n\t\t\t\t\t].sort()\n\t\t\t\t: readers(id, kind);\n\t\tif (kind === 'external-api' || kind === 'credential') {\n\t\t\tif (apps.length > 0) credentials.push({ id, kind, apps });\n\t\t\tcontinue;\n\t\t}\n\t\tif (kind !== 'email' && kind !== 'objects' && kind !== 'file-server') {\n\t\t\tcontinue;\n\t\t}\n\t\tif (apps.length === 0) continue;\n\t\tconst of = declaration.kind === 'file-server' ? declaration.of : undefined;\n\t\tservices.push({ id, kind, ...(of ? { of } : {}), apps });\n\t\tif (of) buckets.add(of);\n\t}\n\tfor (const of of buckets) {\n\t\tif (services.some((s) => s.id === of)) continue;\n\t\tif (manifest[of]?.kind !== 'objects') continue;\n\t\tservices.push({ id: of, kind: 'objects', apps: readers(of, 'objects') });\n\t}\n\n\treturn requiredStageKeys({\n\t\tlocal: input.local,\n\t\tservices,\n\t\tcredentials,\n\t\t...(input.domain ? { domain: input.domain } : {}),\n\t\t...(input.providers ? { providers: input.providers } : {}),\n\t\t...(input.server ? { server: true } : {}),\n\t}).map((key) => ({ ...key, set: input.supplied[key.key] !== undefined }));\n}\n","/**\n * `gkm secrets:add --stage <stage>` — the secrets a stage must be given,\n * built key by key.\n *\n * It offers exactly the keys a deploy would refuse the stage without\n * (`requiredStageKeys`), across every app in the workspace, each once: the\n * stage's buckets, mail and file servers where it is deployed, and every\n * third party's credentials. A key is built by what it is — a bucket from its\n * provider, mail from its SMTP server, credentials field by field from their\n * construct's schema — checked, and saved through the stage's own store, the\n * one `gkm secrets:set` writes. No value is ever printed.\n *\n * The keys are walked one at a time — unset ones first — and each is a\n * checkpoint: set it now, skip it, or stop here. A key is saved the moment it\n * is built, so stopping, Ctrl-C or a failure keeps every key before it, and\n * running the command again picks up what is still missing. A key a provider\n * on the stage creates (a bucket under `deploy.objects.<stage>`, written by\n * `gkm setup`) is not a checkpoint: it is listed, with what creates it.\n *\n * `--json` asks nothing: it prints the keys, for a script or an agent to set\n * with `gkm secrets:set`.\n */\n\nimport { isIPv4, isIPv6 } from 'node:net';\nimport {\n\ttype S3Address,\n\tbuild as s3Build,\n\tparse as s3Parse,\n} from '@geekmidas/storage/s3-url';\nimport prompts, { type PromptObject } from 'prompts';\nimport { z } from 'zod';\nimport { SERVER_IPV4_KEY, SERVER_IPV6_KEY } from '../compose/dnsConfig.js';\nimport { loadWorkspaceSettings } from '../config';\nimport { stageProvider } from '../providers/config.js';\nimport { deploysWithCompose } from '../providers/dns.js';\nimport { provisionCommand, stageProviderNotes } from '../providers/notes.js';\nimport { discover } from '../reconcile/discover';\nimport { constructGlobs } from '../reconcile/workspace';\nimport { assertDeployedStage } from '../workspace/stages';\nimport {\n\ttype CredentialSchemaInfo,\n\ttype CredentialSchemas,\n\tissueLine,\n\tloadCredentialSchemas,\n} from './credentialSchemas';\nimport { type WorkspaceStageKey, workspaceStageKeys } from './stageKeys';\nimport { initStageSecrets } from './storage';\nimport { secretsStoreFor } from './store';\n\nexport interface SecretsAddOptions {\n\tstage: string;\n\t/** Only the keys the stage has not set. */\n\tmissing?: boolean;\n\t/** Print the keys as JSON and ask nothing. */\n\tjson?: boolean;\n\t/** The workspace — the current directory when absent. */\n\tcwd?: string;\n\t/** The CLI's home, for a file store's keys. Defaults to `GKM_HOME`. */\n\thome?: string;\n}\n\n/** Where the command writes, and whether there is anyone to ask. */\nexport interface SecretsAddIo {\n\t/** A line for the person at the terminal. */\n\tlog(line: string): void;\n\t/** `--json`'s output. */\n\twrite(chunk: string): void;\n\t/** Whether prompts can be answered. */\n\tinteractive: boolean;\n}\n\n/** What a run of the command came to. */\nexport interface SecretsAddResult {\n\t/** The keys offered, in the order they were walked. */\n\tkeys: WorkspaceStageKey[];\n\t/** The keys saved, in the order they were built. */\n\tsaved: string[];\n\t/** The keys skipped at their checkpoint. */\n\tskipped: string[];\n\t/** The keys the stage must still be given, once the run is over. */\n\tmissing: string[];\n}\n\n/** One key as `--json` prints it. */\nexport interface StageKeyJson {\n\tkey: string;\n\tkind: WorkspaceStageKey['kind'];\n\tconstruct: string;\n\tapps: string[];\n\tset: boolean;\n\t/** A provider on the stage creates it: `gkm setup --stage <stage>`. */\n\tprovisioned?: true;\n}\n\n/** `gkm secrets:add` with nobody at a terminal to answer it. */\nexport class SecretsAddNeedsTerminal extends Error {\n\tconstructor(readonly stage: string) {\n\t\tsuper(\n\t\t\t`gkm secrets:add asks for each key, and there is no terminal to ask ` +\n\t\t\t\t`at. List what the stage needs with ` +\n\t\t\t\t`gkm secrets:add --stage ${stage} --missing --json, and set each ` +\n\t\t\t\t`with gkm secrets:set <KEY> <value> --stage ${stage}.`,\n\t\t);\n\t\tthis.name = 'SecretsAddNeedsTerminal';\n\t}\n}\n\n/**\n * The person stopped the builder at a prompt (Ctrl-C). Every key saved\n * before it is kept; the run ends with what was saved and what is missing.\n */\nexport class SecretsAddCancelled extends Error {\n\tconstructor(readonly stage: string) {\n\t\tsuper(\n\t\t\t`Stopped. Every key saved to the stage '${stage}' before it is kept.`,\n\t\t);\n\t\tthis.name = 'SecretsAddCancelled';\n\t}\n}\n\nconst defaultIo = (): SecretsAddIo => ({\n\tlog: (line) => console.log(line),\n\twrite: (chunk) => {\n\t\tprocess.stdout.write(chunk);\n\t},\n\tinteractive: process.stdin.isTTY === true && process.stdout.isTTY === true,\n});\n\n/** `gkm secrets:add`. */\nexport async function secretsAddCommand(\n\toptions: SecretsAddOptions,\n\tio: SecretsAddIo = defaultIo(),\n): Promise<SecretsAddResult> {\n\tconst { stage } = options;\n\tconst workspace = await loadWorkspaceSettings(options.cwd ?? process.cwd());\n\tconst local = stage === workspace.stages.local;\n\tif (!local) assertDeployedStage(workspace.stages, stage);\n\n\tconst patterns = constructGlobs(workspace);\n\tconst runnables: Record<string, string[]> = {};\n\tconst manifest = await discover({\n\t\tpatterns,\n\t\tcwd: workspace.root,\n\t\trunnables,\n\t});\n\n\tconst store = await secretsStoreFor(workspace, stage, {\n\t\t...(options.home ? { home: options.home } : {}),\n\t});\n\tconst stored = await store.read(stage);\n\tconst domain = workspace.domains?.[stage];\n\n\tconst all = workspaceStageKeys({\n\t\tmanifest,\n\t\trunnables,\n\t\tlocal,\n\t\tsupplied: stored?.custom ?? {},\n\t\t...(domain ? { domain } : {}),\n\t\tproviders: stageProviderNotes(workspace, stage),\n\t\t// A compose stage that serves a domain names its server.\n\t\t...(!local && domain && deploysWithCompose(workspace)\n\t\t\t? { server: true }\n\t\t\t: {}),\n\t});\n\t// Unset keys first: a run is for what the stage still lacks.\n\tconst scope = [...all.filter((k) => !k.set), ...all.filter((k) => k.set)];\n\tconst keys = options.missing ? scope.filter((k) => !k.set) : scope;\n\n\t// A key a provider on the stage writes — `gkm setup` creates its bucket.\n\tconst objects = stageProvider(workspace as never, 'objects', stage);\n\tconst provisioned = (k: WorkspaceStageKey) =>\n\t\tobjects.mode === 'provider' &&\n\t\tk.hint !== undefined &&\n\t\t(k.kind === 'bucket' || k.kind === 'file-server');\n\n\tif (options.json) {\n\t\tconst listed: StageKeyJson[] = keys.map((k) => ({\n\t\t\tkey: k.key,\n\t\t\tkind: k.kind,\n\t\t\tconstruct: k.id,\n\t\t\tapps: [...(k.apps ?? [])],\n\t\t\tset: k.set,\n\t\t\t...(provisioned(k) ? { provisioned: true as const } : {}),\n\t\t}));\n\t\tio.write(`${JSON.stringify(listed, null, 2)}\\n`);\n\t\treturn { keys, saved: [], skipped: [], missing: [] };\n\t}\n\n\tif (!io.interactive) throw new SecretsAddNeedsTerminal(stage);\n\n\t// A provisioned key is no checkpoint: it is listed, with what creates it.\n\tconst offered = keys.filter((k) => !provisioned(k));\n\tconst created = keys.filter(provisioned);\n\tif (created.length > 0 && objects.mode === 'provider') {\n\t\tio.log('Not offered — a provider on the stage creates them:');\n\t\tfor (const k of created) {\n\t\t\tio.log(\n\t\t\t\t` ${k.key} — created by ${provisionCommand(stage)} ` +\n\t\t\t\t\t`(deploy.objects.${stage} is ${objects.name})`,\n\t\t\t);\n\t\t}\n\t}\n\n\tif (offered.length === 0) {\n\t\tio.log(\n\t\t\tall.every(provisioned)\n\t\t\t\t? `Nothing to add: no construct on '${stage}' needs a key only the stage can supply.`\n\t\t\t\t: `Nothing to add: the stage '${stage}' has every key it needs.`,\n\t\t);\n\t\treturn { keys: offered, saved: [], skipped: [], missing: [] };\n\t}\n\n\tconst ask = asker(stage);\n\tlet current = stored ?? initStageSecrets(stage);\n\tconst saved: string[] = [];\n\tconst skipped: string[] = [];\n\tconst has = (key: string) => current.custom?.[key] !== undefined;\n\tlet schemas: CredentialSchemas | undefined;\n\n\t// Each key is saved as soon as it is built: what is set survives a stop.\n\tconst save = async (values: Record<string, string>) => {\n\t\tcurrent = {\n\t\t\t...current,\n\t\t\tupdatedAt: new Date().toISOString(),\n\t\t\tcustom: { ...current.custom, ...values },\n\t\t};\n\t\tawait store.write(stage, current);\n\t\tsaved.push(...Object.keys(values));\n\t\tio.log(` ✓ Saved ${Object.keys(values).join(', ')}.`);\n\t};\n\n\tconst walk = async () => {\n\t\tfor (const [index, entry] of offered.entries()) {\n\t\t\tio.log(`\\n[${index + 1}/${offered.length}] ${entry.key} — ${entry.what}`);\n\t\t\tif (entry.hint) io.log(` ${entry.hint}`);\n\n\t\t\tconst step = await ask<'set' | 'skip' | 'stop'>({\n\t\t\t\ttype: 'select',\n\t\t\t\tmessage: `${entry.key} ${KIND_LABEL[entry.kind]} '${entry.id}'${entry.apps?.length ? ` · ${entry.apps.join(', ')}` : ''}${entry.set ? ' · set' : ''}`,\n\t\t\t\tchoices: [\n\t\t\t\t\t{ title: 'Set it now', value: 'set' },\n\t\t\t\t\t{ title: 'Skip', value: 'skip' },\n\t\t\t\t\t{ title: 'Stop here', value: 'stop' },\n\t\t\t\t],\n\t\t\t\tinitial: 0,\n\t\t\t});\n\t\t\tif (step === 'stop') return;\n\t\t\tif (step !== 'set') {\n\t\t\t\tskipped.push(entry.key);\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (entry.set) {\n\t\t\t\tconst overwrite: boolean = await ask({\n\t\t\t\t\ttype: 'confirm',\n\t\t\t\t\tmessage: `${entry.key} is set. Replace it?`,\n\t\t\t\t\tinitial: false,\n\t\t\t\t});\n\t\t\t\tif (!overwrite) {\n\t\t\t\t\tskipped.push(entry.key);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tswitch (entry.kind) {\n\t\t\t\tcase 'bucket':\n\t\t\t\t\tawait save(await buildBucket(ask, io, entry, has));\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'email':\n\t\t\t\t\tawait save({\n\t\t\t\t\t\t[entry.key]: entry.key.endsWith('_FROM')\n\t\t\t\t\t\t\t? await askFrom(ask, io)\n\t\t\t\t\t\t\t: await buildSmtpUrl(ask, io),\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'file-server':\n\t\t\t\t\tawait save({\n\t\t\t\t\t\t[entry.key]: await askHttpUrl(ask, io, 'Its public URL'),\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'server':\n\t\t\t\t\tawait save(await buildServer(ask, io, has));\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'external-api':\n\t\t\t\tcase 'credential':\n\t\t\t\t\tschemas ??= await loadCredentialSchemas({\n\t\t\t\t\t\troot: workspace.root,\n\t\t\t\t\t\tpatterns,\n\t\t\t\t\t});\n\t\t\t\t\tawait save({\n\t\t\t\t\t\t[entry.key]: await buildCredentials(ask, io, entry, schemas),\n\t\t\t\t\t});\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t};\n\n\ttry {\n\t\tawait walk();\n\t} catch (error) {\n\t\tif (!(error instanceof SecretsAddCancelled)) throw error;\n\t\tio.log(`\\n${error.message}`);\n\t}\n\n\tconst missing = all\n\t\t.filter((k) => !provisioned(k) && !has(k.key))\n\t\t.map((k) => k.key);\n\tio.log('');\n\tio.log(\n\t\tsaved.length > 0\n\t\t\t? `✓ Saved to the stage '${stage}' (${store.name} store): ${saved.join(', ')}`\n\t\t\t: 'Nothing was saved.',\n\t);\n\tif (skipped.length > 0) io.log(` Skipped: ${skipped.join(', ')}`);\n\tif (missing.length > 0) {\n\t\tio.log(` Still missing: ${missing.join(', ')}`);\n\t\tio.log(\n\t\t\t` Run gkm secrets:add --stage ${stage} --missing again to pick up where this left off.`,\n\t\t);\n\t}\n\treturn { keys: offered, saved, skipped, missing };\n}\n\nconst KIND_LABEL: Record<WorkspaceStageKey['kind'], string> = {\n\tbucket: 'bucket',\n\temail: 'email',\n\t'file-server': 'file server',\n\t'external-api': 'external API',\n\tcredential: 'credential',\n\tserver: 'server address',\n};\n\n/** The server's IPv4 address, and — if it has one — its IPv6 address. */\nasync function buildServer(\n\task: Ask,\n\tio: SecretsAddIo,\n\thas: (key: string) => boolean,\n): Promise<Record<string, string>> {\n\tconst ipv4 = text(\n\t\tawait askUntil<string>(\n\t\t\task,\n\t\t\tio,\n\t\t\t{ type: 'text', message: \"The server's public IPv4 address\" },\n\t\t\t(value) =>\n\t\t\t\tisIPv4(text(value))\n\t\t\t\t\t? undefined\n\t\t\t\t\t: 'An IPv4 address is required, e.g. 203.0.113.10.',\n\t\t),\n\t);\n\tconst values: Record<string, string> = { [SERVER_IPV4_KEY]: ipv4 };\n\tif (has(SERVER_IPV6_KEY)) return values;\n\tconst ipv6 = text(\n\t\tawait askUntil<string>(\n\t\t\task,\n\t\t\tio,\n\t\t\t{\n\t\t\t\ttype: 'text',\n\t\t\t\tmessage: 'Its public IPv6 address (empty for none — no AAAA records)',\n\t\t\t},\n\t\t\t(value) =>\n\t\t\t\t!text(value) || isIPv6(text(value))\n\t\t\t\t\t? undefined\n\t\t\t\t\t: 'An IPv6 address, e.g. 2001:db8::10, or nothing.',\n\t\t),\n\t);\n\tif (ipv6) values[SERVER_IPV6_KEY] = ipv6;\n\treturn values;\n}\n\n/** One question, answered — or the run stopped. */\ntype Ask = <T = string>(question: Omit<PromptObject, 'name'>) => Promise<T>;\n\nfunction asker(stage: string): Ask {\n\treturn async <T>(question: Omit<PromptObject, 'name'>) => {\n\t\tconst answers = await prompts(\n\t\t\t{ ...question, name: 'value' } as PromptObject,\n\t\t\t{\n\t\t\t\tonCancel: () => {\n\t\t\t\t\tthrow new SecretsAddCancelled(stage);\n\t\t\t\t},\n\t\t\t},\n\t\t);\n\t\treturn answers.value as T;\n\t};\n}\n\n/** Ask until `accept` takes the answer; `accept` returns why it did not. */\nasync function askUntil<T>(\n\task: Ask,\n\tio: SecretsAddIo,\n\tquestion: Omit<PromptObject, 'name'>,\n\taccept: (value: T) => string | undefined,\n): Promise<T> {\n\tfor (;;) {\n\t\tconst value = await ask<T>(question);\n\t\tconst problem = accept(value);\n\t\tif (!problem) return value;\n\t\tio.log(` ✗ ${problem}`);\n\t}\n}\n\nconst required =\n\t(what: string) =>\n\t(value: string | undefined): string | undefined =>\n\t\tvalue?.trim() ? undefined : `${what} is required.`;\n\nconst text = (value: unknown) => String(value ?? '').trim();\n\n/** An `http(s)://` URL. */\nconst HttpUrl = z.url({ protocol: /^https?$/ });\n\nasync function askHttpUrl(\n\task: Ask,\n\tio: SecretsAddIo,\n\tmessage: string,\n): Promise<string> {\n\treturn text(\n\t\tawait askUntil<string>(ask, io, { type: 'text', message }, (value) =>\n\t\t\tHttpUrl.safeParse(text(value)).success\n\t\t\t\t? undefined\n\t\t\t\t: 'An http:// or https:// URL is required.',\n\t\t),\n\t);\n}\n\nasync function askFrom(ask: Ask, io: SecretsAddIo): Promise<string> {\n\treturn text(\n\t\tawait askUntil<string>(\n\t\t\task,\n\t\t\tio,\n\t\t\t{ type: 'text', message: 'The address it sends from' },\n\t\t\t(value) =>\n\t\t\t\tz.email().safeParse(text(value)).success\n\t\t\t\t\t? undefined\n\t\t\t\t\t: 'An email address is required, e.g. noreply@example.com.',\n\t\t),\n\t);\n}\n\n/** A mail service whose SMTP settings are known: only its secrets are asked. */\ntype MailService = 'resend' | 'ses' | 'postmark' | 'mailgun' | 'other';\n\n/**\n * An SMTP server as the URL `@geekmidas/emailkit` reads: `smtp://` for\n * STARTTLS, `smtps://` for TLS from the first byte, with the user and\n * password percent-encoded.\n *\n * A known service fills in its own host, port and user; each from its docs:\n * - Resend: smtp.resend.com, 465 implicit TLS, user `resend`, password the\n * API key — https://resend.com/docs/send-with-smtp\n * - Amazon SES: email-smtp.<region>.amazonaws.com, 587 STARTTLS, SMTP\n * credentials — https://docs.aws.amazon.com/ses/latest/dg/smtp-connect.html,\n * https://docs.aws.amazon.com/general/latest/gr/ses.html\n * - Postmark: smtp.postmarkapp.com, 587 STARTTLS, the server API token as\n * user and password —\n * https://postmarkapp.com/developer/user-guide/send-email-with-smtp\n * - Mailgun: smtp.mailgun.org (EU: smtp.eu.mailgun.org), 587 STARTTLS, the\n * domain's SMTP credentials —\n * https://documentation.mailgun.com/docs/mailgun/user-manual/sending-messages/send-smtp,\n * https://documentation.mailgun.com/docs/mailgun/api-reference/api-overview\n */\nasync function buildSmtpUrl(ask: Ask, io: SecretsAddIo): Promise<string> {\n\tconst service = await ask<MailService>({\n\t\ttype: 'select',\n\t\tmessage: 'Which mail service?',\n\t\tchoices: [\n\t\t\t{ title: 'Resend', value: 'resend' },\n\t\t\t{ title: 'Amazon SES', value: 'ses' },\n\t\t\t{ title: 'Postmark', value: 'postmark' },\n\t\t\t{ title: 'Mailgun', value: 'mailgun' },\n\t\t\t{ title: 'Other SMTP server', value: 'other' },\n\t\t],\n\t\tinitial: 0,\n\t});\n\tconst secret = async (message: string, what: string) =>\n\t\ttext(\n\t\t\tawait askUntil<string>(\n\t\t\t\task,\n\t\t\t\tio,\n\t\t\t\t{ type: 'password', message },\n\t\t\t\trequired(what),\n\t\t\t),\n\t\t);\n\tconst login = async (message: string, what: string) =>\n\t\ttext(\n\t\t\tawait askUntil<string>(\n\t\t\t\task,\n\t\t\t\tio,\n\t\t\t\t{ type: 'text', message },\n\t\t\t\trequired(what),\n\t\t\t),\n\t\t);\n\n\tswitch (service) {\n\t\tcase 'resend':\n\t\t\treturn smtpUrl({\n\t\t\t\ttls: 'tls',\n\t\t\t\thost: 'smtp.resend.com',\n\t\t\t\tport: 465,\n\t\t\t\tuser: 'resend',\n\t\t\t\tpassword: await secret('API key (re_…)', 'The API key'),\n\t\t\t});\n\t\tcase 'ses': {\n\t\t\tconst region = await login('Region', 'The region');\n\t\t\treturn smtpUrl({\n\t\t\t\ttls: 'starttls',\n\t\t\t\thost: `email-smtp.${region}.amazonaws.com`,\n\t\t\t\tport: 587,\n\t\t\t\tuser: await login('SMTP user name', 'The SMTP user name'),\n\t\t\t\tpassword: await secret('SMTP password', 'The SMTP password'),\n\t\t\t});\n\t\t}\n\t\tcase 'postmark': {\n\t\t\tconst token = await secret('Server API token', 'The server API token');\n\t\t\treturn smtpUrl({\n\t\t\t\ttls: 'starttls',\n\t\t\t\thost: 'smtp.postmarkapp.com',\n\t\t\t\tport: 587,\n\t\t\t\tuser: token,\n\t\t\t\tpassword: token,\n\t\t\t});\n\t\t}\n\t\tcase 'mailgun': {\n\t\t\tconst region = await ask<'us' | 'eu'>({\n\t\t\t\ttype: 'select',\n\t\t\t\tmessage: 'Region',\n\t\t\t\tchoices: [\n\t\t\t\t\t{ title: 'US (smtp.mailgun.org)', value: 'us' },\n\t\t\t\t\t{ title: 'EU (smtp.eu.mailgun.org)', value: 'eu' },\n\t\t\t\t],\n\t\t\t\tinitial: 0,\n\t\t\t});\n\t\t\treturn smtpUrl({\n\t\t\t\ttls: 'starttls',\n\t\t\t\thost: region === 'eu' ? 'smtp.eu.mailgun.org' : 'smtp.mailgun.org',\n\t\t\t\tport: 587,\n\t\t\t\tuser: await login(\n\t\t\t\t\t'SMTP login (e.g. postmaster@mg.example.com)',\n\t\t\t\t\t'The SMTP login',\n\t\t\t\t),\n\t\t\t\tpassword: await secret('SMTP password', 'The SMTP password'),\n\t\t\t});\n\t\t}\n\t\tcase 'other':\n\t\t\tbreak;\n\t}\n\n\tconst host = text(\n\t\tawait askUntil<string>(\n\t\t\task,\n\t\t\tio,\n\t\t\t{ type: 'text', message: 'SMTP host' },\n\t\t\trequired('The host'),\n\t\t),\n\t);\n\tconst port = await askUntil<string | number>(\n\t\task,\n\t\tio,\n\t\t{ type: 'text', message: 'Port', initial: '587' },\n\t\t(value) => {\n\t\t\tconst n = Number(value);\n\t\t\treturn Number.isInteger(n) && n > 0 && n < 65536\n\t\t\t\t? undefined\n\t\t\t\t: 'A port number is required.';\n\t\t},\n\t);\n\tconst user = text(\n\t\tawait ask<string>({ type: 'text', message: 'User (empty for none)' }),\n\t);\n\tconst password = user\n\t\t? await ask<string>({ type: 'password', message: 'Password' })\n\t\t: '';\n\tconst tls = await ask<'starttls' | 'tls'>({\n\t\ttype: 'select',\n\t\tmessage: 'TLS',\n\t\tchoices: [\n\t\t\t{ title: 'STARTTLS (smtp://, usually port 587)', value: 'starttls' },\n\t\t\t{ title: 'TLS on connect (smtps://, usually port 465)', value: 'tls' },\n\t\t],\n\t\tinitial: 0,\n\t});\n\treturn smtpUrl({ tls, host, port: Number(port), user, password });\n}\n\nfunction smtpUrl(server: {\n\ttls: 'starttls' | 'tls';\n\thost: string;\n\tport: number;\n\tuser?: string;\n\tpassword?: string;\n}): string {\n\tconst url = new URL(\n\t\t`${server.tls === 'tls' ? 'smtps' : 'smtp'}://${server.host}`,\n\t);\n\turl.port = String(server.port);\n\tif (server.user) {\n\t\turl.username = encodeURIComponent(server.user);\n\t\turl.password = encodeURIComponent(server.password ?? '');\n\t}\n\treturn url.toString().replace(/\\/$/, '');\n}\n\n/**\n * A bucket's URL, from its provider — and, optionally, a key for it alone in\n * the URL, or the stage's shared pair. Credentials are never required: without\n * them the SDK's own chain signs, a role included.\n */\nasync function buildBucket(\n\task: Ask,\n\tio: SecretsAddIo,\n\tentry: WorkspaceStageKey,\n\thas: (key: string) => boolean,\n): Promise<Record<string, string>> {\n\tconst provider = await ask<'s3' | 'r2' | 'compatible' | 'url'>({\n\t\ttype: 'select',\n\t\tmessage: 'Where is the bucket?',\n\t\tchoices: [\n\t\t\t{ title: 'AWS S3', value: 's3' },\n\t\t\t{ title: 'Cloudflare R2', value: 'r2' },\n\t\t\t{ title: 'MinIO, or another S3-compatible store', value: 'compatible' },\n\t\t\t{ title: 'Paste an s3:// URL', value: 'url' },\n\t\t],\n\t\tinitial: 0,\n\t});\n\n\tconst bucketName = async () =>\n\t\ttext(\n\t\t\tawait askUntil<string>(\n\t\t\t\task,\n\t\t\t\tio,\n\t\t\t\t{ type: 'text', message: 'Bucket name' },\n\t\t\t\trequired('The bucket name'),\n\t\t\t),\n\t\t);\n\n\tlet address: S3Address;\n\tswitch (provider) {\n\t\tcase 's3': {\n\t\t\tconst bucket = await bucketName();\n\t\t\tconst region = text(\n\t\t\t\tawait askUntil<string>(\n\t\t\t\t\task,\n\t\t\t\t\tio,\n\t\t\t\t\t{ type: 'text', message: 'Region', initial: 'eu-west-1' },\n\t\t\t\t\trequired('The region'),\n\t\t\t\t),\n\t\t\t);\n\t\t\taddress = { bucket, region };\n\t\t\tbreak;\n\t\t}\n\t\tcase 'r2': {\n\t\t\tconst account = text(\n\t\t\t\tawait askUntil<string>(\n\t\t\t\t\task,\n\t\t\t\t\tio,\n\t\t\t\t\t{\n\t\t\t\t\t\ttype: 'text',\n\t\t\t\t\t\tmessage: 'Cloudflare account id, or the R2 endpoint URL',\n\t\t\t\t\t},\n\t\t\t\t\trequired('The account id or endpoint'),\n\t\t\t\t),\n\t\t\t);\n\t\t\tconst bucket = await bucketName();\n\t\t\taddress = {\n\t\t\t\tbucket,\n\t\t\t\tregion: 'auto',\n\t\t\t\tendpoint: /^https?:\\/\\//.test(account)\n\t\t\t\t\t? account\n\t\t\t\t\t: `https://${account}.r2.cloudflarestorage.com`,\n\t\t\t};\n\t\t\tbreak;\n\t\t}\n\t\tcase 'compatible': {\n\t\t\tconst endpoint = await askHttpUrl(ask, io, 'Endpoint URL');\n\t\t\tconst bucket = await bucketName();\n\t\t\tconst region = text(\n\t\t\t\tawait ask<string>({\n\t\t\t\t\ttype: 'text',\n\t\t\t\t\tmessage: 'Region',\n\t\t\t\t\tinitial: 'us-east-1',\n\t\t\t\t}),\n\t\t\t);\n\t\t\tconst forcePathStyle = await ask<boolean>({\n\t\t\t\ttype: 'confirm',\n\t\t\t\tmessage: 'Path-style addressing? (MinIO needs it)',\n\t\t\t\tinitial: true,\n\t\t\t});\n\t\t\taddress = {\n\t\t\t\tbucket,\n\t\t\t\t...(region ? { region } : {}),\n\t\t\t\tendpoint,\n\t\t\t\t...(forcePathStyle ? { forcePathStyle } : {}),\n\t\t\t};\n\t\t\tbreak;\n\t\t}\n\t\tcase 'url': {\n\t\t\tconst pasted = await askUntil<string>(\n\t\t\t\task,\n\t\t\t\tio,\n\t\t\t\t{ type: 'password', message: 'The s3:// URL' },\n\t\t\t\t(value) => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\ts3Parse(text(value));\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\treturn `Not an s3:// URL the storage client can read (${(error as Error).name}).`;\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t);\n\t\t\taddress = s3Parse(text(pasted));\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (!address.accessKeyId) {\n\t\tconst own = await ask<boolean>({\n\t\t\ttype: 'confirm',\n\t\t\tmessage: `A key for '${entry.id}' alone, in its URL? (optional)`,\n\t\t\tinitial: false,\n\t\t});\n\t\tif (own) {\n\t\t\tconst accessKeyId = text(\n\t\t\t\tawait askUntil<string>(\n\t\t\t\t\task,\n\t\t\t\t\tio,\n\t\t\t\t\t{ type: 'text', message: 'Access key id' },\n\t\t\t\t\trequired('The access key id'),\n\t\t\t\t),\n\t\t\t);\n\t\t\tconst secretAccessKey = await askUntil<string>(\n\t\t\t\task,\n\t\t\t\tio,\n\t\t\t\t{ type: 'password', message: 'Secret access key' },\n\t\t\t\trequired('The secret access key'),\n\t\t\t);\n\t\t\taddress = { ...address, accessKeyId, secretAccessKey };\n\t\t}\n\t}\n\n\tconst values: Record<string, string> = { [entry.key]: s3Build(address) };\n\n\tif (!address.accessKeyId && !has('AWS_ACCESS_KEY_ID')) {\n\t\tconst shared = await ask<boolean>({\n\t\t\ttype: 'confirm',\n\t\t\tmessage:\n\t\t\t\t\"Set the stage's shared AWS_ACCESS_KEY_ID/AWS_SECRET_ACCESS_KEY? \" +\n\t\t\t\t'(optional: without them a role signs)',\n\t\t\tinitial: false,\n\t\t});\n\t\tif (shared) {\n\t\t\tvalues.AWS_ACCESS_KEY_ID = text(\n\t\t\t\tawait askUntil<string>(\n\t\t\t\t\task,\n\t\t\t\t\tio,\n\t\t\t\t\t{ type: 'text', message: 'AWS_ACCESS_KEY_ID' },\n\t\t\t\t\trequired('The access key id'),\n\t\t\t\t),\n\t\t\t);\n\t\t\tvalues.AWS_SECRET_ACCESS_KEY = await askUntil<string>(\n\t\t\t\task,\n\t\t\t\tio,\n\t\t\t\t{ type: 'password', message: 'AWS_SECRET_ACCESS_KEY' },\n\t\t\t\trequired('The secret access key'),\n\t\t\t);\n\t\t}\n\t}\n\n\treturn values;\n}\n\n/** A field whose name says it is a secret, asked for without echo. */\nconst SECRET_FIELD = /secret|key|token|password/i;\n\ninterface JsonSchemaProperty {\n\ttype?: string | string[];\n\tenum?: unknown[];\n\tdescription?: string;\n}\n\n/** The properties of an object schema, or nothing for any other shape. */\nfunction objectProperties(\n\tinfo: CredentialSchemaInfo | undefined,\n):\n\t| { properties: Record<string, JsonSchemaProperty>; required: string[] }\n\t| undefined {\n\tconst schema = info?.jsonSchema;\n\tif (!schema || schema.type !== 'object') return undefined;\n\tconst properties = schema.properties;\n\tif (!properties || typeof properties !== 'object') return undefined;\n\treturn {\n\t\tproperties: properties as Record<string, JsonSchemaProperty>,\n\t\trequired: Array.isArray(schema.required)\n\t\t\t? (schema.required as string[])\n\t\t\t: [],\n\t};\n}\n\nfunction typesOf(property: JsonSchemaProperty): string[] {\n\tif (Array.isArray(property.type)) return property.type;\n\treturn property.type ? [property.type] : [];\n}\n\n/**\n * Credentials, field by field where the construct's schema is an object it\n * can describe, and as JSON otherwise — checked against the schema itself\n * before anything is kept, and asked again, with each issue's path, until\n * the schema takes it.\n */\nasync function buildCredentials(\n\task: Ask,\n\tio: SecretsAddIo,\n\tentry: WorkspaceStageKey,\n\tschemas: CredentialSchemas,\n): Promise<string> {\n\tconst info = schemas.schemas.find((s) => s.key === entry.key);\n\tconst shape = objectProperties(info);\n\tif (!info) {\n\t\tio.log(\n\t\t\t` '${entry.id}' exposes no schema to check against; the value is stored as given.`,\n\t\t);\n\t}\n\n\tfor (;;) {\n\t\tconst raw = shape\n\t\t\t? JSON.stringify(await askFields(ask, io, shape))\n\t\t\t: text(\n\t\t\t\t\tawait ask<string>({\n\t\t\t\t\t\ttype: 'password',\n\t\t\t\t\t\tmessage: 'The value, as JSON (or one opaque string)',\n\t\t\t\t\t}),\n\t\t\t\t);\n\n\t\tconst check = await schemas.check(entry.key, raw);\n\t\tif (check.ok) return raw;\n\t\tio.log(` ✗ The schema of '${entry.id}' refuses it:`);\n\t\tfor (const issue of check.issues) {\n\t\t\tio.log(` ${issueLine(entry.key, issue)}`);\n\t\t}\n\t\tio.log(' Again:');\n\t}\n}\n\nasync function askFields(\n\task: Ask,\n\tio: SecretsAddIo,\n\tshape: {\n\t\tproperties: Record<string, JsonSchemaProperty>;\n\t\trequired: string[];\n\t},\n): Promise<Record<string, unknown>> {\n\tconst value: Record<string, unknown> = {};\n\tfor (const [name, property] of Object.entries(shape.properties)) {\n\t\tconst optional = !shape.required.includes(name);\n\t\tconst label = `${name}${optional ? ' (optional)' : ''}${property.description ? ` — ${property.description}` : ''}`;\n\t\tconst types = typesOf(property);\n\n\t\tif (property.enum?.length) {\n\t\t\tconst choices = property.enum.map((option) => ({\n\t\t\t\ttitle: String(option),\n\t\t\t\tvalue: option,\n\t\t\t}));\n\t\t\tconst picked = await ask<unknown>({\n\t\t\t\ttype: 'select',\n\t\t\t\tmessage: label,\n\t\t\t\tchoices: optional\n\t\t\t\t\t? [{ title: '(none)', value: undefined }, ...choices]\n\t\t\t\t\t: choices,\n\t\t\t} as Omit<PromptObject, 'name'>);\n\t\t\tif (picked !== undefined) value[name] = picked;\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (types.includes('boolean')) {\n\t\t\tvalue[name] = await ask<boolean>({\n\t\t\t\ttype: 'confirm',\n\t\t\t\tmessage: label,\n\t\t\t\tinitial: false,\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst numeric = types.includes('number') || types.includes('integer');\n\t\tconst plain = types.length === 0 || types.includes('string');\n\t\tconst answer = await askUntil<string>(\n\t\t\task,\n\t\t\tio,\n\t\t\t{\n\t\t\t\ttype: SECRET_FIELD.test(name) ? 'password' : 'text',\n\t\t\t\tmessage: label,\n\t\t\t},\n\t\t\t(given) => {\n\t\t\t\tconst typed = text(given);\n\t\t\t\tif (!typed) return optional ? undefined : `${name} is required.`;\n\t\t\t\tif (numeric && !plain && Number.isNaN(Number(typed))) {\n\t\t\t\t\treturn `${name} is a number.`;\n\t\t\t\t}\n\t\t\t\tif (!numeric && !plain) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tJSON.parse(typed);\n\t\t\t\t\t} catch {\n\t\t\t\t\t\treturn `${name} is JSON (${types.join(' or ')}).`;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn undefined;\n\t\t\t},\n\t\t);\n\t\tconst typed = text(answer);\n\t\tif (!typed) continue;\n\t\tvalue[name] = plain ? typed : numeric ? Number(typed) : JSON.parse(typed);\n\t}\n\treturn value;\n}\n"],"mappings":"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"}
package/dist/index.cjs CHANGED
@@ -53,7 +53,7 @@ crypto = require_rolldown_runtime.__toESM(crypto, 1);
53
53
  let _geekmidas_constructs_functions = require("@geekmidas/constructs/functions");
54
54
  let _geekmidas_constructs_topic = require("@geekmidas/constructs/topic");
55
55
  //#region package.json
56
- var version = "10.0.0-alpha.90";
56
+ var version = "10.0.0-alpha.91";
57
57
  //#endregion
58
58
  //#region src/prompt.ts
59
59
  /**
@@ -5318,25 +5318,25 @@ function loadPackageJson() {
5318
5318
  * Run: pnpm --filter @geekmidas/cli sync-versions
5319
5319
  */
5320
5320
  const GEEKMIDAS_VERSIONS = {
5321
- "@geekmidas/audit": "~10.0.0-alpha.90",
5322
- "@geekmidas/auth": "~10.0.0-alpha.90",
5323
- "@geekmidas/cache": "~10.0.0-alpha.90",
5324
- "@geekmidas/client": "~10.0.0-alpha.90",
5325
- "@geekmidas/cloud": "~10.0.0-alpha.90",
5326
- "@geekmidas/constructs": "~10.0.0-alpha.90",
5327
- "@geekmidas/db": "~10.0.0-alpha.90",
5328
- "@geekmidas/emailkit": "~10.0.0-alpha.90",
5329
- "@geekmidas/envkit": "~10.0.0-alpha.90",
5330
- "@geekmidas/errors": "~10.0.0-alpha.90",
5331
- "@geekmidas/events": "~10.0.0-alpha.90",
5332
- "@geekmidas/logger": "~10.0.0-alpha.90",
5333
- "@geekmidas/manifest": "~10.0.0-alpha.90",
5334
- "@geekmidas/rate-limit": "~10.0.0-alpha.90",
5335
- "@geekmidas/schema": "~10.0.0-alpha.90",
5336
- "@geekmidas/services": "~10.0.0-alpha.90",
5337
- "@geekmidas/storage": "~10.0.0-alpha.90",
5338
- "@geekmidas/telescope": "~10.0.0-alpha.90",
5339
- "@geekmidas/testkit": "~10.0.0-alpha.90",
5321
+ "@geekmidas/audit": "~10.0.0-alpha.91",
5322
+ "@geekmidas/auth": "~10.0.0-alpha.91",
5323
+ "@geekmidas/cache": "~10.0.0-alpha.91",
5324
+ "@geekmidas/client": "~10.0.0-alpha.91",
5325
+ "@geekmidas/cloud": "~10.0.0-alpha.91",
5326
+ "@geekmidas/constructs": "~10.0.0-alpha.91",
5327
+ "@geekmidas/db": "~10.0.0-alpha.91",
5328
+ "@geekmidas/emailkit": "~10.0.0-alpha.91",
5329
+ "@geekmidas/envkit": "~10.0.0-alpha.91",
5330
+ "@geekmidas/errors": "~10.0.0-alpha.91",
5331
+ "@geekmidas/events": "~10.0.0-alpha.91",
5332
+ "@geekmidas/logger": "~10.0.0-alpha.91",
5333
+ "@geekmidas/manifest": "~10.0.0-alpha.91",
5334
+ "@geekmidas/rate-limit": "~10.0.0-alpha.91",
5335
+ "@geekmidas/schema": "~10.0.0-alpha.91",
5336
+ "@geekmidas/services": "~10.0.0-alpha.91",
5337
+ "@geekmidas/storage": "~10.0.0-alpha.91",
5338
+ "@geekmidas/telescope": "~10.0.0-alpha.91",
5339
+ "@geekmidas/testkit": "~10.0.0-alpha.91",
5340
5340
  "@geekmidas/cli": `~${loadPackageJson().version}`
5341
5341
  };
5342
5342
  //#endregion
@@ -5737,7 +5737,7 @@ const COMMIT = /^[0-9a-f]{40}$/;
5737
5737
  * The `uses:` value for the stages action, with a comment saying which CLI
5738
5738
  * release the pinned commit is.
5739
5739
  */
5740
- function stagesActionUses(commit = "108157b10491802890a494443587d8bbd312f5db", version = cliVersion()) {
5740
+ function stagesActionUses(commit = "b0922ee71407fefc3e94a0352793ef5e78c1bdea", version = cliVersion()) {
5741
5741
  return commit && COMMIT.test(commit) ? `${STAGES_ACTION}@${commit} # @geekmidas/cli ${version}` : `${STAGES_ACTION}@main # pin to a commit SHA: see the action's README`;
5742
5742
  }
5743
5743
  //#endregion
@@ -12202,7 +12202,7 @@ program.command("secrets:add").description("Build the keys a stage must be given
12202
12202
  try {
12203
12203
  const globalOptions = program.opts();
12204
12204
  if (globalOptions.cwd) process.chdir(globalOptions.cwd);
12205
- const { secretsAddCommand } = await Promise.resolve().then(() => require("./add-CNMF0Q3m.cjs"));
12205
+ const { secretsAddCommand } = await Promise.resolve().then(() => require("./add-DmmBDV2e.cjs"));
12206
12206
  await secretsAddCommand(options);
12207
12207
  } catch (error) {
12208
12208
  console.error(formatError(error));