Merge audit16-core/misccode into the September 16 core audit fixes

# Conflicts:
#	CHANGELOG.md
This commit is contained in:
Chuck
2026-09-17 09:18:02 -04:00
20 changed files with 709 additions and 151 deletions
@@ -153,31 +153,54 @@ const PluginAPI = {
options.body = JSON.stringify(data);
}
// NETWORK_ERROR means only that fetch() itself rejected: no HTTP
// answer arrived (connection refused/reset, e.g. the web service
// restarting). Callers retry that (install_manager.js updateAll).
// Any HTTP response is the server's -- or a proxy's -- answer, so
// a 502 HTML page or a JSON error without error_code is API_ERROR
// and is not retried.
let response;
try {
const response = await fetch(url, options);
const responseData = await response.json();
if (!response.ok) {
// Handle structured errors
if (responseData.error_code) {
throw responseData;
}
throw new Error(responseData.message || `HTTP ${response.status}`);
}
return responseData;
response = await fetch(url, options);
} catch (error) {
// Re-throw structured errors
if (error.error_code) {
throw error;
}
// Wrap network errors
throw {
error_code: 'NETWORK_ERROR',
message: error.message || 'Network error',
message: (error && error.message) || 'Network error',
original_error: error
};
}
let responseData = null;
let parseError = null;
try {
responseData = await response.json();
} catch (error) {
parseError = error;
}
if (!response.ok) {
// Handle structured errors
if (responseData && responseData.error_code) {
throw responseData;
}
throw {
error_code: 'API_ERROR',
status: response.status,
message: (responseData && responseData.message) || `HTTP ${response.status}`,
original_error: parseError || undefined
};
}
if (parseError) {
throw {
error_code: 'API_ERROR',
status: response.status,
message: `Unreadable response from the server (HTTP ${response.status})`,
original_error: parseError
};
}
return responseData;
};
// Use throttling for GET requests, immediate execution for POST/PUT/DELETE
@@ -168,6 +168,58 @@ const PluginInstallManager = {
}
return results;
},
/**
* Classify one POST /plugins/update answer.
*
* The route reports what actually happened in `data.update_status`
* (`updated`, `up_to_date`, `local_only`). A plugin the updater had
* nothing to do for -- e.g. a ZIP-installed monorepo plugin already at the
* registry version -- is still a success response, so it must not be
* counted as updated. Older servers only say so in the message.
*
* @param {Object} entry - One element of updateAll()'s results
* @returns {string} 'failed' | 'updated' | 'up_to_date' | 'local_only'
*/
updateOutcome(entry) {
if (!entry || !entry.success) return 'failed';
const result = entry.result || {};
const status = result.data && result.data.update_status;
if (status === 'up_to_date' || status === 'local_only' || status === 'updated') {
return status;
}
const message = typeof result.message === 'string' ? result.message : '';
if (message.includes('already up to date')) return 'up_to_date';
if (message.includes('managed locally')) return 'local_only';
return 'updated';
},
/**
* Summarise updateAll()'s results for the Check & Update All toast.
*
* @param {Array} results - updateAll()'s results
* @returns {{updated: number, upToDate: number, localOnly: number, failed: number, text: string, type: string}}
*/
summarizeUpdateResults(results) {
const counts = { updated: 0, up_to_date: 0, local_only: 0, failed: 0 };
for (const entry of (Array.isArray(results) ? results : [])) {
counts[this.updateOutcome(entry)]++;
}
const parts = [];
if (counts.updated > 0) parts.push(`${counts.updated} updated`);
if (counts.up_to_date > 0) parts.push(`${counts.up_to_date} already up to date`);
if (counts.local_only > 0) parts.push(`${counts.local_only} managed locally`);
if (counts.failed > 0) parts.push(`${counts.failed} failed`);
const type = counts.failed > 0 ? (counts.updated > 0 ? 'warning' : 'error') : 'success';
return {
updated: counts.updated,
upToDate: counts.up_to_date,
localOnly: counts.local_only,
failed: counts.failed,
text: parts.join(', '),
type
};
}
};
+15 -16
View File
@@ -1999,22 +1999,21 @@ function runUpdateAllPlugins() {
showNotification('No plugins to update.', 'info');
return;
}
let updated = 0, upToDate = 0, failed = 0;
for (const r of results) {
if (!r.success) {
failed++;
} else if (r.result && r.result.message && r.result.message.includes('already up to date')) {
upToDate++;
} else {
updated++;
}
}
const parts = [];
if (updated > 0) parts.push(`${updated} updated`);
if (upToDate > 0) parts.push(`${upToDate} already up to date`);
if (failed > 0) parts.push(`${failed} failed`);
const type = failed > 0 ? (updated > 0 ? 'warning' : 'error') : 'success';
showNotification(parts.join(', '), type);
// Counted by install_manager.js from each answer's update_status:
// a no-op update is "already up to date", not "updated". A cached
// install_manager.js from before that helper gets a plain count.
const manager = window.PluginInstallManager;
const summary = (manager && typeof manager.summarizeUpdateResults === 'function')
? manager.summarizeUpdateResults(results)
: (() => {
const failed = results.filter(r => !r.success).length;
const checked = results.length - failed;
return {
text: `${checked} checked` + (failed ? `, ${failed} failed` : ''),
type: failed ? (checked ? 'warning' : 'error') : 'success'
};
})();
showNotification(summary.text, summary.type);
})
.catch(error => {
console.error('Error updating all plugins:', error);