jiradc-cli 1.0.31 → 1.0.32
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/dist/index.js +104 -12
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -263,10 +263,55 @@ var TYPE_EXIT = {
|
|
|
263
263
|
auth: EXIT.AUTH,
|
|
264
264
|
rate_limited: EXIT.GENERIC,
|
|
265
265
|
server: EXIT.GENERIC,
|
|
266
|
+
timeout: EXIT.GENERIC,
|
|
266
267
|
network: EXIT.GENERIC,
|
|
267
268
|
unknown: EXIT.GENERIC
|
|
268
269
|
};
|
|
269
|
-
var
|
|
270
|
+
var TRANSPORT_CODES = {
|
|
271
|
+
/** Our own timeout fired: connected (or tried to), no response in time. */
|
|
272
|
+
readTimeout: ["ECONNABORTED"],
|
|
273
|
+
/** The OS gave up establishing the connection. */
|
|
274
|
+
connectTimeout: ["ETIMEDOUT"],
|
|
275
|
+
/** No such host, or DNS itself is unavailable. */
|
|
276
|
+
unresolved: ["ENOTFOUND", "EAI_AGAIN"],
|
|
277
|
+
/** Host is there, nothing is listening on that port. */
|
|
278
|
+
refused: ["ECONNREFUSED"],
|
|
279
|
+
/** Established, then died mid-flight. */
|
|
280
|
+
dropped: ["ECONNRESET", "EPIPE"]
|
|
281
|
+
};
|
|
282
|
+
function humanMs(ms) {
|
|
283
|
+
return ms % 1e3 === 0 ? `${ms / 1e3}s` : `${ms}ms`;
|
|
284
|
+
}
|
|
285
|
+
function targetHost(err) {
|
|
286
|
+
const config = err?.config;
|
|
287
|
+
const raw = config?.baseURL ?? config?.url;
|
|
288
|
+
if (raw === void 0 || raw === "")
|
|
289
|
+
return void 0;
|
|
290
|
+
try {
|
|
291
|
+
return new URL(raw).host;
|
|
292
|
+
} catch {
|
|
293
|
+
return void 0;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
function configuredTimeoutMs(err) {
|
|
297
|
+
const t = err?.config?.timeout;
|
|
298
|
+
return typeof t === "number" && t > 0 ? t : void 0;
|
|
299
|
+
}
|
|
300
|
+
function retryAfterSeconds(err) {
|
|
301
|
+
const headers = err?.response?.headers;
|
|
302
|
+
const raw = headers?.["retry-after"];
|
|
303
|
+
if (raw === void 0 || raw === "")
|
|
304
|
+
return void 0;
|
|
305
|
+
const seconds = Number(raw);
|
|
306
|
+
if (Number.isFinite(seconds))
|
|
307
|
+
return Math.max(0, Math.round(seconds));
|
|
308
|
+
if (typeof raw !== "string")
|
|
309
|
+
return void 0;
|
|
310
|
+
const at = Date.parse(raw);
|
|
311
|
+
if (Number.isNaN(at))
|
|
312
|
+
return void 0;
|
|
313
|
+
return Math.max(0, Math.round((at - Date.now()) / 1e3));
|
|
314
|
+
}
|
|
270
315
|
function httpStatus(err) {
|
|
271
316
|
const e = err;
|
|
272
317
|
return e?.response?.status ?? e?.statusCode;
|
|
@@ -366,21 +411,24 @@ function normalize(err, opts) {
|
|
|
366
411
|
retryable: false,
|
|
367
412
|
detail
|
|
368
413
|
};
|
|
369
|
-
case 429:
|
|
414
|
+
case 429: {
|
|
415
|
+
const wait = retryAfterSeconds(err);
|
|
370
416
|
return {
|
|
371
417
|
type: "rate_limited",
|
|
372
418
|
status,
|
|
373
|
-
message:
|
|
374
|
-
recovery:
|
|
375
|
-
retryable: true
|
|
419
|
+
message: `${opts.service} is rate limiting these requests`,
|
|
420
|
+
recovery: wait === void 0 ? `${opts.service} did not say how long to wait. Pause a few seconds before retrying, and make fewer requests at once.` : `${opts.service} asked for a ${wait}s pause before the next request. Wait at least that long, then retry.`,
|
|
421
|
+
retryable: true,
|
|
422
|
+
...wait === void 0 ? {} : { detail: { retryAfterSeconds: wait } }
|
|
376
423
|
};
|
|
424
|
+
}
|
|
377
425
|
}
|
|
378
426
|
if (status >= 500) {
|
|
379
427
|
return {
|
|
380
428
|
type: "server",
|
|
381
429
|
status,
|
|
382
|
-
message:
|
|
383
|
-
recovery: `${opts.service}
|
|
430
|
+
message: status === 503 ? `${opts.service} returned 503 (service unavailable)` : `${opts.service} returned ${status}`,
|
|
431
|
+
recovery: status === 503 ? `${opts.service} is temporarily refusing work. Retry shortly.` : `${opts.service} failed to complete the request. Usually temporary, so retry. If it persists, the request itself may be at fault.`,
|
|
384
432
|
retryable: true,
|
|
385
433
|
detail
|
|
386
434
|
};
|
|
@@ -395,15 +443,59 @@ function normalize(err, opts) {
|
|
|
395
443
|
};
|
|
396
444
|
}
|
|
397
445
|
const code = errorCode(err);
|
|
398
|
-
|
|
446
|
+
const transport = classifyTransport(err, code, message, opts);
|
|
447
|
+
if (transport)
|
|
448
|
+
return transport;
|
|
449
|
+
return { type: "unknown", message, recovery: "Unexpected error; inspect the message.", retryable: false };
|
|
450
|
+
}
|
|
451
|
+
function classifyTransport(err, code, message, opts) {
|
|
452
|
+
const matches = (codes) => code !== void 0 && codes.includes(code) || codes.some((c) => message.includes(c));
|
|
453
|
+
const host = targetHost(err);
|
|
454
|
+
const where = host ?? opts.service;
|
|
455
|
+
const urlVar = opts.urlEnvVar ?? "the base-URL environment variable";
|
|
456
|
+
if (matches(TRANSPORT_CODES.readTimeout)) {
|
|
457
|
+
const limit = configuredTimeoutMs(err);
|
|
458
|
+
return {
|
|
459
|
+
type: "timeout",
|
|
460
|
+
message: `${opts.service} did not respond${limit === void 0 ? " in time" : ` within ${humanMs(limit)}`}`,
|
|
461
|
+
recovery: `The request reached ${opts.service} but no response came back in time. Usually the server is busy or the query is too large. Retry, or ask for less: fewer results per page, fewer fields, a narrower query.`,
|
|
462
|
+
retryable: true
|
|
463
|
+
};
|
|
464
|
+
}
|
|
465
|
+
if (matches(TRANSPORT_CODES.connectTimeout)) {
|
|
466
|
+
const limit = configuredTimeoutMs(err);
|
|
467
|
+
return {
|
|
468
|
+
type: "timeout",
|
|
469
|
+
message: `Could not open a connection to ${where}${limit === void 0 ? "" : ` within ${humanMs(limit)}`}`,
|
|
470
|
+
recovery: `The host did not accept a connection in time. If it is reachable at all it is likely overloaded, so retry. If this repeats, confirm ${urlVar} points at a host this machine can reach.`,
|
|
471
|
+
retryable: true
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
if (matches(TRANSPORT_CODES.unresolved)) {
|
|
475
|
+
return {
|
|
476
|
+
type: "network",
|
|
477
|
+
message: `The host ${where} does not resolve`,
|
|
478
|
+
recovery: `${urlVar} points at a hostname that cannot be looked up from this machine. Check it for a typo. Retrying will not help until it changes.`,
|
|
479
|
+
retryable: false
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
if (matches(TRANSPORT_CODES.refused)) {
|
|
399
483
|
return {
|
|
400
484
|
type: "network",
|
|
401
|
-
message: `
|
|
402
|
-
recovery:
|
|
485
|
+
message: `Nothing accepted a connection at ${where}`,
|
|
486
|
+
recovery: `The host resolved but refused the connection, usually a wrong port or a service that is not running. Retrying will not help until that changes.`,
|
|
487
|
+
retryable: false
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
if (matches(TRANSPORT_CODES.dropped)) {
|
|
491
|
+
return {
|
|
492
|
+
type: "network",
|
|
493
|
+
message: `The connection to ${opts.service} closed before a response arrived`,
|
|
494
|
+
recovery: `The connection dropped mid-request. This is usually a blip, so retry.`,
|
|
403
495
|
retryable: true
|
|
404
496
|
};
|
|
405
497
|
}
|
|
406
|
-
return
|
|
498
|
+
return void 0;
|
|
407
499
|
}
|
|
408
500
|
function classifyError(err, opts) {
|
|
409
501
|
const base = normalize(err, opts);
|
|
@@ -4033,5 +4125,5 @@ await runCli(buildProgram(), {
|
|
|
4033
4125
|
credentialInfo: getCredentialInfo,
|
|
4034
4126
|
service: "Jira",
|
|
4035
4127
|
authRecovery: "Set the JIRA_URL and JIRA_TOKEN environment variables.",
|
|
4036
|
-
|
|
4128
|
+
urlEnvVar: "JIRA_URL"
|
|
4037
4129
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jiradc-cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.32",
|
|
4
4
|
"publish": true,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"dependencies": {
|
|
14
14
|
"commander": "^13.1.0",
|
|
15
15
|
"zod": "^3.25.67",
|
|
16
|
-
"jira-data-center-client": "1.0.
|
|
16
|
+
"jira-data-center-client": "1.0.42"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@types/node": "24.10.4",
|
|
@@ -32,6 +32,8 @@
|
|
|
32
32
|
},
|
|
33
33
|
"scripts": {
|
|
34
34
|
"build": "tsup",
|
|
35
|
+
"check-types": "tsc --noEmit -p tsconfig.json",
|
|
36
|
+
"clean": "rm -rf dist",
|
|
35
37
|
"dev": "tsx src/index.ts",
|
|
36
38
|
"lint": "eslint src tests",
|
|
37
39
|
"lint:fix": "eslint src tests --fix",
|