#!/usr/bin/env nu # Load extension metadata and build Directed Acyclic Graph (DAG) # Performs topological sort for initialization order # # Usage: # nu load-extensions.nu --extensions-dir --output-file # nu load-extensions.nu # Uses defaults def main [ --extensions-dir: path = "extensions" # Path to extensions directory --output-file: path = "config/extensions-dag.json" # Output DAG JSON file --verbose # Enable verbose output ] { print "🔍 Loading extension metadata..." if $verbose { print $" Extensions dir: ($extensions_dir)" } # Find all metadata.ncl files using glob (exclude polkadot deployment configs) let metadata_files = ( glob ($extensions_dir + "/**/metadata.ncl") | where { |f| not ($f | str contains "polkadot") } | sort ) if ($metadata_files | is-empty) { print "⚠️ No extension metadata files found" return 1 } print $"📂 Found ($metadata_files | length) extensions" # Load all metadata files let extensions = ( $metadata_files | each { |file| if $verbose { print $" Loading: ($file)" } let result = (do { nickel export $file | from json } | complete) if $result.exit_code == 0 { $result.stdout } else { print $"❌ Failed to load: ($file)" error make { msg: $"Failed to export ($file)" } } } ) print $"✅ Loaded ($extensions | length) extensions" if $verbose { print "\nExtension Summary:" $extensions | each { |ext| let deps_str = ($ext.dependencies | str join ', ') if ($ext.dependencies | is-empty) { print $" • ($ext.name) v($ext.version) [($ext.category)] - deps: []" } else { print $" • ($ext.name) v($ext.version) [($ext.category)] - deps: [($deps_str)]" } } } # Build DAG and detect cycles let dag_result = (build_dag $extensions) if ("error" in $dag_result) { print $"❌ DAG Error: ($dag_result.error)" return 1 } let dag = $dag_result # Topological sort let sorted_result = (topological_sort $extensions $dag) if ("error" in $sorted_result) { print $"❌ Topological Sort Error: ($sorted_result.error)" return 1 } let init_order = $sorted_result if $verbose { print "\nInitialization Order (Topological Sort):" $init_order | enumerate | each { |item| print $" ($item.index). ($item.item)" } } # Build output structure let output = { extensions: $extensions, dag: $dag, initialization_order: $init_order, metadata: { total_extensions: ($extensions | length), exported_at: (date now | format date "%Y-%m-%dT%H:%M:%SZ"), schema_version: "1.0", statistics: (build_stats $extensions), }, } # Write output let dir = $output_file | path dirname if not ($dir | path exists) { mkdir $dir } $output | to json --indent 2 | save --force $output_file print $"✅ Extension DAG exported to: ($output_file)" print $" Extensions: ($output.metadata.total_extensions)" print $" Status: Ready for ai-service initialization" 0 } # Build DAG from extension dependencies def build_dag [extensions: list] { let ext_names = ($extensions | each { |e| $e.name }) # Validate all dependencies exist let invalid_deps = ( $extensions | each { |ext| $ext.dependencies | each { |dep| if not ($ext_names | any { |n| $n == $dep }) { {extension: $ext.name, missing_dep: $dep} } else { null } } | compact } | flatten ) if not ($invalid_deps | is-empty) { let missing = ($invalid_deps | each { |d| $"($d.extension)->($d.missing_dep)" } | str join ", ") return {error: $"Invalid dependencies: ($missing)"} } # Build edges (node -> dependencies) let edges = ( $extensions | each { |ext| $ext.dependencies | each { |dep| {from: $ext.name, to: $dep, label: "depends_on"} } } | flatten ) { nodes: $ext_names, edges: $edges, } } # Topological sort using DFS def topological_sort [extensions: list, dag: record] { let ext_names = $dag.nodes let edges = $dag.edges # Build adjacency list (reverse: what depends on each node) let adj_list = ( $ext_names | each { |node| let deps = ( $edges | where { |e| $e.from == $node } | each { |e| $e.to } ) {node: $node, deps: $deps} } ) # Simple sort by counting in-degree and processing in order # Extensions with no dependencies come first let no_deps = ($adj_list | where { |n| ($n.deps | is-empty) } | each { |n| $n.node } | sort) let with_deps = ($adj_list | where { |n| ($n.deps | length) > 0 } | each { |n| $n.node } | sort) # Check for cycles (simple check: if an extension depends on something not in the list) let all_ext_names = ($extensions | each { |e| $e.name }) let all_deps = ( $extensions | each { |e| $e.dependencies } | flatten ) let invalid_deps = ($all_deps | where { |d| not ($all_ext_names | any { |n| $n == $d }) }) if not ($invalid_deps | is-empty) { return {error: $"Invalid dependencies found: ($invalid_deps | str join ', ')"} } # Simple ordering: return no_deps first, then with_deps sorted by number of dependencies let sorted = ( [$no_deps, ( $with_deps | each { |name| let dep_count = ($extensions | where { |e| $e.name == $name } | first | get dependencies | length) {name: $name, dep_count: $dep_count} } | sort-by dep_count | each { |n| $n.name } )] | flatten ) $sorted } # Build statistics def build_stats [extensions: list] { let categories = ($extensions | each { |e| $e.category } | sort | uniq) let by_category = ( $categories | each { |cat| let count = ($extensions | where { |e| $e.category == $cat } | length) {category: $cat, count: $count} } ) let total_deps = ( $extensions | each { |e| $e.dependencies | length } | math sum ) { by_category: $by_category, total_dependencies: $total_deps, extension_count: ($extensions | length), } } # Utility: List extensions by category export def list_by_category [ --extensions-dir: path = "extensions" ] { let metadata_files = (glob ($extensions_dir + "/**/metadata.ncl")) let extensions = ( $metadata_files | each { |file| let result = (do { nickel export $file | from json } | complete) if $result.exit_code == 0 { $result.stdout } else { null } } | compact ) let categories = ($extensions | each { |e| $e.category } | sort | uniq) $categories | each { |cat| let exts = ($extensions | where { |e| $e.category == $cat }) print $"($cat):" $exts | each { |ext| print $" • ($ext.name) v($ext.version)" } print "" } } # Utility: Check extension dependencies export def check_dependencies [ --extensions-dir: path = "extensions" ] { let metadata_files = (glob ($extensions_dir + "/**/metadata.ncl") | sort) let extensions = ( $metadata_files | each { |file| nickel export $file | from json } ) print "🔍 Extension Dependency Check" print "==============================" print "" let ext_names = ($extensions | each { |e| $e.name }) $extensions | each { |ext| if ($ext.dependencies | is-empty) { print $"✅ ($ext.name): No dependencies" } else { print $"($ext.name):" $ext.dependencies | each { |dep| if ($ext_names | any { |n| $n == $dep }) { print $" ✅ → ($dep)" } else { print $" ❌ → ($dep) [NOT FOUND]" } } } print "" } } main