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)
453 lines
15 KiB
Plaintext
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 ""
|
|
}
|
|
|