Update core components including CLI, Nushell libraries, plugins system, and utility scripts for the provisioning system. CLI Updates: - Command implementations - CLI utilities and dispatching - Help system improvements - Command validation Library Updates: - Configuration management system - Infrastructure validation - Extension system improvements - Secrets management - Workspace operations - Cache management system Plugin System: - Interactive form plugin (inquire) - KCL integration plugin - Performance optimization plugins - Plugin registration system Utilities: - Build and distribution scripts - Installation procedures - Testing utilities - Development tools Documentation: - Library module documentation - Extension API guides - Plugin usage guides - Service management documentation All changes are backward compatible. No breaking changes.
385 lines
11 KiB
Plaintext
385 lines
11 KiB
Plaintext
#!/usr/bin/env nu
|
|
# [command]
|
|
# name = "command metadata traits"
|
|
# group = "infrastructure"
|
|
# tags = ["metadata", "cache", "validation"]
|
|
# version = "1.0.0"
|
|
# requires = ["kcl:0.11.2"]
|
|
# note = "Runtime bridge between KCL metadata schema and Nushell command dispatch"
|
|
|
|
# ============================================================================
|
|
# Command Metadata Cache System
|
|
# Version: 1.0.0
|
|
# Purpose: Load, cache, and validate command metadata from KCL schema
|
|
# ============================================================================
|
|
|
|
# Get cache directory
|
|
def get-cache-dir [] : nothing -> string {
|
|
if ($env.XDG_CACHE_HOME? | is-empty) {
|
|
$"($env.HOME)/.cache/provisioning"
|
|
} else {
|
|
$"($env.XDG_CACHE_HOME)/provisioning"
|
|
}
|
|
}
|
|
|
|
# Get cache file path
|
|
def get-cache-path [] : nothing -> string {
|
|
$"(get-cache-dir)/command_metadata.json"
|
|
}
|
|
|
|
# Get KCL commands file path
|
|
def get-kcl-path [] : nothing -> string {
|
|
let proj = (
|
|
if (($env.PROVISIONING_ROOT? | is-empty)) {
|
|
$"($env.HOME)/project-provisioning"
|
|
} else {
|
|
$env.PROVISIONING_ROOT
|
|
}
|
|
)
|
|
$"($proj)/provisioning/kcl/commands.k"
|
|
}
|
|
|
|
# Get file modification time (macOS / Linux)
|
|
def get-file-mtime [file_path: string] : nothing -> int {
|
|
let result_macos = (do { ^stat -f%m $file_path } | complete)
|
|
if ($result_macos.exit_code == 0) {
|
|
($result_macos.stdout | str trim | into int)
|
|
} else {
|
|
let result_linux = (do { ^stat -c%Y $file_path } | complete)
|
|
if ($result_linux.exit_code == 0) {
|
|
($result_linux.stdout | str trim | into int)
|
|
} else {
|
|
0
|
|
}
|
|
}
|
|
}
|
|
|
|
# Check if cache is valid
|
|
def is-cache-valid [] : nothing -> bool {
|
|
let cache_path = (get-cache-path)
|
|
let kcl_path = (get-kcl-path)
|
|
|
|
if not (($cache_path | path exists)) {
|
|
return false
|
|
}
|
|
|
|
let now = (date now | format date "%s" | into int)
|
|
let cache_mtime = (get-file-mtime $cache_path)
|
|
let kcl_mtime = (get-file-mtime $kcl_path)
|
|
let ttl = 3600
|
|
|
|
let cache_age = ($now - $cache_mtime)
|
|
let not_expired = ($cache_age < $ttl)
|
|
let kcl_not_modified = ($cache_mtime > $kcl_mtime)
|
|
|
|
($not_expired and $kcl_not_modified)
|
|
}
|
|
|
|
# Load metadata from KCL
|
|
def load-from-kcl [] : nothing -> record {
|
|
let kcl_path = (get-kcl-path)
|
|
|
|
let result = (^kcl run $kcl_path -S command_registry --format json | complete)
|
|
|
|
if ($result.exit_code == 0) {
|
|
$result.stdout | from json
|
|
} else {
|
|
{
|
|
error: $"Failed to load KCL"
|
|
commands: {}
|
|
version: "1.0.0"
|
|
}
|
|
}
|
|
}
|
|
|
|
# Save metadata to cache
|
|
export def cache-metadata [metadata: record] : nothing -> nothing {
|
|
let dir = (get-cache-dir)
|
|
let path = (get-cache-path)
|
|
|
|
if not (($dir | path exists)) {
|
|
^mkdir -p $dir
|
|
}
|
|
|
|
# Save metadata to cache file
|
|
$metadata | to json | save --force $path
|
|
}
|
|
|
|
# Load from cache file
|
|
def load-from-cache [] : nothing -> record {
|
|
let path = (get-cache-path)
|
|
|
|
if not (($path | path exists)) {
|
|
return {}
|
|
}
|
|
|
|
(open $path --raw | from json)
|
|
}
|
|
|
|
# Load command metadata with caching
|
|
export def load-command-metadata [] : nothing -> record {
|
|
# Check if cache is valid before loading from KCL
|
|
if (is-cache-valid) {
|
|
# Use cached metadata
|
|
load-from-cache
|
|
} else {
|
|
# Load from KCL and cache it
|
|
let metadata = (load-from-kcl)
|
|
# Cache it for next time
|
|
cache-metadata $metadata
|
|
$metadata
|
|
}
|
|
}
|
|
|
|
# Invalidate cache
|
|
export def invalidate-cache [] : nothing -> record {
|
|
let path = (get-cache-path)
|
|
|
|
let _rm_result = (do {
|
|
if (($path | path exists)) {
|
|
^rm $path
|
|
}
|
|
} | complete)
|
|
|
|
load-from-kcl
|
|
}
|
|
|
|
# Get metadata for specific command
|
|
export def get-command-metadata [name: string] : nothing -> record {
|
|
let metadata = (load-command-metadata)
|
|
|
|
# Check if metadata has commands field
|
|
if ($metadata | type) != "record" {
|
|
return {found: false, command: $name, error: "Invalid metadata"}
|
|
}
|
|
|
|
# Get the commands record
|
|
let commands = (if ($metadata.commands | type) == "record" {
|
|
$metadata.commands
|
|
} else {
|
|
{}
|
|
})
|
|
|
|
# Get the specific command
|
|
let cmd = ($commands | get -o $name)
|
|
|
|
if ($cmd | is-empty) {
|
|
return {found: false, command: $name}
|
|
}
|
|
|
|
{found: true, command: $name, metadata: $cmd}
|
|
}
|
|
|
|
# Check if command is interactive
|
|
export def is-interactive-command [name: string] : nothing -> bool {
|
|
let result = (get-command-metadata $name)
|
|
if not $result.found {false} else {
|
|
$result.metadata.requirements.interactive
|
|
}
|
|
}
|
|
|
|
# Check if command requires auth
|
|
export def requires-auth [name: string] : nothing -> bool {
|
|
let result = (get-command-metadata $name)
|
|
if not $result.found {false} else {
|
|
$result.metadata.requirements.requires_auth
|
|
}
|
|
}
|
|
|
|
# Get auth type
|
|
export def get-auth-type [name: string] : nothing -> string {
|
|
let result = (get-command-metadata $name)
|
|
if not $result.found {"none"} else {
|
|
$result.metadata.requirements.auth_type
|
|
}
|
|
}
|
|
|
|
# Check if command requires workspace
|
|
export def requires-workspace [name: string] : nothing -> bool {
|
|
let result = (get-command-metadata $name)
|
|
if not $result.found {true} else {
|
|
$result.metadata.requirements.requires_workspace
|
|
}
|
|
}
|
|
|
|
# Check if command has side effects
|
|
export def has-side-effects [name: string] : nothing -> bool {
|
|
let result = (get-command-metadata $name)
|
|
if not $result.found {false} else {
|
|
$result.metadata.requirements.side_effects
|
|
}
|
|
}
|
|
|
|
# Get side effect type
|
|
export def get-side-effect-type [name: string] : nothing -> string {
|
|
let result = (get-command-metadata $name)
|
|
if not $result.found {"none"} else {
|
|
$result.metadata.requirements.side_effect_type
|
|
}
|
|
}
|
|
|
|
# Check if confirmation required
|
|
export def requires-confirmation [name: string] : nothing -> bool {
|
|
let result = (get-command-metadata $name)
|
|
if not $result.found {false} else {
|
|
$result.metadata.requirements.requires_confirmation
|
|
}
|
|
}
|
|
|
|
# Get min permission
|
|
export def get-min-permission [name: string] : nothing -> string {
|
|
let result = (get-command-metadata $name)
|
|
if not $result.found {"read"} else {
|
|
$result.metadata.requirements.min_permission
|
|
}
|
|
}
|
|
|
|
# Check if slow operation
|
|
export def is-slow-operation [name: string] : nothing -> bool {
|
|
let result = (get-command-metadata $name)
|
|
if not $result.found {false} else {
|
|
$result.metadata.requirements.slow_operation
|
|
}
|
|
}
|
|
|
|
# Get estimated time
|
|
export def get-estimated-time [name: string] : nothing -> int {
|
|
let result = (get-command-metadata $name)
|
|
if not $result.found {1} else {
|
|
$result.metadata.estimated_time
|
|
}
|
|
}
|
|
|
|
# Get form path
|
|
export def get-form-path [name: string] : nothing -> string {
|
|
let result = (get-command-metadata $name)
|
|
if not $result.found {""} else {
|
|
if (($result.metadata.form_path? | is-empty)) {""} else {
|
|
$result.metadata.form_path
|
|
}
|
|
}
|
|
}
|
|
|
|
# Validate command context
|
|
export def validate-command-context [
|
|
name: string
|
|
flags: record = {}
|
|
] : nothing -> record {
|
|
let metadata_result = (get-command-metadata $name)
|
|
|
|
if not $metadata_result.found {
|
|
return {valid: false, issues: ["Command metadata not found"]}
|
|
}
|
|
|
|
let req = $metadata_result.metadata.requirements
|
|
|
|
let issues = (
|
|
[]
|
|
| if ($req.requires_workspace and (($env.PROVISIONING_WORKSPACE? | is-empty))) {
|
|
append "Active workspace required"
|
|
} else { . }
|
|
| if ($req.requires_confirmation and not (($flags.yes? | default false) or ($flags.confirm? | default false))) {
|
|
append "Confirmation required"
|
|
} else { . }
|
|
| if ($req.side_effects and (($req.side_effect_type | is-empty) or ($req.side_effect_type == "none"))) {
|
|
append "Invalid side_effect_type"
|
|
} else { . }
|
|
)
|
|
|
|
{
|
|
valid: (($issues | length) == 0)
|
|
command: $name
|
|
issues: $issues
|
|
metadata: $metadata_result.metadata
|
|
}
|
|
}
|
|
|
|
# Print validation issues
|
|
export def print-validation-issues [validation: record] : nothing -> nothing {
|
|
if (($validation.issues | is-empty)) {
|
|
return
|
|
}
|
|
|
|
print $"(ansi red_bold)✗ Validation failed(ansi reset)"
|
|
print ""
|
|
|
|
$validation.issues | enumerate | each {|item|
|
|
print $" (ansi yellow)[$($item.index)]:(ansi reset) ($item.item)"
|
|
}
|
|
|
|
print ""
|
|
}
|
|
|
|
# List all commands
|
|
export def list-all-commands [] : nothing -> table {
|
|
let metadata = (load-command-metadata)
|
|
|
|
if (($metadata | has "error") and not (($metadata.error | is-empty))) {
|
|
return []
|
|
}
|
|
|
|
if not (($metadata | has "commands")) {
|
|
return []
|
|
}
|
|
|
|
$metadata.commands
|
|
| keys | each {|cmd_name|
|
|
let cmd = ($metadata.commands | get $cmd_name)
|
|
let req = $cmd.requirements
|
|
{
|
|
name: $cmd_name
|
|
domain: $cmd.domain
|
|
description: $cmd.description
|
|
interactive: $req.interactive
|
|
requires_auth: $req.requires_auth
|
|
auth_type: $req.auth_type
|
|
requires_workspace: $req.requires_workspace
|
|
side_effects: $req.side_effects
|
|
side_effect_type: $req.side_effect_type
|
|
requires_confirmation: $req.requires_confirmation
|
|
min_permission: $req.min_permission
|
|
slow_operation: $req.slow_operation
|
|
estimated_time: $cmd.estimated_time
|
|
}
|
|
}
|
|
}
|
|
|
|
# Filter commands
|
|
export def filter-commands [criteria: record] : nothing -> table {
|
|
let all = (list-all-commands)
|
|
|
|
$all | where {|cmd|
|
|
let domain_match = if (($criteria.domain? | is-empty)) {true} else {$cmd.domain == $criteria.domain}
|
|
let interactive_match = if (($criteria.interactive? | is-empty)) {true} else {$cmd.interactive == $criteria.interactive}
|
|
let side_effects_match = if (($criteria.side_effects? | is-empty)) {true} else {$cmd.side_effects == $criteria.side_effects}
|
|
let auth_match = if (($criteria.requires_auth? | is-empty)) {true} else {$cmd.requires_auth == $criteria.requires_auth}
|
|
let slow_match = if (($criteria.slow? | is-empty)) {true} else {$cmd.slow_operation == $criteria.slow}
|
|
|
|
($domain_match and $interactive_match and $side_effects_match and $auth_match and $slow_match)
|
|
}
|
|
}
|
|
|
|
# Cache statistics
|
|
export def cache-stats [] : nothing -> record {
|
|
let cache_path = (get-cache-path)
|
|
let kcl_path = (get-kcl-path)
|
|
let now = (date now | format date "%s" | into int)
|
|
|
|
let cache_mtime = (get-file-mtime $cache_path)
|
|
let kcl_mtime = (get-file-mtime $kcl_path)
|
|
let cache_age = (if ($cache_mtime > 0) {($now - $cache_mtime)} else {-1})
|
|
let ttl_remain = (if ($cache_age >= 0) {(3600 - $cache_age)} else {0})
|
|
|
|
{
|
|
cache_path: $cache_path
|
|
cache_exists: ($cache_path | path exists)
|
|
cache_age_seconds: $cache_age
|
|
cache_ttl_seconds: 3600
|
|
cache_ttl_remaining: (if ($ttl_remain > 0) {$ttl_remain} else {0})
|
|
cache_valid: (is-cache-valid)
|
|
kcl_path: $kcl_path
|
|
kcl_exists: ($kcl_path | path exists)
|
|
kcl_mtime_ago: (if ($kcl_mtime > 0) {($now - $kcl_mtime)} else {-1})
|
|
}
|
|
}
|