@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.
@@ -1,9 +1,9 @@
1
1
  import { g as url, o as email } from "./schemas-D-xeKJDD.mjs";
2
2
  import { r as discover } from "./discover-BPelzeGW.mjs";
3
3
  import { t as appKey } from "./derive-B4vDpzgP.mjs";
4
- import { N as assertDeployedStage, at as SERVER_IPV6_KEY, it as SERVER_IPV4_KEY } from "./workspace-CJ0lKFq-.mjs";
4
+ import { N as assertDeployedStage, U as stageProvider, at as SERVER_IPV6_KEY, it as SERVER_IPV4_KEY } from "./workspace-CJ0lKFq-.mjs";
5
5
  import { loadWorkspaceSettings } from "./config.mjs";
6
- import { Bt as appEnvKeys, Jn as initStageSecrets, Mn as secretsStoreFor, d as stageProviderNotes, ft as constructGlobs, nt as requiredStageKeys } from "./seeds-odfgSCmu.mjs";
6
+ import { Bt as appEnvKeys, Jn as initStageSecrets, Mn as secretsStoreFor, d as stageProviderNotes, ft as constructGlobs, nt as requiredStageKeys, u as provisionCommand } from "./seeds-odfgSCmu.mjs";
7
7
  import { i as issueLine, o as loadCredentialSchemas } from "./credentialSchemas-BJqRJNK4.mjs";
8
8
  import { t as deploysWithCompose } from "./index.mjs";
9
9
  import { provideKey } from "@geekmidas/manifest";
@@ -111,6 +111,13 @@ function workspaceStageKeys(input) {
111
111
  * construct's schema — checked, and saved through the stage's own store, the
112
112
  * one `gkm secrets:set` writes. No value is ever printed.
113
113
  *
114
+ * The keys are walked one at a time — unset ones first — and each is a
115
+ * checkpoint: set it now, skip it, or stop here. A key is saved the moment it
116
+ * is built, so stopping, Ctrl-C or a failure keeps every key before it, and
117
+ * running the command again picks up what is still missing. A key a provider
118
+ * on the stage creates (a bucket under `deploy.objects.<stage>`, written by
119
+ * `gkm setup`) is not a checkpoint: it is listed, with what creates it.
120
+ *
114
121
  * `--json` asks nothing: it prints the keys, for a script or an agent to set
115
122
  * with `gkm secrets:set`.
116
123
  */
@@ -123,11 +130,14 @@ var SecretsAddNeedsTerminal = class extends Error {
123
130
  this.name = "SecretsAddNeedsTerminal";
124
131
  }
125
132
  };
126
- /** The person stopped the builder; nothing was saved. */
133
+ /**
134
+ * The person stopped the builder at a prompt (Ctrl-C). Every key saved
135
+ * before it is kept; the run ends with what was saved and what is missing.
136
+ */
127
137
  var SecretsAddCancelled = class extends Error {
128
138
  stage;
129
139
  constructor(stage) {
130
- super(`Stopped. Nothing was saved to the stage '${stage}'.`);
140
+ super(`Stopped. Every key saved to the stage '${stage}' before it is kept.`);
131
141
  this.stage = stage;
132
142
  this.name = "SecretsAddCancelled";
133
143
  }
@@ -164,98 +174,142 @@ async function secretsAddCommand(options, io = defaultIo()) {
164
174
  providers: stageProviderNotes(workspace, stage),
165
175
  ...!local && domain && deploysWithCompose(workspace) ? { server: true } : {}
166
176
  });
167
- const keys = options.missing ? all.filter((k) => !k.set) : all;
177
+ const scope = [...all.filter((k) => !k.set), ...all.filter((k) => k.set)];
178
+ const keys = options.missing ? scope.filter((k) => !k.set) : scope;
179
+ const objects = stageProvider(workspace, "objects", stage);
180
+ const provisioned = (k) => objects.mode === "provider" && k.hint !== void 0 && (k.kind === "bucket" || k.kind === "file-server");
168
181
  if (options.json) {
169
182
  const listed = keys.map((k) => ({
170
183
  key: k.key,
171
184
  kind: k.kind,
172
185
  construct: k.id,
173
186
  apps: [...k.apps ?? []],
174
- set: k.set
187
+ set: k.set,
188
+ ...provisioned(k) ? { provisioned: true } : {}
175
189
  }));
176
190
  io.write(`${JSON.stringify(listed, null, 2)}\n`);
177
191
  return {
178
192
  keys,
179
- saved: []
193
+ saved: [],
194
+ skipped: [],
195
+ missing: []
180
196
  };
181
197
  }
182
198
  if (!io.interactive) throw new SecretsAddNeedsTerminal(stage);
183
- if (keys.length === 0) {
184
- 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.`);
199
+ const offered = keys.filter((k) => !provisioned(k));
200
+ const created = keys.filter(provisioned);
201
+ if (created.length > 0 && objects.mode === "provider") {
202
+ io.log("Not offered — a provider on the stage creates them:");
203
+ for (const k of created) io.log(` ${k.key} — created by ${provisionCommand(stage)} (deploy.objects.${stage} is ${objects.name})`);
204
+ }
205
+ if (offered.length === 0) {
206
+ 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.`);
185
207
  return {
186
- keys,
187
- saved: []
208
+ keys: offered,
209
+ saved: [],
210
+ skipped: [],
211
+ missing: []
188
212
  };
189
213
  }
190
214
  const ask = asker(stage);
191
- const chosen = await ask({
192
- type: "multiselect",
193
- message: `Which keys for '${stage}'? (${store.name} store)`,
194
- choices: keys.map((k) => ({
195
- title: `${k.key} ${KIND_LABEL[k.kind]} '${k.id}'${k.apps?.length ? ` · ${k.apps.join(", ")}` : ""}${k.set ? " · set" : ""}`,
196
- value: k.key,
197
- selected: !k.set
198
- })),
199
- instructions: false,
200
- hint: "space to toggle, enter to continue"
201
- });
202
- const selected = keys.filter((k) => (chosen ?? []).includes(k.key));
203
- const values = {};
204
- const has = (key) => values[key] !== void 0 || stored?.custom?.[key] !== void 0;
215
+ let current = stored ?? initStageSecrets(stage);
216
+ const saved = [];
217
+ const skipped = [];
218
+ const has = (key) => current.custom?.[key] !== void 0;
205
219
  let schemas;
206
- for (const entry of selected) {
207
- if (entry.set) {
208
- if (!await ask({
209
- type: "confirm",
210
- message: `${entry.key} is set. Replace it?`,
211
- initial: false
212
- })) continue;
213
- }
214
- io.log(`\n${entry.key} — ${entry.what}`);
215
- if (entry.hint) io.log(` ${entry.hint}`);
216
- switch (entry.kind) {
217
- case "bucket":
218
- Object.assign(values, await buildBucket(ask, io, entry, has));
219
- break;
220
- case "email":
221
- values[entry.key] = entry.key.endsWith("_FROM") ? await askFrom(ask, io) : await buildSmtpUrl(ask, io);
222
- break;
223
- case "file-server":
224
- values[entry.key] = await askHttpUrl(ask, io, "Its public URL");
225
- break;
226
- case "server":
227
- Object.assign(values, await buildServer(ask, io, has));
228
- break;
229
- case "external-api":
230
- case "credential":
231
- schemas ??= await loadCredentialSchemas({
232
- root: workspace.root,
233
- patterns
234
- });
235
- values[entry.key] = await buildCredentials(ask, io, entry, schemas);
220
+ const save = async (values) => {
221
+ current = {
222
+ ...current,
223
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
224
+ custom: {
225
+ ...current.custom,
226
+ ...values
227
+ }
228
+ };
229
+ await store.write(stage, current);
230
+ saved.push(...Object.keys(values));
231
+ io.log(` ✓ Saved ${Object.keys(values).join(", ")}.`);
232
+ };
233
+ const walk = async () => {
234
+ for (const [index, entry] of offered.entries()) {
235
+ io.log(`\n[${index + 1}/${offered.length}] ${entry.key} — ${entry.what}`);
236
+ if (entry.hint) io.log(` ${entry.hint}`);
237
+ const step = await ask({
238
+ type: "select",
239
+ message: `${entry.key} ${KIND_LABEL[entry.kind]} '${entry.id}'${entry.apps?.length ? ` · ${entry.apps.join(", ")}` : ""}${entry.set ? " · set" : ""}`,
240
+ choices: [
241
+ {
242
+ title: "Set it now",
243
+ value: "set"
244
+ },
245
+ {
246
+ title: "Skip",
247
+ value: "skip"
248
+ },
249
+ {
250
+ title: "Stop here",
251
+ value: "stop"
252
+ }
253
+ ],
254
+ initial: 0
255
+ });
256
+ if (step === "stop") return;
257
+ if (step !== "set") {
258
+ skipped.push(entry.key);
259
+ continue;
260
+ }
261
+ if (entry.set) {
262
+ if (!await ask({
263
+ type: "confirm",
264
+ message: `${entry.key} is set. Replace it?`,
265
+ initial: false
266
+ })) {
267
+ skipped.push(entry.key);
268
+ continue;
269
+ }
270
+ }
271
+ switch (entry.kind) {
272
+ case "bucket":
273
+ await save(await buildBucket(ask, io, entry, has));
274
+ break;
275
+ case "email":
276
+ await save({ [entry.key]: entry.key.endsWith("_FROM") ? await askFrom(ask, io) : await buildSmtpUrl(ask, io) });
277
+ break;
278
+ case "file-server":
279
+ await save({ [entry.key]: await askHttpUrl(ask, io, "Its public URL") });
280
+ break;
281
+ case "server":
282
+ await save(await buildServer(ask, io, has));
283
+ break;
284
+ case "external-api":
285
+ case "credential":
286
+ schemas ??= await loadCredentialSchemas({
287
+ root: workspace.root,
288
+ patterns
289
+ });
290
+ await save({ [entry.key]: await buildCredentials(ask, io, entry, schemas) });
291
+ }
236
292
  }
293
+ };
294
+ try {
295
+ await walk();
296
+ } catch (error) {
297
+ if (!(error instanceof SecretsAddCancelled)) throw error;
298
+ io.log(`\n${error.message}`);
237
299
  }
238
- const saved = Object.keys(values);
239
- if (saved.length === 0) {
240
- io.log("\nNothing was saved.");
241
- return {
242
- keys,
243
- saved
244
- };
300
+ const missing = all.filter((k) => !provisioned(k) && !has(k.key)).map((k) => k.key);
301
+ io.log("");
302
+ io.log(saved.length > 0 ? `✓ Saved to the stage '${stage}' (${store.name} store): ${saved.join(", ")}` : "Nothing was saved.");
303
+ if (skipped.length > 0) io.log(` Skipped: ${skipped.join(", ")}`);
304
+ if (missing.length > 0) {
305
+ io.log(` Still missing: ${missing.join(", ")}`);
306
+ io.log(` Run gkm secrets:add --stage ${stage} --missing again to pick up where this left off.`);
245
307
  }
246
- const base = stored ?? initStageSecrets(stage);
247
- await store.write(stage, {
248
- ...base,
249
- updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
250
- custom: {
251
- ...base.custom,
252
- ...values
253
- }
254
- });
255
- io.log(`\n✓ Saved ${saved.join(", ")} to the stage '${stage}' (${store.name} store).`);
256
308
  return {
257
- keys,
258
- saved
309
+ keys: offered,
310
+ saved,
311
+ skipped,
312
+ missing
259
313
  };
260
314
  }
261
315
  const KIND_LABEL = {
@@ -320,8 +374,101 @@ async function askFrom(ask, io) {
320
374
  * An SMTP server as the URL `@geekmidas/emailkit` reads: `smtp://` for
321
375
  * STARTTLS, `smtps://` for TLS from the first byte, with the user and
322
376
  * password percent-encoded.
377
+ *
378
+ * A known service fills in its own host, port and user; each from its docs:
379
+ * - Resend: smtp.resend.com, 465 implicit TLS, user `resend`, password the
380
+ * API key — https://resend.com/docs/send-with-smtp
381
+ * - Amazon SES: email-smtp.<region>.amazonaws.com, 587 STARTTLS, SMTP
382
+ * credentials — https://docs.aws.amazon.com/ses/latest/dg/smtp-connect.html,
383
+ * https://docs.aws.amazon.com/general/latest/gr/ses.html
384
+ * - Postmark: smtp.postmarkapp.com, 587 STARTTLS, the server API token as
385
+ * user and password —
386
+ * https://postmarkapp.com/developer/user-guide/send-email-with-smtp
387
+ * - Mailgun: smtp.mailgun.org (EU: smtp.eu.mailgun.org), 587 STARTTLS, the
388
+ * domain's SMTP credentials —
389
+ * https://documentation.mailgun.com/docs/mailgun/user-manual/sending-messages/send-smtp,
390
+ * https://documentation.mailgun.com/docs/mailgun/api-reference/api-overview
323
391
  */
324
392
  async function buildSmtpUrl(ask, io) {
393
+ const service = await ask({
394
+ type: "select",
395
+ message: "Which mail service?",
396
+ choices: [
397
+ {
398
+ title: "Resend",
399
+ value: "resend"
400
+ },
401
+ {
402
+ title: "Amazon SES",
403
+ value: "ses"
404
+ },
405
+ {
406
+ title: "Postmark",
407
+ value: "postmark"
408
+ },
409
+ {
410
+ title: "Mailgun",
411
+ value: "mailgun"
412
+ },
413
+ {
414
+ title: "Other SMTP server",
415
+ value: "other"
416
+ }
417
+ ],
418
+ initial: 0
419
+ });
420
+ const secret = async (message, what) => text(await askUntil(ask, io, {
421
+ type: "password",
422
+ message
423
+ }, required(what)));
424
+ const login = async (message, what) => text(await askUntil(ask, io, {
425
+ type: "text",
426
+ message
427
+ }, required(what)));
428
+ switch (service) {
429
+ case "resend": return smtpUrl({
430
+ tls: "tls",
431
+ host: "smtp.resend.com",
432
+ port: 465,
433
+ user: "resend",
434
+ password: await secret("API key (re_…)", "The API key")
435
+ });
436
+ case "ses": return smtpUrl({
437
+ tls: "starttls",
438
+ host: `email-smtp.${await login("Region", "The region")}.amazonaws.com`,
439
+ port: 587,
440
+ user: await login("SMTP user name", "The SMTP user name"),
441
+ password: await secret("SMTP password", "The SMTP password")
442
+ });
443
+ case "postmark": {
444
+ const token = await secret("Server API token", "The server API token");
445
+ return smtpUrl({
446
+ tls: "starttls",
447
+ host: "smtp.postmarkapp.com",
448
+ port: 587,
449
+ user: token,
450
+ password: token
451
+ });
452
+ }
453
+ case "mailgun": return smtpUrl({
454
+ tls: "starttls",
455
+ host: await ask({
456
+ type: "select",
457
+ message: "Region",
458
+ choices: [{
459
+ title: "US (smtp.mailgun.org)",
460
+ value: "us"
461
+ }, {
462
+ title: "EU (smtp.eu.mailgun.org)",
463
+ value: "eu"
464
+ }],
465
+ initial: 0
466
+ }) === "eu" ? "smtp.eu.mailgun.org" : "smtp.mailgun.org",
467
+ port: 587,
468
+ user: await login("SMTP login (e.g. postmaster@mg.example.com)", "The SMTP login"),
469
+ password: await secret("SMTP password", "The SMTP password")
470
+ });
471
+ }
325
472
  const host = text(await askUntil(ask, io, {
326
473
  type: "text",
327
474
  message: "SMTP host"
@@ -342,23 +489,31 @@ async function buildSmtpUrl(ask, io) {
342
489
  type: "password",
343
490
  message: "Password"
344
491
  }) : "";
345
- const tls = await ask({
346
- type: "select",
347
- message: "TLS",
348
- choices: [{
349
- title: "STARTTLS (smtp://, usually port 587)",
350
- value: "starttls"
351
- }, {
352
- title: "TLS on connect (smtps://, usually port 465)",
353
- value: "tls"
354
- }],
355
- initial: 0
492
+ return smtpUrl({
493
+ tls: await ask({
494
+ type: "select",
495
+ message: "TLS",
496
+ choices: [{
497
+ title: "STARTTLS (smtp://, usually port 587)",
498
+ value: "starttls"
499
+ }, {
500
+ title: "TLS on connect (smtps://, usually port 465)",
501
+ value: "tls"
502
+ }],
503
+ initial: 0
504
+ }),
505
+ host,
506
+ port: Number(port),
507
+ user,
508
+ password
356
509
  });
357
- const url = new URL(`${tls === "tls" ? "smtps" : "smtp"}://${host}`);
358
- url.port = String(Number(port));
359
- if (user) {
360
- url.username = encodeURIComponent(user);
361
- url.password = encodeURIComponent(password ?? "");
510
+ }
511
+ function smtpUrl(server) {
512
+ const url = new URL(`${server.tls === "tls" ? "smtps" : "smtp"}://${server.host}`);
513
+ url.port = String(server.port);
514
+ if (server.user) {
515
+ url.username = encodeURIComponent(server.user);
516
+ url.password = encodeURIComponent(server.password ?? "");
362
517
  }
363
518
  return url.toString().replace(/\/$/, "");
364
519
  }
@@ -589,4 +744,4 @@ async function askFields(ask, io, shape) {
589
744
  //#endregion
590
745
  export { secretsAddCommand };
591
746
 
592
- //# sourceMappingURL=add-DHk1roh_.mjs.map
747
+ //# sourceMappingURL=add-BSHk5Ngn.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"add-BSHk5Ngn.mjs","names":["z.url","z.email","s3Parse","s3Build"],"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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"}