nushell-plugins/scripts/install_from_manifest.nu

316 lines
8.7 KiB
Plaintext
Raw Normal View History

#!/usr/bin/env nu
# Distribution Plugin Installer - Install & Register from Manifest
#
# Reads a distribution manifest (JSON) and installs/registers selected plugins.
# Plugins are copied to ~/.local/bin/ and registered with Nushell.
#
# Usage:
# install_from_manifest.nu # Uses DISTRIBUTION_MANIFEST.json
# install_from_manifest.nu --manifest manifest.json
# install_from_manifest.nu --list # List available plugins
# install_from_manifest.nu --all # Install all plugins
# install_from_manifest.nu --preset essential # Install preset
# install_from_manifest.nu --select auth kms # Install specific plugins
# install_from_manifest.nu --check # Dry-run
def main [
--manifest: string = "DISTRIBUTION_MANIFEST.json" # Path to manifest file
--list # List available plugins only
--all # Install all plugins
--preset: string # Use preset: essential, development, full
--select: list<string> # Select specific plugins
--check (-c) # Dry-run, don't make changes
--install-only # Only install, don't register
--register-only # Only register, skip install
] {
log_info "📦 Nushell Distribution Plugin Installer"
log_info "=================================================================="
# Step 1: Load manifest
log_info $"\n📋 Step 1: Loading manifest ($manifest)..."
let manifest_data = load_manifest $manifest
if $manifest_data == null {
return
}
let plugins = $manifest_data.plugins
if ($plugins | length) == 0 {
log_error "No plugins in manifest!"
return
}
log_success $"Loaded ($plugins | length) available plugin\(s\)"
# List mode?
if $list {
display_plugin_list $plugins
return
}
# Step 2: Select plugins
log_info "\n🎯 Step 2: Selecting plugins..."
let selected = if ($preset | is-not-empty) {
get_preset_plugins $plugins $preset
} else if ($select | length) > 0 {
filter_by_name $plugins $select
} else if $all {
$plugins
} else {
interactive_select $plugins
}
if ($selected | length) == 0 {
log_warn "No plugins selected"
return
}
# Step 3: Show selection
log_info ""
log_success $"Selected: ($selected | length) plugin\(s\)"
for plugin in $selected {
log_info $" • ($plugin.name) - ($plugin.purpose)"
}
# Step 4: Dry-run?
if $check {
log_info "\n✅ DRY RUN - No changes made"
return
}
# Step 5: Confirm
print ""
let response = try {
input "Proceed with installation? (yes/no): "
} catch {
"yes"
}
if $response != "yes" {
log_info "Cancelled"
return
}
# Step 6: Install
if not $register_only {
log_info "\n📦 Step 3: Installing plugins..."
install_plugins $selected $manifest_data.source_directory
}
# Step 7: Register
if not $install_only {
log_info "\n🔌 Step 4: Registering with Nushell..."
register_plugins $selected
}
# Summary
log_info "\n=================================================================="
log_success "✅ Complete!"
log_info ""
log_info "Next steps:"
log_info " 1. Restart Nushell: exit && nu"
log_info " 2. Verify: nu -c 'plugin list'"
}
# Load manifest from JSON file
def load_manifest [path: string]: nothing -> record {
if not ($path | path exists) {
log_error $"Manifest not found: ($path)"
return null
}
try {
let content = open $path
return $content
} catch {|err|
log_error $"Failed to load manifest: ($err.msg)"
return null
}
}
# Display plugin list
def display_plugin_list [plugins: list<record>] {
print ""
log_success $"Available plugins: ($plugins | length)"
print ""
for plugin in $plugins {
print $" ✓ ($plugin.name)"
print $" ($plugin.purpose)"
print ""
}
}
# Get preset plugins
def get_preset_plugins [plugins: list<record>, preset: string]: nothing -> list<record> {
match $preset {
"essential" => {
$plugins | where {|p|
$p.name in ["nu_plugin_auth", "nu_plugin_kms", "nu_plugin_orchestrator", "nu_plugin_kcl", "nu_plugin_tera"]
}
}
"development" => {
$plugins | where {|p|
$p.name in ["nu_plugin_auth", "nu_plugin_kms", "nu_plugin_orchestrator", "nu_plugin_kcl", "nu_plugin_tera", "nu_plugin_highlight", "nu_plugin_image", "nu_plugin_clipboard"]
}
}
"full" => {
$plugins
}
_ => {
log_error $"Unknown preset: ($preset)"
log_info "Available: essential, development, full"
[]
}
}
}
# Filter by name
def filter_by_name [plugins: list<record>, names: list<string>]: nothing -> list<record> {
$plugins | where {|p|
if $p.name in $names {
true
} else {
($p.name | str replace "^nu_plugin_" "") in $names
}
}
}
# Interactive select
def interactive_select [plugins: list<record>]: nothing -> list<record> {
print ""
log_info "Available presets:"
log_info " 1. Essential (5 core plugins)"
log_info " 2. Development (8 plugins)"
log_info " 3. All ($($plugins | length) plugins)"
print ""
let choice = try {
input "Select (1-3): "
} catch {
"1"
}
match $choice {
"1" => { get_preset_plugins $plugins "essential" }
"2" => { get_preset_plugins $plugins "development" }
"3" => { $plugins }
_ => { [] }
}
}
# Install plugins to ~/.local/bin/
def install_plugins [selected: list<record>, source_dir: string] {
let install_dir = $"($env.HOME)/.local/bin"
# Ensure directory exists
if not ($install_dir | path exists) {
log_info $"Creating directory: ($install_dir)"
mkdir $install_dir
}
for plugin in $selected {
log_info $"Installing: ($plugin.name)"
try {
let source_path = $"($source_dir)/($plugin.name)"
let target_path = $"($install_dir)/($plugin.name)"
if not ($source_path | path exists) {
log_error $" ✗ Source not found: ($source_path)"
continue
}
cp $source_path $target_path
chmod +x $target_path
# Fix macOS code signing issues
fix_macos_binary $target_path
log_success $" ✓ Installed to ($target_path)"
} catch {|err|
log_error $" ✗ Failed: ($err.msg)"
}
}
}
# Fix macOS code signing (remove quarantine, ad-hoc sign)
def fix_macos_binary [binary_path: string] {
let os_type = $nu.os-info.name
if $os_type == "macos" {
# Remove quarantine attribute
try {
^xattr -d com.apple.quarantine $binary_path out+err>| null
} catch {
# Silently ignore if attribute doesn't exist
}
# Ad-hoc sign the binary
try {
^codesign -s - $binary_path out+err>| null
} catch {
# Silently ignore if codesign fails
}
}
}
# Register plugins with Nushell
def register_plugins [selected: list<record>] {
let install_dir = $"($env.HOME)/.local/bin"
let nu_cmd = $"($install_dir)/nu"
# Fix code signing on nu binary if it exists locally
if ($nu_cmd | path exists) {
fix_macos_binary $nu_cmd
}
for plugin in $selected {
let basename = $plugin.name | str replace "^nu_plugin_" ""
let plugin_path = $"($install_dir)/($plugin.name)"
log_info $"Registering: ($plugin.name)"
try {
# Remove old registration if exists
try {
if ($nu_cmd | path exists) {
^$nu_cmd -c $"plugin rm ($basename)" out+err>| null
} else {
nu -c $"plugin rm ($basename)" out+err>| null
}
} catch {}
# Register new - use local nu if available, otherwise system nu
if ($nu_cmd | path exists) {
^$nu_cmd -c $"plugin add ($plugin_path)"
} else {
nu -c $"plugin add ($plugin_path)"
}
log_success $" ✓ Registered"
} catch {|err|
log_error $" ✗ Failed: ($err.msg)"
}
}
}
# Logging
def log_info [msg: string] {
print $" ($msg)"
}
def log_success [msg: string] {
print $"✅ ($msg)"
}
def log_error [msg: string] {
print $"❌ ($msg)"
}
def log_warn [msg: string] {
print $"⚠️ ($msg)"
}
main