# Shared helpers for the ebook-skill command line (Windows PowerShell 5.1+ / pwsh 7+). $ErrorActionPreference = 'Stop' function Get-ProjectRoot { param([string]$Start = $PSScriptRoot) $dir = (Resolve-Path $Start).Path while ($dir) { if (Test-Path (Join-Path $dir 'vendor\so-novel\pom.xml')) { return $dir } $parent = Split-Path $dir -Parent if ($parent -eq $dir -or -not $parent) { break } $dir = $parent } throw "Cannot locate project root (vendor/so-novel/pom.xml not found above $Start). Run: git submodule update --init" } function Get-ToolPaths { param([string]$Root = (Get-ProjectRoot)) [pscustomobject]@{ Root = $Root Vendor = Join-Path $Root 'vendor\so-novel' Tools = Join-Path $Root 'tools' Runtime = Join-Path $Root 'tools\runtime' Build = Join-Path $Root 'tools\build' Classes = Join-Path $Root 'tools\build\classes' AppJar = Join-Path $Root 'tools\runtime\app.jar' ToolJar = Join-Path $Root 'tools\runtime\sonovel-tool.jar' Config = Join-Path $Root 'tools\runtime\config.ini' Rules = Join-Path $Root 'tools\runtime\rules' Books = Join-Path $Root 'books' } } function Test-Jdk21 { param([string]$JdkHome) if (-not $JdkHome) { return $false } $java = Join-Path $JdkHome 'bin\java.exe' if (-not (Test-Path $java)) { return $false } try { $out = (& $java -version 2>&1 | Out-String) } catch { return $false } if ($out -match 'version "(?:1\.)?(\d+)') { return ([int]$Matches[1] -ge 21) } return $false } function Find-Jdk21 { param([switch]$AllowDownload) $candidates = New-Object System.Collections.Generic.List[string] if ($env:EBOOK_NOVEL_JAVA_HOME) { $candidates.Add($env:EBOOK_NOVEL_JAVA_HOME) } if ($env:JAVA_HOME) { $candidates.Add($env:JAVA_HOME) } $patterns = @( (Join-Path $PSScriptRoot '.jdk\*\*'), (Join-Path $PSScriptRoot '.jdk'), 'C:\Program Files\Java\jdk-21*', 'C:\Program Files\Eclipse Adoptium\jdk-21*', 'C:\Program Files\JetBrains\*\jbr', (Join-Path $env:LOCALAPPDATA 'mise\installs\java\21*'), (Join-Path $env:LOCALAPPDATA 'mise\installs\java\2*') ) foreach ($pattern in $patterns) { foreach ($dir in (Get-ChildItem -Path $pattern -Directory -ErrorAction SilentlyContinue)) { $candidates.Add($dir.FullName) } } foreach ($candidate in $candidates) { if (Test-Jdk21 -JdkHome $candidate) { return (Resolve-Path $candidate).Path } } if ($AllowDownload) { return (Install-Jdk21) } return $null } # Downloads a portable Temurin JDK 21 (used only when no local JDK 21 exists). function Install-Jdk21 { $dest = Join-Path $PSScriptRoot '.jdk' New-Item -ItemType Directory -Force $dest | Out-Null Write-Host '[setup] no JDK 21 found, downloading Temurin 21 from the Tsinghua mirror ...' -ForegroundColor Yellow $mirror = 'https://mirrors.tuna.tsinghua.edu.cn/Adoptium/21/jdk/x64/windows/' $listing = (Invoke-WebRequest -Uri $mirror -UseBasicParsing -TimeoutSec 60).Content $names = [regex]::Matches($listing, 'OpenJDK21U-jdk_x64_windows_hotspot_[0-9._]+\.zip') | ForEach-Object { $_.Value } | Sort-Object -Unique if (-not $names) { throw "Could not find a JDK 21 archive at $mirror" } $zipName = $names[$names.Count - 1] $zip = Join-Path $env:TEMP $zipName if (-not (Test-Path $zip)) { Invoke-WebRequest -Uri ($mirror + $zipName) -OutFile $zip -UseBasicParsing -TimeoutSec 1800 } Expand-Archive -Path $zip -DestinationPath $dest -Force $java = Get-ChildItem -Path (Join-Path $dest '*\bin\java.exe') -ErrorAction SilentlyContinue | Select-Object -First 1 if (-not $java) { throw "JDK extraction failed under $dest" } $JdkHome = Split-Path (Split-Path $java.FullName -Parent) -Parent Write-Host "[setup] JDK 21 installed at $JdkHome" -ForegroundColor Green return $JdkHome } function Get-JavaCommand { param([string]$Root = (Get-ProjectRoot)) $jdkHome = Find-Jdk21 if (-not $jdkHome) { throw 'JDK 21 is required. Run tools\build.ps1 (it can download one), or set EBOOK_NOVEL_JAVA_HOME.' } return (Join-Path $jdkHome 'bin\java.exe') } function Find-JarTool { # JBR ships without jar.exe, so fall back to any other JDK on the machine. foreach ($pattern in @('C:\Program Files\Java\*\bin\jar.exe', (Join-Path $env:LOCALAPPDATA 'mise\installs\java\*\bin\jar.exe'), 'C:\Program Files\Eclipse Adoptium\*\bin\jar.exe')) { $exe = Get-ChildItem -Path $pattern -ErrorAction SilentlyContinue | Select-Object -First 1 if ($exe) { return $exe.FullName } } $cmd = Get-Command jar.exe -ErrorAction SilentlyContinue if ($cmd) { return $cmd.Source } return $null }