962 lines
34 KiB
Plaintext
962 lines
34 KiB
Plaintext
|
|
#!/usr/bin/env nu
|
|||
|
|
|
|||
|
|
# Workspace Initialization Tool
|
|||
|
|
# Creates new workspace with proper directory structure for KCL package and module loader systems
|
|||
|
|
|
|||
|
|
use ../core/nulib/lib_provisioning/config/accessor.nu *
|
|||
|
|
|
|||
|
|
# Get dependency configuration (interactive or from flags)
|
|||
|
|
def get-dependency-config [
|
|||
|
|
workspace_path: string
|
|||
|
|
dep_option?: string
|
|||
|
|
dep_url?: string
|
|||
|
|
]: nothing -> record {
|
|||
|
|
# If dep_option provided, use it; otherwise prompt
|
|||
|
|
let option = if ($dep_option | is-not-empty) {
|
|||
|
|
$dep_option
|
|||
|
|
} else {
|
|||
|
|
print "\n📦 Provisioning Dependency Configuration"
|
|||
|
|
print " Choose how to configure the provisioning package dependency:\n"
|
|||
|
|
print " 1. workspace-home (default) - Install in workspace .kcl directory (self-contained)"
|
|||
|
|
print " 2. home-package - Use ~/.kcl/packages/provisioning (shared across workspaces)"
|
|||
|
|
print " 3. git-package - Use Git repository"
|
|||
|
|
print " 4. publish-repo - Use KCL registry\n"
|
|||
|
|
|
|||
|
|
let choice = (input " Select option [1-4] (default: 1): ")
|
|||
|
|
let normalized_choice = if ($choice | is-empty) { "1" } else { $choice }
|
|||
|
|
|
|||
|
|
match $normalized_choice {
|
|||
|
|
"1" => "workspace-home",
|
|||
|
|
"2" => "home-package",
|
|||
|
|
"3" => "git-package",
|
|||
|
|
"4" => "publish-repo",
|
|||
|
|
"workspace-home" => "workspace-home",
|
|||
|
|
"home-package" => "home-package",
|
|||
|
|
"git-package" => "git-package",
|
|||
|
|
"publish-repo" => "publish-repo",
|
|||
|
|
_ => {
|
|||
|
|
print $" ⚠️ Invalid choice '($normalized_choice)', using default: workspace-home"
|
|||
|
|
"workspace-home"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Handle each option
|
|||
|
|
match $option {
|
|||
|
|
"workspace-home" => {
|
|||
|
|
print " ✓ Using workspace-local provisioning package"
|
|||
|
|
|
|||
|
|
# Always run install - it will check version and update if needed
|
|||
|
|
install-workspace-provisioning $workspace_path
|
|||
|
|
|
|||
|
|
{
|
|||
|
|
type: "workspace-home"
|
|||
|
|
path: ".kcl/packages/provisioning"
|
|||
|
|
version: "0.0.1"
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
"home-package" => {
|
|||
|
|
print " ✓ Using home directory provisioning package"
|
|||
|
|
|
|||
|
|
# Always run install - it will check version and update if needed
|
|||
|
|
install-home-provisioning
|
|||
|
|
|
|||
|
|
{
|
|||
|
|
type: "home-package"
|
|||
|
|
path: "~/.kcl/packages/provisioning"
|
|||
|
|
version: "0.0.1"
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
"git-package" => {
|
|||
|
|
let git_url = if ($dep_url | is-not-empty) {
|
|||
|
|
$dep_url
|
|||
|
|
} else {
|
|||
|
|
input " Enter Git repository URL: "
|
|||
|
|
}
|
|||
|
|
print $" ✓ Using Git repository: ($git_url)"
|
|||
|
|
{
|
|||
|
|
type: "git-package"
|
|||
|
|
git: $git_url
|
|||
|
|
version: "0.0.1"
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
"publish-repo" => {
|
|||
|
|
let repo_url = if ($dep_url | is-not-empty) {
|
|||
|
|
$dep_url
|
|||
|
|
} else {
|
|||
|
|
input " Enter KCL registry URL (or leave empty for default): "
|
|||
|
|
}
|
|||
|
|
if ($repo_url | is-not-empty) {
|
|||
|
|
print $" ✓ Using registry: ($repo_url)"
|
|||
|
|
{
|
|||
|
|
type: "publish-repo"
|
|||
|
|
registry: $repo_url
|
|||
|
|
version: "0.0.1"
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
print " ✓ Using default KCL registry"
|
|||
|
|
{
|
|||
|
|
type: "publish-repo"
|
|||
|
|
version: "0.0.1"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
_ => {
|
|||
|
|
print $" ⚠️ Unknown option '($option)', using workspace-home"
|
|||
|
|
{
|
|||
|
|
type: "workspace-home"
|
|||
|
|
path: ".kcl/packages/provisioning"
|
|||
|
|
version: "0.0.1"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Install provisioning package to workspace .kcl directory
|
|||
|
|
def install-workspace-provisioning [workspace_path: string]: nothing -> nothing {
|
|||
|
|
let workspace_abs = ($workspace_path | path expand)
|
|||
|
|
let workspace_kcl_dir = ($workspace_abs | path join ".kcl" "packages")
|
|||
|
|
let workspace_prov_dir = ($workspace_kcl_dir | path join "provisioning")
|
|||
|
|
|
|||
|
|
# Create .kcl/packages directory
|
|||
|
|
mkdir $workspace_kcl_dir
|
|||
|
|
|
|||
|
|
# Determine source path for provisioning/kcl
|
|||
|
|
# Try to find provisioning root
|
|||
|
|
let script_dir = ($env.FILE_PWD? | default (pwd))
|
|||
|
|
let provisioning_root = ($script_dir | path dirname | path dirname)
|
|||
|
|
let kcl_source = ($provisioning_root | path join "kcl")
|
|||
|
|
|
|||
|
|
# Check if source exists
|
|||
|
|
if not ($kcl_source | path exists) {
|
|||
|
|
# Try alternative: check if PROVISIONING env var is set
|
|||
|
|
let alt_source = try {
|
|||
|
|
let config = (get-config)
|
|||
|
|
let base_path = ($config.paths.base? | default "")
|
|||
|
|
if ($base_path | is-not-empty) {
|
|||
|
|
$base_path | path join "kcl"
|
|||
|
|
} else {
|
|||
|
|
""
|
|||
|
|
}
|
|||
|
|
} catch {
|
|||
|
|
""
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ($alt_source | is-not-empty) and ($alt_source | path exists) {
|
|||
|
|
# Check version and update if needed
|
|||
|
|
if (check-and-update-package $workspace_prov_dir $alt_source) {
|
|||
|
|
print $" 📋 Copying provisioning KCL package from: ($alt_source)"
|
|||
|
|
cp -r $alt_source $workspace_prov_dir
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
error make {
|
|||
|
|
msg: $"Cannot find provisioning KCL package. Tried: ($kcl_source), ($alt_source)"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
# Check version and update if needed
|
|||
|
|
if (check-and-update-package $workspace_prov_dir $kcl_source) {
|
|||
|
|
print $" 📋 Copying provisioning KCL package from: ($kcl_source)"
|
|||
|
|
cp -r $kcl_source $workspace_prov_dir
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Also create distribution package for sharing/reuse
|
|||
|
|
build-distribution-package $provisioning_root $kcl_source
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
print $" ✅ Provisioning package ready at: ($workspace_prov_dir)"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Install provisioning package to home .kcl directory
|
|||
|
|
def install-home-provisioning []: nothing -> nothing {
|
|||
|
|
let home_kcl_dir = ($env.HOME | path join ".kcl" "packages")
|
|||
|
|
let home_prov_dir = ($home_kcl_dir | path join "provisioning")
|
|||
|
|
|
|||
|
|
# Create .kcl/packages directory
|
|||
|
|
mkdir $home_kcl_dir
|
|||
|
|
|
|||
|
|
# Determine source path for provisioning/kcl
|
|||
|
|
# Try to find provisioning root
|
|||
|
|
let script_dir = ($env.FILE_PWD? | default (pwd))
|
|||
|
|
let provisioning_root = ($script_dir | path dirname | path dirname)
|
|||
|
|
let kcl_source = ($provisioning_root | path join "kcl")
|
|||
|
|
|
|||
|
|
# Check if source exists
|
|||
|
|
if not ($kcl_source | path exists) {
|
|||
|
|
# Try alternative: check if PROVISIONING env var is set
|
|||
|
|
let alt_source = try {
|
|||
|
|
let config = (get-config)
|
|||
|
|
let base_path = ($config.paths.base? | default "")
|
|||
|
|
if ($base_path | is-not-empty) {
|
|||
|
|
$base_path | path join "kcl"
|
|||
|
|
} else {
|
|||
|
|
""
|
|||
|
|
}
|
|||
|
|
} catch {
|
|||
|
|
""
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if ($alt_source | is-not-empty) and ($alt_source | path exists) {
|
|||
|
|
# Check version and update if needed
|
|||
|
|
if (check-and-update-package $home_prov_dir $alt_source) {
|
|||
|
|
print $" 📋 Copying provisioning KCL package from: ($alt_source)"
|
|||
|
|
cp -r $alt_source $home_prov_dir
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
error make {
|
|||
|
|
msg: $"Cannot find provisioning KCL package. Tried: ($kcl_source), ($alt_source)"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
} else {
|
|||
|
|
# Check version and update if needed
|
|||
|
|
if (check-and-update-package $home_prov_dir $kcl_source) {
|
|||
|
|
print $" 📋 Copying provisioning KCL package from: ($kcl_source)"
|
|||
|
|
cp -r $kcl_source $home_prov_dir
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Also create distribution package for sharing/reuse
|
|||
|
|
build-distribution-package $provisioning_root $kcl_source
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
print $" ✅ Provisioning package ready at: ($home_prov_dir)"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Check package version and determine if update is needed
|
|||
|
|
# Returns true if package should be copied/updated, false if already up to date
|
|||
|
|
def check-and-update-package [
|
|||
|
|
target_dir: string
|
|||
|
|
source_dir: string
|
|||
|
|
]: nothing -> bool {
|
|||
|
|
# If target doesn't exist, needs installation
|
|||
|
|
if not ($target_dir | path exists) {
|
|||
|
|
print $" 📦 Installing new provisioning package..."
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Get source version
|
|||
|
|
let source_version = get-kcl-version $source_dir
|
|||
|
|
|
|||
|
|
# Get installed version
|
|||
|
|
let installed_version = get-kcl-version $target_dir
|
|||
|
|
|
|||
|
|
# Compare versions
|
|||
|
|
if $source_version != $installed_version {
|
|||
|
|
print $" 🔄 Updating provisioning package: ($installed_version) → ($source_version)"
|
|||
|
|
print $" 🗑️ Removing old version..."
|
|||
|
|
rm -rf $target_dir
|
|||
|
|
return true
|
|||
|
|
} else {
|
|||
|
|
print $" ✓ Provisioning package already up to date (v($installed_version))"
|
|||
|
|
return false
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Get KCL package version from kcl.mod file
|
|||
|
|
def get-kcl-version [kcl_path: string]: nothing -> string {
|
|||
|
|
let kcl_mod = ($kcl_path | path join "kcl.mod")
|
|||
|
|
|
|||
|
|
if not ($kcl_mod | path exists) {
|
|||
|
|
return "unknown"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
try {
|
|||
|
|
let mod_content = (open $kcl_mod | from toml)
|
|||
|
|
$mod_content.package?.version? | default "unknown"
|
|||
|
|
} catch {
|
|||
|
|
"unknown"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Build distribution package from KCL source
|
|||
|
|
def build-distribution-package [provisioning_root: string, kcl_source: string]: nothing -> nothing {
|
|||
|
|
let dist_dir = ($provisioning_root | path join "distribution" "packages")
|
|||
|
|
let registry_dir = ($provisioning_root | path join "distribution" "registry")
|
|||
|
|
|
|||
|
|
# Create distribution directories
|
|||
|
|
mkdir $dist_dir
|
|||
|
|
mkdir $registry_dir
|
|||
|
|
|
|||
|
|
# Generate package info
|
|||
|
|
let version = try {
|
|||
|
|
let kcl_mod = ($kcl_source | path join "kcl.mod")
|
|||
|
|
if ($kcl_mod | path exists) {
|
|||
|
|
let mod_content = (open $kcl_mod | from toml)
|
|||
|
|
$mod_content.package?.version? | default "0.0.1"
|
|||
|
|
} else {
|
|||
|
|
"0.0.1"
|
|||
|
|
}
|
|||
|
|
} catch {
|
|||
|
|
"0.0.1"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let package_name = $"provisioning-kcl-($version).tar.gz"
|
|||
|
|
let package_path = ($dist_dir | path join $package_name)
|
|||
|
|
|
|||
|
|
# Check if package already exists and is up to date
|
|||
|
|
if ($package_path | path exists) {
|
|||
|
|
let pkg_time = (ls $package_path | get 0.modified)
|
|||
|
|
let src_time = (ls $kcl_source | sort-by modified -r | get 0.modified)
|
|||
|
|
|
|||
|
|
if $pkg_time > $src_time {
|
|||
|
|
print $" ℹ️ Distribution package is up to date: ($package_name)"
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
print $" 📦 Building distribution package: ($package_name)"
|
|||
|
|
|
|||
|
|
# Create temporary staging directory
|
|||
|
|
let staging_dir = ($dist_dir | path join ".staging")
|
|||
|
|
mkdir $staging_dir
|
|||
|
|
let staging_prov = ($staging_dir | path join "provisioning")
|
|||
|
|
|
|||
|
|
# Copy KCL files to staging
|
|||
|
|
cp -r $kcl_source $staging_prov
|
|||
|
|
|
|||
|
|
# Create README for distribution
|
|||
|
|
let readme_content = $"# Provisioning KCL Package
|
|||
|
|
|
|||
|
|
Version: ($version)
|
|||
|
|
Generated: (date now | format date '%Y-%m-%d %H:%M:%S')
|
|||
|
|
|
|||
|
|
## Installation
|
|||
|
|
|
|||
|
|
### Option 1: Workspace-local installation
|
|||
|
|
Copy this package to your workspace:
|
|||
|
|
```bash
|
|||
|
|
cp -r provisioning /path/to/workspace/.kcl/packages/
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Option 2: User-level installation
|
|||
|
|
Install to your home directory:
|
|||
|
|
```bash
|
|||
|
|
mkdir -p ~/.kcl/packages
|
|||
|
|
cp -r provisioning ~/.kcl/packages/
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Option 3: Using workspace init
|
|||
|
|
When initializing a workspace, choose 'workspace-home' option:
|
|||
|
|
```bash
|
|||
|
|
provisioning ws init myworkspace
|
|||
|
|
# Select option 1 \(workspace-home\)
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Usage
|
|||
|
|
|
|||
|
|
In your KCL files:
|
|||
|
|
```kcl
|
|||
|
|
import provisioning.settings as settings
|
|||
|
|
import provisioning.server as server
|
|||
|
|
import provisioning.defaults as defaults
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Contents
|
|||
|
|
|
|||
|
|
This package contains the core provisioning KCL schemas including:
|
|||
|
|
- settings.k - Main settings schema
|
|||
|
|
- server.k - Server configuration schema
|
|||
|
|
- defaults.k - Default values and constants
|
|||
|
|
- And other core schemas
|
|||
|
|
|
|||
|
|
For more information, visit the provisioning documentation.
|
|||
|
|
"
|
|||
|
|
|
|||
|
|
$readme_content | save ($staging_dir | path join "README.md")
|
|||
|
|
|
|||
|
|
# Create package archive
|
|||
|
|
try {
|
|||
|
|
cd $staging_dir
|
|||
|
|
tar czf $package_path provisioning README.md
|
|||
|
|
cd $dist_dir # Go back before cleanup
|
|||
|
|
print $" ✅ Created distribution package: ($package_path)"
|
|||
|
|
|
|||
|
|
# Update registry
|
|||
|
|
update-package-registry $registry_dir $package_name $version $kcl_source
|
|||
|
|
|
|||
|
|
# Clean up staging
|
|||
|
|
rm -rf $staging_dir
|
|||
|
|
} catch {
|
|||
|
|
print $" ⚠️ Warning: Could not create distribution package: ($in)"
|
|||
|
|
# Try cleanup anyway
|
|||
|
|
try { rm -rf $staging_dir } catch { }
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Update package registry with metadata
|
|||
|
|
def update-package-registry [
|
|||
|
|
registry_dir: string
|
|||
|
|
package_name: string
|
|||
|
|
version: string
|
|||
|
|
source_path: string
|
|||
|
|
]: nothing -> nothing {
|
|||
|
|
let registry_file = ($registry_dir | path join "packages.json")
|
|||
|
|
|
|||
|
|
# Load existing registry or create new
|
|||
|
|
let registry = if ($registry_file | path exists) {
|
|||
|
|
open $registry_file | from json
|
|||
|
|
} else {
|
|||
|
|
{ packages: [] }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Create package entry
|
|||
|
|
let package_entry = {
|
|||
|
|
name: "provisioning"
|
|||
|
|
version: $version
|
|||
|
|
package_file: $package_name
|
|||
|
|
created_at: (date now | format date '%Y-%m-%d %H:%M:%S')
|
|||
|
|
source_path: $source_path
|
|||
|
|
type: "kcl-package"
|
|||
|
|
description: "Core provisioning KCL schemas and modules"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Update or add package entry
|
|||
|
|
let updated_packages = (
|
|||
|
|
$registry.packages
|
|||
|
|
| where name != "provisioning" or version != $version
|
|||
|
|
| append $package_entry
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
let updated_registry = { packages: $updated_packages }
|
|||
|
|
|
|||
|
|
# Save registry
|
|||
|
|
$updated_registry | to json | save -f $registry_file
|
|||
|
|
print $" 📋 Updated package registry: ($registry_file)"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Load default modules to infrastructure layer
|
|||
|
|
def load-default-modules [workspace_path: string, infra_path: string]: nothing -> nothing {
|
|||
|
|
let workspace_abs = ($workspace_path | path expand)
|
|||
|
|
let infra_abs = ($infra_path | path expand)
|
|||
|
|
|
|||
|
|
# Determine provisioning root and module loader script
|
|||
|
|
let script_dir = ($env.FILE_PWD? | default (pwd))
|
|||
|
|
let provisioning_root = ($script_dir | path dirname)
|
|||
|
|
let module_loader = ($provisioning_root | path join "core" "cli" "module-loader")
|
|||
|
|
|
|||
|
|
# Verify module loader exists
|
|||
|
|
if not ($module_loader | path exists) {
|
|||
|
|
print $" ⚠️ Warning: Module loader not found at ($module_loader)"
|
|||
|
|
print $" 💡 You can load modules manually: cd infra/<name> && provisioning mod load taskservs . os"
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Load os taskserv to infrastructure layer
|
|||
|
|
try {
|
|||
|
|
print $" 📦 Loading os taskserv to infrastructure layer..."
|
|||
|
|
^nu $module_loader "load" "taskservs" $infra_abs "os"
|
|||
|
|
print $" ✅ Loaded os taskserv"
|
|||
|
|
} catch {
|
|||
|
|
print $" ⚠️ Warning: Could not load os taskserv: ($in)"
|
|||
|
|
print $" 💡 You can load it manually: cd ($infra_path) && provisioning mod load taskservs . os"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Main workspace initialization command
|
|||
|
|
def main [
|
|||
|
|
workspace_path: string
|
|||
|
|
--infra-name: string # Infrastructure name
|
|||
|
|
--template: string # Template type: minimal, full, example
|
|||
|
|
--workspace-type: string # Workspace type: development, staging, production
|
|||
|
|
--user-name: string # User name for workspace
|
|||
|
|
--taskservs: list<string> # Taskservs to load during init
|
|||
|
|
--providers: list<string> # Providers to load during init
|
|||
|
|
--clusters: list<string> # Clusters to load during init
|
|||
|
|
--dep-option: string # Dependency option: home-package, git-package, publish-repo, workspace-home
|
|||
|
|
--dep-url: string # Dependency URL for git-package or publish-repo
|
|||
|
|
--overwrite # Overwrite existing workspace
|
|||
|
|
--check # Check mode only (dry-run)
|
|||
|
|
] {
|
|||
|
|
# Validate workspace name - no hyphens allowed (causes KCL module resolution issues)
|
|||
|
|
let workspace_name = ($workspace_path | path basename)
|
|||
|
|
if ($workspace_name | str contains "-") {
|
|||
|
|
print $"❌ Error: Workspace name cannot contain hyphens: '($workspace_name)'"
|
|||
|
|
print ""
|
|||
|
|
print " Hyphens in workspace names cause KCL module resolution issues."
|
|||
|
|
print $" Use underscores instead: '($workspace_name | str replace -a '-' '_')'"
|
|||
|
|
print ""
|
|||
|
|
exit 1
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Validate infra name if provided - no hyphens allowed
|
|||
|
|
if ($infra_name | is-not-empty) and ($infra_name | str contains "-") {
|
|||
|
|
print $"❌ Error: Infrastructure name cannot contain hyphens: '($infra_name)'"
|
|||
|
|
print ""
|
|||
|
|
print " Hyphens in infrastructure names cause KCL module resolution issues."
|
|||
|
|
print $" Use underscores instead: '($infra_name | str replace -a '-' '_')'"
|
|||
|
|
print ""
|
|||
|
|
exit 1
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if $check {
|
|||
|
|
let infra_dir_name = if ($infra_name | is-not-empty) { $infra_name } else { "main" }
|
|||
|
|
let template_to_use = if ($template | is-not-empty) { $template } else { "minimal" }
|
|||
|
|
print $"🔍 [CHECK MODE] Would initialize workspace: ($workspace_path)"
|
|||
|
|
print $" Template: ($template_to_use)"
|
|||
|
|
print $" Type: ($workspace_type | default 'development')"
|
|||
|
|
print $" Infrastructure directory: infra/($infra_dir_name)"
|
|||
|
|
print ""
|
|||
|
|
print " Structure to create:"
|
|||
|
|
print $" ($workspace_path)/"
|
|||
|
|
print " ├── infra/"
|
|||
|
|
print $" │ └── ($infra_dir_name)/"
|
|||
|
|
print " │ ├── .taskservs/"
|
|||
|
|
print " │ ├── .providers/"
|
|||
|
|
print " │ ├── .clusters/"
|
|||
|
|
print " │ ├── .manifest/"
|
|||
|
|
print " │ ├── kcl.mod"
|
|||
|
|
print " │ └── servers.k"
|
|||
|
|
print " ├── data/"
|
|||
|
|
print " ├── tmp/"
|
|||
|
|
print " └── resources/"
|
|||
|
|
if not ($taskservs | is-empty) {
|
|||
|
|
print ""
|
|||
|
|
print $" Taskservs to load: ($taskservs | str join ', ')"
|
|||
|
|
}
|
|||
|
|
if not ($providers | is-empty) {
|
|||
|
|
print $" Providers to load: ($providers | str join ', ')"
|
|||
|
|
}
|
|||
|
|
if not ($clusters | is-empty) {
|
|||
|
|
print $" Clusters to load: ($clusters | str join ', ')"
|
|||
|
|
}
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let workspace_abs = ($workspace_path | path expand)
|
|||
|
|
|
|||
|
|
# Check if workspace already exists
|
|||
|
|
if ($workspace_abs | path exists) and not $overwrite {
|
|||
|
|
print $"❌ Workspace already exists: ($workspace_path)"
|
|||
|
|
print " Use --overwrite to overwrite existing workspace"
|
|||
|
|
exit 1
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
print $"🚀 Initializing workspace: ($workspace_path)"
|
|||
|
|
|
|||
|
|
let infra_dir_name = if ($infra_name | is-not-empty) { $infra_name } else { "main" }
|
|||
|
|
let template_to_use = if ($template | is-not-empty) { $template } else { "minimal" }
|
|||
|
|
|
|||
|
|
# Determine provisioning dependency configuration
|
|||
|
|
let dep_config = get-dependency-config $workspace_abs $dep_option $dep_url
|
|||
|
|
|
|||
|
|
# Create workspace directory structure
|
|||
|
|
let workspace_info = create-workspace-structure $workspace_path $infra_dir_name
|
|||
|
|
|
|||
|
|
# Create basic configuration files
|
|||
|
|
create-workspace-config $workspace_path $workspace_info.infra_path $infra_dir_name $dep_config
|
|||
|
|
|
|||
|
|
# Create example files from template
|
|||
|
|
create-workspace-examples $workspace_path $workspace_info.infra_path $infra_dir_name $template_to_use
|
|||
|
|
|
|||
|
|
# Load default os taskserv module to infrastructure
|
|||
|
|
print ""
|
|||
|
|
print "📦 Loading default modules..."
|
|||
|
|
load-default-modules $workspace_path $workspace_info.infra_path
|
|||
|
|
|
|||
|
|
print ""
|
|||
|
|
print $"✅ Workspace initialized successfully: ($workspace_path)"
|
|||
|
|
print $"📁 Workspace structure created"
|
|||
|
|
print $"📂 Infrastructure created: infra/($infra_dir_name)"
|
|||
|
|
print $"📋 Template applied: ($template_to_use)"
|
|||
|
|
print $"📄 Configuration files created"
|
|||
|
|
print $"📦 Default modules loaded: os taskserv"
|
|||
|
|
print ""
|
|||
|
|
print "Next steps:"
|
|||
|
|
print $" 1. cd ($workspace_path)/infra/($infra_dir_name)"
|
|||
|
|
print " 2. Edit servers.k to customize your infrastructure"
|
|||
|
|
print " 3. Load additional modules:"
|
|||
|
|
print " - provisioning dt # Discover available taskservs"
|
|||
|
|
print " - cd <workspace>/infra/<name> && provisioning mod load taskservs . <name>"
|
|||
|
|
print $" 4. Deploy: provisioning s create --infra ($infra_dir_name)"
|
|||
|
|
print ""
|
|||
|
|
print "Quick start:"
|
|||
|
|
print $" cd ($workspace_path)/infra/($infra_dir_name)"
|
|||
|
|
print " kcl run servers.k # Validate configuration"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Create workspace directory structure
|
|||
|
|
def create-workspace-structure [
|
|||
|
|
workspace_path: string
|
|||
|
|
infra_name: string
|
|||
|
|
]: nothing -> record {
|
|||
|
|
let workspace_abs = ($workspace_path | path expand)
|
|||
|
|
let infra_dir_name = if ($infra_name | is-not-empty) { $infra_name } else { "main" }
|
|||
|
|
|
|||
|
|
# Create workspace-level directories (non-infrastructure)
|
|||
|
|
mkdir $workspace_abs
|
|||
|
|
mkdir ($workspace_abs | path join "data")
|
|||
|
|
mkdir ($workspace_abs | path join "tmp")
|
|||
|
|
mkdir ($workspace_abs | path join "resources")
|
|||
|
|
|
|||
|
|
# Create Layer 2 (workspace-level) module directories
|
|||
|
|
mkdir ($workspace_abs | path join ".taskservs")
|
|||
|
|
mkdir ($workspace_abs | path join ".providers")
|
|||
|
|
mkdir ($workspace_abs | path join ".clusters")
|
|||
|
|
mkdir ($workspace_abs | path join ".manifest")
|
|||
|
|
|
|||
|
|
# Create infrastructure directory structure
|
|||
|
|
let infra_path = ($workspace_abs | path join "infra" | path join $infra_dir_name)
|
|||
|
|
mkdir $infra_path
|
|||
|
|
|
|||
|
|
# Create Layer 3 (infrastructure-level) module directories
|
|||
|
|
mkdir ($infra_path | path join ".taskservs")
|
|||
|
|
mkdir ($infra_path | path join ".providers")
|
|||
|
|
mkdir ($infra_path | path join ".clusters")
|
|||
|
|
mkdir ($infra_path | path join ".manifest")
|
|||
|
|
|
|||
|
|
# Create module-loader expected directories
|
|||
|
|
mkdir ($infra_path | path join "taskservs")
|
|||
|
|
mkdir ($infra_path | path join "overrides")
|
|||
|
|
mkdir ($infra_path | path join "defs")
|
|||
|
|
mkdir ($infra_path | path join "config")
|
|||
|
|
|
|||
|
|
{
|
|||
|
|
path: $workspace_abs
|
|||
|
|
infra_path: $infra_path
|
|||
|
|
infra_name: $infra_dir_name
|
|||
|
|
created: (date now | format date "%Y-%m-%d %H:%M:%S")
|
|||
|
|
workspace_dirs: ["data", "tmp", "resources"]
|
|||
|
|
workspace_modules: [".taskservs", ".providers", ".clusters", ".manifest"]
|
|||
|
|
infra_dirs: [".taskservs", ".providers", ".clusters", ".manifest"]
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Create basic workspace configuration files
|
|||
|
|
def create-workspace-config [
|
|||
|
|
workspace_path: string
|
|||
|
|
infra_path: string
|
|||
|
|
infra_name: string
|
|||
|
|
dep_config: record
|
|||
|
|
]: nothing -> nothing {
|
|||
|
|
let workspace_abs = ($workspace_path | path expand)
|
|||
|
|
|
|||
|
|
# Generate dependency line based on configuration
|
|||
|
|
let dep_line = match $dep_config.type {
|
|||
|
|
"workspace-home" => {
|
|||
|
|
$"provisioning = \{ path = \"../../($dep_config.path)\", version = \"($dep_config.version)\" \}"
|
|||
|
|
},
|
|||
|
|
"home-package" => {
|
|||
|
|
$"provisioning = \{ path = \"($dep_config.path)\", version = \"($dep_config.version)\" \}"
|
|||
|
|
},
|
|||
|
|
"git-package" => {
|
|||
|
|
$"provisioning = \{ git = \"($dep_config.git)\", version = \"($dep_config.version)\" \}"
|
|||
|
|
},
|
|||
|
|
"publish-repo" => {
|
|||
|
|
if ($dep_config.registry? | is-not-empty) {
|
|||
|
|
$"provisioning = \{ registry = \"($dep_config.registry)\", version = \"($dep_config.version)\" \}"
|
|||
|
|
} else {
|
|||
|
|
$"provisioning = \{ version = \"($dep_config.version)\" \}"
|
|||
|
|
}
|
|||
|
|
},
|
|||
|
|
_ => {
|
|||
|
|
$"provisioning = \{ path = \"~/.kcl/packages/provisioning\", version = \"0.0.1\" \}"
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Create kcl.mod in infra directory
|
|||
|
|
let kcl_mod_content = $"[package]
|
|||
|
|
name = \"($infra_name)\"
|
|||
|
|
edition = \"v0.11.3\"
|
|||
|
|
version = \"0.0.1\"
|
|||
|
|
|
|||
|
|
[dependencies]
|
|||
|
|
# Provisioning package dependency \(configured during workspace init\)
|
|||
|
|
($dep_line)
|
|||
|
|
"
|
|||
|
|
|
|||
|
|
$kcl_mod_content | save ($infra_path | path join "kcl.mod")
|
|||
|
|
|
|||
|
|
# Create .gitignore at workspace root
|
|||
|
|
let base_gitignore = "# Workspace runtime data
|
|||
|
|
data/
|
|||
|
|
tmp/
|
|||
|
|
*.log
|
|||
|
|
|
|||
|
|
# Infrastructure module directories (managed by module-loader)
|
|||
|
|
infra/**/.taskservs/
|
|||
|
|
infra/**/.providers/
|
|||
|
|
infra/**/.clusters/
|
|||
|
|
infra/**/.manifest/
|
|||
|
|
|
|||
|
|
# Generated files
|
|||
|
|
infra/**/taskservs.k
|
|||
|
|
infra/**/providers.k
|
|||
|
|
infra/**/clusters.k
|
|||
|
|
|
|||
|
|
# Secrets and sensitive data
|
|||
|
|
*.age
|
|||
|
|
*.enc
|
|||
|
|
*.key
|
|||
|
|
"
|
|||
|
|
|
|||
|
|
# Add .kcl to gitignore if using workspace-home
|
|||
|
|
let gitignore_content = if $dep_config.type == "workspace-home" {
|
|||
|
|
$"($base_gitignore)
|
|||
|
|
# Workspace-local KCL packages \(managed by workspace init\)
|
|||
|
|
.kcl/
|
|||
|
|
"
|
|||
|
|
} else {
|
|||
|
|
$base_gitignore
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$gitignore_content | save ($workspace_abs | path join ".gitignore")
|
|||
|
|
|
|||
|
|
# Create empty manifest files in infra directory
|
|||
|
|
let empty_manifest = {
|
|||
|
|
loaded_taskservs: []
|
|||
|
|
loaded_providers: []
|
|||
|
|
loaded_clusters: []
|
|||
|
|
last_updated: (date now | format date "%Y-%m-%d %H:%M:%S")
|
|||
|
|
infra_path: $infra_path
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$empty_manifest | to yaml | save ($infra_path | path join ".manifest" | path join "taskservs.yaml")
|
|||
|
|
$empty_manifest | to yaml | save ($infra_path | path join ".manifest" | path join "providers.yaml")
|
|||
|
|
$empty_manifest | to yaml | save ($infra_path | path join ".manifest" | path join "clusters.yaml")
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Load template file
|
|||
|
|
def load-template [
|
|||
|
|
template_name: string
|
|||
|
|
file_name: string
|
|||
|
|
]: nothing -> string {
|
|||
|
|
# Try to find provisioning root
|
|||
|
|
let provisioning_root = $env.PROVISIONING? | default "/usr/local/provisioning"
|
|||
|
|
let template_path = ($provisioning_root | path join "templates" "workspace" $template_name $file_name)
|
|||
|
|
|
|||
|
|
if ($template_path | path exists) {
|
|||
|
|
open $template_path
|
|||
|
|
} else {
|
|||
|
|
# Return empty string if template not found (will use fallback)
|
|||
|
|
""
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Create example workspace files
|
|||
|
|
def create-workspace-examples [
|
|||
|
|
workspace_path: string
|
|||
|
|
infra_path: string
|
|||
|
|
infra_name: string
|
|||
|
|
template_name: string = "minimal"
|
|||
|
|
]: nothing -> nothing {
|
|||
|
|
let workspace_abs = ($workspace_path | path expand)
|
|||
|
|
|
|||
|
|
# Load servers.k template
|
|||
|
|
let servers_template = load-template $template_name "servers.k"
|
|||
|
|
let servers_example = if ($servers_template | is-not-empty) {
|
|||
|
|
$servers_template
|
|||
|
|
} else {
|
|||
|
|
# Fallback to minimal template if template not found
|
|||
|
|
load-template "minimal" "servers.k"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$servers_example | save ($infra_path | path join "servers.k")
|
|||
|
|
|
|||
|
|
# Load template README if available
|
|||
|
|
let template_readme = load-template $template_name "README.md"
|
|||
|
|
|
|||
|
|
# Create workspace README using simple string concatenation
|
|||
|
|
let ws_name = ($workspace_path | path basename)
|
|||
|
|
|
|||
|
|
# Use simple string interpolation for README content
|
|||
|
|
let readme_content = $"# Workspace: ($ws_name)
|
|||
|
|
|
|||
|
|
This workspace was initialized with the provisioning workspace system.
|
|||
|
|
|
|||
|
|
## Structure
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
($ws_name)/
|
|||
|
|
├── .taskservs/ # Layer 2: Workspace-shared taskserv modules
|
|||
|
|
├── .providers/ # Layer 2: Workspace-shared provider modules
|
|||
|
|
├── .clusters/ # Layer 2: Workspace-shared cluster modules
|
|||
|
|
├── .manifest/ # Layer 2: Workspace-level manifests
|
|||
|
|
├── infra/ # Infrastructure definitions
|
|||
|
|
│ └── ($infra_name)/ # Infrastructure: ($infra_name)
|
|||
|
|
│ ├── .taskservs/ # Layer 3: Infrastructure-specific overrides
|
|||
|
|
│ ├── .providers/ # Layer 3: Infrastructure-specific overrides
|
|||
|
|
│ ├── .clusters/ # Layer 3: Infrastructure-specific overrides
|
|||
|
|
│ ├── .manifest/ # Layer 3: Infrastructure-level manifests
|
|||
|
|
│ ├── kcl.mod # KCL module configuration
|
|||
|
|
│ └── servers.k # Server configuration
|
|||
|
|
├── data/ # Workspace-level runtime data
|
|||
|
|
├── tmp/ # Temporary files and deployment artifacts
|
|||
|
|
└── resources/ # Workspace-level resources and templates
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 3-Layer Module Architecture
|
|||
|
|
|
|||
|
|
The workspace uses a 3-layer module resolution system:
|
|||
|
|
|
|||
|
|
1. **Layer 1 - System**: Global extensions in provisioning/extensions/
|
|||
|
|
2. **Layer 2 - Workspace**: Shared modules in workspace .taskservs/, .providers/, .clusters/
|
|||
|
|
3. **Layer 3 - Infrastructure**: Infrastructure-specific overrides in infra/<name>/.taskservs/, .providers/, .clusters/
|
|||
|
|
|
|||
|
|
**Resolution order**: Layer 3 → Layer 2 → Layer 1 - higher layers override lower
|
|||
|
|
|
|||
|
|
### Infrastructure Directory
|
|||
|
|
|
|||
|
|
Each infrastructure in infra/<name>/ can:
|
|||
|
|
- Inherit shared modules from workspace layer -Layer 2-
|
|||
|
|
- Override with infrastructure-specific versions -Layer 3-
|
|||
|
|
- Define infrastructure-specific configuration -kcl.mod, servers.k-
|
|||
|
|
|
|||
|
|
## Usage
|
|||
|
|
|
|||
|
|
### 1. Navigate to Infrastructure Directory
|
|||
|
|
|
|||
|
|
Use cd to navigate to your infrastructure directory.
|
|||
|
|
|
|||
|
|
### 2. Load Required Modules
|
|||
|
|
|
|||
|
|
**Option A: Load at workspace level - shared across all infrastructures**
|
|||
|
|
|
|||
|
|
Load shared modules at workspace level using provisioning mod load with --level workspace flag.
|
|||
|
|
|
|||
|
|
**Option B: Load at infrastructure level - specific to one infrastructure**
|
|||
|
|
|
|||
|
|
Load infrastructure-specific modules using provisioning mod load with --level infra flag.
|
|||
|
|
|
|||
|
|
### 3. Verify Module Loading
|
|||
|
|
|
|||
|
|
Use provisioning mod list to verify loaded modules.
|
|||
|
|
|
|||
|
|
### 4. Configure Infrastructure
|
|||
|
|
|
|||
|
|
Edit servers.k in the infrastructure directory to define your requirements. The loaded modules will be available for import.
|
|||
|
|
|
|||
|
|
### 5. Deploy Infrastructure
|
|||
|
|
|
|||
|
|
Use kcl run to generate configuration and provisioning s create to deploy.
|
|||
|
|
|
|||
|
|
## Multiple Infrastructures
|
|||
|
|
|
|||
|
|
You can have multiple infrastructure configurations in one workspace by running workspace init with different --infra names.
|
|||
|
|
|
|||
|
|
## Module Management
|
|||
|
|
|
|||
|
|
- Use 'provisioning mod discover <type>' to find available modules
|
|||
|
|
- Use 'provisioning mod load <type> <infra-path> <modules...>' to load modules
|
|||
|
|
- Use 'provisioning mod unload <type> <infra-path> <module>' to remove modules
|
|||
|
|
- Module manifests track all loaded modules in each infra/<name>/.manifest/
|
|||
|
|
|
|||
|
|
The module directories -.taskservs/, .providers/, .clusters/- are managed automatically and should not be edited manually.
|
|||
|
|
"
|
|||
|
|
|
|||
|
|
# Combine workspace README with template README
|
|||
|
|
let final_readme = if ($template_readme | is-not-empty) {
|
|||
|
|
$"($readme_content)\n\n---\n\n($template_readme)"
|
|||
|
|
} else {
|
|||
|
|
$readme_content
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
$final_readme | save ($workspace_abs | path join "README.md")
|
|||
|
|
|
|||
|
|
# Also save template-specific README in infra directory if available
|
|||
|
|
if ($template_readme | is-not-empty) {
|
|||
|
|
$template_readme | save ($infra_path | path join "README.md")
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Initialize workspace with specific modules
|
|||
|
|
export def "main init" [
|
|||
|
|
workspace_path: string
|
|||
|
|
--infra-name: string # Infrastructure name
|
|||
|
|
--template: string # Template type: minimal, full, example
|
|||
|
|
--workspace-type: string # Workspace type: development, staging, production
|
|||
|
|
--user-name: string # User name for workspace
|
|||
|
|
--taskservs: list<string> = [] # Taskservs to load during initialization
|
|||
|
|
--providers: list<string> = [] # Providers to load during initialization
|
|||
|
|
--clusters: list<string> = [] # Clusters to load during initialization
|
|||
|
|
--dep-option: string # Dependency option: home-package, git-package, publish-repo, workspace-home
|
|||
|
|
--dep-url: string # Dependency URL for git-package or publish-repo
|
|||
|
|
--overwrite # Overwrite existing workspace
|
|||
|
|
--check # Check mode only (dry-run)
|
|||
|
|
]: nothing -> nothing {
|
|||
|
|
# Create basic workspace first
|
|||
|
|
main $workspace_path --infra-name $infra_name --template $template --workspace-type $workspace_type --user-name $user_name --taskservs $taskservs --providers $providers --clusters $clusters --dep-option $dep_option --dep-url $dep_url --overwrite=$overwrite --check=$check
|
|||
|
|
|
|||
|
|
# Skip module loading in check mode
|
|||
|
|
if $check {
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Load specified modules
|
|||
|
|
if not ($taskservs | is-empty) {
|
|||
|
|
print $"Loading taskservs: ($taskservs | str join ', ')"
|
|||
|
|
../core/cli/module-loader load taskservs $workspace_path $taskservs
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if not ($providers | is-empty) {
|
|||
|
|
print $"Loading providers: ($providers | str join ', ')"
|
|||
|
|
../core/cli/module-loader load providers $workspace_path $providers
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
if not ($clusters | is-empty) {
|
|||
|
|
print $"Loading clusters: ($clusters | str join ', ')"
|
|||
|
|
../core/cli/module-loader load clusters $workspace_path $clusters
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
print $"✅ Workspace initialized with modules"
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Show workspace information
|
|||
|
|
export def "main info" [workspace_path: string]: nothing -> record {
|
|||
|
|
let workspace_abs = ($workspace_path | path expand)
|
|||
|
|
|
|||
|
|
if not ($workspace_abs | path exists) {
|
|||
|
|
error make { msg: $"Workspace does not exist: ($workspace_abs)" }
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let taskservs_manifest = try {
|
|||
|
|
open ($workspace_abs | path join ".manifest" | path join "taskservs.yaml")
|
|||
|
|
} catch { { loaded_taskservs: [] } }
|
|||
|
|
|
|||
|
|
let providers_manifest = try {
|
|||
|
|
open ($workspace_abs | path join ".manifest" | path join "providers.yaml")
|
|||
|
|
} catch { { loaded_providers: [] } }
|
|||
|
|
|
|||
|
|
let clusters_manifest = try {
|
|||
|
|
open ($workspace_abs | path join ".manifest" | path join "clusters.yaml")
|
|||
|
|
} catch { { loaded_clusters: [] } }
|
|||
|
|
|
|||
|
|
{
|
|||
|
|
workspace: $workspace_abs
|
|||
|
|
taskservs_count: ($taskservs_manifest.loaded_taskservs | length)
|
|||
|
|
providers_count: ($providers_manifest.loaded_providers | length)
|
|||
|
|
clusters_count: ($clusters_manifest.loaded_clusters | length)
|
|||
|
|
taskservs: ($taskservs_manifest.loaded_taskservs | get name)
|
|||
|
|
providers: ($providers_manifest.loaded_providers | get name)
|
|||
|
|
clusters: ($clusters_manifest.loaded_clusters | get name)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
# Validate workspace structure
|
|||
|
|
export def "main validate" [workspace_path: string]: nothing -> record {
|
|||
|
|
let workspace_abs = ($workspace_path | path expand)
|
|||
|
|
|
|||
|
|
let required_dirs = [".taskservs", ".providers", ".clusters", ".manifest"]
|
|||
|
|
let required_files = ["kcl.mod", "servers.k"]
|
|||
|
|
|
|||
|
|
let missing_dirs = $required_dirs | where {|dir|
|
|||
|
|
not (($workspace_abs | path join $dir) | path exists)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let missing_files = $required_files | where {|file|
|
|||
|
|
not (($workspace_abs | path join $file) | path exists)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
let is_valid = ($missing_dirs | is-empty) and ($missing_files | is-empty)
|
|||
|
|
|
|||
|
|
{
|
|||
|
|
valid: $is_valid
|
|||
|
|
workspace: $workspace_abs
|
|||
|
|
missing_directories: $missing_dirs
|
|||
|
|
missing_files: $missing_files
|
|||
|
|
recommendations: (if not $is_valid {
|
|||
|
|
["Run 'workspace-init.nu <path>' to fix workspace structure"]
|
|||
|
|
} else {
|
|||
|
|
[]
|
|||
|
|
})
|
|||
|
|
}
|
|||
|
|
}
|