nushell-plugins/scripts/audit_crate_dependencies.nu
Jesús Pérez be62c8701a feat: Add ARGUMENTS documentation and interactive update mode
- Add `show-arguments` recipe documenting all version update commands
- Add `complete-update-interactive` recipe for manual confirmations
- Maintain `complete-update` as automatic mode (no prompts)
- Update `update-help` to reference new recipes and modes
- Document 7-step workflow and step-by-step differences

Changes:
- complete-update: Automatic mode (recommended for CI/CD)
- complete-update-interactive: Interactive mode (with confirmations)
- show-arguments: Complete documentation of all commands and modes
- Both modes share same 7-step workflow with different behavior in Step 4
2025-10-19 00:05:16 +01:00

397 lines
11 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env nu
# Audit Crate Dependencies Script
# Scans all plugins and analyzes their nu-* crate dependencies
#
# Usage:
# audit_crate_dependencies.nu # Audit all plugins
# audit_crate_dependencies.nu --plugin NAME # Audit specific plugin
# audit_crate_dependencies.nu --export # Export dependency matrix
use lib/common_lib.nu *
# Main entry point
def main [
--plugin: string # Specific plugin to audit
--export # Export dependency matrix to JSON
--system-only # Only audit system plugins
--custom-only # Only audit custom plugins
] {
log_info "Nushell Plugin Dependency Audit"
# Check nushell source exists
let nushell_dir = "./nushell"
if not ($nushell_dir | path exists) {
log_error "Nushell source not found at [$nushell_dir]"
log_info "Run: download_nushell.nu <version>"
exit 1
}
# Get nushell version
let nushell_version = get_nushell_version $nushell_dir
# Audit plugins
if ($plugin | is-not-empty) {
audit_single_plugin $plugin $nushell_version
} else {
let include_system = not $custom_only
let include_custom = not $system_only
let results = audit_all_plugins $nushell_version $include_system $include_custom
# Display results
display_audit_results $results
# Export if requested
if $export {
export_dependency_matrix $results $nushell_version
}
}
}
# Get nushell version from Cargo.toml
def get_nushell_version [
nushell_dir: string
]: nothing -> string {
let cargo_toml = $"($nushell_dir)/Cargo.toml"
open $cargo_toml | get package.version
}
# Audit all plugins (system + custom)
def audit_all_plugins [
nushell_version: string
include_system: bool
include_custom: bool
]: nothing -> table {
mut all_results = []
# Audit system plugins
if $include_system {
log_info "Auditing system plugins..."
let system_results = audit_system_plugins $nushell_version
$all_results = ($all_results | append $system_results)
}
# Audit custom plugins
if $include_custom {
log_info "Auditing custom plugins..."
let custom_results = audit_custom_plugins $nushell_version
$all_results = ($all_results | append $custom_results)
}
$all_results
}
# Audit nushell workspace plugins
def audit_system_plugins [
nushell_version: string
]: nothing -> table {
let nushell_dir = "./nushell"
let cargo_toml = $"($nushell_dir)/Cargo.toml"
# Get workspace members
let workspace_members = try {
open $cargo_toml | get workspace.members
} catch {
log_error "Could not read workspace members"
return []
}
# Filter to plugin members only
let plugin_members = $workspace_members | where {|it| $it | str starts-with "crates/nu_plugin_"}
log_info $"Found ($plugin_members | length) system plugins"
# Audit each system plugin
$plugin_members | each {|member|
let plugin_name = $member | str replace "crates/" ""
let plugin_cargo_toml = $"($nushell_dir)/($member)/Cargo.toml"
audit_plugin_cargo_toml $plugin_name $plugin_cargo_toml $nushell_version "system"
}
}
# Audit custom plugins (nu_plugin_* in root)
def audit_custom_plugins [
nushell_version: string
]: nothing -> table {
let plugin_dirs = get_plugin_directories
log_info $"Found ($plugin_dirs | length) custom plugins"
$plugin_dirs | each {|plugin_dir|
let plugin_name = get_plugin_name $plugin_dir
let plugin_cargo_toml = $"($plugin_dir)/Cargo.toml"
if ($plugin_cargo_toml | path exists) {
audit_plugin_cargo_toml $plugin_name $plugin_cargo_toml $nushell_version "custom"
} else {
log_warn $"Cargo.toml not found for ($plugin_name)"
null
}
} | compact
}
# Audit single plugin Cargo.toml
def audit_plugin_cargo_toml [
plugin_name: string
cargo_toml_path: string
nushell_version: string
plugin_type: string
]: nothing -> record {
let cargo_data = try {
open $cargo_toml_path
} catch {
log_error $"Failed to read [$cargo_toml_path]"
return {
plugin: $plugin_name
type: $plugin_type
status: "error"
error: "Could not read Cargo.toml"
}
}
# Get plugin version
let plugin_version = try {
$cargo_data | get package.version
} catch {
"unknown"
}
# Extract nu-* dependencies
let dependencies = try {
$cargo_data | get dependencies
} catch {
{}
}
# Filter to nu-* crates only
let nu_deps = $dependencies
| transpose name spec
| where {|row| $row.name | str starts-with "nu-"}
| each {|row|
parse_dependency_spec $row.name $row.spec
}
# Check version consistency
let version_issues = check_version_consistency $nu_deps $nushell_version
{
plugin: $plugin_name
type: $plugin_type
plugin_version: $plugin_version
nu_dependencies: $nu_deps
version_issues: $version_issues
status: (if ($version_issues | is-empty) { "ok" } else { "version_mismatch" })
}
}
# Parse dependency specification
def parse_dependency_spec [
dep_name: string
spec: any
]: nothing -> record {
# Spec can be a string (version) or record (detailed spec)
let spec_type = $spec | describe
if $spec_type == "string" {
{
crate: $dep_name
version: $spec
path: null
features: []
}
} else if $spec_type == "record" {
{
crate: $dep_name
version: ($spec | get -i version | default "none")
path: ($spec | get -i path | default null)
features: ($spec | get -i features | default [])
}
} else {
{
crate: $dep_name
version: "unknown"
path: null
features: []
}
}
}
# Check version consistency with nushell
def check_version_consistency [
nu_deps: table
expected_version: string
]: nothing -> list<record> {
$nu_deps | each {|dep|
if $dep.version != $expected_version and $dep.version != "none" {
{
crate: $dep.crate
found: $dep.version
expected: $expected_version
issue: "version_mismatch"
}
} else {
null
}
} | compact
}
# Display audit results
def display_audit_results [
results: table
] {
log_info "\n=== Dependency Audit Results ==="
# Summary statistics
let total_plugins = $results | length
let ok_plugins = $results | where status == "ok" | length
let issues_plugins = $results | where status != "ok" | length
log_info $"Total plugins: ($total_plugins)"
log_success $"Clean: ($ok_plugins)"
if $issues_plugins > 0 {
log_warn $"With issues: ($issues_plugins)"
}
# Group by type
let system_plugins = $results | where type == "system"
let custom_plugins = $results | where type == "custom"
if ($system_plugins | length) > 0 {
log_info "\n--- System Plugins ---"
display_plugin_group $system_plugins
}
if ($custom_plugins | length) > 0 {
log_info "\n--- Custom Plugins ---"
display_plugin_group $custom_plugins
}
# Show detailed issues
let plugins_with_issues = $results | where status != "ok"
if ($plugins_with_issues | length) > 0 {
log_warn "\n=== Plugins with Version Issues ==="
$plugins_with_issues | each {|plugin|
log_error $"[$plugin.plugin]"
$plugin.version_issues | each {|issue|
print $" • ($issue.crate): found [$issue.found], expected [$issue.expected]"
}
}
}
}
# Display plugin group
def display_plugin_group [
plugins: table
] {
$plugins | each {|plugin|
let status_icon = if $plugin.status == "ok" { "✅" } else { "⚠️" }
let dep_count = $plugin.nu_dependencies | length
print $" ($status_icon) ($plugin.plugin) (v($plugin.plugin_version))"
print $" nu-* dependencies: ($dep_count)"
if ($plugin.nu_dependencies | length) > 0 {
$plugin.nu_dependencies | each {|dep|
let path_info = if ($dep.path | is-not-empty) { $" \(path: ($dep.path))" } else { "" }
let features_info = if ($dep.features | is-not-empty) {
$" [($dep.features | str join ', ')]"
} else {
""
}
print $" • ($dep.crate): ($dep.version)($path_info)($features_info)"
}
}
}
}
# Export dependency matrix to JSON
def export_dependency_matrix [
results: table
nushell_version: string
] {
let output_file = "./tmp/dependency_audit.json"
ensure_dir "./tmp"
let matrix = {
nushell_version: $nushell_version
audited_at: (date now | format date "%Y-%m-%d %H:%M:%S")
total_plugins: ($results | length)
plugins_ok: ($results | where status == "ok" | length)
plugins_with_issues: ($results | where status != "ok" | length)
plugins: $results
}
$matrix | to json | save -f $output_file
log_success $"Dependency audit exported to: ($output_file)"
}
# Audit single plugin
def audit_single_plugin [
plugin_name: string
nushell_version: string
] {
log_info $"Auditing plugin: [$plugin_name]"
# Check if it's a custom plugin
let custom_cargo_toml = $"./($plugin_name)/Cargo.toml"
if ($custom_cargo_toml | path exists) {
let result = audit_plugin_cargo_toml $plugin_name $custom_cargo_toml $nushell_version "custom"
display_audit_results [$result]
return
}
# Check if it's a system plugin
let system_cargo_toml = $"./nushell/crates/($plugin_name)/Cargo.toml"
if ($system_cargo_toml | path exists) {
let result = audit_plugin_cargo_toml $plugin_name $system_cargo_toml $nushell_version "system"
display_audit_results [$result]
return
}
log_error $"Plugin '[$plugin_name]' not found"
log_info "Available custom plugins:"
get_plugin_directories | each {|dir|
print $" • (get_plugin_name $dir)"
}
exit 1
}
# Generate dependency matrix report
def "main matrix" [] {
log_info "Generating dependency matrix..."
let nushell_dir = "./nushell"
if not ($nushell_dir | path exists) {
log_error "Nushell source not found"
exit 1
}
let nushell_version = get_nushell_version $nushell_dir
let results = audit_all_plugins $nushell_version true true
# Create matrix of plugins × crates
let all_crates = $results
| each {|r| $r.nu_dependencies | get crate}
| flatten
| uniq
| sort
log_info "\n=== Dependency Matrix ==="
log_info $"Nushell version: [$nushell_version]"
log_info $"Unique nu-* crates: ($all_crates | length)"
# Show which plugins use which crates
$all_crates | each {|crate|
let users = $results | where {|r|
$crate in ($r.nu_dependencies | get crate)
} | get plugin
print $"\n($crate):"
$users | each {|user|
print $" • ($user)"
}
}
}