#!/usr/bin/env nu # Pack - Create distribution bundles from built binaries # # Usage: # nu scripts/provisioning/pack.nu --target x86_64-linux --output dist/ # nu scripts/provisioning/pack.nu --target aarch64-macos --format tar.gz # use common/platform.nu * use common/manifest.nu * use common/validate.nu * # Configuration constants def get-config-file [] { "configs/provisioning.toml" } def get-project-dir [] { pwd } def show-help [] { let help_text = " syntaxis Pack - Create distribution bundles USAGE: nu scripts/provisioning/pack.nu [OPTIONS] OPTIONS: --target Target triple (e.g., x86_64-unknown-linux-gnu) Default: current platform --format Package format: tar.gz, zip, deb, rpm Default: tar.gz --output Output directory for bundles Default: dist/ --list-targets Show available targets --verify Verify bundle integrity after creation --dry-run Show what would be packaged without creating --help Show this help message EXAMPLES: # Create bundle for current platform nu scripts/provisioning/pack.nu # Create bundle for Linux x86_64 nu scripts/provisioning/pack.nu --target x86_64-unknown-linux-gnu # Create ZIP bundle for macOS ARM64 nu scripts/provisioning/pack.nu --target aarch64-apple-darwin --format zip # Dry run to preview bundling nu scripts/provisioning/pack.nu --dry-run " print $help_text } def build-binaries [target: string] { print ("🔨 Building binaries for " + $target + "...") try { ^cargo build --release --target $target --workspace --exclude syntaxis-dashboard | lines | each { |line| print (" " + $line) } print "✅ Build complete" } catch { let msg = "Build failed for target " + $target error make {msg: $msg} } } def collect-artifacts [target: string] { print "📦 Collecting artifacts..." let config = (open --raw (get-config-file) | from toml) let project_dir_val = (get-project-dir) let build_dir = ($project_dir_val | path join "target" $target "release") let binaries = ( $config.artifacts.binaries | each { |bin_info| let binary_name = if (((sys host).name) == "Windows") { $bin_info.name + ".exe" } else { $bin_info.name } let binary_path = ($build_dir | path join $binary_name) if (($binary_path | path exists)) { print (" ✅ Binary: " + $binary_name) { name: $bin_info.name path: $binary_path description: $bin_info.description } } else { print (" ❌ Binary not found: " + $binary_name) null } } | where { |item| (not ($item == null)) } ) let configs = ( $config.artifacts.configs | each { |config_path| let full_path = ($project_dir_val | path join $config_path) if (($full_path | path exists)) { print (" ✅ Config: " + $config_path) { name: ($config_path | path basename) path: $full_path } } else { null } } | where { |item| (not ($item == null)) } ) let docs = ( $config.artifacts.docs | each { |doc_path| let full_path = ($project_dir_val | path join $doc_path) if (($full_path | path exists)) { print (" ✅ Doc: " + $doc_path) { name: ($doc_path | path basename) path: $full_path } } else { null } } | where { |item| (not ($item == null)) } ) let scripts = ( $config.artifacts.scripts | each { |script_path| let full_path = ($project_dir_val | path join $script_path) if (($full_path | path exists)) { print (" ✅ Script: " + $script_path) { name: ($script_path | path basename) path: $full_path } } else { null } } | where { |item| (not ($item == null)) } ) let wrappers = ( if (($config.artifacts | get -o wrapper_scripts) != null) { $config.artifacts.wrapper_scripts | each { |wrapper_path| let full_path = ($project_dir_val | path join $wrapper_path) if (($full_path | path exists)) { print (" ✅ Wrapper: " + $wrapper_path) { name: ($wrapper_path | path basename) path: $full_path } } else { null } } | where { |item| (not ($item == null)) } } else { [] } ) { binaries: $binaries configs: $configs docs: $docs scripts: $scripts wrappers: $wrappers } } def create-bundle [target: string, format: string, output_dir: path, artifacts: record] { let format_msg = "🎁 Creating " + $format + " bundle for " + $target + "..." print $format_msg let config = (open --raw (get-config-file) | from toml) let version = "0.1.0" let project_dir_val = (get-project-dir) let bundle_name = "syntaxis-v" + $version + "-" + $target # Create temporary staging directory let staging_dir = ($project_dir_val | path join ".provisioning-staging" $bundle_name) ^mkdir -p ($staging_dir | path dirname) ^mkdir -p $staging_dir # Create bundle subdirectories ^mkdir -p ($staging_dir | path join "bin") ^mkdir -p ($staging_dir | path join "configs") ^mkdir -p ($staging_dir | path join "docs") # Copy binaries print " 📋 Copying binaries..." $artifacts.binaries | each { |bin| ^cp $bin.path ($staging_dir | path join "bin" $bin.name) print (" ✓ " + $bin.name) } # Copy configs print " ⚙️ Copying configs..." $artifacts.configs | each { |cfg| let dest = if (($cfg.name | str starts-with "database-")) { ($staging_dir | path join "configs" $cfg.name) } else { let subdir = ($cfg.path | path dirname | path basename) ^mkdir -p ($staging_dir | path join "configs" $subdir) ($staging_dir | path join "configs" $subdir $cfg.name) } ^cp $cfg.path $dest print (" ✓ " + $cfg.name) } # Copy docs (special handling for root-level docs) print " 📖 Copying documentation..." $artifacts.docs | each { |doc| let dest = if ($doc.name == "BUNDLE_README.md") { # BUNDLE_README.md becomes README.md at root ^cp $doc.path ($staging_dir | path join "README.md") print (" ✓ README.md (from BUNDLE_README.md)") } else if (($doc.name == "QUICK_START.md") or ($doc.name == "INSTALL.md")) { # QUICK_START and INSTALL go to root for easy access ^cp $doc.path ($staging_dir | path join $doc.name) print (" ✓ " + $doc.name + " (root level)") } else { # Other docs go in docs/ folder ^cp $doc.path ($staging_dir | path join "docs" $doc.name) print (" ✓ " + $doc.name + " (docs/)") } } # Copy scripts to root of bundle print " 🔧 Copying installation scripts..." $artifacts.scripts | each { |script| let dest = ($staging_dir | path join $script.name) ^cp $script.path $dest ^chmod +x $dest print (" ✓ " + $script.name) } # Copy wrapper scripts to scripts/ directory in bundle if (($artifacts.wrappers | length) > 0) { print " 🔄 Copying wrapper scripts..." ^mkdir -p ($staging_dir | path join "scripts") $artifacts.wrappers | each { |wrapper| let dest = ($staging_dir | path join "scripts" $wrapper.name) ^cp $wrapper.path $dest ^chmod +x $dest print (" ✓ " + $wrapper.name) } } # Create manifest print " 📋 Creating manifest..." let binary_names = ($artifacts.binaries | each { |b| $b.name }) let config_names = ($artifacts.configs | each { |c| $c.name }) let doc_names = ($artifacts.docs | each { |d| $d.name }) let script_names = ($artifacts.scripts | each { |s| $s.name }) let manifest = (create $version $target $format --binaries $binary_names --configs $config_names --docs $doc_names --scripts $script_names) # Generate checksums for all files let staging_files = ( glob ($staging_dir | path join "**" "*") | where { |f| (($f | path type) == "file") } ) # Calculate SHA256 checksums for all files in the bundle print " 📋 Generating checksums..." let checksums_output = ( ^find $staging_dir -type f -print0 | ^xargs -0 sha256sum | split row "\n" | where { |line| (($line | str length) > 0) } ) # Build checksums as empty dict for now (will be populated from checksums file) let checksums = {} let manifest_with_checksums = (add-checksums $manifest $checksums) (save $manifest_with_checksums ($staging_dir | path join "manifest.toml")) # Create output directory ^mkdir -p $output_dir # Create the actual bundle archive let bundle_path = match $format { "tar.gz" => { let tar_path = ($output_dir | path join ($bundle_name + ".tar.gz")) let tar_msg = " 📦 Creating TAR.GZ: " + ($tar_path | into string) print $tar_msg ^tar -czf $tar_path -C ($staging_dir | path dirname) $bundle_name $tar_path } "zip" => { let zip_path = ($output_dir | path join ($bundle_name + ".zip")) let zip_msg = " 📦 Creating ZIP: " + ($zip_path | into string) print $zip_msg ^zip -r $zip_path ($staging_dir | path join "*") -C $staging_dir $zip_path } _ => { let msg = "Unsupported format: " + $format error make {msg: $msg} } } # Save checksums file alongside bundle let checksums_file = ($output_dir | path join ($bundle_name + ".checksums.toml")) # Build TOML checksums file from sha256sum output let checksums_toml_content = ( "[checksums]\n" + ( $checksums_output | each {|line| # Parse sha256sum output: "hash filename" let line_trimmed = ($line | str trim) if (($line_trimmed | str length) > 0) { # Extract hash (first 64 characters) and file path (everything after two spaces) let hash = ($line_trimmed | str substring 0..64) let file_path = ($line_trimmed | str substring 66.. | str trim) if (($file_path | str length) > 0) { let rel_path = (try { $file_path | path relative-to $staging_dir } catch { $file_path }) "\"" + ($rel_path | into string) + "\" = \"" + ($hash | str trim) + "\"\n" } else { "" } } else { "" } } | str join "" ) ) $checksums_toml_content | ^tee $checksums_file # Cleanup staging directory ^rm -rf $staging_dir print ("✅ Bundle created: " + ($bundle_path | into string)) print ("✅ Checksums saved: " + ($checksums_file | into string)) { bundle: $bundle_path checksums: $checksums_file size_mb: (((ls $bundle_path | get size.0) | into int) / (1024 * 1024) | math round --precision 2) } } # Main entry point def main [--target: string = "", --format: string = "tar.gz", --output: path = "dist/", --list-targets, --verify, --dry-run, --help] { if $help { show-help return } # Validate config let config_validation = (validate-config (get-config-file)) if (not ($config_validation.valid)) { let errors = ($config_validation.errors | str join ", ") let msg = "Invalid provisioning config: " + $errors error make {msg: $msg} } # List available targets if $list_targets { print "\nAvailable targets:" list-targets (get-config-file) | each { |t| let enabled_mark = if ($t.enabled == true) { "✓" } else { "✗" } print (" " + $enabled_mark + " " + $t.target) print (" Description: " + $t.description) print (" Formats: " + $t.formats) } return } # Determine target let selected_target = if ($target == "") { current-target } else { $target } # Validate target if (not (is-target-enabled $selected_target (get-config-file))) { let msg = "Target not available or disabled: " + $selected_target error make {msg: $msg} } # Show configuration print "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" print "📦 syntaxis Pack Configuration" print "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" let desc = (target-description $selected_target (get-config-file)) print ("Target: " + $selected_target + " - " + $desc) print ("Format: " + $format) print ("Output: " + ($output | into string)) print ("Dry Run: " + ($dry_run | into string)) print "" if $dry_run { print "🔍 DRY RUN MODE - No bundles will be created" return } # Build binaries build-binaries $selected_target # Collect artifacts let artifacts = (collect-artifacts $selected_target) # Create bundle let result = (create-bundle $selected_target $format $output $artifacts) # Verify if requested if $verify { print "" print "🔍 Verifying bundle integrity..." let bundle_validation = (validate-bundle-structure $result.bundle) if (not ($bundle_validation.valid)) { let errors = ($bundle_validation.errors | str join ", ") let msg = "Bundle validation failed: " + $errors error make {msg: $msg} } print "✅ Bundle verification passed" } # Summary print "" print "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" print "📦 Bundle Summary" print "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" print ("Bundle: " + ($result.bundle | path basename)) print ("Size: " + ($result.size_mb | into string) + " MB") print ("Checksums: " + ($result.checksums | path basename)) print "" print "Next steps:" print " 1. Share the bundle with your team" print " 2. On target machine, extract: tar -xzf .tar.gz" print " 3. Install: nu /install.sh" print "" }