Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4d32d5e71e | |||
| a4552498ac | |||
| 4585801f32 | |||
| 3dc75f5cda |
16
changelog.md
16
changelog.md
@@ -1,5 +1,21 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2026-02-07 - 1.17.3 - fix(registry)
|
||||||
|
increase default maxRetries in fetchWithRetry from 3 to 6 to improve resilience when fetching registry resources
|
||||||
|
|
||||||
|
- Changed default maxRetries from 3 to 6 in ts/classes.registrycopy.ts
|
||||||
|
- Reduces failures from transient network or registry errors by allowing more retry attempts
|
||||||
|
- No API or behavior changes besides the increased default retry count
|
||||||
|
|
||||||
|
## 2026-02-07 - 1.17.2 - fix(registry)
|
||||||
|
improve HTTP fetch retry logging, backoff calculation, and token-cache warning
|
||||||
|
|
||||||
|
- Include HTTP method in logs and normalize method to uppercase for consistency
|
||||||
|
- Log retry attempts with method, URL and calculated exponential backoff delay
|
||||||
|
- Compute and reuse exponential backoff delay variable instead of inline calculation
|
||||||
|
- Log error when a 5xx response persists after all retry attempts and when fetch ultimately fails
|
||||||
|
- Add a warning log when clearing cached token after a 401 response
|
||||||
|
|
||||||
## 2026-02-07 - 1.17.1 - fix(registrycopy)
|
## 2026-02-07 - 1.17.1 - fix(registrycopy)
|
||||||
add fetchWithRetry wrapper to apply timeouts, retries with exponential backoff, and token cache handling; use it for registry HTTP requests
|
add fetchWithRetry wrapper to apply timeouts, retries with exponential backoff, and token cache handling; use it for registry HTTP requests
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@git.zone/tsdocker",
|
"name": "@git.zone/tsdocker",
|
||||||
"version": "1.17.1",
|
"version": "1.17.3",
|
||||||
"private": false,
|
"private": false,
|
||||||
"description": "develop npm modules cross platform with docker",
|
"description": "develop npm modules cross platform with docker",
|
||||||
"main": "dist_ts/index.js",
|
"main": "dist_ts/index.js",
|
||||||
|
|||||||
@@ -3,6 +3,6 @@
|
|||||||
*/
|
*/
|
||||||
export const commitinfo = {
|
export const commitinfo = {
|
||||||
name: '@git.zone/tsdocker',
|
name: '@git.zone/tsdocker',
|
||||||
version: '1.17.1',
|
version: '1.17.3',
|
||||||
description: 'develop npm modules cross platform with docker'
|
description: 'develop npm modules cross platform with docker'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,28 +29,38 @@ export class RegistryCopy {
|
|||||||
url: string,
|
url: string,
|
||||||
options: RequestInit & { duplex?: string },
|
options: RequestInit & { duplex?: string },
|
||||||
timeoutMs: number = 300_000,
|
timeoutMs: number = 300_000,
|
||||||
maxRetries: number = 3,
|
maxRetries: number = 6,
|
||||||
): Promise<Response> {
|
): Promise<Response> {
|
||||||
|
const method = (options.method || 'GET').toUpperCase();
|
||||||
let lastError: Error | null = null;
|
let lastError: Error | null = null;
|
||||||
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
||||||
try {
|
try {
|
||||||
|
if (attempt > 1) {
|
||||||
|
logger.log('info', `Retry ${attempt}/${maxRetries} for ${method} ${url}`);
|
||||||
|
}
|
||||||
const resp = await fetch(url, {
|
const resp = await fetch(url, {
|
||||||
...options,
|
...options,
|
||||||
signal: AbortSignal.timeout(timeoutMs),
|
signal: AbortSignal.timeout(timeoutMs),
|
||||||
});
|
});
|
||||||
// Retry on 5xx server errors (but not 4xx)
|
// Retry on 5xx server errors (but not 4xx)
|
||||||
if (resp.status >= 500 && attempt < maxRetries) {
|
if (resp.status >= 500 && attempt < maxRetries) {
|
||||||
logger.log('warn', `Request to ${url} returned ${resp.status}, retrying (${attempt}/${maxRetries})...`);
|
const delay = 1000 * Math.pow(2, attempt - 1);
|
||||||
await new Promise(r => setTimeout(r, 1000 * Math.pow(2, attempt - 1)));
|
logger.log('warn', `${method} ${url} returned ${resp.status}, retrying in ${delay}ms (attempt ${attempt}/${maxRetries})...`);
|
||||||
|
await new Promise(r => setTimeout(r, delay));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
if (resp.status >= 500) {
|
||||||
|
logger.log('error', `${method} ${url} returned ${resp.status} after ${maxRetries} attempts, giving up`);
|
||||||
|
}
|
||||||
return resp;
|
return resp;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
lastError = err as Error;
|
lastError = err as Error;
|
||||||
if (attempt < maxRetries) {
|
if (attempt < maxRetries) {
|
||||||
const delay = 1000 * Math.pow(2, attempt - 1);
|
const delay = 1000 * Math.pow(2, attempt - 1);
|
||||||
logger.log('warn', `fetch failed (attempt ${attempt}/${maxRetries}): ${lastError.message}, retrying in ${delay}ms...`);
|
logger.log('warn', `${method} ${url} failed (attempt ${attempt}/${maxRetries}): ${lastError.message}, retrying in ${delay}ms...`);
|
||||||
await new Promise(r => setTimeout(r, delay));
|
await new Promise(r => setTimeout(r, delay));
|
||||||
|
} else {
|
||||||
|
logger.log('error', `${method} ${url} failed after ${maxRetries} attempts: ${lastError.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -231,6 +241,7 @@ export class RegistryCopy {
|
|||||||
// Token expired — clear cache so next call re-authenticates
|
// Token expired — clear cache so next call re-authenticates
|
||||||
if (resp.status === 401 && token) {
|
if (resp.status === 401 && token) {
|
||||||
const cacheKey = `${registry}/${`repository:${repo}:${actions}`}`;
|
const cacheKey = `${registry}/${`repository:${repo}:${actions}`}`;
|
||||||
|
logger.log('warn', `Got 401 for ${registry}${path} — clearing cached token for ${cacheKey}`);
|
||||||
delete this.tokenCache[cacheKey];
|
delete this.tokenCache[cacheKey];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user