usertold 1.9.0 → 1.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/usertold +103 -23
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "usertold",
3
- "version": "1.9.0",
3
+ "version": "1.9.2",
4
4
  "description": "UserTold.ai CLI",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
package/usertold CHANGED
@@ -310,8 +310,8 @@ class HttpError extends CliError {
310
310
  this.details = details;
311
311
  }
312
312
  }
313
- async function requestJson(input) {
314
- const response = await requestRaw(input);
313
+ async function requestJson(input, retryOptions) {
314
+ const response = await requestRaw(input, retryOptions);
315
315
  if (!response.ok) {
316
316
  throw new HttpError(response.status, extractErrorDetails(response));
317
317
  }
@@ -371,7 +371,7 @@ async function requestJson(input) {
371
371
  const arrayBuffer = await response.arrayBuffer();
372
372
  return Buffer.from(arrayBuffer);
373
373
  }
374
- async function requestRaw(input) {
374
+ async function requestRaw(input, retryOptions) {
375
375
  const method = input.method.toUpperCase();
376
376
  const authMode = input.authMode ?? 'required';
377
377
  const headers = {
@@ -395,21 +395,41 @@ async function requestRaw(input) {
395
395
  }
396
396
  const baseUrl = resolveBaseUrl(input.env);
397
397
  const path = input.path.startsWith('/') ? input.path : `/${input.path}`;
398
- const response = await fetch(`${baseUrl}${path}`, {
399
- method,
400
- headers,
401
- body
402
- });
403
- const text = await response.text();
404
- const contentType = response.headers.get('content-type') ?? '';
405
- const json = contentType.includes('application/json') && text.length > 0 ? safeParseJson(text) : null;
406
- return {
407
- status: response.status,
408
- ok: response.ok,
409
- headers: response.headers,
410
- text,
411
- json
412
- };
398
+ const url = `${baseUrl}${path}`;
399
+ const retries = retryOptions?.retries ?? 0;
400
+ const initialDelayMs = retryOptions?.initialDelayMs ?? 500;
401
+ const maxDelayMs = retryOptions?.maxDelayMs ?? 4_000;
402
+ let delayMs = initialDelayMs;
403
+ let lastError;
404
+ for(let attempt = 0; attempt <= retries; attempt++){
405
+ try {
406
+ const response = await fetch(url, {
407
+ method,
408
+ headers,
409
+ body
410
+ });
411
+ const parsed = await toApiResponse(response);
412
+ if (attempt >= retries || !retryOptions?.shouldRetry?.({
413
+ attempt,
414
+ response: parsed
415
+ })) {
416
+ return parsed;
417
+ }
418
+ const retryAfterMs = parseRetryAfterMs(parsed.headers.get('Retry-After'));
419
+ await sleep$1(jitter$1(retryAfterMs ?? delayMs));
420
+ } catch (error) {
421
+ lastError = error;
422
+ if (attempt >= retries || !retryOptions?.shouldRetry?.({
423
+ attempt,
424
+ error
425
+ })) {
426
+ throw error;
427
+ }
428
+ await sleep$1(jitter$1(delayMs));
429
+ }
430
+ delayMs = Math.min(maxDelayMs, delayMs * 2);
431
+ }
432
+ throw lastError ?? new Error('Request failed');
413
433
  }
414
434
  async function resolveToken(env, authMode) {
415
435
  if (authMode === 'none') {
@@ -435,6 +455,9 @@ async function resolveToken(env, authMode) {
435
455
  return config.token.accessToken;
436
456
  }
437
457
  function extractErrorDetails(response) {
458
+ if (isCloudflare1010Response(response)) {
459
+ return 'Cloudflare blocked this request (error 1010). Wait a few seconds and retry, or run pushes sequentially.';
460
+ }
438
461
  if (response.json && typeof response.json === 'object' && response.json !== null) {
439
462
  const body = response.json;
440
463
  const maybeErrorCode = body.error_code;
@@ -472,6 +495,43 @@ function safeParseJson(text) {
472
495
  return null;
473
496
  }
474
497
  }
498
+ async function toApiResponse(response) {
499
+ const text = await response.text();
500
+ const contentType = response.headers.get('content-type') ?? '';
501
+ const json = contentType.includes('application/json') && text.length > 0 ? safeParseJson(text) : null;
502
+ return {
503
+ status: response.status,
504
+ ok: response.ok,
505
+ headers: response.headers,
506
+ text,
507
+ json
508
+ };
509
+ }
510
+ function parseRetryAfterMs(value) {
511
+ if (!value) return null;
512
+ const seconds = Number(value);
513
+ if (!Number.isNaN(seconds) && seconds >= 0) {
514
+ return seconds * 1000;
515
+ }
516
+ const dateMs = Date.parse(value);
517
+ if (Number.isNaN(dateMs)) {
518
+ return null;
519
+ }
520
+ return Math.max(0, dateMs - Date.now());
521
+ }
522
+ function jitter$1(ms) {
523
+ return ms * (0.7 + Math.random() * 0.6);
524
+ }
525
+ function sleep$1(ms) {
526
+ return new Promise((resolve)=>setTimeout(resolve, ms));
527
+ }
528
+ function isCloudflare1010Response(response) {
529
+ const text = response.text.toLowerCase();
530
+ if (text.includes('error code 1010') || text.includes('error code: 1010')) {
531
+ return true;
532
+ }
533
+ return text.includes('access denied') && text.includes("browser's signature");
534
+ }
475
535
 
476
536
  /**
477
537
  * Returns true when output should be JSON:
@@ -9280,7 +9340,8 @@ function requestProjectContract(options) {
9280
9340
  body: options.body,
9281
9341
  authMode: options.authMode,
9282
9342
  projectKey: options.projectKey,
9283
- headers: options.headers
9343
+ headers: options.headers,
9344
+ retryOptions: options.retryOptions
9284
9345
  };
9285
9346
  return doRequestContract({
9286
9347
  env: options.env
@@ -9347,7 +9408,7 @@ async function doRequestContract(defaults, key, options) {
9347
9408
  authMode: options.authMode ?? defaults.authMode,
9348
9409
  projectKey: options.projectKey ?? defaults.projectKey,
9349
9410
  headers: mergeHeaders(defaults.headers, options.headers)
9350
- });
9411
+ }, options.retryOptions);
9351
9412
  }
9352
9413
  function mergeHeaders(defaults, overrides) {
9353
9414
  if (!defaults && !overrides) {
@@ -11050,6 +11111,12 @@ const FLAGS$a = {
11050
11111
  ],
11051
11112
  'push-status': []
11052
11113
  };
11114
+ const TASK_PUSH_RETRY_OPTIONS = {
11115
+ retries: 4,
11116
+ initialDelayMs: 500,
11117
+ maxDelayMs: 4_000,
11118
+ shouldRetry: ({ response, error })=>isRetryableTaskPushFailure(response, error)
11119
+ };
11053
11120
  async function handleTaskCommand(subcommand, parsed) {
11054
11121
  if (!subcommand || hasHelpFlag(parsed) || subcommand === 'help' || subcommand === '--help' || subcommand === '-h') {
11055
11122
  printTaskHelp();
@@ -11279,7 +11346,8 @@ async function handleTaskCommand(subcommand, parsed) {
11279
11346
  sourceLabel: '<projectRef>',
11280
11347
  pathParams: {
11281
11348
  taskId: taskId
11282
- }
11349
+ },
11350
+ retryOptions: TASK_PUSH_RETRY_OPTIONS
11283
11351
  });
11284
11352
  printOutput(data, parsed);
11285
11353
  return;
@@ -11354,6 +11422,18 @@ Examples:
11354
11422
  function printTaskHelp() {
11355
11423
  console.log(TASK_HELP);
11356
11424
  }
11425
+ function isRetryableTaskPushFailure(response, error) {
11426
+ if (response) {
11427
+ if (response.status === 429) {
11428
+ return true;
11429
+ }
11430
+ if (response.status >= 500 && response.status <= 504) {
11431
+ return true;
11432
+ }
11433
+ return response.status === 403 && isCloudflare1010Response(response);
11434
+ }
11435
+ return error instanceof Error;
11436
+ }
11357
11437
 
11358
11438
  const FLAGS$9 = {
11359
11439
  list: [],
@@ -14633,7 +14713,7 @@ function printExtractHelp() {
14633
14713
  console.log(EXTRACT_HELP);
14634
14714
  }
14635
14715
 
14636
- const CLI_VERSION$1 = '1.9.0';
14716
+ const CLI_VERSION$1 = '1.9.2';
14637
14717
  const GLOBAL_FLAGS = [
14638
14718
  'env',
14639
14719
  'json',
@@ -14929,7 +15009,7 @@ function printCompletionsHelp() {
14929
15009
  console.log(COMPLETIONS_HELP);
14930
15010
  }
14931
15011
 
14932
- const CLI_VERSION = '1.9.0';
15012
+ const CLI_VERSION = '1.9.2';
14933
15013
  function detectJsonMode() {
14934
15014
  const argv = process$2.argv.slice(2);
14935
15015
  if (argv.includes('--json')) return true;