provisioning/tools/create-extension.nu

982 lines
30 KiB
Plaintext
Raw Normal View History

2025-10-07 11:12:02 +01:00
#!/usr/bin/env nu
# Extension Creation Tool (Updated for Modern Nushell & Grouped Structure)
# Creates new extensions from templates with variable substitution
use ../core/nulib/taskservs/discover.nu *
# Main extension creation command
def main [
type: string, # Extension type: taskserv, provider, cluster
name: string, # Extension name (kebab-case)
--template: string = "basic", # Template to use
--author: string = "Unknown", # Author name
--description: string = "", # Extension description
--category: string = "", # Category for taskservs (required for taskservs)
--port: int = 8080, # Default port for services
--output: string = "provisioning/extensions" # Output directory
] {
print $"🚀 Creating ($type) extension: ($name)"
# Validate extension type
if $type not-in ["taskserv", "provider", "cluster"] {
error make { msg: "Extension type must be one of: taskserv, provider, cluster" }
}
# Validate name format
if ($name | str contains " ") or ($name | str contains "_") or ($name != ($name | str downcase)) {
error make { msg: "Extension name must be in kebab-case format (e.g., my-service)" }
}
# For taskservs, require category
if $type == "taskserv" and ($category | is-empty) {
error make { msg: "Category is required for taskservs. Use --category <category>" }
}
# Create extension from template
let extension_info = create_extension_from_template $type $name $template $author $description $category $port $output
print $"✅ Extension created successfully:"
print $"📁 Location: ($extension_info.path)"
print $"📄 Files created: ($extension_info.files | length)"
print ""
print "🔄 Next steps:"
print $" 1. cd ($extension_info.path)"
print " 2. Customize the configuration in the main .k file"
print " 3. Update README.md with specific details"
if $type == "taskserv" {
print " 4. Test discovery:"
print $" nu -c \"use provisioning/core/nulib/taskservs/discover.nu *; get-taskserv-info ($name)\""
print " 5. Test layer resolution:"
print $" nu -c \"use provisioning/workspace/tools/layer-utils.nu *; test_layer_resolution ($name) wuji upcloud\""
}
}
# Create extension from template
def create_extension_from_template [
type: string,
name: string,
template: string,
author: string,
description: string,
category: string,
port: int,
output_dir: string
]: nothing -> record {
# Determine template path based on type
let template_base = "provisioning/templates/extensions"
let template_path = match $type {
"taskserv" => ($template_base | path join "taskservs" "basic-taskserv")
"provider" => ($template_base | path join "providers" "basic-provider")
"cluster" => ($template_base | path join "clusters" "basic-cluster")
}
# Create extension path (with category for taskservs)
let extension_path = if $type == "taskserv" {
($output_dir | path join "taskservs" $category $name "kcl")
} else {
($output_dir | path join ($type + "s") $name "kcl")
}
# Create output directory
print $"📁 Creating directories..."
mkdir $extension_path
# Also create default directory for taskservs
if $type == "taskserv" {
let default_path = ([$extension_path, "..", "default"] | path join)
mkdir $default_path
}
# Generate variables for templates
let variables = generate_template_variables $type $name $author $description $category $port
# Check if template exists, if not create basic structure
if ($template_path | path exists) {
# Copy and process template files
let template_files = try { ls $template_path | get name } catch { [] }
let created_files = ($template_files | each { |file|
process_template_file $file $extension_path $variables
})
# Create additional files for taskservs
if $type == "taskserv" {
create_taskserv_extras $extension_path $variables
}
{
path: ([$extension_path, ".."] | path join),
files: $created_files,
variables: $variables
}
} else {
# Create basic structure without templates
print $"⚠️ Template not found: ($template_path)"
print "📄 Creating basic structure..."
let created_files = create_basic_structure $type $extension_path $variables
# Create additional files for taskservs
if $type == "taskserv" {
create_taskserv_extras $extension_path $variables
}
{
path: ([$extension_path, ".."] | path join),
files: $created_files,
variables: $variables
}
}
}
# Generate template variables for substitution
def generate_template_variables [
type: string,
name: string,
author: string,
description: string,
category: string,
port: int
]: nothing -> record {
let name_pascal = ($name | split row "-" | each { |word| $word | str capitalize } | str join "")
let name_snake = ($name | str replace -a "-" "_")
let display_name = ($name | split row "-" | each { |word| $word | str capitalize } | str join " ")
let final_description = if ($description | is-empty) {
$"Deployment and management for ($display_name)"
} else {
$description
}
# Set defaults based on type and name
let default_port = if $port != 8080 {
$port
} else {
match $name {
$name if ($name | str contains "redis") => 6379
$name if ($name | str contains "postgres") => 5432
$name if ($name | str contains "mysql") => 3306
$name if ($name | str contains "http") => 8080
$name if ($name | str contains "api") => 8080
_ => 8080
}
}
let final_category = if ($category | is-empty) {
match $name {
$name if ($name | str contains "database" or $name | str contains "db" or $name | str contains "postgres" or $name | str contains "mysql" or $name | str contains "redis") => "databases"
$name if ($name | str contains "web" or $name | str contains "http" or $name | str contains "nginx" or $name | str contains "proxy") => "networking"
$name if ($name | str contains "monitor" or $name | str contains "metric" or $name | str contains "log") => "infrastructure"
$name if ($name | str contains "security" or $name | str contains "auth" or $name | str contains "cert") => "infrastructure"
_ => "development"
}
} else {
$category
}
{
# Old template compatibility
TASKSERV_NAME: $name,
TASKSERV_NAME_PASCAL: $name_pascal,
TASKSERV_NAME_SNAKE: $name_snake,
TASKSERV_DISPLAY_NAME: $display_name,
TASKSERV_DESCRIPTION: $final_description,
# New extension variables
EXTENSION_TYPE: $type,
EXTENSION_NAME: $name,
EXTENSION_NAME_PASCAL: $name_pascal,
EXTENSION_NAME_SNAKE: $name_snake,
EXTENSION_DISPLAY_NAME: $display_name,
EXTENSION_DESCRIPTION: $final_description,
EXTENSION_CATEGORY: $final_category,
DEFAULT_PORT: $default_port,
AUTHOR_NAME: $author,
LICENSE: "MIT",
REPOSITORY_URL: $"https://github.com/($author | str downcase)/($name)-($type)",
SERVICE_VERSION: "latest",
TAGS: $"\"($final_category)\", \"kubernetes\", \"($name)\"",
DOCS_URL: $"https://docs.example.com/($name)",
SUPPORT_URL: $"https://github.com/($author | str downcase)/($name)-($type)/issues",
DATE: (date now | format date "%Y-%m-%d"),
VERSION: "1.0.0"
}
}
# Process a single template file
def process_template_file [
file: string,
extension_path: string,
variables: record
]: nothing -> string {
let filename = ($file | path basename)
let target_file = process_filename $filename $variables
let target_path = ($extension_path | path join $target_file)
# Read template content
let content = try {
open $file
} catch {
""
}
# Process template variables
let processed_content = process_template_content $content $variables
# Save processed file
$processed_content | save $target_path
print $" ✓ Created ($target_file)"
$target_path
}
# Process template filename with variables
def process_filename [filename: string, variables: record]: nothing -> string {
mut processed = $filename
# Use fold instead of reduce for modern Nushell
for key_value in ($variables | transpose key value) {
$processed = ($processed | str replace -a $"{{($key_value.key)}}" $key_value.value)
}
$processed
}
# Process template content with variables
def process_template_content [content: string, variables: record]: nothing -> string {
mut processed = $content
# Use fold instead of reduce for modern Nushell
for key_value in ($variables | transpose key value) {
$processed = ($processed | str replace -a $"{{($key_value.key)}}" ($key_value.value | into string))
}
$processed
}
# Create basic structure without templates
def create_basic_structure [
type: string,
extension_path: string,
variables: record
]: nothing -> list<string> {
mut created_files = []
match $type {
"taskserv" => {
# Create kcl.mod
let kcl_mod_content = create_basic_kcl_mod $variables
($kcl_mod_content | save ($extension_path | path join "kcl.mod"))
$created_files = ($created_files | append "kcl.mod")
# Create main KCL file
let main_kcl_content = create_basic_taskserv_kcl $variables
($main_kcl_content | save ($extension_path | path join $"($variables.EXTENSION_NAME).k"))
$created_files = ($created_files | append $"($variables.EXTENSION_NAME).k")
# Create version file
let version_content = create_basic_version_kcl $variables
($version_content | save ($extension_path | path join "version.k"))
$created_files = ($created_files | append "version.k")
# Create README
let readme_content = create_basic_readme $variables
($readme_content | save ([$extension_path, "..", "README.md"] | path join))
$created_files = ($created_files | append "README.md")
print $" ✓ Created kcl.mod"
print $" ✓ Created ($variables.EXTENSION_NAME).k"
print $" ✓ Created version.k"
print $" ✓ Created README.md"
}
"provider" => {
# Create basic provider structure
let provider_content = create_basic_provider_kcl $variables
($provider_content | save ($extension_path | path join $"($variables.EXTENSION_NAME).k"))
$created_files = ($created_files | append $"($variables.EXTENSION_NAME).k")
print $" ✓ Created ($variables.EXTENSION_NAME).k"
}
"cluster" => {
# Create basic cluster structure
let cluster_content = create_basic_cluster_kcl $variables
($cluster_content | save ($extension_path | path join $"($variables.EXTENSION_NAME).k"))
$created_files = ($created_files | append $"($variables.EXTENSION_NAME).k")
print $" ✓ Created ($variables.EXTENSION_NAME).k"
}
}
$created_files
}
# Create extra files for taskservs
def create_taskserv_extras [
extension_path: string,
variables: record
] {
let default_path = ([$extension_path, "..", "default"] | path join)
# Create defs.toml
let defs_content = create_basic_defs_toml $variables
($defs_content | save ($default_path | path join "defs.toml"))
# Create install script
let install_content = create_basic_install_script $variables
($install_content | save ($default_path | path join $"install-($variables.EXTENSION_NAME).sh"))
chmod +x ($default_path | path join $"install-($variables.EXTENSION_NAME).sh")
print $" ✓ Created defs.toml"
print $" ✓ Created install-($variables.EXTENSION_NAME).sh"
}
# Template content generators
def create_basic_kcl_mod [variables: record]: nothing -> string {
$"[package]
name = \"($variables.EXTENSION_NAME)\"
version = \"($variables.VERSION)\"
description = \"($variables.EXTENSION_DESCRIPTION)\"
authors = [\"($variables.AUTHOR_NAME)\"]
[dependencies]
k8s = { oci = \"oci://ghcr.io/kcl-lang/k8s\", tag = \"1.30\" }
"
}
def create_basic_taskserv_kcl [variables: record]: nothing -> string {
$"# ($variables.EXTENSION_DISPLAY_NAME) Taskserv Configuration
# ($variables.EXTENSION_DESCRIPTION)
schema ($variables.EXTENSION_NAME_PASCAL) {
# Service metadata
name: str = \"($variables.EXTENSION_NAME)\"
version: str = \"latest\"
namespace: str = \"default\"
# Service configuration
replicas: int = 1
port: int = ($variables.DEFAULT_PORT)
# Resource requirements
resources: {
cpu: str = \"100m\"
memory: str = \"128Mi\"
limits?: {
cpu?: str = \"500m\"
memory?: str = \"512Mi\"
}
} = {
cpu = \"100m\"
memory = \"128Mi\"
}
# Service specific configuration
config?: {str: any} = {}
# Health checks
health?: {
enabled: bool = true
path: str = \"/health\"
initial_delay: int = 30
period: int = 10
} = {
enabled = true
path = \"/health\"
initial_delay = 30
period = 10
}
}
# Default configuration
($variables.EXTENSION_NAME_SNAKE)_config: ($variables.EXTENSION_NAME_PASCAL) = ($variables.EXTENSION_NAME_PASCAL) {
name = \"($variables.EXTENSION_NAME)\"
version = \"latest\"
replicas = 1
port = ($variables.DEFAULT_PORT)
}
"
}
def create_basic_version_kcl [variables: record]: nothing -> string {
$"# Version information for ($variables.EXTENSION_NAME) taskserv
schema ($variables.EXTENSION_NAME_PASCAL)Version {
current: str = \"($variables.VERSION)\"
compatible: [str] = [\"($variables.VERSION)\"]
deprecated?: [str] = []
changelog?: {str: str} = {}
}
($variables.EXTENSION_NAME_SNAKE)_version: ($variables.EXTENSION_NAME_PASCAL)Version = ($variables.EXTENSION_NAME_PASCAL)Version {
current = \"($variables.VERSION)\"
changelog = {
\"($variables.VERSION)\" = \"Initial release\"
}
}
"
}
def create_basic_defs_toml [variables: record]: nothing -> string {
$"# Default configuration for ($variables.EXTENSION_NAME)
# Generated on ($variables.DATE)
[service]
name = \"($variables.EXTENSION_NAME)\"
version = \"latest\"
port = ($variables.DEFAULT_PORT)
replicas = 1
[deployment]
strategy = \"RollingUpdate\"
max_unavailable = 1
max_surge = 1
[resources]
cpu_request = \"100m\"
cpu_limit = \"500m\"
memory_request = \"128Mi\"
memory_limit = \"512Mi\"
[health]
enabled = true
path = \"/health\"
initial_delay_seconds = 30
period_seconds = 10
timeout_seconds = 5
[networking]
service_type = \"ClusterIP\"
expose_metrics = false
metrics_port = 9090
"
}
def create_basic_install_script [variables: record]: nothing -> string {
$"#!/bin/bash
set -euo pipefail
# ($variables.EXTENSION_DISPLAY_NAME) Installation Script
# Generated on ($variables.DATE)
echo \"🚀 Installing ($variables.EXTENSION_NAME)...\"
# Configuration
SERVICE_NAME=\"${SERVICE_NAME:-($variables.EXTENSION_NAME)}\"
SERVICE_VERSION=\"${SERVICE_VERSION:-latest}\"
NAMESPACE=\"${NAMESPACE:-default}\"
REPLICAS=\"${REPLICAS:-1}\"
PORT=\"${PORT:-($variables.DEFAULT_PORT)}\"
# Validation
if [[ -z \"$SERVICE_NAME\" ]]; then
echo \"❌ SERVICE_NAME is required\"
exit 1
fi
echo \"📋 Configuration:\"
echo \" Service: $SERVICE_NAME\"
echo \" Version: $SERVICE_VERSION\"
echo \" Namespace: $NAMESPACE\"
echo \" Port: $PORT\"
echo \" Replicas: $REPLICAS\"
# Create namespace
echo \"🏗️ Creating namespace...\"
kubectl create namespace \"$NAMESPACE\" --dry-run=client -o yaml | kubectl apply -f -
# Apply configuration
echo \"🚢 Deploying service...\"
cat <<EOF | envsubst | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: $SERVICE_NAME
namespace: $NAMESPACE
labels:
app: $SERVICE_NAME
spec:
replicas: $REPLICAS
selector:
matchLabels:
app: $SERVICE_NAME
template:
metadata:
labels:
app: $SERVICE_NAME
spec:
containers:
- name: $SERVICE_NAME
image: $SERVICE_NAME:$SERVICE_VERSION
ports:
- containerPort: $PORT
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
livenessProbe:
httpGet:
path: /health
port: $PORT
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: $PORT
initialDelaySeconds: 5
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: $SERVICE_NAME
namespace: $NAMESPACE
labels:
app: $SERVICE_NAME
spec:
selector:
app: $SERVICE_NAME
ports:
- port: $PORT
targetPort: $PORT
type: ClusterIP
EOF
# Wait for deployment
echo \"⏳ Waiting for deployment to be ready...\"
kubectl wait --for=condition=available --timeout=300s deployment/$SERVICE_NAME -n $NAMESPACE
echo \"✅ ($variables.EXTENSION_DISPLAY_NAME) installed successfully\"
echo \"🔍 Check status with: kubectl get pods -n $NAMESPACE -l app=$SERVICE_NAME\"
"
}
def create_basic_readme [variables: record]: nothing -> string {
let readme_template = '# {{EXTENSION_DISPLAY_NAME}} Extension
{{EXTENSION_DESCRIPTION}}
## Overview
This extension provides {{EXTENSION_TYPE}} capabilities for {{EXTENSION_DISPLAY_NAME}}.
## Configuration
### Basic Usage
```kcl
import {{EXTENSION_TYPE}}s.{{EXTENSION_CATEGORY}}.{{EXTENSION_NAME}}.kcl.{{EXTENSION_NAME}} as {{EXTENSION_NAME_SNAKE}}
{{EXTENSION_NAME_SNAKE}}_config: {{EXTENSION_NAME_SNAKE}}.{{EXTENSION_NAME_PASCAL}} = {{EXTENSION_NAME_SNAKE}}.{{EXTENSION_NAME_SNAKE}}_config {
# Customize configuration
version = "1.2.3"
port = {{DEFAULT_PORT}}
}
```
## Installation
### Using Provisioning System
```bash
# Deploy with provisioning CLI
provisioning/core/cli/provisioning {{EXTENSION_TYPE}} create {{EXTENSION_NAME}} --infra my-infra --check
```
### Direct Installation
```bash
# Set environment variables
export SERVICE_VERSION="1.0.0"
export NAMESPACE="production"
# Run installation script
./default/install-{{EXTENSION_NAME}}.sh
```
## Development
### Testing
```bash
# Test discovery (for taskservs)
nu -c "use provisioning/core/nulib/taskservs/discover.nu *; get-taskserv-info {{EXTENSION_NAME}}"
# Test layer resolution (for taskservs)
nu -c "use provisioning/workspace/tools/layer-utils.nu *; test_layer_resolution {{EXTENSION_NAME}} wuji upcloud"
```
### Validation
```bash
# Check KCL syntax
kcl check kcl/{{EXTENSION_NAME}}.k
# Validate extension structure
nu provisioning/tools/create-extension.nu validate .
```
## License
{{LICENSE}}
## Author
{{AUTHOR_NAME}}
## Generated
Created on {{DATE}} using the Extension Creation Tool.
'
process_template_content $readme_template $variables
}
def create_basic_provider_kcl [variables: record]: nothing -> string {
$"# ($variables.EXTENSION_DISPLAY_NAME) Provider Configuration
# ($variables.EXTENSION_DESCRIPTION)
schema ($variables.EXTENSION_NAME_PASCAL)Provider {
# Provider metadata
name: str = \"($variables.EXTENSION_NAME)\"
version: str = \"latest\"
region?: str = \"default\"
# Provider-specific configuration
config?: {str: any} = {}
# Authentication
auth?: {
type: str = \"default\"
credentials?: {str: any} = {}
} = {}
# Resource limits
limits?: {
instances?: int = 10
storage?: str = \"100GB\"
} = {}
}
# Default configuration
($variables.EXTENSION_NAME_SNAKE)_provider: ($variables.EXTENSION_NAME_PASCAL)Provider = ($variables.EXTENSION_NAME_PASCAL)Provider {
name = \"($variables.EXTENSION_NAME)\"
version = \"latest\"
}
"
}
def create_basic_cluster_kcl [variables: record]: nothing -> string {
$"# ($variables.EXTENSION_DISPLAY_NAME) Cluster Configuration
# ($variables.EXTENSION_DESCRIPTION)
schema ($variables.EXTENSION_NAME_PASCAL)Cluster {
# Cluster metadata
name: str = \"($variables.EXTENSION_NAME)\"
version: str = \"latest\"
namespace: str = \"default\"
# Cluster configuration
nodes: int = 3
node_type: str = \"standard\"
# Cluster components
components: [str] = []
# Networking
networking?: {
cidr?: str = \"10.0.0.0/16\"
service_cidr?: str = \"10.1.0.0/16\"
} = {}
# Storage
storage?: {
type: str = \"persistent\"
size: str = \"10Gi\"
} = {}
}
# Default configuration
($variables.EXTENSION_NAME_SNAKE)_cluster: ($variables.EXTENSION_NAME_PASCAL)Cluster = ($variables.EXTENSION_NAME_PASCAL)Cluster {
name = \"($variables.EXTENSION_NAME)\"
version = \"latest\"
nodes = 3
}
"
}
# List available templates
export def "main list-templates" [type?: string]: nothing -> table {
let templates_base = "provisioning/templates/extensions"
if ($type | is-empty) {
# List all templates
mut all_templates = []
for ext_type in ["taskservs", "providers", "clusters"] {
let type_dir = ($templates_base | path join $ext_type)
if ($type_dir | path exists) {
let templates = try {
ls $type_dir | get name | each { |path|
{ type: ($ext_type | str replace "s$" ""), name: ($path | path basename) }
}
} catch { [] }
$all_templates = ($all_templates | append $templates)
}
}
$all_templates
} else {
# List templates for specific type
let type_plural = $type + "s"
let template_dir = ($templates_base | path join $type_plural)
if ($template_dir | path exists) {
ls $template_dir | get name | each { |path|
{ type: $type, name: ($path | path basename) }
}
} else {
[]
}
}
}
# Show template information
export def "main template-info" [type: string, template: string]: nothing -> record {
let template_path = $"provisioning/templates/extensions/($type)s/($template)"
if not ($template_path | path exists) {
error make { msg: $"Template not found: ($template_path)" }
}
let template_files = try {
ls $template_path | get name | each { |file| $file | path basename }
} catch { [] }
let readme_path = ($template_path | path join "README.md")
let readme_content = if ($readme_path | path exists) {
try {
open $readme_path | lines | take 10 | str join "\n"
} catch {
"No README content available"
}
} else {
"No README available"
}
{
type: $type,
template: $template,
path: $template_path,
files: $template_files,
readme_preview: $readme_content
}
}
# Interactive extension creation
export def "main interactive" []: nothing -> nothing {
print "🚀 Interactive Extension Creator"
print ""
# Get extension type
let type = (["taskserv", "provider", "cluster"] | input list "Select extension type:")
# Get extension name
let name = (input "Enter extension name (kebab-case):")
if ($name | is-empty) or ($name | str contains " ") or ($name | str contains "_") {
error make { msg: "Extension name must be kebab-case format (e.g., my-service)" }
}
# Get category for taskservs
let category = if $type == "taskserv" {
let categories = [
"container-runtime"
"databases"
"development"
"infrastructure"
"kubernetes"
"networking"
"storage"
]
print ""
print "📂 Available categories:"
for i in 0..($categories | length) {
let cat = ($categories | get $i)
print $" ($i + 1). ($cat)"
}
let category_choice = input "Select category (1-7):"
let category_index = (($category_choice | into int) - 1)
if $category_index < 0 or $category_index >= ($categories | length) {
error make { msg: "Invalid category selection" }
}
($categories | get $category_index)
} else {
""
}
# Get port for taskservs
let port = if $type == "taskserv" {
let port_input = input "Enter port number (default: 8080):"
if ($port_input | is-empty) { 8080 } else { $port_input | into int }
} else {
8080
}
# Get optional information
let author = input "Enter author name:" --default "Developer"
let description = input $"Enter description for ($name):" --default ""
# Show available templates
let available_templates = (main list-templates $type)
let template = if ($available_templates | length) > 0 {
let template_names = ($available_templates | get name)
if ($template_names | length) > 1 {
$template_names | input list "Select template:"
} else {
$template_names | first
}
} else {
"basic"
}
# Get output directory
let output = input "Enter output directory:" --default "provisioning/extensions"
# Create the extension
print ""
print "📋 Creating extension with:"
print $" Type: ($type)"
print $" Name: ($name)"
if $type == "taskserv" {
print $" Category: ($category)"
print $" Port: ($port)"
}
print $" Template: ($template)"
print $" Author: ($author)"
print ""
let confirm = input "✅ Proceed with creation? (y/N):"
if not ($confirm | str downcase | str starts-with "y") {
print "❌ Creation cancelled"
return
}
if $type == "taskserv" {
main $type $name --template $template --author $author --description $description --category $category --port $port --output $output
} else {
main $type $name --template $template --author $author --description $description --output $output
}
}
# Validate extension structure
export def "main validate" [extension_path: string]: nothing -> record {
print $"🔍 Validating extension: ($extension_path)"
let kcl_path = ($extension_path | path join "kcl")
if not ($kcl_path | path exists) {
error make { msg: $"KCL directory not found: ($kcl_path)" }
}
# Check required files
let required_files = ["kcl.mod"]
let optional_files = ["version.k", "dependencies.k", "README.md"]
let missing_required = ($required_files | where { |file|
not (($kcl_path | path join $file) | path exists)
})
let missing_optional = ($optional_files | where { |file|
not (($kcl_path | path join $file) | path exists)
})
# Check KCL syntax
let kcl_files = try {
glob ($kcl_path | path join "*.k") | get name
} catch {
[]
}
let syntax_errors = ($kcl_files | each { |file|
try {
^kcl check $file
null
} catch { |err|
{ file: $file, error: $err.msg }
}
} | compact)
let is_valid = ($missing_required | is-empty) and ($syntax_errors | is-empty)
{
valid: $is_valid,
extension_path: $extension_path,
missing_required_files: $missing_required,
missing_optional_files: $missing_optional,
syntax_errors: $syntax_errors,
recommendations: (
if not $is_valid {
[
"Fix missing required files",
"Resolve KCL syntax errors",
"Add optional files for better documentation"
]
} else {
["Extension structure is valid"]
}
)
}
}
# Show help
export def "main help" [] {
print "🛠️ Extension Creation Tool (Updated)"
print ""
print "USAGE:"
print " nu provisioning/tools/create-extension.nu <command> [options]"
print ""
print "COMMANDS:"
print " create <type> <name> Create new extension"
print " interactive Interactive extension creator"
print " list-templates [type] List available templates"
print " template-info <type> <template> Show template information"
print " validate <path> Validate extension structure"
print " help Show this help"
print ""
print "EXTENSION TYPES:"
print " taskserv Infrastructure task services"
print " provider Cloud providers"
print " cluster Cluster configurations"
print ""
print "CREATE OPTIONS:"
print " --template <template> Template to use (default: basic)"
print " --author <author> Author name (default: Unknown)"
print " --description <desc> Extension description"
print " --category <category> Category (required for taskservs)"
print " --port <port> Port number (default: 8080, for taskservs)"
print " --output <dir> Output directory (default: provisioning/extensions)"
print ""
print "EXAMPLES:"
print " # Interactive creation"
print " nu provisioning/tools/create-extension.nu interactive"
print ""
print " # Create taskserv"
print " nu provisioning/tools/create-extension.nu taskserv my-api \\"
print " --category development \\"
print " --port 8080 \\"
print " --description \"My API service\""
print ""
print " # List templates"
print " nu provisioning/tools/create-extension.nu list-templates taskserv"
print ""
print " # Validate extension"
print " nu provisioning/tools/create-extension.nu validate /path/to/extension"
}