@iann29/synapse 1.8.5 → 1.8.7

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.
package/lib/prompts.js CHANGED
@@ -187,6 +187,55 @@ async function choose(label, choices, {
187
187
  }
188
188
  }
189
189
 
190
+ // typedConfirm prompts the user to literally re-type a string (typically
191
+ // the name of a resource about to be destroyed) before proceeding. Match
192
+ // is case-sensitive and exact — `--yes` does NOT bypass this prompt,
193
+ // only an explicit pre-supplied `typed` answer does (for non-interactive
194
+ // CI where the operator already scripted the confirmation).
195
+ //
196
+ // Why we need this in addition to `confirm`: y/n prompts get muscle-
197
+ // memory-yes'd. Typing the literal name of a prod deployment makes the
198
+ // operator look at what they're deleting one more time. Mirrors the
199
+ // dashboard's `delete-deployment` flow shipped in v1.7.2.
200
+ async function typedConfirm(label, expected, {
201
+ input = process.stdin,
202
+ output = process.stderr,
203
+ typed = null,
204
+ maxAttempts = 3,
205
+ } = {}) {
206
+ if (typeof expected !== "string" || expected === "") {
207
+ throw new Error("typedConfirm requires a non-empty `expected` string");
208
+ }
209
+ // Non-interactive shortcut: caller pre-supplied the typed string via
210
+ // a flag (`--confirm=<name>`). We still require exact match; passing
211
+ // the wrong value should NOT proceed even if the caller meant well.
212
+ if (typed !== null) {
213
+ return typed === expected;
214
+ }
215
+ if (!input.isTTY) {
216
+ throw new Error(
217
+ `Refusing to ${label} without a TTY. Re-run with --confirm=${expected} to confirm non-interactively.`,
218
+ );
219
+ }
220
+ const rl = readline.createInterface({ input, output });
221
+ try {
222
+ for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
223
+ const raw = await new Promise((resolve) =>
224
+ rl.question(`Type ${expected} to confirm ${label}: `, resolve),
225
+ );
226
+ const answer = raw.trim();
227
+ if (answer === expected) return true;
228
+ if (answer === "" || answer.toLowerCase() === "n" || answer.toLowerCase() === "no") {
229
+ return false;
230
+ }
231
+ output.write(`Doesn't match. Type the exact name "${expected}" or press Enter to cancel.\n`);
232
+ }
233
+ return false;
234
+ } finally {
235
+ rl.close();
236
+ }
237
+ }
238
+
190
239
  module.exports = {
191
240
  BACK,
192
241
  ask,
@@ -195,4 +244,5 @@ module.exports = {
195
244
  choose,
196
245
  confirm,
197
246
  parseCredentialsInput,
247
+ typedConfirm,
198
248
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iann29/synapse",
3
- "version": "1.8.5",
3
+ "version": "1.8.7",
4
4
  "description": "Thin CLI wrapper for using the official Convex CLI with Synapse-managed deployments.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {