mirror of
https://github.com/SillyTavern/SillyTavern.git
synced 2025-06-05 21:59:27 +02:00
add ability for exts to req other exts (#4023)
* add ability for exts to req other exts * Get rid of toasts. Collect errors on load, display in manager * Remove unused variable * Only show missing modules/dependencies * Prefer display names in validation messages * Prefer internal name for console warn --------- Co-authored-by: Cohee <18619528+Cohee1207@users.noreply.github.com>
This commit is contained in:
@@ -36,7 +36,13 @@ export let modules = [];
|
|||||||
* A set of active extensions.
|
* A set of active extensions.
|
||||||
* @type {Set<string>}
|
* @type {Set<string>}
|
||||||
*/
|
*/
|
||||||
let activeExtensions = new Set();
|
const activeExtensions = new Set();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Errors that occurred while loading extensions.
|
||||||
|
* @type {Set<string>}
|
||||||
|
*/
|
||||||
|
const extensionLoadErrors = new Set();
|
||||||
|
|
||||||
const getApiUrl = () => extension_settings.apiUrl;
|
const getApiUrl = () => extension_settings.apiUrl;
|
||||||
const sortManifestsByOrder = (a, b) => parseInt(a.loading_order) - parseInt(b.loading_order) || String(a.display_name).localeCompare(String(b.display_name));
|
const sortManifestsByOrder = (a, b) => parseInt(a.loading_order) - parseInt(b.loading_order) || String(a.display_name).localeCompare(String(b.display_name));
|
||||||
@@ -380,36 +386,88 @@ async function getManifests(names) {
|
|||||||
*/
|
*/
|
||||||
async function activateExtensions() {
|
async function activateExtensions() {
|
||||||
const extensions = Object.entries(manifests).sort((a, b) => sortManifestsByOrder(a[1], b[1]));
|
const extensions = Object.entries(manifests).sort((a, b) => sortManifestsByOrder(a[1], b[1]));
|
||||||
|
const extensionNames = extensions.map(x => x[0]);
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
|
||||||
for (let entry of extensions) {
|
for (let entry of extensions) {
|
||||||
const name = entry[0];
|
const name = entry[0];
|
||||||
const manifest = entry[1];
|
const manifest = entry[1];
|
||||||
|
const extrasRequirements = manifest.requires;
|
||||||
|
const extensionDependencies = manifest.dependencies;
|
||||||
|
const displayName = manifest.display_name || name;
|
||||||
|
|
||||||
if (activeExtensions.has(name)) {
|
if (activeExtensions.has(name)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const meetsModuleRequirements = !Array.isArray(manifest.requires) || isSubsetOf(modules, manifest.requires);
|
// Module requirements: pass if 'requires' is undefined, null, or not an array; check subset if it's an array
|
||||||
|
let meetsModuleRequirements = true;
|
||||||
|
let missingModules = [];
|
||||||
|
if (extrasRequirements !== undefined) {
|
||||||
|
if (Array.isArray(extrasRequirements)) {
|
||||||
|
meetsModuleRequirements = isSubsetOf(modules, extrasRequirements);
|
||||||
|
missingModules = extrasRequirements.filter(req => !modules.includes(req));
|
||||||
|
} else {
|
||||||
|
console.warn(`Extension ${name}: manifest.json 'requires' field is not an array. Loading allowed, but any intended requirements were not verified to exist.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extension dependencies: pass if 'dependencies' is undefined or not an array; check subset and disabled status if it's an array
|
||||||
|
let meetsExtensionDeps = true;
|
||||||
|
let missingDependencies = [];
|
||||||
|
let disabledDependencies = [];
|
||||||
|
if (extensionDependencies !== undefined) {
|
||||||
|
if (Array.isArray(extensionDependencies)) {
|
||||||
|
// Check if all dependencies exist
|
||||||
|
meetsExtensionDeps = isSubsetOf(extensionNames, extensionDependencies);
|
||||||
|
missingDependencies = extensionDependencies.filter(dep => !extensionNames.includes(dep));
|
||||||
|
// Check for disabled dependencies
|
||||||
|
if (meetsExtensionDeps) {
|
||||||
|
disabledDependencies = extensionDependencies.filter(dep => extension_settings.disabledExtensions.includes(dep));
|
||||||
|
if (disabledDependencies.length > 0) {
|
||||||
|
// Fail if any dependencies are disabled
|
||||||
|
meetsExtensionDeps = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn(`Extension ${name}: manifest.json 'dependencies' field is not an array. Loading allowed, but any intended requirements were not verified to exist.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const isDisabled = extension_settings.disabledExtensions.includes(name);
|
const isDisabled = extension_settings.disabledExtensions.includes(name);
|
||||||
|
|
||||||
if (meetsModuleRequirements && !isDisabled) {
|
if (meetsModuleRequirements && meetsExtensionDeps && !isDisabled) {
|
||||||
try {
|
try {
|
||||||
console.debug('Activating extension', name);
|
console.debug('Activating extension', name);
|
||||||
const promise = addExtensionLocale(name, manifest).finally(() => Promise.all([addExtensionScript(name, manifest), addExtensionStyle(name, manifest)]));
|
const promise = addExtensionLocale(name, manifest).finally(() =>
|
||||||
|
Promise.all([addExtensionScript(name, manifest), addExtensionStyle(name, manifest)]),
|
||||||
|
);
|
||||||
await promise
|
await promise
|
||||||
.then(() => activeExtensions.add(name))
|
.then(() => activeExtensions.add(name))
|
||||||
.catch(err => console.log('Could not activate extension', name, err));
|
.catch(err => {
|
||||||
|
console.log('Could not activate extension', name, err);
|
||||||
|
extensionLoadErrors.add(t`Extension "${displayName}" failed to load: ${err}`);
|
||||||
|
});
|
||||||
promises.push(promise);
|
promises.push(promise);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Could not activate extension', name, error);
|
||||||
}
|
}
|
||||||
catch (error) {
|
} else if (!meetsModuleRequirements && !isDisabled) {
|
||||||
console.error('Could not activate extension', name);
|
console.warn(t`Extension "${name}" did not load. Missing required Extras module(s): "${missingModules.join(', ')}"`);
|
||||||
console.error(error);
|
extensionLoadErrors.add(t`Extension "${displayName}" did not load. Missing required Extras module(s): "${missingModules.join(', ')}"`);
|
||||||
|
} else if (!meetsExtensionDeps && !isDisabled) {
|
||||||
|
if (disabledDependencies.length > 0) {
|
||||||
|
console.warn(t`Extension "${name}" did not load. Required extensions exist but are disabled: "${disabledDependencies.join(', ')}". Enable them first, then reload.`);
|
||||||
|
extensionLoadErrors.add(t`Extension "${displayName}" did not load. Required extensions exist but are disabled: "${disabledDependencies.join(', ')}". Enable them first, then reload.`);
|
||||||
|
} else {
|
||||||
|
console.warn(t`Extension "${name}" did not load. Missing required extensions: "${missingDependencies.join(', ')}"`);
|
||||||
|
extensionLoadErrors.add(t`Extension "${displayName}" did not load. Missing required extensions: "${missingDependencies.join(', ')}"`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.allSettled(promises);
|
await Promise.allSettled(promises);
|
||||||
|
$('#extensions_details').toggleClass('warning', extensionLoadErrors.size > 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function connectClickHandler() {
|
async function connectClickHandler() {
|
||||||
@@ -751,6 +809,27 @@ function getModuleInformation() {
|
|||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates HTML for the extension load errors.
|
||||||
|
* @returns {string} HTML string containing the errors that occurred while loading extensions.
|
||||||
|
*/
|
||||||
|
function getExtensionLoadErrorsHtml() {
|
||||||
|
if (extensionLoadErrors.size === 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const container = document.createElement('div');
|
||||||
|
container.classList.add('info-block', 'error');
|
||||||
|
|
||||||
|
for (const error of extensionLoadErrors) {
|
||||||
|
const errorElement = document.createElement('div');
|
||||||
|
errorElement.textContent = error;
|
||||||
|
container.appendChild(errorElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
return container.outerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates the HTML strings for all extensions and displays them in a popup.
|
* Generates the HTML strings for all extensions and displays them in a popup.
|
||||||
*/
|
*/
|
||||||
@@ -765,6 +844,7 @@ async function showExtensionsDetails() {
|
|||||||
initialScrollTop = oldPopup.content.scrollTop;
|
initialScrollTop = oldPopup.content.scrollTop;
|
||||||
await oldPopup.completeCancelled();
|
await oldPopup.completeCancelled();
|
||||||
}
|
}
|
||||||
|
const htmlErrors = getExtensionLoadErrorsHtml();
|
||||||
const htmlDefault = $('<div class="marginBot10"><h3 class="textAlignCenter">' + t`Built-in Extensions:` + '</h3></div>');
|
const htmlDefault = $('<div class="marginBot10"><h3 class="textAlignCenter">' + t`Built-in Extensions:` + '</h3></div>');
|
||||||
const htmlExternal = $('<div class="marginBot10"><h3 class="textAlignCenter">' + t`Installed Extensions:` + '</h3></div>');
|
const htmlExternal = $('<div class="marginBot10"><h3 class="textAlignCenter">' + t`Installed Extensions:` + '</h3></div>');
|
||||||
const htmlLoading = $(`<div class="flex-container alignItemsCenter justifyCenter marginTop10 marginBot5">
|
const htmlLoading = $(`<div class="flex-container alignItemsCenter justifyCenter marginTop10 marginBot5">
|
||||||
@@ -787,6 +867,7 @@ async function showExtensionsDetails() {
|
|||||||
|
|
||||||
const html = $('<div></div>')
|
const html = $('<div></div>')
|
||||||
.addClass('extensions_info')
|
.addClass('extensions_info')
|
||||||
|
.append(htmlErrors)
|
||||||
.append(htmlDefault)
|
.append(htmlDefault)
|
||||||
.append(htmlExternal)
|
.append(htmlExternal)
|
||||||
.append(getModuleInformation());
|
.append(getModuleInformation());
|
||||||
|
Reference in New Issue
Block a user