303 lines
8.4 KiB
Plaintext
303 lines
8.4 KiB
Plaintext
|
|
#!/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 <path> --output-file <path>
|
||
|
|
# 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|
|
||
|
|
try {
|
||
|
|
if $verbose { print $" Loading: ($file)" }
|
||
|
|
nickel export $file | from json
|
||
|
|
} catch {
|
||
|
|
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|
|
||
|
|
try {
|
||
|
|
nickel export $file | from json
|
||
|
|
} catch {
|
||
|
|
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
|