diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..76a3262 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,137 @@ +name: Release + +# Pushing a v* tag publishes a release. workflow_dispatch runs the same pipeline +# without publishing, so the packaging can be exercised end to end without +# creating a release that then has to be deleted. +on: + push: + tags: ["v*"] + # Packaging changes are built (never published) on their own pull requests, so + # a broken .iss or script is caught before it can break a real release. The + # publish step below is gated on a tag, so these runs only produce artifacts. + pull_request: + paths: + - "packaging/**" + - ".github/workflows/release.yml" + - "VerseLinkWindows/Version.h" + workflow_dispatch: + inputs: + publish: + description: "Publish a GitHub Release (off: build and upload artifacts only)" + type: boolean + default: false + +jobs: + release: + runs-on: windows-latest + permissions: + contents: write + steps: + - name: Checkout + uses: actions/checkout@v7 + + # A tag must match Version.h, or the release would advertise a version the + # binary does not report. build-installer.ps1 enforces this too; doing it + # up front fails in seconds instead of after a full build. + - name: Determine and check version + id: version + shell: pwsh + run: | + $header = "VerseLinkWindows\Version.h" + $match = Select-String -Path $header -Pattern '#define\s+VERSELINK_VERSION_STRING\s+"([^"]+)"' + if (-not $match) { throw "VERSELINK_VERSION_STRING not found in $header" } + $version = $match.Matches[0].Groups[1].Value + Write-Host "Version.h says $version" + + if ("${{ github.ref_type }}" -eq "tag") { + $tag = "${{ github.ref_name }}".TrimStart('v') + if ($tag -ne $version) { + throw "Tag ${{ github.ref_name }} does not match Version.h ($version). Bump Version.h, or tag the version that is actually in the tree." + } + Write-Host "Tag matches Version.h" + } + + "version=$version" >> $env:GITHUB_OUTPUT + + - name: Build application (Release x64) + shell: pwsh + run: | + $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" + $vsRoot = & $vswhere -latest -products * -requires Microsoft.Component.MSBuild -property installationPath + & "$vsRoot\MSBuild\Current\Bin\MSBuild.exe" "VerseLinkWindows\VerseLinkWindows.vcxproj" /p:Configuration=Release /p:Platform=x64 /m /v:m /nologo + exit $LASTEXITCODE + + # Never package a build that cannot pass its own checks. + - name: Run headless self test + shell: cmd + run: | + VerseLinkWindows\x64\Release\VerseLinkWindows.exe --selftest + if errorlevel 1 exit /b 1 + + - name: Install Inno Setup + shell: pwsh + run: choco install innosetup --no-progress -y + + - name: Build installer + shell: pwsh + run: | + $expect = if ("${{ github.ref_type }}" -eq "tag") { "${{ github.ref_name }}" } else { "" } + .\packaging\build-installer.ps1 -SkipBuild -ExpectVersion $expect + + - name: Build portable zip + shell: pwsh + run: .\packaging\package.ps1 -SkipBuild -Platform x64 + + - name: Upload packages as artifacts + uses: actions/upload-artifact@v7 + with: + name: VerseLink-${{ steps.version.outputs.version }} + path: dist/* + if-no-files-found: error + + - name: Publish GitHub Release + if: github.ref_type == 'tag' || inputs.publish + shell: pwsh + env: + GH_TOKEN: ${{ github.token }} + run: | + $version = "${{ steps.version.outputs.version }}" + $tag = if ("${{ github.ref_type }}" -eq "tag") { "${{ github.ref_name }}" } else { "v$version" } + + $notes = @" + ## Install + + Download **VerseLink-$version-Setup.exe** and run it. You choose where it goes - + ``C:\VerseLink``, Program Files, anywhere. It defaults to a per-user location so there is + no administrator prompt; pick "all users" on the first dialog if you want a shared one. + + ## Updating an existing installation + + Installing over an existing copy does **not** uninstall or delete it. The previous + executable is moved to ``\previous-versions\VerseLinkWindows-.exe`` + before the new one is copied in, so rolling back is just copying that file back. + + Your settings in ``%APPDATA%\VerseLink`` are never touched. If VerseLink is running, Setup + closes it before updating and starts it again afterwards. + + ## If you previously ran the zip + + An unzipped copy is invisible to the installer - nothing recorded it as installed, so it is + neither updated nor removed. Your settings are carried over on first run, but delete the old + folder yourself, and remove its Startup shortcut if you added one, so you do not end up + running two copies. + + ## Files + + | File | What it is | + |---|---| + | ``VerseLink-$version-Setup.exe`` | Installer (recommended) | + | ``VerseLink-$version-x64-portable.zip`` | Portable copy, no installer | + + Settings and log live in ``%APPDATA%\VerseLink``. Only the King James Version is bundled; + other translations are copyrighted and must be supplied by you. + "@ + + gh release create $tag (Get-ChildItem dist\* | ForEach-Object { $_.FullName }) ` + --title "VerseLink $version" ` + --notes $notes diff --git a/VerseLinkWindows/VerseLinkWindows.cpp b/VerseLinkWindows/VerseLinkWindows.cpp index 5d9f9cf..a665ca4 100644 --- a/VerseLinkWindows/VerseLinkWindows.cpp +++ b/VerseLinkWindows/VerseLinkWindows.cpp @@ -371,6 +371,28 @@ LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) return 0; } + switch (uMsg) { + // An outside request to shut down: the installer closing us before it + // replaces the exe, or Windows signing the user out. Restart Manager + // sends WM_QUERYENDSESSION then WM_ENDSESSION to top-level windows, so + // handling these is what lets an upgrade close VerseLink cleanly + // instead of failing on a locked exe. + // + // WM_CLOSE previously fell through to DefWindowProc, which destroyed + // the window but left the message loop and the worker thread running - + // the process stayed alive with no window and no tray icon. + case WM_QUERYENDSESSION: + return TRUE; // yes, we can shut down + + case WM_CLOSE: + case WM_ENDSESSION: + LOG_INFO("Shutdown requested by the system or an installer"); + g_shouldExit = true; + g_taskQueue.Shutdown(); + PostQuitMessage(0); + return 0; + } + return DefWindowProc(hwnd, uMsg, wParam, lParam); } @@ -449,6 +471,9 @@ int main() WNDCLASS wc = {}; wc.lpfnWndProc = WindowProc; wc.hInstance = GetModuleHandle(nullptr); + // The installer finds this window by class name to ask VerseLink to close + // before it replaces the exe (packaging\VerseLink.iss, AppWindowClass). + // Renaming it breaks that, and updates would fail on a locked file. wc.lpszClassName = L"VerseLinkHiddenWindow"; if (!RegisterClass(&wc)) { diff --git a/packaging/DISTRIBUTION-NOTES.txt b/packaging/DISTRIBUTION-NOTES.txt new file mode 100644 index 0000000..54f29ba --- /dev/null +++ b/packaging/DISTRIBUTION-NOTES.txt @@ -0,0 +1,41 @@ +VerseLink - distribution notes +============================== + +Bible translations +------------------ +This package includes the King James Version (public domain). + +Other translations such as the NASB or ESV are copyrighted and are NOT +included. To use one, obtain the text legally in the same XML format and place +the file into the Bibles folder inside the installation directory, then pick it +in the tray icon's Settings dialog. + +Where your files live +--------------------- +Settings and log: %APPDATA%\VerseLink +Program files: the folder you choose during installation + +Settings are never touched by the installer. They are kept across updates, and +uninstalling asks before removing them. + +Updating an existing installation +--------------------------------- +Installing a newer build over an existing one does NOT uninstall or delete the +old version. The previous executable is moved to: + + \previous-versions\VerseLinkWindows-.exe + +before the new one is copied in, so you can roll back by copying that file back +over VerseLinkWindows.exe. + +If VerseLink is running, Setup closes it before updating and starts it again +afterwards, so you are not left without your hotkey. + +Upgrading from a zip copy +------------------------- +If you previously ran VerseLink from an unzipped folder rather than an +installer, that copy is invisible to this installer - nothing recorded it as +installed, so it is not updated or removed. Your settings are carried over on +first run, but you should delete the old folder yourself, and remove its +shortcut from your Startup folder if you added one, so you do not end up +running two copies. diff --git a/packaging/VerseLink.iss b/packaging/VerseLink.iss new file mode 100644 index 0000000..2c5155c --- /dev/null +++ b/packaging/VerseLink.iss @@ -0,0 +1,271 @@ +; VerseLink installer (Inno Setup 6) +; +; Compile through packaging\build-installer.ps1, which supplies AppVersion from +; VerseLinkWindows\Version.h so the installer and the exe can never disagree. +; +; Update behaviour, by design: +; - The install location is chosen by the user (C:\VerseLink, Program Files, +; anywhere). AppId lets Setup remember and default to where a previous +; version went, but the directory page is always offered. +; - An existing version is NOT uninstalled or deleted. The old executable is +; moved into {app}\previous-versions\VerseLinkWindows-.exe before +; the new one is copied over, so rolling back is a matter of copying a file. +; - Settings are never touched. They live in %APPDATA%\VerseLink, which this +; installer neither writes nor removes, and no config.json is shipped. +; - If VerseLink is running it is closed before the update and relaunched +; afterwards, so an update does not leave the user without their hotkey. + +#define AppName "VerseLink" +#define AppExeName "VerseLinkWindows.exe" +#define AppPublisher "sethdtwigg" +#define AppURL "https://github.com/sethdtwigg/VerseLinkWindows" +#define AppMutexName "VerseLinkWindows.SingleInstance" +; Must match the class registered in VerseLinkWindows.cpp. +#define AppWindowClass "VerseLinkHiddenWindow" +#define BackupDirName "previous-versions" + +#ifndef AppVersion + #define AppVersion "0.0.0" +#endif +#ifndef SourceDir + #define SourceDir "..\" +#endif + +[Setup] +AppId={{B60AB966-8EA7-43D5-AF1F-4DD311F75EA9} +AppName={#AppName} +AppVersion={#AppVersion} +AppVerName={#AppName} {#AppVersion} +AppPublisher={#AppPublisher} +AppPublisherURL={#AppURL} +AppSupportURL={#AppURL}/issues +AppUpdatesURL={#AppURL}/releases +VersionInfoVersion={#AppVersion} + +; Default to a per-user location so the common case needs no UAC prompt, but +; allow elevating from the first dialog so somewhere like C:\VerseLink or +; Program Files can be chosen. The app itself does not need a writable install +; directory - its settings live in %APPDATA%. +PrivilegesRequired=lowest +PrivilegesRequiredOverridesAllowed=dialog +DefaultDirName={autopf}\{#AppName} +DefaultGroupName={#AppName} +DisableProgramGroupPage=yes + +; The directory page is the point of this installer - never skip it, and show +; the chosen path again on the confirmation page. +DisableDirPage=no +AlwaysShowDirOnReadyPage=yes +UsePreviousAppDir=yes + +; A tray app keeps its own exe locked, so a running VerseLink has to be closed +; before its executable can be replaced. +; +; AppMutex is deliberately NOT used. It only *blocks*: it tells the user to +; close the app and aborts if they do not, which fails outright under /SILENT +; ("Defaulting to Cancel for suppressed message box... Got EAbort exception"). +; Instead PrepareToInstall closes VerseLink itself by posting WM_CLOSE to its +; window, which the app handles as a clean shutdown. CloseApplications stays on +; as a Restart Manager backstop for any other locked file, and restarting is +; done in [Code] rather than by RestartApplications so it happens exactly once. +CloseApplications=yes +RestartApplications=no + +ArchitecturesAllowed=x64compatible +ArchitecturesInstallIn64BitMode=x64compatible + +OutputDir={#SourceDir}\dist +OutputBaseFilename=VerseLink-{#AppVersion}-Setup +SetupIconFile={#SourceDir}\VerseLinkWindows\VerseLinkIcon.ico +UninstallDisplayIcon={app}\{#AppExeName} +UninstallDisplayName={#AppName} {#AppVersion} +Compression=lzma2/max +SolidCompression=yes +WizardStyle=modern + +; Shown before installing rather than as a licence: it explains that only the +; KJV is bundled and where settings live. No acceptance checkbox. +InfoBeforeFile={#SourceDir}\packaging\DISTRIBUTION-NOTES.txt + +[Languages] +Name: "english"; MessagesFile: "compiler:Default.isl" + +[Tasks] +; A hotkey utility that is not running does nothing, so this is on by default. +Name: "startup"; Description: "Start {#AppName} automatically when I sign in"; GroupDescription: "Additional options:" +Name: "desktopicon"; Description: "Create a desktop shortcut"; GroupDescription: "Additional options:"; Flags: unchecked + +[Files] +Source: "{#SourceDir}\VerseLinkWindows\x64\Release\{#AppExeName}"; DestDir: "{app}"; Flags: ignoreversion +Source: "{#SourceDir}\VerseLinkWindows\VerseLinkIcon.ico"; DestDir: "{app}"; Flags: ignoreversion +Source: "{#SourceDir}\README.md"; DestDir: "{app}"; Flags: ignoreversion +Source: "{#SourceDir}\packaging\DISTRIBUTION-NOTES.txt"; DestDir: "{app}"; Flags: ignoreversion + +; Only the KJV is redistributable. NASB, ESV and other modern translations are +; copyrighted and must be supplied by the user into this folder. +Source: "{#SourceDir}\Bibles\KJV.xml"; DestDir: "{app}\Bibles"; Flags: ignoreversion + +; config.json is deliberately not installed. Settings live in +; %APPDATA%\VerseLink\config.json, created with defaults on first run; shipping +; one here would be picked up by the app's legacy-config migration and could +; overwrite what an updating user actually had. + +[Icons] +Name: "{group}\{#AppName}"; Filename: "{app}\{#AppExeName}"; WorkingDir: "{app}" +Name: "{group}\Uninstall {#AppName}"; Filename: "{uninstallexe}" +Name: "{userstartup}\{#AppName}"; Filename: "{app}\{#AppExeName}"; WorkingDir: "{app}"; Tasks: startup +Name: "{autodesktop}\{#AppName}"; Filename: "{app}\{#AppExeName}"; WorkingDir: "{app}"; Tasks: desktopicon + +[Run] +; Only offered when VerseLink was not already running - if it was, [Code] +; relaunches it automatically and this would just start a second copy that +; immediately exits on the mutex. +Filename: "{app}\{#AppExeName}"; Description: "Launch {#AppName}"; WorkingDir: "{app}"; \ + Flags: nowait postinstall skipifsilent; Check: NotRunningBeforeInstall + +[UninstallDelete] +; Backups of previous executables are created at runtime, so they are not +; tracked as installed files and would otherwise keep {app} from being removed. +Type: filesandordirs; Name: "{app}\{#BackupDirName}" +; The log is generated at runtime. +Type: files; Name: "{userappdata}\{#AppName}\verselink.log" + +[Code] +var + WasRunning: Boolean; + +function InitializeSetup(): Boolean; +begin + // Recorded before Setup asks the user to close the app, so the decision to + // relaunch afterwards reflects whether it was actually running to begin with. + WasRunning := CheckForMutexes('{#AppMutexName}'); + Result := True; +end; + +function NotRunningBeforeInstall(): Boolean; +begin + Result := not WasRunning; +end; + +// Closes a running VerseLink the way its own tray Quit does: WM_CLOSE to the +// hidden window, which the app handles by shutting down cleanly (removing its +// tray icon, releasing the hotkey and joining its worker thread). Waits for the +// single-instance mutex to clear so the exe is genuinely unlocked before Setup +// tries to replace it. +function CloseRunningInstance(): Boolean; +var + Wnd: HWND; + Waited: Integer; +begin + Result := True; + if not CheckForMutexes('{#AppMutexName}') then + exit; + + Wnd := FindWindowByClassName('{#AppWindowClass}'); + if Wnd <> 0 then + begin + Log('Asking the running VerseLink to close'); + PostMessage(Wnd, $0010 { WM_CLOSE }, 0, 0); + end + else + Log('VerseLink appears to be running but its window was not found'); + + Waited := 0; + while (Waited < 15000) and CheckForMutexes('{#AppMutexName}') do + begin + Sleep(250); + Waited := Waited + 250; + end; + + Result := not CheckForMutexes('{#AppMutexName}'); + if Result then + Log('Running VerseLink closed after ' + IntToStr(Waited) + ' ms'); +end; + +function PrepareToInstall(var NeedsRestart: Boolean): String; +begin + Result := ''; + if not CloseRunningInstance() then + Result := 'VerseLink is still running and could not be closed automatically.' + #13#10 + + 'Close it from its system tray icon, then run Setup again.'; +end; + +// Keeps the outgoing executable instead of letting it be overwritten. Named by +// the version it reports, so several updates do not collide and it is obvious +// which build each backup is. +procedure BackupExistingBinary(); +var + ExistingExe, BackupDir, OldVersion, Target: String; +begin + ExistingExe := ExpandConstant('{app}\{#AppExeName}'); + if not FileExists(ExistingExe) then + exit; + + BackupDir := ExpandConstant('{app}\{#BackupDirName}'); + if not DirExists(BackupDir) then + begin + if not ForceDirectories(BackupDir) then + begin + Log('Could not create ' + BackupDir + '; leaving the existing binary in place'); + exit; + end; + end; + + if not GetVersionNumbersString(ExistingExe, OldVersion) then + OldVersion := GetDateTimeString('yyyymmdd-hhnnss', '-', ''); + + Target := BackupDir + '\VerseLinkWindows-' + OldVersion + '.exe'; + + // Same version installed twice: keep the newer backup rather than failing. + if FileExists(Target) then + DeleteFile(Target); + + if RenameFile(ExistingExe, Target) then + Log('Kept the previous executable as ' + Target) + else + Log('Could not move ' + ExistingExe + ' to ' + Target + '; it will be overwritten'); +end; + +procedure CurStepChanged(CurStep: TSetupStep); +var + ResultCode: Integer; +begin + if CurStep = ssInstall then + begin + BackupExistingBinary(); + end + else if CurStep = ssPostInstall then + begin + // Put the user back where they were: if VerseLink was running when Setup + // started, it is running when Setup finishes. + if WasRunning then + begin + if not Exec(ExpandConstant('{app}\{#AppExeName}'), '', ExpandConstant('{app}'), + SW_SHOW, ewNoWait, ResultCode) then + Log('Could not relaunch VerseLink after the update'); + end; + end; +end; + +// Settings are user data, so they are kept by default and only removed if the +// user asks. Silent uninstalls always keep them. +procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep); +var + SettingsDir: String; +begin + if CurUninstallStep = usPostUninstall then + begin + SettingsDir := ExpandConstant('{userappdata}\{#AppName}'); + if DirExists(SettingsDir) then + begin + if not UninstallSilent then + begin + if MsgBox('Also remove your VerseLink settings?' + #13#10 + #13#10 + + SettingsDir + #13#10 + #13#10 + + 'Choose No to keep them for a future install.', + mbConfirmation, MB_YESNO) = IDYES then + DelTree(SettingsDir, True, True, True); + end; + end; + end; +end; diff --git a/packaging/build-installer.ps1 b/packaging/build-installer.ps1 new file mode 100644 index 0000000..36cada6 --- /dev/null +++ b/packaging/build-installer.ps1 @@ -0,0 +1,98 @@ +# Builds VerseLink and compiles the Inno Setup installer into dist\. +# +# The version comes from VerseLinkWindows\Version.h and is passed to the .iss, +# so the installer, the exe's VERSIONINFO and the About dialog can never +# disagree about which build this is. + +param( + [ValidateSet("Release", "Debug")] + [string]$Configuration = "Release", + [switch]$SkipBuild, + # Fail unless Version.h matches this (e.g. "1.1.0"). The release workflow + # passes the tag so a mistagged release cannot ship. + [string]$ExpectVersion +) + +$ErrorActionPreference = "Stop" +$repoRoot = Split-Path $PSScriptRoot -Parent +$projDir = Join-Path $repoRoot "VerseLinkWindows" + +function Get-VerseLinkVersion { + $versionHeader = Join-Path $projDir "Version.h" + if (-not (Test-Path $versionHeader)) { throw "Version.h not found at $versionHeader" } + + $match = Select-String -Path $versionHeader -Pattern '#define\s+VERSELINK_VERSION_STRING\s+"([^"]+)"' + if (-not $match) { throw "VERSELINK_VERSION_STRING not found in $versionHeader" } + return $match.Matches[0].Groups[1].Value +} + +function Find-InnoCompiler { + $candidates = @( + "${env:ProgramFiles(x86)}\Inno Setup 6\ISCC.exe", + "${env:ProgramFiles}\Inno Setup 6\ISCC.exe" + ) + $found = $candidates | Where-Object { Test-Path $_ } | Select-Object -First 1 + if ($found) { return $found } + + $onPath = Get-Command ISCC.exe -ErrorAction SilentlyContinue + if ($onPath) { return $onPath.Source } + + throw "Inno Setup 6 not found. Install it (winget install JRSoftware.InnoSetup) or add ISCC.exe to PATH." +} + +$version = Get-VerseLinkVersion +Write-Host "== VerseLink $version ==" -ForegroundColor Cyan + +if ($ExpectVersion) { + $expected = $ExpectVersion.TrimStart('v') + if ($version -ne $expected) { + throw "Version mismatch: Version.h says $version but $expected was expected. Bump Version.h, or tag the version that is actually in the tree." + } + Write-Host " version matches the expected $expected" -ForegroundColor DarkGray +} + +if (-not $SkipBuild) { + $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" + if (-not (Test-Path $vswhere)) { throw "vswhere.exe not found - install Visual Studio." } + $vsRoot = & $vswhere -latest -products * -requires Microsoft.Component.MSBuild -property installationPath + $msbuild = Join-Path $vsRoot "MSBuild\Current\Bin\MSBuild.exe" + + Write-Host "== Building $Configuration|x64 ==" -ForegroundColor Cyan + & $msbuild (Join-Path $projDir "VerseLinkWindows.vcxproj") /p:Configuration=$Configuration /p:Platform=x64 /m /v:m /nologo + if ($LASTEXITCODE -ne 0) { throw "Build failed with exit code $LASTEXITCODE" } +} + +$exePath = Join-Path $projDir "x64\$Configuration\VerseLinkWindows.exe" +if (-not (Test-Path $exePath)) { throw "Built exe not found: $exePath" } + +# The exe's own resource must agree with Version.h, or the installer would +# advertise a version the installed file does not report. +$exeVersion = (Get-Item $exePath).VersionInfo.FileVersion +if ($exeVersion -and ($exeVersion.Trim() -ne $version)) { + throw "Built exe reports version '$exeVersion' but Version.h says '$version'. Rebuild so the resource is current." +} + +# Refuse to package a build that cannot pass its own checks. +Write-Host "== Self test ==" -ForegroundColor Cyan +Push-Location $repoRoot +try { + & $exePath --selftest + if ($LASTEXITCODE -ne 0) { throw "Self test failed; refusing to package this build." } +} finally { + Pop-Location +} + +$iscc = Find-InnoCompiler +Write-Host "== Compiling installer with $iscc ==" -ForegroundColor Cyan + +$distDir = Join-Path $repoRoot "dist" +New-Item -ItemType Directory -Path $distDir -Force | Out-Null + +& $iscc "/DAppVersion=$version" "/DSourceDir=$repoRoot" (Join-Path $PSScriptRoot "VerseLink.iss") +if ($LASTEXITCODE -ne 0) { throw "Inno Setup failed with exit code $LASTEXITCODE" } + +$installer = Join-Path $distDir "VerseLink-$version-Setup.exe" +if (-not (Test-Path $installer)) { throw "Installer not produced at $installer" } + +Write-Host "== Installer ready ==" -ForegroundColor Green +Get-Item $installer | Select-Object Name, @{n = "SizeMB"; e = { [Math]::Round($_.Length / 1MB, 2) } } diff --git a/packaging/package.ps1 b/packaging/package.ps1 index 94c93aa..32fd3fb 100644 --- a/packaging/package.ps1 +++ b/packaging/package.ps1 @@ -56,8 +56,14 @@ if (-not $SkipBuild -and (Get-Item $exePath).LastWriteTime -lt $buildStart) { Write-Host "== Packaging $exePath ==" -ForegroundColor Cyan -$stamp = Get-Date -Format "yyyyMMdd" -$distName = "VerseLinkWindows-$stamp-$Platform" +# Named by version rather than by date so a zip pairs unambiguously with the +# installer and the release it belongs to. +$versionHeader = Join-Path $projDir "Version.h" +$match = Select-String -Path $versionHeader -Pattern '#define\s+VERSELINK_VERSION_STRING\s+"([^"]+)"' +if (-not $match) { throw "VERSELINK_VERSION_STRING not found in $versionHeader" } +$version = $match.Matches[0].Groups[1].Value + +$distName = "VerseLink-$version-$Platform-portable" $stage = Join-Path $PSScriptRoot "$distName" $zip = Join-Path $repoRoot "dist\$distName.zip" @@ -65,22 +71,15 @@ if (Test-Path $stage) { Remove-Item $stage -Recurse -Force } New-Item -ItemType Directory -Path $stage\Bibles -Force | Out-Null Copy-Item $exePath $stage -Copy-Item (Join-Path $repoRoot "config.json") $stage Copy-Item (Join-Path $projDir "VerseLinkIcon.ico") $stage -ErrorAction SilentlyContinue Copy-Item (Join-Path $repoRoot "README.md") $stage Copy-Item (Join-Path $repoRoot "Bibles\KJV.xml") $stage\Bibles +Copy-Item (Join-Path $PSScriptRoot "DISTRIBUTION-NOTES.txt") $stage -@' -VerseLink Windows - distribution notes -====================================== - -This package includes the King James Version (public domain). - -Other translations such as the NASB or ESV are copyrighted. To use them, -obtain the text legally and place the XML file(s) into the Bibles folder -next to VerseLinkWindows.exe, then pick the version in the tray icon's -Settings dialog. -'@ | Set-Content (Join-Path $stage "DISTRIBUTION-NOTES.txt") +# config.json is deliberately not shipped. Settings live in +# %APPDATA%\VerseLink\config.json and are created with defaults on first run; +# a config.json next to the exe would be picked up by the legacy-config +# migration and could overwrite what an upgrading user actually had. New-Item -ItemType Directory -Path (Split-Path $zip) -Force | Out-Null if (Test-Path $zip) { Remove-Item $zip -Force }