feat: enhance PluginToggle and BuildNew components with support for implicit dependencies and dynamic labeling

This commit is contained in:
Ben Allfree
2025-12-04 17:13:49 -08:00
parent 7e5777f1b4
commit 5b4a7a2e36
4 changed files with 380 additions and 41 deletions
+81
View File
@@ -44,3 +44,84 @@ export function humanizeStatus(status: string): string {
.map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join(' ')
}
/**
* Resolves plugin dependencies recursively from registry.
* Returns all plugins that should be enabled (selected plugins + their dependencies).
* Filters out "meshtastic" as it's a firmware version requirement, not a plugin.
*/
export function getDependedPlugins(
selectedPlugins: string[],
registry: Record<string, { dependencies?: Record<string, string> }>
): string[] {
const result = new Set<string>()
const visited = new Set<string>()
function resolveDependencies(pluginId: string) {
// Prevent circular dependencies
if (visited.has(pluginId)) return
visited.add(pluginId)
const plugin = registry[pluginId]
if (!plugin || !plugin.dependencies) return
// Process each dependency
for (const [depId] of Object.entries(plugin.dependencies)) {
// Skip "meshtastic" - it's a firmware version requirement, not a plugin
if (depId === 'meshtastic') continue
// Only include dependencies that exist in the registry
if (depId in registry) {
result.add(depId)
// Recursively resolve transitive dependencies
resolveDependencies(depId)
}
}
}
// Start with selected plugins
for (const pluginId of selectedPlugins) {
if (pluginId in registry) {
result.add(pluginId)
resolveDependencies(pluginId)
}
}
return Array.from(result)
}
/**
* Gets only the implicit dependencies (dependencies that are not explicitly selected).
* Returns a set of plugin IDs that are dependencies but not in the explicitly selected list.
*/
export function getImplicitDependencies(
explicitlySelectedPlugins: string[],
registry: Record<string, { dependencies?: Record<string, string> }>
): Set<string> {
const allDependencies = getDependedPlugins(explicitlySelectedPlugins, registry)
const explicitSet = new Set(explicitlySelectedPlugins)
return new Set(allDependencies.filter((id) => !explicitSet.has(id)))
}
/**
* Checks if a plugin is required by any other explicitly selected plugin.
* Returns true if the plugin is a dependency (direct or transitive) of at least one explicitly selected plugin.
*/
export function isRequiredByOther(
pluginId: string,
explicitlySelectedPlugins: string[],
registry: Record<string, { dependencies?: Record<string, string> }>
): boolean {
// Check if any explicitly selected plugin depends on this plugin
for (const selectedId of explicitlySelectedPlugins) {
if (selectedId === pluginId) continue // Skip self
// Get all dependencies (including transitive) of this selected plugin
const allDeps = getDependedPlugins([selectedId], registry)
if (allDeps.includes(pluginId)) {
return true
}
}
return false
}