Jesús Pérez 9cef9b8d57 refactor: consolidate configuration directories
Merge _configs/ into config/ for single configuration directory.
Update all path references.

Changes:
- Move _configs/* to config/
- Update .gitignore for new patterns
- No code references to _configs/ found

Impact: -1 root directory (layout_conventions.md compliance)
2025-12-26 18:36:23 +00:00

453 lines
15 KiB
Plaintext

#!/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> Target triple (e.g., x86_64-unknown-linux-gnu)
Default: current platform
--format <FORMAT> Package format: tar.gz, zip, deb, rpm
Default: tar.gz
--output <DIR> 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 <bundle>.tar.gz"
print " 3. Install: nu <bundle>/install.sh"
print ""
}