build-update-packages.ps1

2026-06-29




<#

.SYNOPSIS

  Build per-component update ZIP packages and a remote version.json manifest.

 

.DESCRIPTION

  This script packages each updatable component into a separate ZIP file and

  generates a version.json manifest under output/v{Version}/ for hosting.

  The manifest contains SHA256 checksums and file sizes for integrity verification.

 

  Server-side directory structure (versioned folders for rollback support):

    {OutputDir}/

      setup/

        v0.4.0/

          MEC_Agent-Setup-v0.4.0.exe   ← installer EXE (managed by build-release.ps1)

      updates/

        index.json                     ← version list (updated after each build)

        v0.4.0/

          manifest.json               ← per-version manifest (ZIPs referenced by URL)

          api_server-0.1.2.zip        ← only changed components

          mecui-0.1.5.zip

        v0.3.1/

          manifest.json

          mec_agent-0.3.1.zip

          ...

      models/                         ← model weight files (managed separately)

        index.json

        gemma-4-E4B-it-Q4_K_M.gguf

 

  All components (incl. python_embed, llama_cpp, downloads) use the app version

  number for tracking. Actual binary versions are stored in the metadata field.

  Model weight files (.gguf) are managed separately in models/ — not packaged here.

 

  Automatically packaged (9 lightweight components):

    mec_agent, api_server, whisper, mecui, skills, launcher, config, proxy, ein_wiki

 

  Large binary components (packaged when their source directories exist):

    python_embed  — agent_packaging/python-embed/  (built by build-installer.ps1)

    llama_cpp     — agent_packaging/llama/          (llama-b????-bin-win-*.zip files)

    whl           — {repo}/whl/                    (offline Python wheel cache)

    downloads     — agent_packaging/downloads/      (raw Python embed ZIP)

  Model weight files (.gguf) are managed separately — use build-model-index.ps1.

 

.PARAMETER OutputDir

  Root directory for all versioned packages. Default: installer/output

  Each build creates a v{Version}/ subdirectory inside.

  The index.json is written to this root directory.

 

.PARAMETER BaseUrl

  Base URL where the packages will be hosted. Used to generate download URLs in the manifest.

  Example: http://update-server/packages

  ZIPs will be served at {BaseUrl}/v{Version}/{component}-{version}.zip

 

.PARAMETER Version

  Version string for this release. Default: read from NanobotSetup.iss

 

.PARAMETER ReleaseNotes

  Release notes text to include in the manifest and index.json.

 

.PARAMETER PythonEmbedMetaVersion

  Actual Python interpreter version for metadata (e.g. "3.12.10").

  Defaults to auto-detected from python.exe. Used only as metadata — the

  update tracking version is always the app version.

 

.PARAMETER LlamaCppMetaVersion

  Actual llama.cpp build tag for metadata (e.g. "b9240").

  Defaults to auto-detected from ZIP filename. Used only as metadata.

 

.PARAMETER Components

  Optional list of component names to package. When specified, only those components

  are packaged and the versioned output directory is NOT cleaned (preserving existing ZIPs).

  When omitted, all components are packaged.

  Valid names: mec_agent, api_server, whisper, mecui, skills, launcher, config, proxy,

               ein_wiki, python_embed, downloads, llama_cpp, whl

  (model weight files are managed separately via build-model-index.ps1)

 

.EXAMPLE

  # Package all components (standalone, without installer EXE)

  .\build-update-packages.ps1 -BaseUrl "http://172.18.212.49/mec-updates" -ReleaseNotes "Bug fixes"

 

.EXAMPLE

  # Package only mecui (fast, incremental — other ZIPs in v{Ver}/ are preserved)

  .\build-update-packages.ps1 -BaseUrl "http://172.18.212.49/mec-updates" -Components mecui

 

.EXAMPLE

  # Full release (EXE + update packages in same versioned folder) — preferred workflow

  .\build-release.ps1 -BaseUrl "http://172.18.212.49/mec-updates" -ReleaseNotes "Bug fixes"

#>

param(

  [string]$OutputDir               = (Join-Path $PSScriptRoot "output"),

  [string]$BaseUrl                 = "http://update-server/packages",

  [string]$Version                 = "",

  [string]$ReleaseNotes            = "",

  [string]$PythonEmbedMetaVersion  = "",   # actual Python version for metadata only

  [string]$LlamaCppMetaVersion     = "",   # actual llama build tag for metadata only

  [string[]]$Components            = @()   # empty = all; specify names to package only those

)

 

$ErrorActionPreference = "Stop"

$AgentPackagingRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path

$RepoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).Path

 

# Load .NET ZIP support (no 2 GB per-file limit, unlike Compress-Archive in PS 5.1)

Add-Type -AssemblyName System.IO.Compression.FileSystem

 

# Determine version from .iss if not specified

if (-not $Version) {

  $issFile = Join-Path $PSScriptRoot "NanobotSetup.iss"

  if (Test-Path $issFile) {

    $issContent = Get-Content $issFile -Raw -Encoding UTF8

    if ($issContent -match '#define\s+MyAppVersion\s+"([^"]+)"') {

      $Version = $Matches[1]

    }

  }

  if (-not $Version) { $Version = "0.0.0" }

}

 

# ── 載入各模組的獨立版號(來自 version.json)────────────────────────────────

# 若 version.json 存在,優先使用各模組的 components[name].version;

# 找不到的模組才 fall back 到應用程式版號 $Version。

$script:moduleVersions = @{}

$_vjPath = Join-Path $AgentPackagingRoot "update\version.json"

if (Test-Path $_vjPath) {

  try {

    $_vj = Get-Content $_vjPath -Raw -Encoding UTF8 | ConvertFrom-Json

    foreach ($_prop in $_vj.components.PSObject.Properties) {

      if ($_prop.Value.PSObject.Properties["version"]) {

        $script:moduleVersions[$_prop.Name] = $_prop.Value.version

      }

    }

    Write-Host "Loaded module versions from version.json ($($script:moduleVersions.Count) components)"

  } catch {

    Write-Warning "Cannot read version.json module versions: $_"

  }

}


 

# Server layout separates setup (full installer) from updates (delta modules):

#   {OutputDir}/setup/v{ver}/   ← EXE managed by build-release.ps1

#   {OutputDir}/updates/v{ver}/ ← update ZIPs + manifest (this script)

#   {OutputDir}/updates/index.json

$VersionSlug = "v$Version"

$UpdatesRoot = Join-Path $OutputDir "updates"

$VersionedOutputDir = Join-Path $UpdatesRoot $VersionSlug

 

Write-Host "Building update packages for v$Version"

Write-Host "Output: $VersionedOutputDir"

Write-Host "Base URL: $BaseUrl/updates/$VersionSlug"

if ($Components.Count -gt 0) {

  Write-Host "Components filter: $($Components -join ', ')"

}

 

# ── Auto-detect changed components when -Components is not specified ────────────

# Compare version.json module versions against the most recent published manifest.

# Only package modules whose version has changed.

# Falls back to packaging ALL when no previous manifest is found (first release).

if ($Components.Count -eq 0) {

  $prevManifestObj = $null

  $idxPath = Join-Path $UpdatesRoot "index.json"

 

  if (Test-Path $idxPath) {

    try {

      $idx = Get-Content $idxPath -Raw -Encoding UTF8 | ConvertFrom-Json

      foreach ($entry in @($idx.versions)) {

        if ("$($entry.version)" -eq "$Version") { continue }   # skip current version

        $prevSlug = "v$($entry.version)"

        $prevMPath = Join-Path $UpdatesRoot "$prevSlug\manifest.json"

        if (Test-Path $prevMPath) {

          $candidate = Get-Content $prevMPath -Raw -Encoding UTF8 | ConvertFrom-Json

          if (($candidate.components.PSObject.Properties | Measure-Object).Count -gt 0) {

            $prevManifestObj = $candidate

            Write-Host "Auto-detect: comparing against v$($entry.version) manifest"

            break

          }

        }

      }

    } catch {

      Write-Warning "Auto-detect: failed to read index.json — will package all components"

    }

  }

 

  if ($null -eq $prevManifestObj) {

    Write-Host "Auto-detect: no previous manifest found — packaging ALL components (first release)"

    # $Components stays empty → package all

  } else {

    $autoList = [System.Collections.Generic.List[string]]::new()

 

    # Modules whose version changed relative to previous manifest

    foreach ($prop in $prevManifestObj.components.PSObject.Properties) {

      $cName   = $prop.Name

      $prevVer = $prop.Value.version

      $curVer  = if ($script:moduleVersions.ContainsKey($cName)) { $script:moduleVersions[$cName] } else { $Version }

      if ($curVer -ne $prevVer) { $autoList.Add($cName) }

    }

    # New modules not present in previous manifest

    foreach ($cName in $script:moduleVersions.Keys) {

      if (-not $prevManifestObj.components.PSObject.Properties[$cName] -and

          -not $autoList.Contains($cName)) {

        $autoList.Add($cName)

      }

    }

 

    if ($autoList.Count -eq 0) {

      Write-Host "Auto-detect: no version changes found — nothing to package" -ForegroundColor Yellow

      Write-Host "  (manifest will be rebuilt inheriting all components from previous version)"

      # Keep $Components empty so the manifest is still regenerated (with inherited data)

    } else {

      $Components = $autoList.ToArray()

      Write-Host "Auto-detect: changed components → $($Components -join ', ')" -ForegroundColor Cyan

    }

  }

}

 

# Clean the versioned subdirectory only when packaging all components.

# Partial builds (-Components) keep existing ZIPs so other components are preserved.

if ($Components.Count -eq 0) {

  if (Test-Path $VersionedOutputDir) { Remove-Item $VersionedOutputDir -Recurse -Force }

}

New-Item -ItemType Directory -Path $VersionedOutputDir -Force | Out-Null

# Ensure updates root and root OutputDir exist (needed for index.json)

New-Item -ItemType Directory -Path $UpdatesRoot -Force | Out-Null

New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null

 

$manifest = @{

  version          = $Version

  buildDate        = (Get-Date -Format "yyyy-MM-dd")

  minClientVersion = "0.3.0"

  releaseNotes     = $ReleaseNotes

  components       = @{}

}

 

# Returns $true if $name should be packaged given the -Components filter.

function Should-Package([string]$name) {

  return ($Components.Count -eq 0 -or $Components -contains $name)

}

 

# Add-ComponentPackage: all components use the unified app $Version for update tracking.

# Optional $metadataFields hashtable is merged into the manifest entry (e.g. actualVersion).

function Add-ComponentPackage([string]$name, [string[]]$sourcePaths, [hashtable]$metadataFields = @{}) {

  if (-not (Should-Package $name)) {

    Write-Host "  [$name] skipped (not in -Components filter)"

    return

  }

 

  # Use the component-specific version from version.json; fall back to app version.

  $componentVersion = if ($script:moduleVersions.ContainsKey($name)) { $script:moduleVersions[$name] } else { $Version }

 

  $zipFile = Join-Path $VersionedOutputDir "$name-$componentVersion.zip"

  $tempDir = Join-Path $env:TEMP "mec_update_pkg_$name"

 

  if (Test-Path $tempDir) { Remove-Item $tempDir -Recurse -Force }

  New-Item -ItemType Directory -Path $tempDir -Force | Out-Null

 

  $hasContent = $false

  foreach ($src in $sourcePaths) {

    if (-not (Test-Path $src)) {

      Write-Warning "Source not found for $name`: $src — skipping"

      continue

    }

    if ((Get-Item $src).PSIsContainer) {

      Copy-Item -Path (Join-Path $src "*") -Destination $tempDir -Recurse -Force -ErrorAction SilentlyContinue

    } else {

      Copy-Item -Path $src -Destination $tempDir -Force

    }

    $hasContent = $true

  }

 

  if (-not $hasContent) {

    Write-Warning "No content for component '$name' — skipping"

    Remove-Item $tempDir -Recurse -Force -ErrorAction SilentlyContinue

    return

  }

 

  # Use ZipFile::CreateFromDirectory instead of Compress-Archive:

  # Compress-Archive in PS 5.1 silently fails for files > 2 GB (e.g. large .gguf models).

  if (Test-Path $zipFile) { Remove-Item $zipFile -Force }

  [System.IO.Compression.ZipFile]::CreateFromDirectory(

    $tempDir, $zipFile,

    [System.IO.Compression.CompressionLevel]::Optimal,

    $false   # includeBaseDirectory = false

  )

  Remove-Item $tempDir -Recurse -Force

 

  if (-not (Test-Path $zipFile)) {

    Write-Warning "ZIP not created for '$name' — skipping component"

    return

  }

  $hash = (Get-FileHash -Path $zipFile -Algorithm SHA256).Hash.ToLower()

  $size = (Get-Item $zipFile).Length

  # URL: {BaseUrl}/updates/{VersionSlug}/{name}-{ver}.zip

  $url  = "$BaseUrl/updates/$VersionSlug/$name-$componentVersion.zip"

 

  $entry = @{

    version = $componentVersion

    sha256  = $hash

    size    = $size

    url     = $url

  }

  # Merge optional metadata fields (e.g. actualVersion for python_embed / llama_cpp)

  foreach ($k in $metadataFields.Keys) { $entry[$k] = $metadataFields[$k] }

 

  $manifest.components[$name] = $entry

  Write-Host "  [$name] v$componentVersion — $([math]::Round($size / 1MB, 2)) MB — $hash"

}

 

Write-Host ""

Write-Host "Packaging components..."

 

# 1. mec_agent.exe (source is nanobot.exe, rename to mec_agent.exe in package)

$nanobotExe = Join-Path $AgentPackagingRoot "nanobot_packaging\dist\nanobot.exe"

if (Test-Path $nanobotExe) {

  $tmpMecAgent = Join-Path $env:TEMP "mec_agent_rename"

  if (Test-Path $tmpMecAgent) { Remove-Item $tmpMecAgent -Recurse -Force }

  New-Item -ItemType Directory -Path $tmpMecAgent -Force | Out-Null

  Copy-Item $nanobotExe (Join-Path $tmpMecAgent "mec_agent.exe") -Force

  Add-ComponentPackage "mec_agent" @((Join-Path $tmpMecAgent "mec_agent.exe"))

  Remove-Item $tmpMecAgent -Recurse -Force

} else {

  Write-Warning "nanobot.exe not found — skipping mec_agent component"

}

 

# 2. api_server — exe only; server.py / update_manager.py / integrity_monitor.py

#    are compiled into api_server.exe and must NOT be distributed as visible source.

$apiServerExe = Join-Path $AgentPackagingRoot "api_server\api_server.exe"

Add-ComponentPackage "api_server" @($apiServerExe)

 

# 2b. whisper models (faster-whisper-tiny; model.bin included when present on build machine)

$whisperDir = Join-Path $AgentPackagingRoot "api_server\whisper"

Add-ComponentPackage "whisper" @($whisperDir)

 

# 3. mecui

$mecuiDir = Join-Path $AgentPackagingRoot "web\mecui"

if (-not (Test-Path $mecuiDir)) {

  $mecuiDir = Join-Path $RepoRoot "nanobot\web\dist"

}

Add-ComponentPackage "mecui" @($mecuiDir)

 

# 4. skills

$skillsDir = Join-Path $AgentPackagingRoot ".github\skills"

Add-ComponentPackage "skills" @($skillsDir)

 

# 5. launcher scripts

$launcherFiles = @(

  (Join-Path $AgentPackagingRoot "launcher\launch_mec_agent.ps1"),

  (Join-Path $AgentPackagingRoot "launcher\launch_mec_agent.cmd"),

  (Join-Path $AgentPackagingRoot "launcher\launch_mec_agent.vbs")

)

Add-ComponentPackage "launcher" $launcherFiles

 

# 6. config templates

$configFiles = @(

  (Join-Path $AgentPackagingRoot "config\config.json"),

  (Join-Path $AgentPackagingRoot "config\config_llama_cpp.json")

)

Add-ComponentPackage "config" $configFiles

 

# 7. proxy

$proxyDir = Join-Path $AgentPackagingRoot "proxy"

Add-ComponentPackage "proxy" @($proxyDir)

 

# 8. updater — mec_updater.ps1 is the primary updater (used by launcher for apply-staged).

$updaterFile = Join-Path $AgentPackagingRoot "update\mec_updater.ps1"

Add-ComponentPackage "updater" @($updaterFile)

 

# 9. ein_wiki

$einWikiDir = Join-Path $AgentPackagingRoot "bundled\ein-wiki"

if (Test-Path $einWikiDir) {

  Add-ComponentPackage "ein_wiki" @($einWikiDir)

} else {

  Write-Warning "bundled/ein-wiki/ not found — run build-installer.ps1 (Prepare-EinWiki) first to generate it"

}

 

# 10. python_embed

#     Tracking version = app version (unified). Actual Python interpreter version

#     stored in metadata.actualVersion for display purposes only.

$pythonEmbedDir = Join-Path $AgentPackagingRoot "python-embed"

if (Test-Path $pythonEmbedDir) {

  $pyActualVer = $PythonEmbedMetaVersion

  if (-not $pyActualVer) {

    $pyExe = Join-Path $pythonEmbedDir "python.exe"

    if (Test-Path $pyExe) {

      try {

        $pyActualVer = (& $pyExe -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}.{sys.version_info.micro}')" 2>$null).Trim()

      } catch { }

    }

    if (-not $pyActualVer) { $pyActualVer = "3.12.10" }

  }

  Add-ComponentPackage "python_embed" @($pythonEmbedDir) @{ actualVersion = $pyActualVer }

} else {

  Write-Warning "python-embed/ not found — run build-installer.ps1 (without -SkipPythonEmbed) first to generate it"

}

 

# 11. downloads — raw Python embed ZIP (recovery fallback for launcher self-heal)

$downloadsDir = Join-Path $AgentPackagingRoot "downloads"

if (Test-Path $downloadsDir) {

  $embedZips = @(Get-ChildItem -Path $downloadsDir -Filter "python-*-embed-amd64.zip" -ErrorAction SilentlyContinue)

  if ($embedZips.Count -gt 0) {

    # Detect actual Python version from ZIP filename for metadata

    $dlActualVer = $PythonEmbedMetaVersion

    if (-not $dlActualVer -and $embedZips[0].Name -match 'python-([\d.]+)-embed') {

      $dlActualVer = $Matches[1]

    }

    if (-not $dlActualVer) { $dlActualVer = if ($pyActualVer) { $pyActualVer } else { "3.12.10" } }

    # Pass only the python embed ZIP file(s) — exclude build-only artifacts

    Add-ComponentPackage "downloads" @($embedZips.FullName) @{ actualVersion = $dlActualVer }

  } else {

    Write-Warning "downloads/ found but no python-*-embed-amd64.zip — skipping downloads component"

  }

} else {

  Write-Warning "downloads/ not found — place python-*-embed-amd64.zip in agent_packaging/downloads/ first"

}

 

# 12. llama_cpp

#     Tracking version = app version (unified). Actual llama.cpp build stored in metadata.

$llamaDir = Join-Path $AgentPackagingRoot "llama"

if (Test-Path $llamaDir) {

  $llamaActualVer = $LlamaCppMetaVersion

  if (-not $llamaActualVer) {

    $llamaZip = Get-ChildItem -Path $llamaDir -Filter "llama-b*-bin-win-*.zip" |

      Sort-Object Name -Descending | Select-Object -First 1

    if ($llamaZip -and $llamaZip.Name -match 'llama-(b\d+)-bin-win') {

      $llamaActualVer = $Matches[1]

    }

  }

  if (-not $llamaActualVer) { $llamaActualVer = "b0000" }

  Add-ComponentPackage "llama_cpp" @($llamaDir) @{ actualVersion = $llamaActualVer }

} else {

  Write-Warning "llama/ not found — place llama-b????-bin-win-*.zip files in agent_packaging/llama/ first"

}

 

# 13. whl

$whlDir = Join-Path $RepoRoot "whl"

if (Test-Path $whlDir) {

  Add-ComponentPackage "whl" @($whlDir)

} else {

  Write-Warning "whl/ not found at $whlDir"

}

 

# NOTE: model weight files (.gguf) are managed separately in models/ on the server.

# Use build-model-index.ps1 to update models/index.json after uploading a new .gguf.

 

# ── Partial build: inherit unpackaged components from the previous manifest ───

# The spec requires manifest.json to declare ALL components for the version,

# with unchanged components pointing to the URL of the last version that changed them.

if ($Components.Count -gt 0) {

  $prevManifestObj = $null

  $prevManifestLabel = ""

 

  # Priority 1: existing manifest for THIS version (same-version patch / rebuild).

  # The versioned output dir is NOT cleaned during partial builds, so the file survives.

  $sameVersionManifestPath = Join-Path $VersionedOutputDir "manifest.json"

  if (Test-Path $sameVersionManifestPath) {

    try {

      $candidate = Get-Content $sameVersionManifestPath -Raw -Encoding UTF8 | ConvertFrom-Json

      # Only use it if it contains components BEYOND what we are currently packaging.

      # If it only has the same components we're building now, it was itself a partial build

      # and cannot serve as a reliable source for the other components.

      $otherCount = @($candidate.components.PSObject.Properties |

        Where-Object { $Components -notcontains $_.Name }).Count

      if ($otherCount -gt 0) {

        $prevManifestObj   = $candidate

        $prevManifestLabel = "existing v$Version"

      }

    } catch { }

  }

 

  # Priority 2: latest previous version from updates/index.json.

  # Iterate in order (newest first) until we find one whose manifest exists locally.

  if (-not $prevManifestObj) {

    $indexFile = Join-Path $UpdatesRoot "index.json"

    if (Test-Path $indexFile) {

      try {

        $idx = Get-Content $indexFile -Raw -Encoding UTF8 | ConvertFrom-Json

        foreach ($prevEntry in @($idx.versions)) {

          if ("$($prevEntry.version)" -eq "$Version") { continue }

          $prevVersionSlug  = "v$($prevEntry.version)"

          $prevManifestPath = Join-Path $UpdatesRoot "$prevVersionSlug\manifest.json"

          if (Test-Path $prevManifestPath) {

            $candidate = Get-Content $prevManifestPath -Raw -Encoding UTF8 | ConvertFrom-Json

            # Skip if this manifest is also a partial (no extra components to inherit)

            $otherCount = @($candidate.components.PSObject.Properties |

              Where-Object { $Components -notcontains $_.Name }).Count

            if ($otherCount -gt 0) {

              $prevManifestObj   = $candidate

              $prevManifestLabel = "v$($prevEntry.version)"

              break

            }

          }

        }

      } catch { }

    }

  }

 

  if ($prevManifestObj) {

    Write-Host ""

    Write-Host "Inheriting unpackaged components from $prevManifestLabel ..."

    foreach ($prop in $prevManifestObj.components.PSObject.Properties) {

      if (-not $manifest.components.ContainsKey($prop.Name)) {

        $pe = $prop.Value

        $entry = @{ version = $pe.version; sha256 = $pe.sha256; size = $pe.size; url = $pe.url }

        # Preserve optional metadata fields (e.g. actualVersion)

        if ($pe.PSObject.Properties['actualVersion']) { $entry['actualVersion'] = $pe.actualVersion }

        $manifest.components[$prop.Name] = $entry

        Write-Host "  [$($prop.Name)] v$($pe.version) — inherited from $prevManifestLabel"

      }

    }

  } else {

    Write-Warning ""

    Write-Warning "No previous manifest found to inherit from."

    Write-Warning "Partial build manifest will only contain: $($Components -join ', ')"

    Write-Warning "This does NOT comply with the manifest spec (all components must be declared)."

    Write-Warning "Run a full build first, then re-run with -Components for subsequent patches."

  }

}

 

# ── Write per-version manifest (BOM-free UTF-8) ────────────────────────────────

$manifestPath = Join-Path $VersionedOutputDir "manifest.json"

$manifestJson = $manifest | ConvertTo-Json -Depth 5

[System.IO.File]::WriteAllText($manifestPath, $manifestJson, (New-Object System.Text.UTF8Encoding $false))

 

# ── Update updates/index.json (version history list) ──────────────────────────

# index.json lives in updates/ alongside all versioned subdirectories.

$indexPath = Join-Path $UpdatesRoot "index.json"

$index = @{ latest = $Version; versions = @() }

if (Test-Path $indexPath) {

  try {

    $existing = Get-Content $indexPath -Raw -Encoding UTF8 | ConvertFrom-Json

    $index.versions = @($existing.versions | Where-Object { $_.version -ne $Version })

  } catch { }

}

# Prepend current version at the top

$newEntry = @{

  version      = $Version

  buildDate    = (Get-Date -Format "yyyy-MM-dd")

  releaseNotes = $ReleaseNotes

  manifestUrl  = "$BaseUrl/updates/$VersionSlug/manifest.json"

}

$index.versions = @($newEntry) + @($index.versions)

$indexJson = $index | ConvertTo-Json -Depth 5

[System.IO.File]::WriteAllText($indexPath, $indexJson, (New-Object System.Text.UTF8Encoding $false))

 

# ── Copy CHANGELOG.md to versioned output directory ──────────────────────

# The update server hosts CHANGELOG.md alongside manifest.json so that

# update_manager.py can download it after applying component updates.

$_changelogSrc = Join-Path $AgentPackagingRoot "update\CHANGELOG.md"

if (Test-Path $_changelogSrc) {

  Copy-Item $_changelogSrc (Join-Path $VersionedOutputDir "CHANGELOG.md") -Force

  Write-Host "Copied CHANGELOG.md to output directory"

} else {

  Write-Warning "update\CHANGELOG.md not found — clients will not be able to download it via auto-update"

}

 

Write-Host ""

Write-Host "Versioned manifest : $manifestPath"

Write-Host "Version index      : $indexPath"

if ($Components.Count -gt 0) {

  Write-Host "NOTE: Partial build — packaged: $($Components -join ', ')"

  Write-Host "      All components declared in manifest: $($manifest.components.Keys -join ', ')"

} else {

  Write-Host "Upload to your update server:"

  Write-Host "  $VersionedOutputDir  →  $BaseUrl/updates/$VersionSlug/"

  Write-Host "  $indexPath  →  $BaseUrl/updates/index.json"

}

Write-Host ""

Write-Host "Done. $($manifest.components.Count) component(s) packaged for v$Version."

 







Login to like - 0 Likes



Comments...


No Comments Yet...



Add Comment...



shumin

A graduated biotechnology engineer. Now is a software engineer


Latest Posts



Footer with Icons