Initial release

This commit is contained in:
civ
2026-08-16 18:24:52 +07:00
commit 876886a39a
13244 changed files with 2353959 additions and 0 deletions
+38
View File
@@ -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
+46
View File
@@ -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
+107
View File
@@ -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
+425
View File
@@ -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
+84
View File
@@ -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');
+5
View File
@@ -0,0 +1,5 @@
{
"devDependencies": {
"esbuild": "^0.28.0"
}
}