# Nickel Module Loader Library # Provides functions for discovering, syncing, and managing Nickel modules # Used by CLI commands and other components # Author: JesusPerezLorenzo # Date: 2025-09-29 use config/accessor.nu * use config/cache/simple-cache.nu * use utils * # Discover Nickel modules from extensions (providers, taskservs, clusters) export def "discover-nickel-modules" [ type: string # "providers" | "taskservs" | "clusters" ]: nothing -> table { # Fast path: don't load config, just use extensions path directly # This avoids Nickel evaluation which can hang the system let proj_root = ($env.PROVISIONING_ROOT? | default "/Users/Akasha/project-provisioning") let base_path = ($proj_root | path join "provisioning" "extensions" $type) if not ($base_path | path exists) { return [] } # Discover modules using directory structure # Use proper Nushell ls with null stdin to avoid hanging let modules = (ls $base_path | where type == "dir" | get name | path basename) # Build table with Nickel information $modules | each {|module_name| let module_path = ($base_path | path join $module_name) let schema_path = ($module_path | path join "nickel") # Check if Nickel directory exists if not ($schema_path | path exists) { return null } # Read nickel.mod for metadata let mod_path = ($schema_path | path join "nickel.mod") let metadata = if ($mod_path | path exists) { parse-nickel-mod $mod_path } else { {name: "", version: "0.0.1", edition: "v0.11.3"} } # Determine Nickel module name based on type let module_name = match $type { "providers" => $"($module_name)_prov" "taskservs" => $"($module_name)_task" "clusters" => $"($module_name)_cluster" _ => $module_name } { name: $module_name type: $type path: $module_path schema_path: $schema_path module_name: $module_name version: $metadata.version edition: $metadata.edition has_nickel: true } } | compact } # Cached version of discover-nickel-modules # NOTE: In practice, OS filesystem caching (dentry cache, inode cache) is more efficient # than custom caching due to Nushell's JSON serialization overhead. # This function is provided for future optimization when needed. export def "discover-nickel-modules-cached" [ type: string # "providers" | "taskservs" | "clusters" ]: nothing -> table { # Direct call - relies on OS filesystem cache for performance discover-nickel-modules $type } # Parse nickel.mod file and extract metadata def "parse-nickel-mod" [ mod_path: string ]: nothing -> record { let content = (open $mod_path) # Simple TOML parsing for [package] section let lines = ($content | lines) mut name = "" mut version = "0.0.1" mut edition = "v0.11.3" for line in $lines { if ($line | str starts-with "name") { $name = ($line | parse "name = \"{name}\"" | get name.0? | default "") } else if ($line | str starts-with "version") { $version = ($line | parse "version = \"{version}\"" | get version.0? | default "0.0.1") } else if ($line | str starts-with "edition") { $edition = ($line | parse "edition = \"{edition}\"" | get edition.0? | default "v0.11.3") } } {name: $name, version: $version, edition: $edition} } # Sync Nickel dependencies for an infrastructure workspace export def "sync-nickel-dependencies" [ infra_path: string --manifest: string = "providers.manifest.yaml" ] { let manifest_path = ($infra_path | path join $manifest) if not ($manifest_path | path exists) { error make {msg: $"Manifest not found: ($manifest_path)"} } let manifest = (open $manifest_path) let modules_dir_name = (config-get "nickel.modules_dir" "nickel") let modules_dir = ($infra_path | path join $modules_dir_name) # Create modules directory if it doesn't exist mkdir $modules_dir _print $"🔄 Syncing Nickel dependencies for ($infra_path | path basename)..." # Sync each provider from manifest if ($manifest | get providers? | is-not-empty) { $manifest.providers | each {|provider| sync-provider-module $provider $modules_dir } } # Update nickel.mod update-nickel-mod $infra_path $manifest _print "✅ Nickel dependencies synced successfully" } # Sync a single provider module (create symlink) def "sync-provider-module" [ provider: record modules_dir: string ] { let discovered = (discover-nickel-modules-cached "providers" | where name == $provider.name) if ($discovered | is-empty) { error make {msg: $"Provider '($provider.name)' not found in extensions/providers"} } let module_info = ($discovered | first) let link_path = ($modules_dir | path join $module_info.module_name) # Remove existing symlink if present if ($link_path | path exists) { rm -f $link_path } # Create symlink (relative path for portability) let relative_path = (get-relative-path $modules_dir $module_info.schema_path) # Use ln -sf for symlink ^ln -sf $relative_path $link_path _print $" ✓ ($provider.name) → ($link_path | path basename)" } # Get relative path from source to target def "get-relative-path" [ from: string to: string ]: nothing -> string { # Calculate relative path # For now, use absolute path (Nickel handles this fine) $to } # Update nickel.mod with provider dependencies export def "update-nickel-mod" [ infra_path: string manifest: record ] { let mod_path = ($infra_path | path join "nickel.mod") if not ($mod_path | path exists) { error make {msg: $"nickel.mod not found at ($mod_path)"} } let current_mod = (open $mod_path) let modules_dir_name = (get-config | get nickel.modules_dir) # Generate provider dependencies let provider_deps = if ($manifest | get providers? | is-not-empty) { # Load all providers once to cache them let all_providers = (discover-nickel-modules-cached "providers") $manifest.providers | each {|provider| let discovered = ($all_providers | where name == $provider.name) if ($discovered | is-empty) { return "" } let module_info = ($discovered | first) $"($module_info.module_name) = { path = \"./($modules_dir_name)/($module_info.module_name)\", version = \"($provider.version)\" }" } | str join "\n" } else { "" } # Parse current nickel.mod and update dependencies section let lines = ($current_mod | lines) mut in_deps = false mut new_lines = [] mut deps_section = [] for line in $lines { if ($line | str trim | str starts-with "[dependencies]") { $in_deps = true $new_lines = ($new_lines | append $line) continue } if $in_deps { if ($line | str trim | str starts-with "[") { # End of dependencies section # Add provider deps before closing if ($provider_deps | str length) > 0 { for dep_line in ($provider_deps | lines) { $new_lines = ($new_lines | append $dep_line) } } $in_deps = false $new_lines = ($new_lines | append $line) } else if (($line | str trim | str length) > 0) and not (($line | str contains "_prov") or ($line | str contains "_task") or ($line | str contains "_cluster")) { # Keep non-module-loader dependencies (like provisioning core) $new_lines = ($new_lines | append $line) } } else { $new_lines = ($new_lines | append $line) } } # If we're still in deps section at end, add provider deps if $in_deps and (($provider_deps | str length) > 0) { for dep_line in ($provider_deps | lines) { $new_lines = ($new_lines | append $dep_line) } } # Write updated nickel.mod $new_lines | str join "\n" | save -f $mod_path _print $" ✓ Updated nickel.mod with provider dependencies" } # Install a provider to an infrastructure export def "install-provider" [ provider_name: string infra_path: string --version: string = "0.0.1" ] { # Discover provider using cached version let available = (discover-nickel-modules-cached "providers" | where name == $provider_name) if ($available | is-empty) { error make {msg: $"Provider '($provider_name)' not found"} } let provider_info = ($available | first) _print $"📦 Installing provider ($provider_name) for ($infra_path | path basename)..." # Update or create manifest update-manifest $infra_path $provider_name $version # Sync Nickel dependencies sync-nickel-dependencies $infra_path _print $"✅ Provider ($provider_name) installed successfully" } # Update providers.manifest.yaml with new provider def "update-manifest" [ infra_path: string provider_name: string version: string ] { let manifest_path = ($infra_path | path join "providers.manifest.yaml") let manifest = if ($manifest_path | path exists) { open $manifest_path } else { {providers: []} } # Check if provider already exists let existing_providers = ($manifest | get providers) let provider_exists = ($existing_providers | any {|p| $p.name == $provider_name}) let updated_providers = if $provider_exists { # Update version $existing_providers | each {|p| if $p.name == $provider_name { {name: $provider_name, version: $version, enabled: true} } else { $p } } } else { # Add new provider $existing_providers | append {name: $provider_name, version: $version, enabled: true} } let updated_manifest = ($manifest | upsert providers $updated_providers) $updated_manifest | to yaml | save -f $manifest_path _print $" ✓ Updated providers.manifest.yaml" } # Remove a provider from an infrastructure export def "remove-provider" [ provider_name: string infra_path: string ] { _print $"🗑️ Removing provider ($provider_name) from ($infra_path | path basename)..." let manifest_path = ($infra_path | path join "providers.manifest.yaml") if not ($manifest_path | path exists) { error make {msg: "providers.manifest.yaml not found"} } let manifest = (open $manifest_path) let updated_providers = ($manifest.providers | where name != $provider_name) let updated_manifest = ($manifest | upsert providers $updated_providers) $updated_manifest | to yaml | save -f $manifest_path # Remove symlink let modules_dir_name = (get-config | get nickel.modules_dir) let modules_dir = ($infra_path | path join $modules_dir_name) let discovered = (discover-nickel-modules-cached "providers" | where name == $provider_name) if not ($discovered | is-empty) { let module_info = ($discovered | first) let link_path = ($modules_dir | path join $module_info.module_name) if ($link_path | path exists) { rm -f $link_path _print $" ✓ Removed symlink ($link_path | path basename)" } } # Sync to update nickel.mod sync-nickel-dependencies $infra_path _print $"✅ Provider ($provider_name) removed successfully" } # List all available Nickel modules export def "list-nickel-modules" [ type: string # "providers" | "taskservs" | "clusters" | "all" ]: nothing -> table { if $type == "all" { let providers = (discover-nickel-modules-cached "providers" | insert module_type "provider") let taskservs = (discover-nickel-modules-cached "taskservs" | insert module_type "taskserv") let clusters = (discover-nickel-modules-cached "clusters" | insert module_type "cluster") $providers | append $taskservs | append $clusters } else { discover-nickel-modules-cached $type | insert module_type $type } }