Merge _configs/ into config/ for single configuration directory. Update all path references. Changes: - Move _configs/* to config/ - Update .gitignore for new patterns - No code references to _configs/ found Impact: -1 root directory (layout_conventions.md compliance)
322 lines
10 KiB
Plaintext
322 lines
10 KiB
Plaintext
#!/usr/bin/env nu
|
||
# audit-nu-scripts.nu - Automated Nushell script auditor for 0.109+ compatibility
|
||
# Usage: nu .claude/guidelines/nushell/audit-nu-scripts.nu [--fix] [--verbose]
|
||
|
||
def main [
|
||
--fix # Automatically fix simple issues
|
||
--verbose # Show detailed output
|
||
--output: string = "" # Output report to file
|
||
] {
|
||
print_header
|
||
|
||
# Find all Nushell scripts
|
||
let scripts = glob **/*.nu | where {|path| not ($path | str starts-with "target/")}
|
||
|
||
print $"Found ($scripts | length) Nushell script(s) to audit\n"
|
||
|
||
mut issues = []
|
||
mut summary = {
|
||
total_scripts: ($scripts | length)
|
||
scripts_with_issues: 0
|
||
total_issues: 0
|
||
critical: 0
|
||
warning: 0
|
||
info: 0
|
||
}
|
||
|
||
# Audit each script
|
||
for script_path in $scripts {
|
||
let script_issues = audit_script $script_path $verbose
|
||
|
||
if ($script_issues | length) > 0 {
|
||
$summary.scripts_with_issues = $summary.scripts_with_issues + 1
|
||
$summary.total_issues = $summary.total_issues + ($script_issues | length)
|
||
|
||
# Count by severity
|
||
for issue in $script_issues {
|
||
match $issue.severity {
|
||
"critical" => { $summary.critical = $summary.critical + 1 }
|
||
"warning" => { $summary.warning = $summary.warning + 1 }
|
||
"info" => { $summary.info = $summary.info + 1 }
|
||
_ => {}
|
||
}
|
||
}
|
||
|
||
# Add to issues list
|
||
$issues = ($issues | append {
|
||
script: $script_path
|
||
issues: $script_issues
|
||
})
|
||
}
|
||
}
|
||
|
||
# Print results
|
||
print_divider
|
||
print_summary $summary
|
||
print_divider
|
||
|
||
if ($issues | length) > 0 {
|
||
print "\n📋 DETAILED FINDINGS:\n"
|
||
for script_result in $issues {
|
||
print_script_issues $script_result.script $script_result.issues
|
||
}
|
||
}
|
||
|
||
# Generate report if requested
|
||
if $output != "" {
|
||
generate_report $issues $summary $output
|
||
print $"\n✓ Report saved to: ($output)"
|
||
}
|
||
|
||
# Exit code based on critical issues
|
||
if $summary.critical > 0 {
|
||
exit 1
|
||
}
|
||
}
|
||
|
||
# Audit a single script
|
||
def audit_script [path: string, verbose: bool] -> list {
|
||
if $verbose {
|
||
print $"Auditing: ($path)"
|
||
}
|
||
|
||
mut issues = []
|
||
|
||
# Read script content
|
||
let content = try {
|
||
open --raw $path
|
||
} catch {
|
||
return [{
|
||
line: 0
|
||
severity: "critical"
|
||
category: "file_access"
|
||
message: $"Cannot read file: ($path)"
|
||
suggestion: "Check file permissions"
|
||
}]
|
||
}
|
||
|
||
let lines = $content | lines
|
||
|
||
# Check 1: Missing shebang
|
||
if (($lines | first) !~ "#!/usr/bin/env nu") {
|
||
$issues = ($issues | append {
|
||
line: 1
|
||
severity: "warning"
|
||
category: "shebang"
|
||
message: "Missing or incorrect shebang"
|
||
suggestion: "Add: #!/usr/bin/env nu"
|
||
})
|
||
}
|
||
|
||
# Check 2: Old closure syntax (using $in in where/each)
|
||
for (idx, line) in ($lines | enumerate) {
|
||
let line_num = $idx + 1
|
||
|
||
# Check for old-style closures
|
||
if ($line =~ '\| where \{ \$in') or ($line =~ '\| each \{ \$in') {
|
||
$issues = ($issues | append {
|
||
line: $line_num
|
||
severity: "warning"
|
||
category: "closure_syntax"
|
||
message: "Old-style closure using $in"
|
||
suggestion: "Use explicit parameter: {|x| $x ...} instead of { $in ... }"
|
||
})
|
||
}
|
||
|
||
# Check for missing type annotations on exported functions
|
||
if ($line =~ 'export def .+\[') and not ($line =~ ': \w+') {
|
||
$issues = ($issues | append {
|
||
line: $line_num
|
||
severity: "info"
|
||
category: "type_annotations"
|
||
message: "Function parameters missing type annotations"
|
||
suggestion: "Add type annotations: [param: type]"
|
||
})
|
||
}
|
||
|
||
# Check for unwrap-like patterns (get without -i)
|
||
if ($line =~ '\| get [a-zA-Z_]') and not ($line =~ 'get -i') {
|
||
if not ($line =~ '\| get \w+ \|') {
|
||
# Potentially unsafe get (not in pipeline continuation)
|
||
$issues = ($issues | append {
|
||
line: $line_num
|
||
severity: "info"
|
||
category: "null_safety"
|
||
message: "Potentially unsafe 'get' without -i flag"
|
||
suggestion: "Use 'get -i' for safe access or add error handling"
|
||
})
|
||
}
|
||
}
|
||
|
||
# Check for hardcoded paths (common anti-pattern)
|
||
if ($line =~ '/Users/[^/]+/') or ($line =~ 'C:\\\\') {
|
||
$issues = ($issues | append {
|
||
line: $line_num
|
||
severity: "warning"
|
||
category: "hardcoded_paths"
|
||
message: "Hardcoded absolute path detected"
|
||
suggestion: "Use $env.HOME or relative paths"
|
||
})
|
||
}
|
||
|
||
# Check for missing error handling in external commands
|
||
if ($line =~ '\^[a-zA-Z_-]+') and not ($line =~ 'try \{') {
|
||
# External command not wrapped in try-catch
|
||
# Check if previous or next line has try
|
||
let has_try = if $idx > 0 {
|
||
($lines | get ($idx - 1) | str contains "try")
|
||
} else {
|
||
false
|
||
}
|
||
|
||
if not $has_try {
|
||
$issues = ($issues | append {
|
||
line: $line_num
|
||
severity: "info"
|
||
category: "error_handling"
|
||
message: "External command without error handling"
|
||
suggestion: "Wrap in try-catch block"
|
||
})
|
||
}
|
||
}
|
||
|
||
# Check for deprecated mut syntax
|
||
if ($line =~ 'let mut ') {
|
||
# This is actually fine in 0.109, just noting it
|
||
# $issues = append with info if we want to track mutable usage
|
||
}
|
||
|
||
# Check for missing return type annotations
|
||
if ($line =~ 'export def .+\]') and not ($line =~ '-> \w+') and not ($line =~ '\{$') {
|
||
$issues = ($issues | append {
|
||
line: $line_num
|
||
severity: "info"
|
||
category: "type_annotations"
|
||
message: "Function missing return type annotation"
|
||
suggestion: "Add return type: ] -> type {"
|
||
})
|
||
}
|
||
}
|
||
|
||
# Check 3: No error handling in main functions
|
||
let has_try = $content | str contains "try"
|
||
let has_catch = $content | str contains "catch"
|
||
if not ($has_try and $has_catch) {
|
||
$issues = ($issues | append {
|
||
line: 0
|
||
severity: "info"
|
||
category: "error_handling"
|
||
message: "Script may lack error handling (no try-catch found)"
|
||
suggestion: "Add try-catch blocks for robust error handling"
|
||
})
|
||
}
|
||
|
||
$issues
|
||
}
|
||
|
||
# Print header
|
||
def print_header [] {
|
||
print ""
|
||
print "╔════════════════════════════════════════════════════════╗"
|
||
print "║ Nushell 0.109+ Compatibility Audit Tool ║"
|
||
print "║ syntaxis Project - Script Quality Checker ║"
|
||
print "╚════════════════════════════════════════════════════════╝"
|
||
print ""
|
||
}
|
||
|
||
# Print divider
|
||
def print_divider [] {
|
||
print "─────────────────────────────────────────────────────────"
|
||
}
|
||
|
||
# Print summary
|
||
def print_summary [summary: record] {
|
||
print "\n📊 AUDIT SUMMARY:\n"
|
||
print $" Total scripts: ($summary.total_scripts)"
|
||
print $" Scripts with issues: ($summary.scripts_with_issues)"
|
||
print $" Total issues: ($summary.total_issues)"
|
||
print ""
|
||
print " By Severity:"
|
||
print $" (ansi red)● Critical: ($summary.critical)(ansi reset)"
|
||
print $" (ansi yellow)● Warnings: ($summary.warning)(ansi reset)"
|
||
print $" (ansi cyan)● Info: ($summary.info)(ansi reset)"
|
||
}
|
||
|
||
# Print issues for a script
|
||
def print_script_issues [script: string, issues: list] {
|
||
print $"\n(ansi cyan)📄 ($script)(ansi reset)"
|
||
|
||
for issue in $issues {
|
||
let severity_icon = match $issue.severity {
|
||
"critical" => $"(ansi red)✗(ansi reset)"
|
||
"warning" => $"(ansi yellow)⚠(ansi reset)"
|
||
"info" => $"(ansi cyan)ℹ(ansi reset)"
|
||
_ => "•"
|
||
}
|
||
|
||
let line_info = if $issue.line > 0 {
|
||
$" (Line ($issue.line))"
|
||
} else {
|
||
""
|
||
}
|
||
|
||
print $" ($severity_icon) [($issue.category)]($line_info): ($issue.message)"
|
||
print $" → ($issue.suggestion)"
|
||
}
|
||
}
|
||
|
||
# Generate markdown report
|
||
def generate_report [issues: list, summary: record, output_path: string] {
|
||
let timestamp = date now | format date "%Y-%m-%d %H:%M:%S"
|
||
|
||
mut report = $"# Nushell Script Audit Report
|
||
Generated: ($timestamp)
|
||
|
||
## Summary
|
||
|
||
- **Total Scripts**: ($summary.total_scripts)
|
||
- **Scripts with Issues**: ($summary.scripts_with_issues)
|
||
- **Total Issues**: ($summary.total_issues)
|
||
- Critical: ($summary.critical)
|
||
- Warnings: ($summary.warning)
|
||
- Info: ($summary.info)
|
||
|
||
## Detailed Findings
|
||
|
||
"
|
||
|
||
for script_result in $issues {
|
||
$report = $report + $"\n### ($script_result.script)\n\n"
|
||
|
||
for issue in $script_result.issues {
|
||
let line_ref = if $issue.line > 0 {
|
||
$" (Line ($issue.line))"
|
||
} else {
|
||
""
|
||
}
|
||
|
||
$report = $report + $"- **[($issue.severity | str upcase)] ($issue.category)**($line_ref)
|
||
- Issue: ($issue.message)
|
||
- Suggestion: ($issue.suggestion)
|
||
|
||
"
|
||
}
|
||
}
|
||
|
||
$report = $report + $"\n## Recommendations
|
||
|
||
1. Review all **critical** issues immediately
|
||
2. Address **warnings** before next release
|
||
3. Consider **info** items for code quality improvements
|
||
4. Follow guidelines in `.claude/guidelines/nushell/NUSHELL_0.109_GUIDELINES.md`
|
||
|
||
---
|
||
*Generated by syntaxis Nushell Audit Tool*
|
||
"
|
||
|
||
$report | save --force $output_path
|
||
}
|
||
|
||
# Run main if executed directly
|
||
main
|