Initial release
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
@echo off
|
||||
echo =============================================
|
||||
echo HOT-Step 9000 CPP
|
||||
echo High-Performance Music Generation
|
||||
echo =============================================
|
||||
echo.
|
||||
|
||||
REM Set the distribution root for portable mode detection.
|
||||
REM The server reads HOT_STEP_ROOT to resolve all paths.
|
||||
set HOT_STEP_ROOT=%~dp0
|
||||
|
||||
REM Create models directory if it doesn't exist (first run)
|
||||
if not exist "%~dp0models" mkdir "%~dp0models"
|
||||
|
||||
REM Open browser if no existing tab is found
|
||||
start /MIN "" powershell -ExecutionPolicy Bypass -File "%~dp0open-browser-if-needed.ps1" "http://localhost:3001/" 5
|
||||
|
||||
REM ── Restart loop ──────────────────────────────────────────
|
||||
REM The server writes .restart-requested when the user clicks
|
||||
REM "Restart" in the UI. After node exits, we check for the
|
||||
REM marker — if it exists, we delete it and relaunch.
|
||||
:start
|
||||
echo Starting server...
|
||||
echo.
|
||||
"%~dp0runtime\node.exe" "%~dp0server\server.mjs"
|
||||
|
||||
REM Check for restart marker
|
||||
if exist "%~dp0.restart-requested" (
|
||||
del "%~dp0.restart-requested"
|
||||
echo.
|
||||
echo [HOT-Step] Restarting...
|
||||
echo.
|
||||
goto start
|
||||
)
|
||||
|
||||
echo.
|
||||
echo [HOT-Step] Server stopped. Press any key to exit.
|
||||
pause >nul
|
||||
@@ -0,0 +1,46 @@
|
||||
#!/bin/bash
|
||||
# HOT-Step 9000 CPP — Linux launch script
|
||||
# Portable distribution launcher with restart loop support.
|
||||
#
|
||||
# Usage: ./HOT-Step.sh
|
||||
# The script sets HOT_STEP_ROOT for portable path resolution,
|
||||
# opens the browser after a short delay, and loops on restart
|
||||
# requests from the UI (same behaviour as HOT-Step.bat on Windows).
|
||||
|
||||
echo "============================================="
|
||||
echo " HOT-Step 9000 CPP"
|
||||
echo " High-Performance Music Generation"
|
||||
echo "============================================="
|
||||
echo ""
|
||||
|
||||
DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
export HOT_STEP_ROOT="$DIR"
|
||||
|
||||
# Create models directory if it doesn't exist (first run)
|
||||
mkdir -p "$DIR/models"
|
||||
|
||||
# Open browser after a short delay (desktop only — silent on headless)
|
||||
(sleep 5 && xdg-open "http://localhost:3001" 2>/dev/null) &
|
||||
|
||||
# ── Restart loop ──────────────────────────────────────────
|
||||
# The server writes .restart-requested when the user clicks
|
||||
# "Restart" in the UI. After node exits, we check for the
|
||||
# marker — if it exists, we delete it and relaunch.
|
||||
while true; do
|
||||
echo "Starting server..."
|
||||
echo ""
|
||||
"$DIR/runtime/bin/node" "$DIR/server/server.mjs"
|
||||
|
||||
# Check for restart marker
|
||||
if [ -f "$DIR/.restart-requested" ]; then
|
||||
rm "$DIR/.restart-requested"
|
||||
echo ""
|
||||
echo "[HOT-Step] Restarting..."
|
||||
echo ""
|
||||
continue
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "[HOT-Step] Server stopped."
|
||||
break
|
||||
done
|
||||
@@ -0,0 +1,107 @@
|
||||
==============================================================
|
||||
HOT-Step 9000 — High-Performance AI Music Generation
|
||||
==============================================================
|
||||
|
||||
QUICK START
|
||||
-----------
|
||||
1. Extract this folder anywhere you like.
|
||||
2. Double-click "HOT-Step.bat" to start.
|
||||
3. Your browser will open to http://localhost:3001 automatically.
|
||||
4. On first launch, go to Settings > Model Manager to download
|
||||
the AI models (~7 GB). You'll need an internet connection.
|
||||
|
||||
That's it! No installation required.
|
||||
|
||||
|
||||
REQUIREMENTS
|
||||
------------
|
||||
- Windows 10/11 (64-bit)
|
||||
- NVIDIA GPU with recent drivers (RTX 2060 or newer recommended)
|
||||
* CPU-only mode works but is significantly slower
|
||||
- ~10 GB free disk space (for models + generated audio)
|
||||
- Internet connection for first-run model downloads
|
||||
|
||||
|
||||
GPU SUPPORT
|
||||
-----------
|
||||
CUDA Build (NVIDIA):
|
||||
Supports all NVIDIA GPUs with recent drivers:
|
||||
- RTX 2000 series (Turing)
|
||||
- RTX 3000 series (Ampere)
|
||||
- RTX 4000 series (Ada Lovelace)
|
||||
- RTX 5000 series (Blackwell)
|
||||
|
||||
Vulkan Build (AMD / NVIDIA / Intel):
|
||||
Uses Vulkan for GPU acceleration. Works with most modern GPUs
|
||||
that have Vulkan 1.1+ support. Requires GPU drivers with the
|
||||
Vulkan runtime installed (included with most driver packages).
|
||||
|
||||
IMPORTANT: Use the 4B LM model with the Vulkan build.
|
||||
The 1.7B LM model produces corrupted output due to precision
|
||||
issues in the Vulkan compute shaders. The 4B model works
|
||||
correctly. This does not affect the DiT or VAE pipelines.
|
||||
|
||||
CPU Build:
|
||||
Runs entirely on CPU. No GPU required, but generation will be
|
||||
significantly slower.
|
||||
|
||||
Make sure your GPU drivers are up to date:
|
||||
https://www.nvidia.com/en-us/drivers/
|
||||
|
||||
|
||||
STEM SEPARATION (Optional)
|
||||
---------------------------
|
||||
Stem separation requires additional runtime files (~1.3 GB).
|
||||
When you first use Stem Studio, you'll be prompted to download
|
||||
them from the Model Manager.
|
||||
|
||||
These files include ONNX Runtime and cuDNN libraries required
|
||||
for the neural network stem separator.
|
||||
|
||||
|
||||
CONFIGURATION
|
||||
-------------
|
||||
To customize settings, copy ".env.example" to ".env" and edit it.
|
||||
Most settings can also be changed from the Settings page in the app.
|
||||
|
||||
|
||||
FOLDER STRUCTURE
|
||||
----------------
|
||||
HOT-Step.bat — Launch the application
|
||||
runtime/ — Node.js runtime (do not modify)
|
||||
engine/ — C++ inference engine
|
||||
server/ — Application server
|
||||
ui/ — Web interface
|
||||
models/ — AI model files (downloaded on first run)
|
||||
adapters/ — LoRA adapters (optional)
|
||||
Essentia/ — Audio analysis tool
|
||||
noise_samples/ — Noise profiles for denoising (optional)
|
||||
|
||||
|
||||
TROUBLESHOOTING
|
||||
---------------
|
||||
Q: The app won't start.
|
||||
A: Make sure you extracted the FULL zip. Don't move individual
|
||||
files. Try running HOT-Step.bat from an elevated command prompt.
|
||||
|
||||
Q: Generation is very slow.
|
||||
A: Check that your NVIDIA drivers are installed. The app will
|
||||
fall back to CPU if no GPU is detected. See Settings > Health
|
||||
for GPU status.
|
||||
|
||||
Q: No sound / playback issues.
|
||||
A: Make sure your browser allows audio playback. Try a different
|
||||
browser (Chrome or Edge recommended).
|
||||
|
||||
Q: Model download fails.
|
||||
A: Check your internet connection. Downloads can be resumed —
|
||||
just click the download button again.
|
||||
|
||||
|
||||
LICENSE & CREDITS
|
||||
-----------------
|
||||
Built on ACE-Step (MIT License) — https://github.com/ace-step
|
||||
GGML inference framework — https://github.com/ggml-org/ggml
|
||||
|
||||
For support and updates:
|
||||
https://github.com/scragnog/HOT-Step-CPP
|
||||
@@ -0,0 +1,425 @@
|
||||
# release/build-release.ps1 — Build a complete portable release of HOT-Step CPP
|
||||
#
|
||||
# Usage:
|
||||
# .\release\build-release.ps1 [-Version "1.5.0"] [-SkipEngine] [-SkipUI] [-Variant cuda]
|
||||
#
|
||||
# Produces:
|
||||
# release/out/HOT-Step-CPP-v{version}-win-x64-{variant}.zip
|
||||
#
|
||||
# Requirements:
|
||||
# - Node.js 22 LTS (for building with correct native module ABI)
|
||||
# - Visual Studio 2022 Build Tools with C++ workload
|
||||
# - CUDA Toolkit 12.x (for CUDA variant)
|
||||
# - Vulkan SDK (for Vulkan variant)
|
||||
|
||||
param(
|
||||
[string]$Version = "0.0.0",
|
||||
[switch]$SkipEngine,
|
||||
[switch]$SkipUI,
|
||||
[string]$Variant = "cuda", # cuda, vulkan, cpu
|
||||
[string]$NodeVersion = "22.16.0" # Node.js LTS version to bundle
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$ProjectRoot = Split-Path -Parent $PSScriptRoot
|
||||
$ReleaseDir = Join-Path $ProjectRoot "release"
|
||||
$StagingDir = Join-Path $ReleaseDir "staging"
|
||||
$OutputDir = Join-Path $ReleaseDir "out"
|
||||
|
||||
# Portable Node.js download cache
|
||||
$NodeCacheDir = Join-Path $ReleaseDir ".node-cache"
|
||||
$NodeZipName = "node-v${NodeVersion}-win-x64.zip"
|
||||
$NodeUrl = "https://nodejs.org/dist/v${NodeVersion}/${NodeZipName}"
|
||||
$NodeExe = Join-Path (Join-Path $NodeCacheDir "node-v${NodeVersion}-win-x64") "node.exe"
|
||||
|
||||
Write-Host "`n════════════════════════════════════════════════════════" -ForegroundColor Cyan
|
||||
Write-Host " HOT-Step CPP Release Builder" -ForegroundColor Cyan
|
||||
Write-Host " Version: $Version | Variant: $Variant" -ForegroundColor Cyan
|
||||
Write-Host "════════════════════════════════════════════════════════`n" -ForegroundColor Cyan
|
||||
|
||||
# ── Phase 0: Clean staging ────────────────────────────────────────────
|
||||
|
||||
Write-Host "[Phase 0] Cleaning staging directory..." -ForegroundColor Yellow
|
||||
if (Test-Path $StagingDir) { Remove-Item -Recurse -Force $StagingDir }
|
||||
New-Item -ItemType Directory -Force $StagingDir | Out-Null
|
||||
New-Item -ItemType Directory -Force $OutputDir | Out-Null
|
||||
|
||||
# ── Phase 1: Download portable Node.js ────────────────────────────────
|
||||
|
||||
Write-Host "`n[Phase 1] Portable Node.js $NodeVersion..." -ForegroundColor Yellow
|
||||
|
||||
if (-not (Test-Path $NodeExe)) {
|
||||
Write-Host " Downloading from nodejs.org..."
|
||||
New-Item -ItemType Directory -Force $NodeCacheDir | Out-Null
|
||||
$zipPath = Join-Path $NodeCacheDir $NodeZipName
|
||||
|
||||
if (-not (Test-Path $zipPath)) {
|
||||
Invoke-WebRequest -Uri $NodeUrl -OutFile $zipPath -UseBasicParsing
|
||||
}
|
||||
|
||||
Write-Host " Extracting..."
|
||||
Expand-Archive -Path $zipPath -DestinationPath $NodeCacheDir -Force
|
||||
Remove-Item $zipPath -ErrorAction SilentlyContinue
|
||||
|
||||
if (-not (Test-Path $NodeExe)) {
|
||||
throw "Node.js extraction failed - $NodeExe not found"
|
||||
}
|
||||
}
|
||||
|
||||
$nodeVer = & $NodeExe --version
|
||||
Write-Host " Using: $NodeExe ($nodeVer)" -ForegroundColor Green
|
||||
|
||||
# ── Phase 2: Build C++ Engine ─────────────────────────────────────────
|
||||
|
||||
if (-not $SkipEngine) {
|
||||
Write-Host "`n[Phase 2] Building C++ engine ($Variant)..." -ForegroundColor Yellow
|
||||
|
||||
$engineDir = Join-Path $ProjectRoot "engine"
|
||||
|
||||
# Use buildall.cmd for multi-arch release builds
|
||||
$buildScript = Join-Path $engineDir "buildall.cmd"
|
||||
|
||||
# For release builds, we want static MSVC runtime and multi-arch CUDA
|
||||
# buildall.cmd already does multi-arch + Vulkan.
|
||||
# We modify the cmake flags by setting them before calling the script.
|
||||
Write-Host " Running buildall.cmd with static runtime..."
|
||||
|
||||
# Smart CMake cache handling: only clear if build flags have changed.
|
||||
# Deleting CMakeCache.txt forces a full reconfigure -> full rebuild of ALL
|
||||
# targets including 100+ CUDA vendor kernels (~1 hour). If the cache already
|
||||
# has the correct release flags, we skip deletion for a fast incremental build.
|
||||
$buildDir = Join-Path $engineDir "build"
|
||||
$cacheFile = Join-Path $buildDir "CMakeCache.txt"
|
||||
$needsCacheClear = $false
|
||||
|
||||
if (Test-Path $cacheFile) {
|
||||
$cacheContent = Get-Content $cacheFile -Raw
|
||||
$requiredFlags = @{
|
||||
"HOT_STEP_STATIC_RUNTIME:BOOL=ON" = "Static runtime (/MT)"
|
||||
"GGML_CUDA:BOOL=ON" = "CUDA backend"
|
||||
"GGML_VULKAN:BOOL=ON" = "Vulkan backend"
|
||||
"GGML_CPU_ALL_VARIANTS:BOOL=ON" = "CPU all variants"
|
||||
}
|
||||
foreach ($flag in $requiredFlags.Keys) {
|
||||
if ($cacheContent -notmatch [regex]::Escape($flag)) {
|
||||
Write-Host " Cache mismatch: $($requiredFlags[$flag]) ($flag)" -ForegroundColor Yellow
|
||||
$needsCacheClear = $true
|
||||
}
|
||||
}
|
||||
if ($needsCacheClear) {
|
||||
Write-Host " Clearing CMake cache (flags changed since last build)..." -ForegroundColor Yellow
|
||||
Remove-Item $cacheFile -Force
|
||||
} else {
|
||||
Write-Host " CMake cache valid -- incremental build (CUDA vendor files will NOT rebuild)" -ForegroundColor Green
|
||||
}
|
||||
} else {
|
||||
Write-Host " No CMake cache -- full build required" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# Set cmake flags for the build
|
||||
# buildall.cmd runs cmake with its own flags, so we override via env
|
||||
$env:RELEASE_CMAKE_EXTRA = "-DHOT_STEP_STATIC_RUNTIME=ON"
|
||||
|
||||
Push-Location $engineDir
|
||||
try {
|
||||
# buildall.cmd handles vcvars, ORT download, and multi-config build
|
||||
cmd /c "buildall.cmd"
|
||||
if ($LASTEXITCODE -ne 0) { throw "Engine build failed (exit code $LASTEXITCODE)" }
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
# Verify output
|
||||
$aceServerExe = Join-Path (Join-Path $buildDir "Release") "ace-server.exe"
|
||||
if (-not (Test-Path $aceServerExe)) {
|
||||
throw "Build succeeded but ace-server.exe not found at $aceServerExe"
|
||||
}
|
||||
Write-Host " Engine build complete" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "`n[Phase 2] SKIPPED (engine build)" -ForegroundColor DarkGray
|
||||
}
|
||||
|
||||
# ── Phase 3: Bundle Server ────────────────────────────────────────────
|
||||
|
||||
Write-Host "`n[Phase 3] Bundling server..." -ForegroundColor Yellow
|
||||
|
||||
# Install server deps with system npm (dev machine only)
|
||||
Write-Host " Installing server dependencies..."
|
||||
Push-Location (Join-Path $ProjectRoot "server")
|
||||
try {
|
||||
# Use cmd /c to prevent PS treating npm stderr warnings as errors
|
||||
cmd /c "npm install --ignore-scripts 2>&1" | Out-Null
|
||||
# Verify portable Node works
|
||||
& $NodeExe --version | Out-Null
|
||||
Write-Host " Dependencies installed" -ForegroundColor Green
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
# Rebuild better-sqlite3 native addon for the portable Node.js ABI
|
||||
# Dev machine may run Node 24 (ABI 137) but portable bundle ships Node 22 (ABI 127)
|
||||
# IMPORTANT: We backup and restore the original .node file so the dev environment
|
||||
# is not contaminated by the release build.
|
||||
Write-Host " Rebuilding better-sqlite3 for Node $NodeVersion..."
|
||||
$bsqlPkg = Join-Path (Join-Path $ProjectRoot "server") "node_modules\better-sqlite3"
|
||||
$nativeAddon = Join-Path $bsqlPkg "build\Release\better_sqlite3.node"
|
||||
$nativeBackup = Join-Path $bsqlPkg "build\Release\better_sqlite3.node.dev-backup"
|
||||
|
||||
# Backup the dev machine's native addon
|
||||
if (Test-Path $nativeAddon) {
|
||||
Copy-Item $nativeAddon $nativeBackup -Force
|
||||
Write-Host " Backed up dev addon"
|
||||
}
|
||||
|
||||
Push-Location $bsqlPkg
|
||||
try {
|
||||
cmd /c "npx prebuild-install --runtime node --target $NodeVersion --arch x64 --platform win32 2>&1"
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Warning " prebuild-install failed, trying npm rebuild..."
|
||||
Pop-Location
|
||||
Push-Location (Join-Path $ProjectRoot "server")
|
||||
cmd /c "npm rebuild better-sqlite3 2>&1" | Out-Null
|
||||
}
|
||||
Write-Host " Native addon rebuilt for Node $NodeVersion" -ForegroundColor Green
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
# Copy the Node 22 addon to staging before restoring dev version
|
||||
$stagingBsqlBuild = Join-Path $StagingDir "server\node_modules\better-sqlite3\build\Release"
|
||||
New-Item -ItemType Directory -Force $stagingBsqlBuild | Out-Null
|
||||
Copy-Item $nativeAddon (Join-Path $stagingBsqlBuild "better_sqlite3.node") -Force -ErrorAction SilentlyContinue
|
||||
|
||||
# Restore the dev machine's native addon
|
||||
if (Test-Path $nativeBackup) {
|
||||
Copy-Item $nativeBackup $nativeAddon -Force
|
||||
Remove-Item $nativeBackup -Force
|
||||
Write-Host " Restored dev addon"
|
||||
}
|
||||
|
||||
# Install esbuild in release dir (dev dependency for bundling)
|
||||
Write-Host " Installing esbuild..."
|
||||
Push-Location $ReleaseDir
|
||||
try {
|
||||
if (-not (Test-Path (Join-Path $ReleaseDir "node_modules\esbuild"))) {
|
||||
npm install esbuild --save-dev 2>&1 | Out-Null
|
||||
}
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
# Run esbuild (cmd /c prevents PS from treating esbuild stderr warnings as fatal)
|
||||
Write-Host " Running esbuild..."
|
||||
cmd /c "node `"$(Join-Path $ReleaseDir 'esbuild.config.mjs')`" 2>&1"
|
||||
if ($LASTEXITCODE -ne 0) { throw "esbuild bundle failed" }
|
||||
|
||||
Write-Host " Server bundle complete" -ForegroundColor Green
|
||||
|
||||
# ── Phase 4: Build UI ─────────────────────────────────────────────────
|
||||
|
||||
if (-not $SkipUI) {
|
||||
Write-Host "`n[Phase 4] Building UI..." -ForegroundColor Yellow
|
||||
|
||||
Push-Location (Join-Path $ProjectRoot "ui")
|
||||
try {
|
||||
cmd /c "npm install 2>&1" | Out-Null
|
||||
cmd /c "npm run build 2>&1"
|
||||
if ($LASTEXITCODE -ne 0) { throw "UI build failed" }
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
Write-Host " UI build complete" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "`n[Phase 4] SKIPPED (UI build)" -ForegroundColor DarkGray
|
||||
}
|
||||
|
||||
# ── Phase 5: Assemble Release ─────────────────────────────────────────
|
||||
|
||||
Write-Host "`n[Phase 5] Assembling release..." -ForegroundColor Yellow
|
||||
|
||||
$dist = $StagingDir
|
||||
|
||||
# Runtime: portable Node.js
|
||||
Write-Host " Copying runtime..."
|
||||
New-Item -ItemType Directory -Force (Join-Path $dist "runtime") | Out-Null
|
||||
Copy-Item $NodeExe (Join-Path $dist "runtime\node.exe")
|
||||
|
||||
# Engine: binaries + DLLs
|
||||
Write-Host " Copying engine binaries..."
|
||||
$engineOut = Join-Path $dist "engine"
|
||||
New-Item -ItemType Directory -Force $engineOut | Out-Null
|
||||
$buildRelease = Join-Path (Join-Path $ProjectRoot "engine") "build\Release"
|
||||
|
||||
$engineFiles = @(
|
||||
"ace-server.exe", "mastering.exe", "mp3-codec.exe",
|
||||
"neural-codec.exe", "vst-host.exe", "quantize.exe",
|
||||
"ggml.dll", "ggml-base.dll", "ggml-cpu.dll"
|
||||
)
|
||||
|
||||
# Add variant-specific DLLs
|
||||
switch ($Variant) {
|
||||
"cuda" { $engineFiles += "ggml-cuda.dll" }
|
||||
"vulkan" { $engineFiles += "ggml-vulkan.dll" }
|
||||
# cpu: no GPU backend DLL needed
|
||||
}
|
||||
|
||||
foreach ($file in $engineFiles) {
|
||||
$src = Join-Path $buildRelease $file
|
||||
if (Test-Path $src) {
|
||||
Copy-Item $src (Join-Path $engineOut $file)
|
||||
} else {
|
||||
Write-Warning " Missing engine file: $file"
|
||||
}
|
||||
}
|
||||
|
||||
# Also copy any ggml-cpu-* variant DLLs (from GGML_CPU_ALL_VARIANTS)
|
||||
Get-ChildItem (Join-Path $buildRelease "ggml-cpu-*.dll") -ErrorAction SilentlyContinue | ForEach-Object {
|
||||
Copy-Item $_.FullName (Join-Path $engineOut $_.Name)
|
||||
}
|
||||
|
||||
# Lua plugins — dual directory system:
|
||||
# engine/plugins/ = native/built-in plugins (solvers, schedulers, guidance)
|
||||
# plugins/ = community/user plugins (same structure, overrides native)
|
||||
# ace-server scans both via engine_dir and project_dir resolution from binary path
|
||||
Write-Host " Copying Lua plugins..."
|
||||
$enginePluginsSrc = Join-Path (Join-Path $ProjectRoot "engine") "plugins"
|
||||
if (Test-Path $enginePluginsSrc) {
|
||||
$enginePluginsDst = Join-Path $engineOut "plugins"
|
||||
Copy-Item -Recurse $enginePluginsSrc $enginePluginsDst
|
||||
$pluginCount = (Get-ChildItem -Recurse $enginePluginsDst -Filter "*.lua" | Measure-Object).Count
|
||||
Write-Host " $pluginCount native plugins (engine/plugins/)" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Warning " engine/plugins/ not found -- engine will have no built-in plugins!"
|
||||
}
|
||||
$communityPluginsSrc = Join-Path $ProjectRoot "plugins"
|
||||
if (Test-Path $communityPluginsSrc) {
|
||||
Copy-Item -Recurse $communityPluginsSrc (Join-Path $dist "plugins")
|
||||
Write-Host " Community plugins dir copied (plugins/)" -ForegroundColor Green
|
||||
}
|
||||
|
||||
# MSVC C++ Runtime - bundle so the app works on clean machines without VC++ Redistributable
|
||||
$msvcRedist = Get-ChildItem "C:\Program Files\Microsoft Visual Studio" -Recurse -Filter "vcruntime140.dll" -ErrorAction SilentlyContinue |
|
||||
Where-Object { $_.FullName -match 'x64' -and $_.FullName -notmatch 'debug' -and $_.FullName -match 'Redist' } |
|
||||
Select-Object -First 1
|
||||
if ($msvcRedist) {
|
||||
$msvcDir = $msvcRedist.DirectoryName
|
||||
foreach ($dll in @("vcruntime140.dll", "vcruntime140_1.dll", "msvcp140.dll")) {
|
||||
$src = Join-Path $msvcDir $dll
|
||||
if (Test-Path $src) {
|
||||
Copy-Item $src (Join-Path $engineOut $dll)
|
||||
}
|
||||
}
|
||||
Write-Host " Bundled MSVC runtime DLLs"
|
||||
} else {
|
||||
Write-Warning " MSVC Redist not found - vcruntime DLLs not bundled"
|
||||
}
|
||||
|
||||
# Server: bundled JS + native deps
|
||||
Write-Host " Copying server..."
|
||||
$serverOut = Join-Path $dist "server"
|
||||
# server.mjs was already placed by esbuild into staging/server/
|
||||
# Copy better-sqlite3 minimal package
|
||||
$bsqlSrc = Join-Path (Join-Path $ProjectRoot "server") "node_modules\better-sqlite3"
|
||||
$bsqlDst = Join-Path $serverOut "node_modules\better-sqlite3"
|
||||
New-Item -ItemType Directory -Force $bsqlDst | Out-Null
|
||||
|
||||
# Copy the JS package + native binding
|
||||
Copy-Item (Join-Path $bsqlSrc "package.json") (Join-Path $bsqlDst "package.json")
|
||||
New-Item -ItemType Directory -Force (Join-Path $bsqlDst "lib") | Out-Null
|
||||
Copy-Item -Recurse (Join-Path $bsqlSrc "lib\*") (Join-Path $bsqlDst "lib")
|
||||
# NOTE: Do NOT copy build/Release/better_sqlite3.node from source tree here!
|
||||
# The correct Node 22 addon was already staged at Phase 3 (line ~166).
|
||||
# Copying from $bsqlSrc would overwrite it with the restored dev addon (Node 24 ABI).
|
||||
# See: https://github.com/scragnog/HOT-Step-CPP/issues/18
|
||||
|
||||
# Copy better-sqlite3 runtime dependencies: bindings + file-uri-to-path
|
||||
$nmSrc = Join-Path (Join-Path $ProjectRoot "server") "node_modules"
|
||||
foreach ($dep in @("bindings", "file-uri-to-path")) {
|
||||
$depSrc = Join-Path $nmSrc $dep
|
||||
$depDst = Join-Path $serverOut "node_modules\$dep"
|
||||
if (Test-Path $depSrc) {
|
||||
Copy-Item -Recurse $depSrc $depDst
|
||||
Write-Host " Copied $dep"
|
||||
} else {
|
||||
Write-Warning " Missing dependency: $dep"
|
||||
}
|
||||
}
|
||||
|
||||
# Copy ffmpeg.exe
|
||||
$ffmpegSrc = Join-Path (Join-Path (Join-Path $ProjectRoot "server") "node_modules\ffmpeg-static") "ffmpeg.exe"
|
||||
if (Test-Path $ffmpegSrc) {
|
||||
Copy-Item $ffmpegSrc (Join-Path $serverOut "ffmpeg.exe")
|
||||
} else {
|
||||
Write-Warning " ffmpeg.exe not found - audio conversion will be limited"
|
||||
}
|
||||
|
||||
# Copy data files (model-registry.json, assistant-knowledge.md)
|
||||
$dataOut = Join-Path $serverOut "data"
|
||||
New-Item -ItemType Directory -Force $dataOut | Out-Null
|
||||
Copy-Item (Join-Path (Join-Path (Join-Path $ProjectRoot "server") "src\data") "model-registry.json") (Join-Path $dataOut "model-registry.json")
|
||||
Copy-Item (Join-Path (Join-Path (Join-Path $ProjectRoot "server") "src\data") "assistant-knowledge.md") (Join-Path $dataOut "assistant-knowledge.md")
|
||||
|
||||
# UI: pre-built dist
|
||||
Write-Host " Copying UI..."
|
||||
$uiSrc = Join-Path (Join-Path $ProjectRoot "ui") "dist"
|
||||
$uiDst = Join-Path (Join-Path $dist "ui") "dist"
|
||||
if (Test-Path $uiSrc) {
|
||||
New-Item -ItemType Directory -Force (Join-Path $dist "ui") | Out-Null
|
||||
Copy-Item -Recurse $uiSrc $uiDst
|
||||
} else {
|
||||
Write-Warning " UI dist not found - skipping"
|
||||
}
|
||||
|
||||
# Essentia
|
||||
$essentiaSrc = Join-Path $ProjectRoot "Essentia"
|
||||
if (Test-Path $essentiaSrc) {
|
||||
Write-Host " Copying Essentia..."
|
||||
Copy-Item -Recurse $essentiaSrc (Join-Path $dist "Essentia")
|
||||
}
|
||||
|
||||
# Noise samples
|
||||
$noiseSrc = Join-Path $ProjectRoot "noise_samples"
|
||||
if (Test-Path $noiseSrc) {
|
||||
Copy-Item -Recurse $noiseSrc (Join-Path $dist "noise_samples")
|
||||
}
|
||||
|
||||
# Empty directories for user content
|
||||
New-Item -ItemType Directory -Force (Join-Path $dist "models") | Out-Null
|
||||
New-Item -ItemType Directory -Force (Join-Path $dist "adapters") | Out-Null
|
||||
|
||||
# Config and docs
|
||||
Copy-Item (Join-Path $ProjectRoot ".env.example") (Join-Path $dist ".env.example")
|
||||
Copy-Item (Join-Path $ReleaseDir "HOT-Step.bat") (Join-Path $dist "HOT-Step.bat")
|
||||
Copy-Item (Join-Path $ReleaseDir "README.txt") (Join-Path $dist "README.txt")
|
||||
|
||||
Write-Host " Assembly complete" -ForegroundColor Green
|
||||
|
||||
# ── Phase 6: Package ──────────────────────────────────────────────────
|
||||
|
||||
Write-Host "`n[Phase 6] Packaging..." -ForegroundColor Yellow
|
||||
|
||||
$zipName = "HOT-Step-CPP-v${Version}-win-x64-${Variant}.zip"
|
||||
$zipPath = Join-Path $OutputDir $zipName
|
||||
|
||||
if (Test-Path $zipPath) { Remove-Item $zipPath }
|
||||
|
||||
# Compress — use .NET for better compression than Compress-Archive
|
||||
Add-Type -AssemblyName System.IO.Compression.FileSystem
|
||||
[System.IO.Compression.ZipFile]::CreateFromDirectory($dist, $zipPath, [System.IO.Compression.CompressionLevel]::Optimal, $false)
|
||||
|
||||
$zipSize = (Get-Item $zipPath).Length
|
||||
$zipSizeMB = [math]::Round($zipSize / 1MB, 1)
|
||||
|
||||
# Generate SHA256
|
||||
$hash = (Get-FileHash $zipPath -Algorithm SHA256).Hash
|
||||
$hashFile = Join-Path $OutputDir "${zipName}.sha256"
|
||||
"$hash $zipName" | Set-Content $hashFile -NoNewline
|
||||
|
||||
Write-Host " $zipName ($zipSizeMB MB)" -ForegroundColor Green
|
||||
Write-Host " SHA256: $hash" -ForegroundColor DarkGray
|
||||
|
||||
Write-Host "`n════════════════════════════════════════════════════════" -ForegroundColor Cyan
|
||||
Write-Host " Release build complete!" -ForegroundColor Green
|
||||
Write-Host " Output: $zipPath" -ForegroundColor Cyan
|
||||
Write-Host "════════════════════════════════════════════════════════`n" -ForegroundColor Cyan
|
||||
@@ -0,0 +1,84 @@
|
||||
// release/esbuild.config.mjs - Bundle the HOT-Step CPP server for portable distribution
|
||||
//
|
||||
// Usage: node release/esbuild.config.mjs
|
||||
//
|
||||
// Produces: release/staging/server/server.mjs
|
||||
// - All TypeScript/JS source bundled into a single ESM file
|
||||
// - better-sqlite3 marked external (native addon, can't be bundled)
|
||||
// - ffmpeg-static excluded (replaced by config-based path resolution)
|
||||
// - model-registry.json loaded via fs.readFileSync (not bundled inline)
|
||||
|
||||
import esbuild from 'esbuild';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { builtinModules } from 'module';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const projectRoot = path.resolve(__dirname, '..');
|
||||
|
||||
// Build complete list of Node.js built-in modules (with and without node: prefix)
|
||||
const nodeExternals = [
|
||||
...builtinModules,
|
||||
...builtinModules.map(m => `node:${m}`),
|
||||
];
|
||||
|
||||
const result = await esbuild.build({
|
||||
entryPoints: [path.join(projectRoot, 'server/src/index.ts')],
|
||||
bundle: true,
|
||||
platform: 'node',
|
||||
target: 'node22',
|
||||
format: 'esm',
|
||||
outfile: path.join(projectRoot, 'release/staging/server/server.mjs'),
|
||||
|
||||
// External: native addon + all Node.js built-in modules
|
||||
// Node builtins must be external because esbuild's ESM CJS-compat shim
|
||||
// can't handle dynamic require("node:events") from Express and other CJS deps.
|
||||
external: ['better-sqlite3', ...nodeExternals],
|
||||
|
||||
// Plugin: redirect browser polyfill packages (with trailing slash) to Node.js
|
||||
// built-ins. e.g. require('process/') -> require('process')
|
||||
plugins: [{
|
||||
name: 'node-polyfill-redirect',
|
||||
setup(build) {
|
||||
// Match requires like 'process/', 'string_decoder/', 'events/', etc.
|
||||
build.onResolve({ filter: /^(process|string_decoder|events|buffer|stream|util|path)\/$/ }, (args) => {
|
||||
const builtin = args.path.replace(/\/$/, '');
|
||||
return { path: builtin, external: true };
|
||||
});
|
||||
},
|
||||
}],
|
||||
|
||||
// Don't minify - keep readable for debugging production issues
|
||||
minify: false,
|
||||
sourcemap: false,
|
||||
|
||||
// Tree-shake unused exports
|
||||
treeShaking: true,
|
||||
|
||||
// CJS interop banner: provide require(), __filename, __dirname for ESM bundle.
|
||||
// Express and other CJS dependencies need require() to work inside the ESM wrapper.
|
||||
banner: {
|
||||
js: [
|
||||
'// HOT-Step CPP Server - bundled build',
|
||||
'// Generated by release/esbuild.config.mjs',
|
||||
'',
|
||||
'// CJS interop: provide require() for ESM bundle',
|
||||
'import { createRequire as __bundleCreateRequire } from "module";',
|
||||
'const require = __bundleCreateRequire(import.meta.url);',
|
||||
'',
|
||||
].join('\n'),
|
||||
},
|
||||
|
||||
// Log build stats
|
||||
logLevel: 'info',
|
||||
metafile: true,
|
||||
});
|
||||
|
||||
// Print bundle size summary
|
||||
const outputs = result.metafile?.outputs || {};
|
||||
for (const [file, info] of Object.entries(outputs)) {
|
||||
const sizeKB = (info.bytes / 1024).toFixed(1);
|
||||
console.log(` ${file}: ${sizeKB} KB`);
|
||||
}
|
||||
|
||||
console.log('\n\u2705 Server bundle complete');
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"esbuild": "^0.28.0"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user