diff --git a/vix-site/public/install.ps1 b/vix-site/public/install.ps1 index 53d97d04..7d94eaa5 100644 --- a/vix-site/public/install.ps1 +++ b/vix-site/public/install.ps1 @@ -3,29 +3,46 @@ # irm https://vixcpp.com/install.ps1 | iex # # Optional: -# $env:VIX_VERSION="v2.7.0" +# $env:VIX_VERSION="v2.7.8" # $env:VIX_REPO="vixcpp/vix" +# $env:VIX_STABLE_URL="https://vixcpp.com/releases/stable.txt" +# $env:VIX_FALLBACK_VERSION="v2.7.8" # $env:VIX_INSTALL_DIR="$env:LOCALAPPDATA\Vix\bin" +# $env:VIX_INSTALL_SHARE_DIR="$env:LOCALAPPDATA\Vix\share" $ErrorActionPreference = "Stop" $ProgressPreference = "SilentlyContinue" +# Ensure GitHub and vixcpp.com work on Windows PowerShell 5.1. +try { + [Net.ServicePointManager]::SecurityProtocol = + [Net.ServicePointManager]::SecurityProtocol -bor + [Net.SecurityProtocolType]::Tls12 +} catch { + # PowerShell editions using HttpClient do not require this setting. +} + $MinisignPubkey = "RWSIfpPSznK9A1gWUc8Eg2iXXQwU5d9BYuQNKGOcoujAF2stPu5rKFjQ" +$RequestHeaders = @{ "User-Agent" = "vix-installer" } + +function Step([string]$Message) { + Write-Host " → $Message" +} -function Step($msg) { - Write-Host " → $msg" +function Ok([string]$Message) { + Write-Host " ✓ $Message" -ForegroundColor Green } -function Ok($msg) { - Write-Host " ✓ $msg" -ForegroundColor Green +function Warn([string]$Message) { + Write-Host " ! $Message" -ForegroundColor Yellow } -function Hint($msg) { - Write-Host " · $msg" -ForegroundColor DarkGray +function Hint([string]$Message) { + Write-Host " · $Message" -ForegroundColor DarkGray } -function Die($msg) { - Write-Host " ✗ $msg" -ForegroundColor Red +function Die([string]$Message) { + Write-Host " ✗ $Message" -ForegroundColor Red exit 1 } @@ -37,12 +54,53 @@ Usage: install.ps1 Environment: - VIX_VERSION Release version. Example: v2.7.0. Default: latest - VIX_REPO GitHub repo. Default: vixcpp/vix - VIX_INSTALL_DIR CLI bin dir. Default: %LOCALAPPDATA%\Vix\bin + VIX_VERSION + Release version to install. + + Examples: + latest + v2.7.8 + v2.8.3 + + Default: latest + + "latest" means the latest release validated by the complete + Vix release CI, not necessarily the newest GitHub tag. + + VIX_STABLE_URL + URL containing the latest validated release tag. + + Default: + https://vixcpp.com/releases/stable.txt + + VIX_FALLBACK_VERSION + Emergency fallback used when the stable release pointer is + unavailable, invalid, or incomplete. + + Default: + v2.7.8 + + VIX_REPO + GitHub repository containing release assets. + + Default: + vixcpp/vix + + VIX_INSTALL_DIR + CLI installation directory. + + Default: + %LOCALAPPDATA%\Vix\bin + + VIX_INSTALL_SHARE_DIR + Runtime assets installation directory. -After install: + Default: + %LOCALAPPDATA%\Vix\share + +After installation: vix upgrade + vix upgrade --check vix upgrade --sdk list vix upgrade --sdk web "@ @@ -54,19 +112,24 @@ foreach ($arg in $args) { Show-Help exit 0 } + "-h" { Show-Help exit 0 } + "--cli-only" { - # Kept for compatibility. The installer is CLI-only now. + # Kept for backward compatibility. } + "--cli" { - # Kept for compatibility. The installer is CLI-only now. + # Kept for backward compatibility. } + "--sdk" { - Die "SDK install moved to: vix upgrade --sdk" + Die "SDK installation moved to: vix upgrade --sdk" } + default { Die "unknown option: $arg" } @@ -85,48 +148,172 @@ $Version = if ($env:VIX_VERSION) { "latest" } +$StableUrl = if ($env:VIX_STABLE_URL) { + $env:VIX_STABLE_URL +} else { + "https://vixcpp.com/releases/stable.txt" +} + +$FallbackVersion = if ($env:VIX_FALLBACK_VERSION) { + $env:VIX_FALLBACK_VERSION +} else { + "v2.7.8" +} + $BinDir = if ($env:VIX_INSTALL_DIR) { $env:VIX_INSTALL_DIR } else { Join-Path $env:LOCALAPPDATA "Vix\bin" } -$BinName = "vix.exe" +$ShareDir = if ($env:VIX_INSTALL_SHARE_DIR) { + $env:VIX_INSTALL_SHARE_DIR +} else { + $installRoot = Split-Path -Parent $BinDir + Join-Path $installRoot "share" +} -function Resolve-LatestTag([string]$repo) { - $api = "https://api.github.com/repos/$repo/releases/latest" +$BinName = "vix.exe" - try { - $resp = Invoke-RestMethod -Uri $api -Headers @{ "User-Agent" = "vix-installer" } +function Test-ReleaseTag([string]$Tag) { + if ([string]::IsNullOrWhiteSpace($Tag)) { + return $false + } - if (-not $resp.tag_name) { - Die "could not resolve latest tag. Set VIX_VERSION=vX.Y.Z" - } + return $Tag -match '^v[0-9]+\.[0-9]+\.[0-9]+$' +} - return $resp.tag_name - } catch { - Die "could not resolve latest tag. Set VIX_VERSION=vX.Y.Z" +function Get-NativeArchitectureName { + if ($env:PROCESSOR_ARCHITEW6432) { + return $env:PROCESSOR_ARCHITEW6432 } + + return $env:PROCESSOR_ARCHITECTURE } function Detect-Architecture { - $archRaw = $env:PROCESSOR_ARCHITECTURE + $archRaw = Get-NativeArchitectureName switch -Regex ($archRaw) { - "AMD64" { + "^AMD64$" { return "x86_64" } - "^ARM" { + + "^ARM64$" { return "aarch64" } + default { Die "unsupported architecture: $archRaw" } } } -function Verify-Checksum([string]$archivePath, [string]$shaPath) { - $first = (Get-Content -LiteralPath $shaPath -TotalCount 1).Trim() +function Resolve-StableTag { + try { + $response = Invoke-WebRequest ` + -Uri $StableUrl ` + -Headers $RequestHeaders ` + -UseBasicParsing + + $lines = ([string]$response.Content) -split '\r?\n' + + $tag = $lines | + ForEach-Object { $_.Trim() } | + Where-Object { $_ -ne "" } | + Select-Object -First 1 + + if (-not (Test-ReleaseTag $tag)) { + return $null + } + + return $tag + } catch { + return $null + } +} + +function Test-UrlExists([string]$Url) { + try { + Invoke-WebRequest ` + -Uri $Url ` + -Method Head ` + -MaximumRedirection 10 ` + -Headers $RequestHeaders ` + -UseBasicParsing | + Out-Null + + return $true + } catch { + return $false + } +} + +function Test-ReleaseInstallable( + [string]$Tag, + [string]$Repository, + [string]$AssetName +) { + if (-not (Test-ReleaseTag $Tag)) { + return $false + } + + $baseUrl = "https://github.com/$Repository/releases/download/$Tag" + + if (-not (Test-UrlExists "$baseUrl/$AssetName")) { + return $false + } + + if (-not (Test-UrlExists "$baseUrl/$AssetName.sha256")) { + return $false + } + + return $true +} + +function Resolve-Version( + [string]$RequestedVersion, + [string]$Repository, + [string]$AssetName +) { + if ($RequestedVersion -ne "latest") { + if (-not (Test-ReleaseTag $RequestedVersion)) { + Die "invalid release version: $RequestedVersion" + } + + if (-not (Test-ReleaseInstallable $RequestedVersion $Repository $AssetName)) { + Die "release $RequestedVersion is incomplete for windows/$Arch" + } + + return $RequestedVersion + } + + $stable = Resolve-StableTag + + if ($stable) { + if (Test-ReleaseInstallable $stable $Repository $AssetName) { + return $stable + } + + Warn "validated stable release $stable is incomplete for windows/$Arch" + } else { + Warn "could not resolve the validated stable release" + } + + if ( + (Test-ReleaseTag $FallbackVersion) -and + ($FallbackVersion -ne $stable) -and + (Test-ReleaseInstallable $FallbackVersion $Repository $AssetName) + ) { + Step "Falling back to stable release $FallbackVersion" + return $FallbackVersion + } + + Die "no installable Vix release found for windows/$Arch" +} + +function Verify-Checksum([string]$ArchivePath, [string]$ShaPath) { + $first = (Get-Content -LiteralPath $ShaPath -TotalCount 1).Trim() if (-not $first) { Die "invalid sha256 file" @@ -134,8 +321,8 @@ function Verify-Checksum([string]$archivePath, [string]$shaPath) { $expected = $null - if ($first -match "^[0-9a-fA-F]{64}") { - $expected = ($first -split "\s+")[0] + if ($first -match "^([0-9a-fA-F]{64})(?:\s+.*)?$") { + $expected = $Matches[1] } elseif ($first -match "=\s*([0-9a-fA-F]{64})\s*$") { $expected = $Matches[1] } @@ -144,21 +331,26 @@ function Verify-Checksum([string]$archivePath, [string]$shaPath) { Die "invalid sha256 format" } - $actual = (Get-FileHash -Algorithm SHA256 -LiteralPath $archivePath).Hash + $actual = (Get-FileHash -Algorithm SHA256 -LiteralPath $ArchivePath).Hash if ($expected.ToLowerInvariant() -ne $actual.ToLowerInvariant()) { Die "sha256 mismatch" } } -function Verify-Signature([string]$archivePath, [string]$sigPath) { +function Verify-Signature([string]$ArchivePath, [string]$SigPath) { $minisign = Get-Command minisign -ErrorAction SilentlyContinue if (-not $minisign) { + Hint "minisign is not installed; signature verification skipped" return } - & minisign -Vm $archivePath -x $sigPath -P $MinisignPubkey *> $null + & $minisign.Path ` + -Vm $ArchivePath ` + -x $SigPath ` + -P $MinisignPubkey ` + *> $null if ($LASTEXITCODE -ne 0) { Die "signature verification failed" @@ -167,61 +359,90 @@ function Verify-Signature([string]$archivePath, [string]$sigPath) { Ok "minisign verified" } -function Download-And-Verify-Asset([string]$baseUrl, [string]$asset, [string]$tmpDir) { - $archivePath = Join-Path $tmpDir $asset - $shaPath = Join-Path $tmpDir ($asset + ".sha256") - $sigPath = Join-Path $tmpDir ($asset + ".minisig") +function Download-And-Verify-Asset( + [string]$BaseUrl, + [string]$AssetName, + [string]$TmpDir +) { + $archivePath = Join-Path $TmpDir $AssetName + $shaPath = Join-Path $TmpDir ($AssetName + ".sha256") + $sigPath = Join-Path $TmpDir ($AssetName + ".minisig") - $assetUrl = "$baseUrl/$asset" - $shaUrl = "$baseUrl/$asset.sha256" - $sigUrl = "$baseUrl/$asset.minisig" + $assetUrl = "$BaseUrl/$AssetName" + $shaUrl = "$BaseUrl/$AssetName.sha256" + $sigUrl = "$BaseUrl/$AssetName.minisig" - Step "Downloading $asset" + Step "Downloading $AssetName" try { - Invoke-WebRequest -Uri $assetUrl -OutFile $archivePath + Invoke-WebRequest ` + -Uri $assetUrl ` + -OutFile $archivePath ` + -Headers $RequestHeaders ` + -UseBasicParsing | + Out-Null } catch { - Die "release asset not found: $asset" + Die "release asset not found: $AssetName" } try { - Invoke-WebRequest -Uri $shaUrl -OutFile $shaPath + Invoke-WebRequest ` + -Uri $shaUrl ` + -OutFile $shaPath ` + -Headers $RequestHeaders ` + -UseBasicParsing | + Out-Null } catch { - Die "checksum file not found: $asset.sha256" + Die "checksum file not found: $AssetName.sha256" } Verify-Checksum $archivePath $shaPath Ok "sha256 verified" + $signatureDownloaded = $false + try { - Invoke-WebRequest -Uri $sigUrl -OutFile $sigPath - Verify-Signature $archivePath $sigPath + Invoke-WebRequest ` + -Uri $sigUrl ` + -OutFile $sigPath ` + -Headers $RequestHeaders ` + -UseBasicParsing | + Out-Null + + $signatureDownloaded = $true } catch { - # minisign is optional for bootstrap install. + $signatureDownloaded = $false + } + + if ($signatureDownloaded) { + Verify-Signature $archivePath $sigPath } return $archivePath } -function Install-SqliteDll([string]$installBin, [string]$tmpDir) { - $sqliteDll = Join-Path $installBin "sqlite3.dll" +function Install-SqliteDll([string]$InstallBin, [string]$TmpDir) { + $sqliteDll = Join-Path $InstallBin "sqlite3.dll" - if (Test-Path -LiteralPath $sqliteDll) { + if (Test-Path -LiteralPath $sqliteDll -PathType Leaf) { return } - $archRaw = $env:PROCESSOR_ARCHITECTURE + $archRaw = Get-NativeArchitectureName switch -Regex ($archRaw) { - "AMD64" { + "^AMD64$" { $sqliteAsset = "sqlite-dll-win-x64-3530200.zip" } - "^ARM" { + + "^ARM64$" { $sqliteAsset = "sqlite-dll-win-arm64-3530200.zip" } - "x86" { + + "^x86$" { $sqliteAsset = "sqlite-dll-win-x86-3530200.zip" } + default { Hint "sqlite runtime skipped: unsupported architecture $archRaw" return @@ -229,29 +450,41 @@ function Install-SqliteDll([string]$installBin, [string]$tmpDir) { } $sqliteUrl = "https://www.sqlite.org/2026/$sqliteAsset" - $sqliteDir = Join-Path $tmpDir "sqlite" + $sqliteDir = Join-Path $TmpDir "sqlite" $sqliteZip = Join-Path $sqliteDir $sqliteAsset New-Item -ItemType Directory -Force -Path $sqliteDir | Out-Null - New-Item -ItemType Directory -Force -Path $installBin | Out-Null + New-Item -ItemType Directory -Force -Path $InstallBin | Out-Null Step "Installing SQLite runtime" try { - Invoke-WebRequest -Uri $sqliteUrl -OutFile $sqliteZip + Invoke-WebRequest ` + -Uri $sqliteUrl ` + -OutFile $sqliteZip ` + -Headers $RequestHeaders ` + -UseBasicParsing | + Out-Null } catch { Hint "sqlite runtime skipped" return } try { - Expand-Archive -LiteralPath $sqliteZip -DestinationPath $sqliteDir -Force + Expand-Archive ` + -LiteralPath $sqliteZip ` + -DestinationPath $sqliteDir ` + -Force } catch { Hint "sqlite runtime skipped" return } - $dllCandidate = Get-ChildItem -LiteralPath $sqliteDir -Recurse -File -Filter "sqlite3.dll" | + $dllCandidate = Get-ChildItem ` + -LiteralPath $sqliteDir ` + -Recurse ` + -File ` + -Filter "sqlite3.dll" | Select-Object -First 1 if (-not $dllCandidate) { @@ -259,88 +492,155 @@ function Install-SqliteDll([string]$installBin, [string]$tmpDir) { return } - Copy-Item -LiteralPath $dllCandidate.FullName -Destination $sqliteDll -Force + Copy-Item ` + -LiteralPath $dllCandidate.FullName ` + -Destination $sqliteDll ` + -Force - if (Test-Path -LiteralPath $sqliteDll) { + if (Test-Path -LiteralPath $sqliteDll -PathType Leaf) { Ok "sqlite3.dll installed" } } -function Add-To-UserPath([string]$pathToAdd) { +function Add-To-UserPath([string]$PathToAdd) { $userPath = [Environment]::GetEnvironmentVariable("Path", "User") if (-not $userPath) { $userPath = "" } - $segments = $userPath -split ";" | + $segments = @( + $userPath -split ";" | ForEach-Object { $_.Trim() } | Where-Object { $_ -ne "" } - - $already = $false + ) foreach ($segment in $segments) { if ([string]::Equals( $segment.TrimEnd("\"), - $pathToAdd.TrimEnd("\"), + $PathToAdd.TrimEnd("\"), [System.StringComparison]::OrdinalIgnoreCase )) { - $already = $true - break + return $true } } - if (-not $already) { - $newPath = ($segments + $pathToAdd) -join ";" - [Environment]::SetEnvironmentVariable("Path", $newPath, "User") - return $false - } + $newPath = (@($segments) + $PathToAdd) -join ";" + [Environment]::SetEnvironmentVariable("Path", $newPath, "User") - return $true + return $false } -function Install-Cli([string]$archivePath, [string]$tmpDir) { - $extractDir = Join-Path $tmpDir "cli" +function Install-Cli( + [string]$ArchivePath, + [string]$TmpDir +) { + $extractDir = Join-Path $TmpDir "cli" New-Item -ItemType Directory -Force -Path $extractDir | Out-Null New-Item -ItemType Directory -Force -Path $BinDir | Out-Null + New-Item -ItemType Directory -Force -Path $ShareDir | Out-Null - Step "Installing to $BinDir\$BinName" + Step "Extracting $Asset" - Expand-Archive -LiteralPath $archivePath -DestinationPath $extractDir -Force + try { + Expand-Archive ` + -LiteralPath $ArchivePath ` + -DestinationPath $extractDir ` + -Force + } catch { + Die "failed to extract $Asset" + } - $exeCandidate = Get-ChildItem -LiteralPath $extractDir -Recurse -File -Filter $BinName | + $exeCandidate = Get-ChildItem ` + -LiteralPath $extractDir ` + -Recurse ` + -File ` + -Filter $BinName | Select-Object -First 1 if (-not $exeCandidate) { Die "CLI archive does not contain $BinName" } + $noteSource = Join-Path $extractDir "share\vix\note" + $noteDestination = Join-Path $ShareDir "vix\note" + $noteParent = Split-Path -Parent $noteDestination + + $noteIndex = Join-Path $noteSource "index.html" + $noteCss = Join-Path $noteSource "assets\note.css" + $noteJs = Join-Path $noteSource "assets\note.js" + + if (-not (Test-Path -LiteralPath $noteIndex -PathType Leaf)) { + Die "CLI archive does not contain Vix Note index.html" + } + + if (-not (Test-Path -LiteralPath $noteCss -PathType Leaf)) { + Die "CLI archive does not contain Vix Note note.css" + } + + if (-not (Test-Path -LiteralPath $noteJs -PathType Leaf)) { + Die "CLI archive does not contain Vix Note note.js" + } + $exe = Join-Path $BinDir $BinName - if (-not [string]::Equals( - $exeCandidate.FullName, - $exe, - [System.StringComparison]::OrdinalIgnoreCase - )) { - Copy-Item -LiteralPath $exeCandidate.FullName -Destination $exe -Force + Step "Installing to $exe" + + Copy-Item ` + -LiteralPath $exeCandidate.FullName ` + -Destination $exe ` + -Force + + Step "Installing Vix Note assets to $noteDestination" + + New-Item ` + -ItemType Directory ` + -Force ` + -Path $noteParent | + Out-Null + + if (Test-Path -LiteralPath $noteDestination) { + Remove-Item ` + -LiteralPath $noteDestination ` + -Recurse ` + -Force } - return $exe -} + Copy-Item ` + -LiteralPath $noteSource ` + -Destination $noteDestination ` + -Recurse ` + -Force -$Arch = Detect-Architecture + $installedIndex = Join-Path $noteDestination "index.html" + $installedCss = Join-Path $noteDestination "assets\note.css" + $installedJs = Join-Path $noteDestination "assets\note.js" -$Tag = if ($Version -eq "latest") { - Resolve-LatestTag $Repo -} else { - $Version + if (-not (Test-Path -LiteralPath $installedIndex -PathType Leaf)) { + Die "failed to install Vix Note index.html" + } + + if (-not (Test-Path -LiteralPath $installedCss -PathType Leaf)) { + Die "failed to install Vix Note note.css" + } + + if (-not (Test-Path -LiteralPath $installedJs -PathType Leaf)) { + Die "failed to install Vix Note note.js" + } + + Ok "Vix Note assets installed" + + return $exe } +$Arch = Detect-Architecture $Asset = "vix-windows-$Arch.zip" -$BaseUrl = "https://github.com/$Repo/releases/download/$Tag" -$TmpDir = Join-Path ([System.IO.Path]::GetTempPath()) ("vix-" + [System.Guid]::NewGuid().ToString("N")) +$TmpDir = Join-Path ` + ([System.IO.Path]::GetTempPath()) ` + ("vix-" + [System.Guid]::NewGuid().ToString("N")) + New-Item -ItemType Directory -Force -Path $TmpDir | Out-Null try { @@ -348,6 +648,10 @@ try { Write-Host "Vix.cpp" -NoNewline -ForegroundColor Green Write-Host " installer" Write-Host " ------------------------------------" + + $Tag = Resolve-Version $Version $Repo $Asset + $BaseUrl = "https://github.com/$Repo/releases/download/$Tag" + Write-Host " version $Tag" Write-Host " platform windows/$Arch" Write-Host "" @@ -361,6 +665,11 @@ try { try { & $Exe --version *> $null + + if ($LASTEXITCODE -ne 0) { + throw "vix --version returned exit code $LASTEXITCODE" + } + Ok "Done — vix $Tag installed" } catch { Die "installed, but 'vix --version' failed" @@ -375,5 +684,10 @@ try { } } finally { - Remove-Item -LiteralPath $TmpDir -Recurse -Force -ErrorAction SilentlyContinue | Out-Null + Remove-Item ` + -LiteralPath $TmpDir ` + -Recurse ` + -Force ` + -ErrorAction SilentlyContinue | + Out-Null } diff --git a/vix-site/public/install.sh b/vix-site/public/install.sh index 9eb8e64d..571863de 100644 --- a/vix-site/public/install.sh +++ b/vix-site/public/install.sh @@ -1,20 +1,42 @@ #!/usr/bin/env sh set -eu +# Public key used to verify release archives when minisign is installed. MINISIGN_PUBKEY="RWSIfpPSznK9A1gWUc8Eg2iXXQwU5d9BYuQNKGOcoujAF2stPu5rKFjQ" VIX_REPO="${VIX_REPO:-vixcpp/vix}" + +# "latest" means the latest release validated by the complete release CI. VIX_VERSION="${VIX_VERSION:-latest}" + +# This file must be updated only after: +# - all CI jobs succeed; +# - all release assets are uploaded; +# - checksums are uploaded; +# - installation tests succeed. +VIX_STABLE_URL="${VIX_STABLE_URL:-https://vixcpp.com/releases/stable.txt}" + +# Emergency fallback used if stable.txt is unavailable, invalid, +# or points to an incomplete release. +VIX_FALLBACK_VERSION="${VIX_FALLBACK_VERSION:-v2.7.8}" + VIX_INSTALL_BIN_DIR="${VIX_INSTALL_BIN_DIR:-$HOME/.local/bin}" +VIX_INSTALL_SHARE_DIR="${VIX_INSTALL_SHARE_DIR:-$HOME/.local/share}" BIN_NAME="vix" +TMP_DIR="" +BIN_STAGE="" +NOTE_STAGE="" +DEST="" + if [ -t 2 ] && [ "${NO_COLOR:-}" = "" ]; then C_RESET="$(printf '\033[0m')" C_BOLD="$(printf '\033[1m')" C_RED="$(printf '\033[31m')" C_GREEN="$(printf '\033[32m')" C_YELLOW="$(printf '\033[33m')" + C_CYAN="$(printf '\033[36m')" C_DIM="$(printf '\033[2m')" else C_RESET="" @@ -22,6 +44,7 @@ else C_RED="" C_GREEN="" C_YELLOW="" + C_CYAN="" C_DIM="" fi @@ -38,6 +61,10 @@ ok() { printf " %s✓%s %s\n" "$C_GREEN" "$C_RESET" "$*" >&2 } +warn() { + printf " %s!%s %s\n" "$C_YELLOW" "$C_RESET" "$*" >&2 +} + hint() { printf " · %s%s%s\n" "$C_DIM" "$*" "$C_RESET" >&2 } @@ -50,21 +77,118 @@ need_cmd() { have "$1" || die "missing dependency: $1" } +cleanup() { + if [ -n "${BIN_STAGE:-}" ]; then + rm -f "$BIN_STAGE" >/dev/null 2>&1 || true + fi + + if [ -n "${NOTE_STAGE:-}" ]; then + rm -rf "$NOTE_STAGE" >/dev/null 2>&1 || true + fi + + if [ -n "${TMP_DIR:-}" ]; then + rm -rf "$TMP_DIR" >/dev/null 2>&1 || true + fi +} + fetch() { url="$1" out="$2" if have curl; then - curl -fsSL "$url" -o "$out" >/dev/null 2>&1 + curl \ + -fsSL \ + --retry 3 \ + --retry-delay 1 \ + --connect-timeout 15 \ + --max-time 300 \ + "$url" \ + -o "$out" return fi if have wget; then - wget -qO "$out" "$url" >/dev/null 2>&1 + wget \ + -q \ + --tries=3 \ + --timeout=30 \ + -O "$out" \ + "$url" return fi - die "need curl or wget" + return 1 +} + +fetch_text() { + url="$1" + + if have curl; then + curl \ + -fsSL \ + --retry 3 \ + --retry-delay 1 \ + --connect-timeout 15 \ + --max-time 60 \ + "$url" + return + fi + + if have wget; then + wget \ + -q \ + --tries=3 \ + --timeout=30 \ + -O- \ + "$url" + return + fi + + return 1 +} + +url_exists() { + url="$1" + + if have curl; then + curl \ + -fsSIL \ + --retry 2 \ + --retry-delay 1 \ + --connect-timeout 15 \ + --max-time 60 \ + "$url" \ + >/dev/null 2>&1 + return + fi + + if have wget; then + wget \ + -q \ + --spider \ + --tries=2 \ + --timeout=30 \ + "$url" \ + >/dev/null 2>&1 + return + fi + + return 1 +} + +valid_release_tag() { + value="$1" + + printf "%s\n" "$value" | + awk ' + /^v[0-9]+\.[0-9]+\.[0-9]+$/ { + valid = 1 + } + + END { + exit valid ? 0 : 1 + } + ' } show_help() { @@ -75,12 +199,53 @@ Usage: install.sh Environment: - VIX_VERSION Release version. Example: v2.7.0. Default: latest - VIX_REPO GitHub repo. Default: vixcpp/vix - VIX_INSTALL_BIN_DIR CLI install dir. Default: \$HOME/.local/bin + VIX_VERSION + Release version to install. + + Examples: + latest + v2.7.8 + v2.8.3 + + Default: latest + + "latest" means the latest release validated by the complete + Vix release CI, not necessarily the newest GitHub tag. + + VIX_STABLE_URL + URL containing the latest validated release tag. -After install: + Default: + https://vixcpp.com/releases/stable.txt + + VIX_FALLBACK_VERSION + Emergency fallback used when the stable release pointer is + unavailable or incomplete. + + Default: + v2.7.8 + + VIX_REPO + GitHub repository containing release assets. + + Default: + vixcpp/vix + + VIX_INSTALL_BIN_DIR + CLI installation directory. + + Default: + \$HOME/.local/bin + + VIX_INSTALL_SHARE_DIR + Runtime assets installation directory. + + Default: + \$HOME/.local/share + +After installation: vix upgrade + vix upgrade --check vix upgrade --sdk list vix upgrade --sdk web EOF @@ -92,12 +257,16 @@ for arg in "$@"; do show_help exit 0 ;; + --cli-only|--cli) - # Kept for compatibility. The installer is CLI-only now. + # Kept for backward compatibility. + # The installer installs the Vix CLI and its required runtime assets. ;; + --sdk) - die "SDK install moved to: vix upgrade --sdk" + die "SDK installation moved to: vix upgrade --sdk" ;; + *) die "unknown option: $arg" ;; @@ -107,6 +276,17 @@ done need_cmd uname need_cmd mktemp need_cmd tar +need_cmd awk +need_cmd find +need_cmd mkdir +need_cmd rm +need_cmd cp +need_cmd mv +need_cmd chmod + +if ! have curl && ! have wget; then + die "need curl or wget" +fi detect_platform() { os="$(uname -s)" @@ -116,9 +296,11 @@ detect_platform() { Linux) OS="linux" ;; + Darwin) OS="macos" ;; + *) die "unsupported OS: $os" ;; @@ -128,28 +310,84 @@ detect_platform() { x86_64|amd64) ARCH="x86_64" ;; + arm64|aarch64) ARCH="aarch64" ;; + *) die "unsupported architecture: $arch" ;; esac } +resolve_stable_pointer() { + stable="$( + fetch_text "$VIX_STABLE_URL" 2>/dev/null | + awk ' + { + gsub(/\r/, "", $0) + } + + NF { + print $1 + exit + } + ' + )" || return 1 + + [ -n "$stable" ] || return 1 + valid_release_tag "$stable" || return 1 + + printf "%s" "$stable" +} + +release_is_installable() { + tag="$1" + base_url="https://github.com/${VIX_REPO}/releases/download/${tag}" + + valid_release_tag "$tag" || return 1 + + url_exists "$base_url/$ASSET" || return 1 + url_exists "$base_url/$ASSET.sha256" || return 1 + + return 0 +} + resolve_version() { - if [ "$VIX_VERSION" = "latest" ]; then - have curl || die "curl is required to resolve latest, or set VIX_VERSION=vX.Y.Z" + if [ "$VIX_VERSION" != "latest" ]; then + valid_release_tag "$VIX_VERSION" \ + || die "invalid release version: $VIX_VERSION" + + release_is_installable "$VIX_VERSION" \ + || die "release $VIX_VERSION is incomplete for $OS/$ARCH" + + printf "%s" "$VIX_VERSION" + return + fi - final="$(curl -fsSLI -o /dev/null -w '%{url_effective}' "https://github.com/$VIX_REPO/releases/latest")" - tag="${final##*/}" + stable="$(resolve_stable_pointer || true)" - [ -n "$tag" ] || die "could not resolve latest version" + if [ -n "$stable" ]; then + if release_is_installable "$stable"; then + printf "%s" "$stable" + return + fi - printf "%s" "$tag" + warn "validated stable release $stable is incomplete for $OS/$ARCH" else - printf "%s" "$VIX_VERSION" + warn "could not resolve the validated stable release" + fi + + if valid_release_tag "$VIX_FALLBACK_VERSION" && + [ "$VIX_FALLBACK_VERSION" != "$stable" ] && + release_is_installable "$VIX_FALLBACK_VERSION"; then + step "Falling back to stable release $VIX_FALLBACK_VERSION" + printf "%s" "$VIX_FALLBACK_VERSION" + return fi + + die "no installable Vix release found for $OS/$ARCH" } verify_checksum() { @@ -161,29 +399,39 @@ verify_checksum() { fi expected="$( - sed -n 's/^\([0-9a-fA-F][0-9a-fA-F]*\).*/\1/p' "$sha_file" | head -n 1 + sed -n 's/^\([0-9a-fA-F]\{64\}\).*/\1/p' "$sha_file" | + head -n 1 | + tr 'A-F' 'a-f' )" [ -n "$expected" ] || die "invalid sha256 file" if have sha256sum; then - actual="$(sha256sum "$archive" | awk '{print $1}')" + actual="$( + sha256sum "$archive" | + awk '{print $1}' | + tr 'A-F' 'a-f' + )" else - actual="$(shasum -a 256 "$archive" | awk '{print $1}')" + actual="$( + shasum -a 256 "$archive" | + awk '{print $1}' | + tr 'A-F' 'a-f' + )" fi + [ -n "$actual" ] || die "could not calculate archive sha256" [ "$expected" = "$actual" ] || die "sha256 mismatch" } - verify_signature() { archive="$1" sig_file="$2" - if ! have minisign; then - return - fi - - minisign -Vm "$archive" -x "$sig_file" -P "$MINISIGN_PUBKEY" >/dev/null 2>&1 \ + minisign \ + -Vm "$archive" \ + -x "$sig_file" \ + -P "$MINISIGN_PUBKEY" \ + >/dev/null 2>&1 \ || die "signature verification failed" } @@ -197,16 +445,21 @@ download_and_verify_asset() { step "Downloading $asset" - fetch "$base_url/$asset" "$archive" || die "download failed" - fetch "$base_url/$asset.sha256" "$sha_file" || die "checksum not found" + fetch "$base_url/$asset" "$archive" \ + || die "failed to download $asset" + + fetch "$base_url/$asset.sha256" "$sha_file" \ + || die "checksum not found for $asset" verify_checksum "$archive" "$sha_file" ok "sha256 verified" - if fetch "$base_url/$asset.minisig" "$sig_file"; then - verify_signature "$archive" "$sig_file" + if fetch "$base_url/$asset.minisig" "$sig_file" 2>/dev/null; then if have minisign; then + verify_signature "$archive" "$sig_file" ok "minisign verified" + else + hint "minisign is not installed; signature verification skipped" fi fi @@ -218,43 +471,123 @@ install_cli() { extract_dir="$TMP_DIR/cli" rm -rf "$extract_dir" - mkdir -p "$extract_dir" "$VIX_INSTALL_BIN_DIR" + mkdir -p "$extract_dir" - step "Installing to $VIX_INSTALL_BIN_DIR/$BIN_NAME" + step "Extracting $ASSET" - tar -xzf "$archive" -C "$extract_dir" + tar -xzf "$archive" -C "$extract_dir" \ + || die "failed to extract $ASSET" if [ -f "$extract_dir/$BIN_NAME" ]; then src="$extract_dir/$BIN_NAME" elif [ -f "$extract_dir/bin/$BIN_NAME" ]; then src="$extract_dir/bin/$BIN_NAME" else - src="$(find "$extract_dir" -type f -name "$BIN_NAME" 2>/dev/null | head -n 1 || true)" + src="$( + find "$extract_dir" -type f -name "$BIN_NAME" 2>/dev/null | + awk 'NR == 1 { print; exit }' + )" fi [ -n "$src" ] || die "$BIN_NAME not found in archive" + [ -f "$src" ] || die "invalid $BIN_NAME executable in archive" + + note_src="$extract_dir/share/vix/note" + note_dest="$VIX_INSTALL_SHARE_DIR/vix/note" + note_parent="$VIX_INSTALL_SHARE_DIR/vix" + + [ -f "$note_src/index.html" ] \ + || die "missing Vix Note asset: index.html" + + [ -f "$note_src/assets/note.css" ] \ + || die "missing Vix Note asset: note.css" + + [ -f "$note_src/assets/note.js" ] \ + || die "missing Vix Note asset: note.js" - chmod +x "$src" - cp "$src" "$VIX_INSTALL_BIN_DIR/$BIN_NAME" - chmod +x "$VIX_INSTALL_BIN_DIR/$BIN_NAME" + mkdir -p "$VIX_INSTALL_BIN_DIR" + mkdir -p "$note_parent" + + BIN_STAGE="$VIX_INSTALL_BIN_DIR/.${BIN_NAME}.install.$$" + NOTE_STAGE="$note_parent/.note.install.$$" + + rm -f "$BIN_STAGE" + rm -rf "$NOTE_STAGE" + + step "Preparing $VIX_INSTALL_BIN_DIR/$BIN_NAME" + + cp "$src" "$BIN_STAGE" \ + || die "failed to prepare $BIN_NAME executable" + + chmod +x "$BIN_STAGE" \ + || die "failed to make $BIN_NAME executable" + + if ! "$BIN_STAGE" --version >/dev/null 2>&1; then + die "downloaded $BIN_NAME executable failed its version check" + fi + + step "Preparing Vix Note assets" + + cp -R "$note_src" "$NOTE_STAGE" \ + || die "failed to prepare Vix Note assets" + + [ -f "$NOTE_STAGE/index.html" ] \ + || die "failed to prepare Vix Note index.html" + + [ -f "$NOTE_STAGE/assets/note.css" ] \ + || die "failed to prepare Vix Note note.css" + + [ -f "$NOTE_STAGE/assets/note.js" ] \ + || die "failed to prepare Vix Note note.js" + + step "Installing Vix Note assets to $note_dest" + + rm -rf "$note_dest" + + mv "$NOTE_STAGE" "$note_dest" \ + || die "failed to install Vix Note assets" + + NOTE_STAGE="" + + step "Installing to $VIX_INSTALL_BIN_DIR/$BIN_NAME" DEST="$VIX_INSTALL_BIN_DIR/$BIN_NAME" + + mv "$BIN_STAGE" "$DEST" \ + || die "failed to install $BIN_NAME" + + BIN_STAGE="" + + chmod +x "$DEST" \ + || die "failed to make installed $BIN_NAME executable" + + ok "Vix Note assets installed" } detect_platform +ASSET="vix-${OS}-${ARCH}.tar.gz" + TMP_DIR="$(mktemp -d 2>/dev/null || mktemp -d -t vix)" -cleanup() { - rm -rf "$TMP_DIR" -} -trap cleanup EXIT INT TERM + +trap cleanup EXIT HUP INT TERM + +printf " %s▲%s %s%sVix.cpp%s installer\n" \ + "$C_CYAN" \ + "$C_RESET" \ + "$C_BOLD" \ + "$C_GREEN" \ + "$C_RESET" \ + >&2 + +printf " %s------------------------------------%s\n" \ + "$C_DIM" \ + "$C_RESET" \ + >&2 TAG="$(resolve_version)" BASE_URL="https://github.com/${VIX_REPO}/releases/download/${TAG}" -ASSET="vix-${OS}-${ARCH}.tar.gz" -printf " ▲ %sVix.cpp%s %sinstaller%s\n" "$C_BOLD" "$C_RESET" "$C_DIM" "$C_RESET" >&2 -printf " ------------------------------------\n" >&2 printf " version %s\n" "$TAG" >&2 printf " platform %s/%s\n" "$OS" "$ARCH" >&2 printf "\n" >&2 @@ -272,6 +605,7 @@ case ":$PATH:" in hint "run: vix upgrade --check" hint "sdk: vix upgrade --sdk list" ;; + *) hint "add $VIX_INSTALL_BIN_DIR to PATH" hint "then run: vix upgrade --sdk list" diff --git a/vix-site/public/releases/stable.txt b/vix-site/public/releases/stable.txt new file mode 100644 index 00000000..5d92048c --- /dev/null +++ b/vix-site/public/releases/stable.txt @@ -0,0 +1 @@ +v2.7.8 diff --git a/vix-site/src/components/home/AppModulesSection.vue b/vix-site/src/components/home/AppModulesSection.vue new file mode 100644 index 00000000..52204f3c --- /dev/null +++ b/vix-site/src/components/home/AppModulesSection.vue @@ -0,0 +1,396 @@ + + + + + diff --git a/vix-site/src/components/home/ComparisonSection.vue b/vix-site/src/components/home/ComparisonSection.vue index 8491fd49..37b4efec 100644 --- a/vix-site/src/components/home/ComparisonSection.vue +++ b/vix-site/src/components/home/ComparisonSection.vue @@ -3,62 +3,98 @@
-
-
-
- Project scope - What each tool is mainly responsible for -
-
Vix.cpp
-
Drogon
-
Crow
+
+
+ Command + wrk -t8 -c800 -d30s --latency +
+
+ Endpoint + /bench +
+
+ Machine + HP EliteBook, 8 CPU threads
+
+ +
+
+
+
+ {{ item.rank }} +

{{ item.name }}

+
- +
{{ item.requests }}
+
{{ item.avg }}
+
{{ item.p50 }}
+
{{ item.p90 }}
+
{{ item.p99 }}
+

- Drogon and Crow answer a focused question: - how do I build a C++ web service? - Vix.cpp answers a wider one: - how do I build, run, test, package, extend, and maintain a real - native C++ application? + Drogon is slightly ahead in raw throughput, but Vix.cpp stays very + close while showing the best p99 latency in this test. Compared to + Crow, Vix.cpp is faster and keeps a lower tail latency.

@@ -66,141 +102,47 @@ @@ -210,169 +152,225 @@ const rows = [ position: relative; } -.comparison__table { - margin-top: 42px; - overflow: hidden; - border: 1px solid var(--line); - border-radius: var(--radius-lg); - background: var(--bg-ink); - box-shadow: var(--shadow-lg); +.comparison__section-title :deep(h2) { + max-width: 860px; } -.comparison__head, -.comparison__row { +.comparison__meta { display: grid; - grid-template-columns: minmax(280px, 1.55fr) repeat(3, minmax(130px, 0.75fr)); -} -.comparison__head { - background: var(--bg-panel-strong); - border-bottom: 1px solid var(--line); + grid-template-columns: repeat(3, 1fr); + gap: 14px; + margin-top: 40px; } -.comparison__head > div { - padding: 18px 22px; - color: var(--text); - font-size: 0.86rem; - font-weight: 800; +.comparison__meta > div { + padding: 18px 20px; + border: 1px solid var(--line); + border-radius: var(--radius-lg); + background: rgba(255, 255, 255, 0.035); } -.comparison__head > div:not(:first-child) { - display: flex; - align-items: center; - justify-content: center; +.comparison__meta span { + display: block; + margin-bottom: 6px; + color: var(--text-muted); + font-size: 0.78rem; + font-weight: 700; + text-transform: uppercase; + letter-spacing: 0.08em; } -.comparison__head strong { - display: block; +.comparison__meta strong { color: var(--text); - font-size: 0.95rem; + font-size: 0.92rem; + line-height: 1.5; } -.comparison__section-title :deep(h2) { - max-width: 820px; + +.comparison__cards { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 18px; + margin-top: 22px; } -.comparison__head span { - display: block; - margin-top: 4px; - color: var(--text-muted); - font-size: 0.78rem; - font-weight: 500; + +.comparison-card { + position: relative; + overflow: hidden; + padding: 24px; + border: 1px solid var(--line); + border-radius: var(--radius-lg); + background: var(--bg-ink); + box-shadow: var(--shadow-md); } -.comparison__group { - padding: 16px 22px; - background: rgba(255, 255, 255, 0.035); - border-bottom: 1px solid var(--line); +.comparison-card--highlight { + border-color: rgba(34, 197, 94, 0.45); + background: + radial-gradient( + circle at top right, + rgba(34, 197, 94, 0.18), + transparent 34% + ), + var(--bg-ink); } -.comparison__group strong { - display: block; - color: var(--text); - font-size: 0.95rem; - font-weight: 800; +.comparison-card__top { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 16px; } -.comparison__group span { - display: block; - margin-top: 4px; +.comparison-card__rank { + display: inline-flex; + margin-bottom: 8px; color: var(--text-muted); font-size: 0.78rem; - font-weight: 500; + font-weight: 800; + letter-spacing: 0.08em; + text-transform: uppercase; } -.comparison__group > div { - padding: 16px 22px; +.comparison-card h3 { + margin: 0; color: var(--text); - font-size: 0.82rem; + font-size: clamp(1.25rem, 1.8vw, 1.55rem); + letter-spacing: -0.03em; +} + +.comparison-card__badge { + flex: 0 0 auto; + padding: 7px 10px; + border: 1px solid rgba(34, 197, 94, 0.28); + border-radius: 999px; + background: rgba(34, 197, 94, 0.1); + color: #86efac; + font-size: 0.72rem; font-weight: 800; + white-space: nowrap; } -.comparison__group > div:not(:first-child) { - display: flex; - align-items: center; - justify-content: center; +.comparison-card__main { + margin-top: 28px; } -.comparison__group strong { +.comparison-card__main strong { display: block; color: var(--text); - font-size: 0.95rem; + font-size: clamp(2rem, 4vw, 3rem); + font-weight: 900; + line-height: 1; + letter-spacing: -0.06em; } -.comparison__group span { +.comparison-card__main span { display: block; - margin-top: 4px; + margin-top: 8px; color: var(--text-muted); - font-size: 0.78rem; - font-weight: 500; + font-size: 0.9rem; + font-weight: 700; } -.comparison__row { - border-bottom: 1px solid var(--line-soft); +.comparison-card__stats { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 12px; + margin-top: 24px; } -.comparison__row:last-child { - border-bottom: 0; +.comparison-card__stats div { + padding: 14px; + border: 1px solid var(--line-soft); + border-radius: var(--radius-md); + background: rgba(255, 255, 255, 0.035); } -.comparison__row > div { - min-width: 0; - padding: 18px 22px; +.comparison-card__stats span { + display: block; + color: var(--text-muted); + font-size: 0.75rem; + font-weight: 700; +} + +.comparison-card__stats strong { + display: block; + margin-top: 4px; + color: var(--text); + font-size: 1rem; +} + +.comparison-card p { + margin: 22px 0 0; color: var(--text-soft); font-size: 0.9rem; - line-height: 1.6; + line-height: 1.65; } -.comparison__row > div:not(:first-child) { - display: flex; - align-items: center; - justify-content: center; - text-align: center; +.comparison__table { + margin-top: 24px; + overflow: hidden; + border: 1px solid var(--line); + border-radius: var(--radius-lg); + background: var(--bg-ink); + box-shadow: var(--shadow-lg); } -.comparison__area { +.comparison__head, +.comparison__row { display: grid; - gap: 5px; + grid-template-columns: minmax(210px, 1.45fr) repeat(5, minmax(110px, 0.8fr)); } -.comparison__area strong { +.comparison__head { + background: var(--bg-panel-strong); + border-bottom: 1px solid var(--line); +} + +.comparison__head > div { + padding: 16px 18px; color: var(--text); - font-size: 0.95rem; + font-size: 0.78rem; + font-weight: 900; + text-transform: uppercase; + letter-spacing: 0.08em; } -.comparison__area span { - color: var(--text-muted); - font-size: 0.8rem; - line-height: 1.5; +.comparison__head > div:not(:first-child), +.comparison__row > div:not(:first-child) { + text-align: center; } -:deep(.status-icon) { - width: 28px; - height: 28px; - display: inline-flex; - align-items: center; - justify-content: center; - border-radius: 999px; +.comparison__row { + border-bottom: 1px solid var(--line-soft); } -:deep(.status-icon svg) { - width: 15px; - height: 15px; - fill: currentColor; +.comparison__row:last-child { + border-bottom: 0; } -:deep(.status-icon--core) { - color: #86efac; - background: rgba(34, 197, 94, 0.18); +.comparison__row--highlight { + background: rgba(34, 197, 94, 0.055); } -:deep(.status-icon--partial) { - color: #fbbf24; - background: rgba(251, 191, 36, 0.18); +.comparison__row > div { + min-width: 0; + padding: 18px; + color: var(--text-soft); + font-size: 0.9rem; + line-height: 1.5; } -:deep(.status-icon--external) { - color: #f87171; - background: rgba(248, 113, 113, 0.18); +.comparison__row strong { + display: block; + color: var(--text); + font-size: 0.95rem; +} + +.comparison__row span { + display: block; + margin-top: 4px; + color: var(--text-muted); + font-size: 0.78rem; } .comparison__summary { @@ -387,7 +385,7 @@ const rows = [ rgba(255, 255, 255, 0.025) ); color: var(--text-soft); - font-size: clamp(1rem, 1.25vw, 1.1rem); + font-size: clamp(1rem, 1.25vw, 1.08rem); line-height: 1.75; text-align: center; } @@ -396,19 +394,37 @@ const rows = [ margin: 0; } -.comparison__summary strong { - color: var(--text); -} +@media (max-width: 980px) { + .comparison__meta, + .comparison__cards { + grid-template-columns: 1fr; + } -@media (max-width: 920px) { .comparison__table { overflow-x: auto; } .comparison__head, - .comparison__row, - .comparison__group { - min-width: 880px; + .comparison__row { + min-width: 860px; + } +} + +@media (max-width: 560px) { + .comparison__meta { + margin-top: 30px; + } + + .comparison-card { + padding: 20px; + } + + .comparison-card__top { + flex-direction: column; + } + + .comparison-card__stats { + grid-template-columns: 1fr; } } diff --git a/vix-site/src/components/home/DiagnosticsSection.vue b/vix-site/src/components/home/DiagnosticsSection.vue new file mode 100644 index 00000000..0767c5f8 --- /dev/null +++ b/vix-site/src/components/home/DiagnosticsSection.vue @@ -0,0 +1,407 @@ + + + + + diff --git a/vix-site/src/components/home/InstallSection.vue b/vix-site/src/components/home/InstallSection.vue index 367ec56d..7b867f0f 100644 --- a/vix-site/src/components/home/InstallSection.vue +++ b/vix-site/src/components/home/InstallSection.vue @@ -11,7 +11,12 @@
Install guide - + Read the docs
diff --git a/vix-site/src/components/home/PerformanceSection.vue b/vix-site/src/components/home/PerformanceSection.vue index bf292e13..fbce3f4b 100644 --- a/vix-site/src/components/home/PerformanceSection.vue +++ b/vix-site/src/components/home/PerformanceSection.vue @@ -3,16 +3,35 @@
+
+
+ Build + Release +
+
+ Compiler + GCC 13.3.0 +
+
+ Machine + HP EliteBook, x86_64 +
+
+ CPU + 8 threads +
+
+
-
-
+
+
-

{{ activeTab.kicker }}

+

{{ activeTab.kicker }}

{{ activeTab.title }}

{{ activeTab.subtitle }}
@@ -42,104 +61,67 @@
-
+
-
{{ bar.display }}
+ {{ metric.label }} + {{ metric.value }} + {{ metric.note }} -
+ - - {{ bar.name }} - {{ bar.note }}
-
-
- {{ fact.value }} - {{ fact.label }} +
+
+
Benchmark
+
Median
+
Notes
-
-
-
- -
-
- Run fast - Small C++ files can be edited and executed directly with vix - run. -
- -
- Build smarter - Clean target builds can return through the build-state fast - path. -
-
- Protect baselines - Runtime, executor, router, HTTP, session, and app paths are - benchmarked. +
+
+ {{ row.name }} + {{ row.group }} +
+
{{ row.value }}
+
{{ row.note }}
+
+
- - + + + +

- The vix run numbers show a local single-file edit/run feedback loop. The - build numbers describe a clean no-op target build where the fast path - can skip the full pipeline. The HTTP result comes from a local v2.7 - release benchmark on an HP EliteBook, not a dedicated benchmark server. + These are local benchmark numbers, not universal production claims. Very + small microbenchmarks can produce extremely high ops/sec values, so they + are used mainly as regression guardrails. The broader router, runtime, + HTTP, executor, session, and app groups are the most useful signals for + release tracking.

@@ -150,202 +132,403 @@ import { computed, ref } from "vue"; import CommandLine from "@/components/common/CommandLine.vue"; import SectionTitle from "@/components/common/SectionTitle.vue"; -const activeKey = ref("run"); +const activeKey = ref("http"); const tabs = [ { - key: "run", - label: "vix run", - kicker: "Single-file feedback loop", - title: "Edit a C++ file and run it in about half a second.", + key: "http", + label: "HTTP", + kicker: "Local HTTP benchmark", + title: "Vix.cpp reached 112k requests per second locally.", subtitle: - "A tiny C++ file was edited and executed twice locally with vix run. The full command completed around 0.57s.", - version: "local", - mode: "dev loop", - bars: [ + "A simple /bench endpoint was measured with wrk using 8 threads, 800 connections, and a 30 second run.", + version: "v2.7.0", + mode: "Release", + metrics: [ { - name: "First run", - display: "0.579s", - height: 100, - note: "Hello, world", - logo: true, + label: "Requests/sec", + value: "112,539", + note: "local /bench endpoint", + width: 100, + featured: true, }, { - name: "After edit", - display: "0.569s", - height: 96, - note: "Hellodd, world", - logo: false, + label: "Total requests", + value: "3,386,276", + note: "completed in 30.09s", + width: 86, + featured: false, }, { - name: "Feedback loop", - display: "~0.57s", - height: 82, - note: "edit → run → output", - logo: false, + label: "Average latency", + value: "7.31ms", + note: "800 connections", + width: 62, + featured: false, + }, + { + label: "P99 latency", + value: "12.19ms", + note: "tail latency", + width: 72, + featured: true, }, ], - facts: [ - { value: "0.579s", label: "first local run" }, - { value: "0.569s", label: "after source edit" }, - { value: "1 file", label: "main.cpp" }, - { value: "direct", label: "vix run workflow" }, + rows: [ + { + name: "wrk throughput", + group: "http.bench", + value: "112,539 req/s", + note: "Simple local endpoint under 800 concurrent connections.", + }, + { + name: "Average latency", + group: "http.bench", + value: "7.31ms", + note: "Measured during the same 30 second run.", + }, + { + name: "P90 latency", + group: "http.bench", + value: "8.73ms", + note: "Good mid-tail signal for the current HTTP runtime.", + }, + { + name: "P99 latency", + group: "http.bench", + value: "12.19ms", + note: "The most important latency number from this run.", + }, ], }, { - key: "build", - label: "vix build", - kicker: "Build-state fast path", - title: "No-op target builds can return in hundreds of milliseconds.", + key: "router", + label: "Router", + kicker: "Core router benchmark", + title: "Route matching paths are tracked separately.", subtitle: - "When the project state proves nothing changed, vix build --fast can skip the full build pipeline and return early.", - version: "build graph", - mode: "fast path", - bars: [ + "Static routes, parameterized routes, query strings, wrong methods, and mixed route tables are measured as separate benchmark cases.", + version: "current", + mode: "Release", + metrics: [ + { + label: "Strip query", + value: "40.64M", + note: "ops/sec", + width: 100, + featured: true, + }, { - name: "vix build --fast", - display: "303ms", - height: 100, - note: "clean no-op target", - logo: true, + label: "Static route", + value: "9.43M", + note: "ops/sec", + width: 68, + featured: true, }, { - name: "normal build", - display: "6.10s", - height: 42, - note: "full no-op path", - logo: false, + label: "Param route", + value: "5.62M", + note: "ops/sec", + width: 52, + featured: false, }, { - name: "graph disabled", - display: "6.16s", - height: 40, - note: "compat path", - logo: false, + label: "Many static", + value: "5.96M", + note: "ops/sec", + width: 55, + featured: false, }, ], - facts: [ - { value: "303ms", label: "fast no-op build" }, - { value: "6.10s", label: "normal no-op build" }, - { value: "6.16s", label: "graph disabled" }, - { value: "~20x", label: "faster no-op path" }, + rows: [ + { + name: "router.match/strip_query", + group: "router.match", + value: "40.64M ops/sec", + note: "Fast query stripping path before route matching.", + }, + { + name: "router.match/static_route", + group: "router.match", + value: "9.43M ops/sec", + note: "Simple static route lookup path.", + }, + { + name: "router.match/param_route", + group: "router.match", + value: "5.62M ops/sec", + note: "Parameterized route matching path.", + }, + { + name: "router.match/many_static_routes", + group: "router.match", + value: "5.96M ops/sec", + note: "Route table with many static entries.", + }, ], }, { - key: "http", - label: "HTTP", - kicker: "Local v2.7 benchmark", - title: "HTTP endpoint reached 112k requests per second locally.", + key: "runtime", + label: "Runtime", + kicker: "Core runtime benchmark", + title: "Runtime queues and worker paths are part of the baseline.", subtitle: - "Measured with wrk, 8 threads, 800 connections, and a 30 second run against a local release build.", - version: "v2.7", + "The low-level queue, scheduler, and worker paths are benchmarked because the higher-level HTTP and app layers depend on them.", + version: "current", mode: "Release", - bars: [ + metrics: [ { - name: "Vix HTTP", - display: "112,539", - height: 100, - note: "requests/sec", - logo: true, + label: "Queue push/pop", + value: "55.52M", + note: "ops/sec", + width: 100, + featured: true, }, { - name: "Requests", - display: "3,386,276", - height: 74, - note: "completed in 30.09s", - logo: false, + label: "Queue push/clear", + value: "33.86M", + note: "ops/sec", + width: 76, + featured: false, + }, + { + label: "Worker tasks", + value: "1.29M", + note: "ops/sec", + width: 48, + featured: true, }, { - name: "p99 latency", - display: "12.19ms", - height: 38, - note: "at 800 connections", - logo: false, + label: "Scheduler tasks", + value: "692K", + note: "ops/sec", + width: 38, + featured: false, }, ], - facts: [ - { value: "112,539", label: "requests/sec" }, - { value: "7.31ms", label: "average latency" }, - { value: "12.19ms", label: "p99 latency" }, - { value: "800", label: "connections" }, + rows: [ + { + name: "runtime.queue/push_pop", + group: "runtime.queue", + value: "55.52M ops/sec", + note: "Queue push and pop path.", + }, + { + name: "runtime.queue/push_clear", + group: "runtime.queue", + value: "33.86M ops/sec", + note: "Queue push and clear path.", + }, + { + name: "runtime.worker/submit_complete_tasks", + group: "runtime.worker", + value: "1.29M ops/sec", + note: "Worker submit and completion path.", + }, + { + name: "runtime.scheduler/submit_complete_tasks", + group: "runtime.scheduler", + value: "692K ops/sec", + note: "Scheduler submit and completion path.", + }, ], }, { - key: "router", - label: "Router", - kicker: "Core benchmark baseline", - title: "Route matching costs are visible and tracked.", + key: "objects", + label: "HTTP objects", + kicker: "HTTP object benchmark", + title: "Request and response object paths are measured directly.", subtitle: - "Static, parameterized, nested, and query-string route paths are measured separately.", - version: "v2.6.3", + "These benchmarks track object construction and response helper paths before full network and application costs are added.", + version: "current", mode: "Release", - bars: [ + metrics: [ + { + label: "Response body", + value: "65.92M", + note: "ops/sec", + width: 100, + featured: true, + }, { - name: "Strip query", - display: "41.6M", - height: 100, + label: "Request default", + value: "10.85M", note: "ops/sec", - logo: true, + width: 72, + featured: true, }, { - name: "Static route", - display: "9.37M", - height: 66, + label: "Static target", + value: "1.06M", note: "ops/sec", - logo: false, + width: 46, + featured: false, }, { - name: "Param route", - display: "5.67M", - height: 48, + label: "Query target", + value: "656K", note: "ops/sec", - logo: false, + width: 36, + featured: false, }, ], - facts: [ - { value: "41.6M", label: "strip query ops/sec" }, - { value: "9.37M", label: "static route ops/sec" }, - { value: "5.67M", label: "param route ops/sec" }, - { value: "3.91M", label: "nested param ops/sec" }, + rows: [ + { + name: "http.response/construct_status_body", + group: "http.response", + value: "65.92M ops/sec", + note: "Status + body response construction path.", + }, + { + name: "http.request/default_construct", + group: "http.request", + value: "10.85M ops/sec", + note: "Default request object construction.", + }, + { + name: "http.request/construct_static_target", + group: "http.request", + value: "1.06M ops/sec", + note: "Request object with static target.", + }, + { + name: "http.request/construct_query_target", + group: "http.request", + value: "656K ops/sec", + note: "Request object with query target parsing.", + }, ], }, { - key: "runtime", - label: "Runtime", - kicker: "Core benchmark baseline", - title: "Runtime queues and scheduler paths are benchmarked.", + key: "executor", + label: "Executor", + kicker: "Executor benchmark", + title: "Executor submit, post, and metrics paths are tracked.", subtitle: - "Low-level runtime pieces are measured because higher-level HTTP, sessions, and app APIs depend on them.", - version: "v2.6.3", + "Executor numbers are useful for detecting regressions in task scheduling, completion, and runtime metric reads.", + version: "current", mode: "Release", - bars: [ + metrics: [ { - name: "Queue push/pop", - display: "49.6M", - height: 100, + label: "Idle reads", + value: "107.44M", + note: "ops/sec guardrail", + width: 100, + featured: false, + }, + { + label: "Running reads", + value: "2.72M", + note: "ops/sec", + width: 64, + featured: false, + }, + { + label: "Submit task", + value: "494K", + note: "ops/sec", + width: 44, + featured: true, + }, + { + label: "Post void", + value: "402K", + note: "ops/sec", + width: 40, + featured: true, + }, + ], + rows: [ + { + name: "executor.metrics/idle_reads", + group: "executor.metrics", + value: "107.44M ops/sec", + note: "Very small guardrail benchmark, not a marketing number.", + }, + { + name: "executor.metrics/running_idle_reads", + group: "executor.metrics", + value: "2.72M ops/sec", + note: "Metric reads while the executor is active.", + }, + { + name: "executor.submit/task_complete", + group: "executor.submit", + value: "494K ops/sec", + note: "Submit and complete task path.", + }, + { + name: "executor.post/void_tasks", + group: "executor.post", + value: "402K ops/sec", + note: "Post void task path.", + }, + ], + }, + { + key: "app", + label: "App/session", + kicker: "Application benchmark", + title: "App registration and fake session transport are benchmarked.", + subtitle: + "These numbers are closer to framework-level behavior because they touch app route registration and fake request transport paths.", + version: "current", + mode: "Release", + metrics: [ + { + label: "Session root", + value: "181K", + note: "ops/sec", + width: 100, + featured: true, + }, + { + label: "Session health", + value: "168K", note: "ops/sec", - logo: true, + width: 92, + featured: false, }, { - name: "Queue push/clear", - display: "33.3M", - height: 78, + label: "Route get", + value: "132K", note: "ops/sec", - logo: false, + width: 72, + featured: true, }, { - name: "Worker tasks", - display: "747K", - height: 42, + label: "Group objects", + value: "73K", note: "ops/sec", - logo: false, + width: 48, + featured: false, }, ], - facts: [ - { value: "49.6M", label: "queue push/pop" }, - { value: "33.3M", label: "queue push/clear" }, - { value: "747K", label: "worker tasks" }, - { value: "677K", label: "scheduler tasks" }, + rows: [ + { + name: "session.fake_transport/single_get_root", + group: "session.fake_transport", + value: "181K ops/sec", + note: "Fake transport GET / path.", + }, + { + name: "session.fake_transport/single_get_health", + group: "session.fake_transport", + value: "168K ops/sec", + note: "Fake transport health endpoint.", + }, + { + name: "app.route_registration/get_routes", + group: "app.route_registration", + value: "132K ops/sec", + note: "Route registration lookup path.", + }, + { + name: "app.group_registration/create_group_objects", + group: "app.group_registration", + value: "73K ops/sec", + note: "Group object creation path.", + }, ], }, ]; @@ -369,14 +552,14 @@ const activeTab = computed( background: radial-gradient( circle at 50% 0%, - rgba(34, 197, 94, 0.1), - transparent 24rem + rgba(34, 197, 94, 0.12), + transparent 25rem ), radial-gradient(rgba(255, 255, 255, 0.055) 1px, transparent 1px); background-size: auto, 22px 22px; - opacity: 0.5; + opacity: 0.55; mask-image: linear-gradient(#000, transparent 88%); } @@ -384,15 +567,47 @@ const activeTab = computed( position: relative; } +.performance__meta { + max-width: 980px; + margin: 40px auto 0; + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 14px; +} + +.performance__meta > div { + padding: 17px 18px; + border: 1px solid var(--line); + border-radius: var(--radius-lg); + background: rgba(255, 255, 255, 0.035); +} + +.performance__meta span { + display: block; + margin-bottom: 7px; + color: var(--text-muted); + font-size: 0.72rem; + font-weight: 850; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.performance__meta strong { + display: block; + color: var(--text); + font-size: 0.95rem; + line-height: 1.4; +} + .performance__shell { - max-width: 900px; - margin: 42px auto 0; + max-width: 980px; + margin: 26px auto 0; } .performance__tabs { display: flex; justify-content: center; - gap: 10px; + gap: 8px; border-bottom: 1px solid var(--line); overflow-x: auto; scrollbar-width: none; @@ -408,9 +623,9 @@ const activeTab = computed( border: 0; background: transparent; color: var(--text-soft); - padding: 0.95rem 1.2rem; - font-size: 0.92rem; - font-weight: 750; + padding: 0.95rem 1.05rem; + font-size: 0.88rem; + font-weight: 800; cursor: pointer; } @@ -437,49 +652,51 @@ const activeTab = computed( background: var(--green); } -.performance__chart-card { - position: relative; +.performance__panel { overflow: hidden; border: 1px solid var(--line); - border-radius: var(--radius-lg); + border-top: 0; + border-radius: 0 0 var(--radius-lg) var(--radius-lg); background: linear-gradient(180deg, rgba(255, 255, 255, 0.035), transparent), var(--bg-ink); box-shadow: var(--shadow-lg); } -.performance__chart-head { +.performance__panel-head { display: flex; justify-content: space-between; gap: 24px; - padding: 26px 28px; + padding: 28px; border-bottom: 1px solid var(--line-soft); } -.performance__chart-kicker { +.performance__kicker { margin: 0 0 8px; color: var(--green-bright); font-family: var(--font-mono); font-size: 0.72rem; - font-weight: 800; + font-weight: 850; letter-spacing: 0.1em; text-transform: uppercase; } -.performance__chart-head h3 { +.performance__panel-head h3 { + max-width: 680px; margin: 0; color: var(--text); font-size: clamp(1.35rem, 2.2vw, 2rem); - line-height: 1.1; + line-height: 1.12; letter-spacing: -0.04em; } -.performance__chart-head span { +.performance__panel-head span { display: block; - margin-top: 8px; + max-width: 720px; + margin-top: 9px; color: var(--text-soft); font-size: 0.92rem; - line-height: 1.55; + line-height: 1.6; } .performance__badge { @@ -487,8 +704,8 @@ const activeTab = computed( display: grid; place-items: center; align-content: center; - min-width: 92px; - height: 66px; + min-width: 98px; + height: 68px; padding: 0 14px; border: 1px solid rgba(34, 197, 94, 0.25); border-radius: var(--radius-md); @@ -504,230 +721,197 @@ const activeTab = computed( .performance__badge span { margin: 2px 0 0; - color: rgba(187, 247, 208, 0.7); + color: rgba(187, 247, 208, 0.72); font-size: 0.72rem; - font-weight: 750; + font-weight: 800; } -.performance__bars { +.performance__metrics { display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - align-items: end; - gap: 28px; - min-height: 290px; - padding: 34px 42px 30px; + grid-template-columns: repeat(4, 1fr); + gap: 14px; + padding: 24px; } -.performance__bar-item { - display: grid; - justify-items: center; - align-items: end; - min-width: 0; -} - -.performance__bar-value { - margin-bottom: 8px; - color: var(--text); - font-family: var(--font-mono); - font-size: 0.98rem; - font-weight: 850; - letter-spacing: -0.03em; -} - -.performance__bar-track { +.performance-metric { position: relative; - display: flex; - align-items: end; - justify-content: center; - width: min(100%, 104px); - height: 170px; + overflow: hidden; + min-width: 0; + padding: 18px; + border: 1px solid var(--line-soft); + border-radius: var(--radius-md); + background: rgba(255, 255, 255, 0.03); } -.performance__bar-fill { - position: relative; - width: 100%; - min-height: 38px; - border-radius: 12px 12px 0 0; +.performance-metric--featured { + border-color: rgba(34, 197, 94, 0.32); background: - linear-gradient(180deg, rgba(134, 239, 172, 0.95), rgba(34, 197, 94, 0.74)), - var(--green); - box-shadow: 0 16px 38px rgba(34, 197, 94, 0.18); -} - -.performance__bar-item:nth-child(2) .performance__bar-fill { - background: linear-gradient(180deg, #7dd3fc, #64748b); - box-shadow: 0 16px 38px rgba(125, 211, 252, 0.12); + radial-gradient( + circle at top right, + rgba(34, 197, 94, 0.14), + transparent 42% + ), + rgba(255, 255, 255, 0.035); } -.performance__bar-item:nth-child(3) .performance__bar-fill { - background: linear-gradient(180deg, #c4b5fd, #64748b); - box-shadow: 0 16px 38px rgba(196, 181, 253, 0.12); +.performance-metric span { + display: block; + color: var(--text-muted); + font-size: 0.72rem; + font-weight: 850; + letter-spacing: 0.08em; + text-transform: uppercase; } -.performance__bar-logo { - position: absolute; - left: 50%; - bottom: 16px; - width: 44px; - height: 44px; - transform: translateX(-50%); +.performance-metric strong { + display: block; + margin-top: 10px; + color: var(--text); + font-family: var(--font-mono); + font-size: clamp(1.35rem, 2vw, 1.75rem); + font-weight: 900; + letter-spacing: -0.06em; } -.performance__bar-logo svg { - width: 100%; - height: 100%; +.performance-metric small { + display: block; + margin-top: 5px; + color: var(--text-soft); + font-size: 0.78rem; + line-height: 1.4; } -.performance__bar-item strong { - margin-top: 14px; - color: var(--text); - font-size: 0.9rem; - font-weight: 850; - text-align: center; +.performance-metric__track { + margin-top: 16px; + height: 7px; + overflow: hidden; + border-radius: 999px; + background: rgba(255, 255, 255, 0.08); } -.performance__bar-item span { - margin-top: 4px; - color: var(--text-muted); - font-size: 0.76rem; - font-weight: 650; - text-align: center; +.performance-metric__fill { + height: 100%; + border-radius: inherit; + background: linear-gradient(90deg, #22c55e, #86efac); } -.performance__facts { - display: grid; - grid-template-columns: repeat(4, minmax(0, 1fr)); +.performance__table { border-top: 1px solid var(--line-soft); - background: rgba(255, 255, 255, 0.025); } -.performance__facts div { - padding: 18px 16px; - border-right: 1px solid var(--line-soft); - text-align: center; +.performance__table-head, +.performance__table-row { + display: grid; + grid-template-columns: minmax(260px, 1.25fr) minmax(150px, 0.65fr) minmax( + 260px, + 1.1fr + ); } -.performance__facts div:last-child { - border-right: 0; +.performance__table-head { + background: rgba(255, 255, 255, 0.035); } -.performance__facts strong { - display: block; +.performance__table-head > div { + padding: 14px 20px; color: var(--text); - font-family: var(--font-mono); - font-size: 0.95rem; - font-weight: 850; -} - -.performance__facts span { - display: block; - margin-top: 6px; - color: var(--text-muted); font-size: 0.72rem; - font-weight: 700; + font-weight: 900; + letter-spacing: 0.08em; text-transform: uppercase; - letter-spacing: 0.06em; } -.performance__trust { - max-width: 900px; - margin: 24px auto 0; - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - gap: 12px; +.performance__table-row { + border-top: 1px solid var(--line-soft); } -.performance__trust article { - padding: 18px; - border: 1px solid var(--line); - border-radius: var(--radius-md); - background: rgba(255, 255, 255, 0.025); +.performance__table-row > div { + min-width: 0; + padding: 17px 20px; + color: var(--text-soft); + font-size: 0.88rem; + line-height: 1.55; } -.performance__trust strong { +.performance__table-row strong { display: block; color: var(--text); font-size: 0.9rem; - font-weight: 850; } -.performance__trust span { +.performance__table-row span { display: block; - margin-top: 6px; + margin-top: 4px; color: var(--text-muted); - font-size: 0.8rem; - line-height: 1.55; + font-family: var(--font-mono); + font-size: 0.75rem; +} + +.performance__table-row > div:nth-child(2) { + color: #86efac; + font-family: var(--font-mono); + font-weight: 850; } .performance__commands { - max-width: 900px; + max-width: 980px; margin: 26px auto 0; display: grid; gap: 8px; } .performance__note { - max-width: 860px; + max-width: 900px; margin: 20px auto 0; color: var(--text-muted); font-size: 0.88rem; - line-height: 1.7; + line-height: 1.75; text-align: center; } -@media (max-width: 760px) { - .performance__chart-head { - flex-direction: column; - } - - .performance__badge { - place-items: start; - align-content: center; - text-align: left; +@media (max-width: 980px) { + .performance__meta, + .performance__metrics { + grid-template-columns: repeat(2, 1fr); } - .performance__bars { - gap: 18px; - padding: 28px 20px; + .performance__table { + overflow-x: auto; } - .performance__facts { - grid-template-columns: repeat(2, minmax(0, 1fr)); + .performance__table-head, + .performance__table-row { + min-width: 780px; } +} - .performance__facts div:nth-child(2) { - border-right: 0; +@media (max-width: 720px) { + .performance__panel-head { + flex-direction: column; } - .performance__facts div:nth-child(-n + 2) { - border-bottom: 1px solid var(--line-soft); + .performance__badge { + place-items: start; + text-align: left; } - .performance__trust { + .performance__meta { grid-template-columns: 1fr; } } -@media (max-width: 520px) { - .performance__bars { +@media (max-width: 560px) { + .performance__metrics { grid-template-columns: 1fr; - min-height: auto; - } - - .performance__bar-track { - width: 100%; - height: 78px; - align-items: center; - justify-content: flex-start; + padding: 18px; } - .performance__bar-fill { - height: 34px !important; - border-radius: 999px; + .performance__panel-head { + padding: 22px; } - .performance__bar-logo { - display: none; + .performance__tab { + padding-inline: 0.9rem; } } diff --git a/vix-site/src/components/home/ProductionSection.vue b/vix-site/src/components/home/ProductionSection.vue new file mode 100644 index 00000000..ccd73cf5 --- /dev/null +++ b/vix-site/src/components/home/ProductionSection.vue @@ -0,0 +1,406 @@ + + + + + diff --git a/vix-site/src/components/home/RendererSection.vue b/vix-site/src/components/home/RendererSection.vue new file mode 100644 index 00000000..9118523d --- /dev/null +++ b/vix-site/src/components/home/RendererSection.vue @@ -0,0 +1,425 @@ + + + + + diff --git a/vix-site/src/components/home/ReplaySection.vue b/vix-site/src/components/home/ReplaySection.vue new file mode 100644 index 00000000..7b4d5d03 --- /dev/null +++ b/vix-site/src/components/home/ReplaySection.vue @@ -0,0 +1,450 @@ + + + + + diff --git a/vix-site/src/components/home/ReplySection.vue b/vix-site/src/components/home/ReplySection.vue new file mode 100644 index 00000000..68aee695 --- /dev/null +++ b/vix-site/src/components/home/ReplySection.vue @@ -0,0 +1,431 @@ + + + + + diff --git a/vix-site/src/components/home/TemplatesSection.vue b/vix-site/src/components/home/TemplatesSection.vue new file mode 100644 index 00000000..0abe0519 --- /dev/null +++ b/vix-site/src/components/home/TemplatesSection.vue @@ -0,0 +1,447 @@ + + + + + diff --git a/vix-site/src/components/home/VixAppSection.vue b/vix-site/src/components/home/VixAppSection.vue new file mode 100644 index 00000000..76997581 --- /dev/null +++ b/vix-site/src/components/home/VixAppSection.vue @@ -0,0 +1,413 @@ + + + + + diff --git a/vix-site/src/pages/HomePage.vue b/vix-site/src/pages/HomePage.vue index 04e3f79a..588e2ffd 100644 --- a/vix-site/src/pages/HomePage.vue +++ b/vix-site/src/pages/HomePage.vue @@ -9,6 +9,14 @@ + + + + + + + + @@ -56,6 +64,14 @@ import DirectionSection from "@/components/home/DirectionSection.vue"; import SoftadastraSection from "@/components/home/SoftadastraSection.vue"; import ResourcesSection from "@/components/home/ResourcesSection.vue"; +import AppModulesSection from "@/components/home/AppModulesSection.vue"; +import ProductionSection from "@/components/home/ProductionSection.vue"; +import VixAppSection from "@/components/home/VixAppSection.vue"; +import TemplatesSection from "@/components/home/TemplatesSection.vue"; +import ReplySection from "@/components/home/ReplySection.vue"; +import DiagnosticsSection from "@/components/home/DiagnosticsSection.vue"; +import ReplaySection from "@/components/home/ReplaySection.vue"; +import RendererSection from "@/components/home/RendererSection.vue";