diff --git a/install-eap.ps1 b/install-eap.ps1 index 26d56218..44f93006 100644 --- a/install-eap.ps1 +++ b/install-eap.ps1 @@ -221,6 +221,17 @@ set "CURRENT_FILE=%JUNIE_DATA%\current" set "PENDING_UPDATE=%UPDATES_DIR%\pending-update.json" set "PROCESSING=%UPDATES_DIR%\pending-update.json.processing" +:: Persistent upgrade log (mirrors the POSIX shim). Lives under +:: %USERPROFILE%\.junie\logs -- separate from the data dir so a reinstall that +:: wipes ~/.local/share/junie does not lose update history. Always appended, +:: never truncated. Override the directory with JUNIE_LOG_DIR. A per-invocation +:: session id (LOG_SID) is stamped on every line so one launch can be followed. +set "UPGRADE_LOG_DIR=%USERPROFILE%\.junie\logs" +if defined JUNIE_LOG_DIR set "UPGRADE_LOG_DIR=%JUNIE_LOG_DIR%" +set "UPGRADE_LOG=%UPGRADE_LOG_DIR%\upgrade.log" +set "LOG_SID=%RANDOM%%RANDOM%" +if not exist "%UPGRADE_LOG_DIR%" mkdir "%UPGRADE_LOG_DIR%" 2>nul + :: === Channel Switching (one-shot) === :: :: `junie --eap` (also --nightly / --release / --experimental, or --channel=) @@ -369,6 +380,7 @@ if exist "%PENDING_UPDATE%" ( for /f "usebackq delims=" %%V in (`powershell -NoProfile -Command "try { $c = Get-Content -Raw -LiteralPath '%PENDING_UPDATE%' -ErrorAction Stop; if ([string]::IsNullOrWhiteSpace($c)) { 'bad' } else { $j = $c | ConvertFrom-Json; if ($j.version -and $j.zipPath) { 'ok' } else { 'bad' } } } catch { 'bad' }"`) do set "PEND_OK=%%V" if /i not "!PEND_OK!"=="ok" ( echo [Junie] Removing invalid update artifact: %PENDING_UPDATE% 1>&2 + call :log_upgrade "sanitize: removing invalid update artifact pending-update.json" del /f /q "%PENDING_UPDATE%" 2>nul ) ) @@ -377,6 +389,7 @@ if exist "!PROCESSING!" ( for /f "usebackq delims=" %%V in (`powershell -NoProfile -Command "try { $c = Get-Content -Raw -LiteralPath '!PROCESSING!' -ErrorAction Stop; if ([string]::IsNullOrWhiteSpace($c)) { 'bad' } else { $j = $c | ConvertFrom-Json; if ($j.version -and $j.zipPath) { 'ok' } else { 'bad' } } } catch { 'bad' }"`) do set "PROC_OK=%%V" if /i not "!PROC_OK!"=="ok" ( echo [Junie] Removing invalid update artifact: !PROCESSING! 1>&2 + call :log_upgrade "sanitize: removing invalid update artifact pending-update.json.processing" del /f /q "!PROCESSING!" 2>nul ) ) @@ -392,39 +405,60 @@ if errorlevel 1 ( goto :resolve_version ) set "CLAIMED_UPDATE=%UPDATES_DIR%\pending-update.json.processing" +call :log_upgrade "apply: claimed pending update for processing" :: Parse the claimed manifest using PowerShell (reliable JSON parsing) for /f "usebackq delims=" %%V in (`powershell -NoProfile -Command "(Get-Content '!CLAIMED_UPDATE!' | ConvertFrom-Json).version"`) do set "UPD_VERSION=%%V" for /f "usebackq delims=" %%Z in (`powershell -NoProfile -Command "(Get-Content '!CLAIMED_UPDATE!' | ConvertFrom-Json).zipPath"`) do set "UPD_ZIP=%%Z" for /f "usebackq delims=" %%S in (`powershell -NoProfile -Command "(Get-Content '!CLAIMED_UPDATE!' | ConvertFrom-Json).sha256"`) do set "UPD_SHA256=%%S" +call :log_upgrade "apply: manifest parsed version=!UPD_VERSION! zipPath=!UPD_ZIP! sha256=!UPD_SHA256!" if not defined UPD_VERSION ( echo [Junie] Invalid pending update manifest, skipping 1>&2 + call :log_upgrade "apply: invalid manifest (missing version); dropping manifest" del /f /q "!CLAIMED_UPDATE!" 2>nul goto :resolve_version ) if not defined UPD_ZIP ( echo [Junie] Invalid pending update manifest, skipping 1>&2 + call :log_upgrade "apply: invalid manifest (missing zipPath); dropping manifest" del /f /q "!CLAIMED_UPDATE!" 2>nul goto :resolve_version ) if not exist "!UPD_ZIP!" ( echo [Junie] Update file not found: !UPD_ZIP! 1>&2 + call :log_upgrade "apply: update archive not found at !UPD_ZIP!; dropping manifest" del /f /q "!CLAIMED_UPDATE!" 2>nul goto :resolve_version ) :: Verify SHA-256 checksum if defined UPD_SHA256 ( + call :log_upgrade "checksum: starting SHA-256 validation expected=!UPD_SHA256!" + set "ACTUAL_SHA=" for /f "usebackq delims=" %%H in (`powershell -NoProfile -Command "(Get-FileHash '!UPD_ZIP!' -Algorithm SHA256).Hash.ToLower()"`) do set "ACTUAL_SHA=%%H" + if not defined ACTUAL_SHA ( + :: Get-FileHash produced no output -- the zip could not be read/hashed, + :: almost always because it is still locked or being scanned by antivirus. + :: This is a transient condition, NOT corruption: PRESERVE the manifest and + :: zip and retry on the next launch. Deleting them here (as a plain mismatch + :: would) makes the binary re-download and re-fail every startup -- an + :: endless update loop that never recovers. + echo [Junie] Could not read update zip ^(locked or AV scan?^); will retry next launch 1>&2 + call :log_upgrade "checksum: UNREADABLE -- Get-FileHash returned no output; preserving manifest and archive for retry" + rename "!CLAIMED_UPDATE!" "pending-update.json" 2>nul + goto :resolve_version + ) if /i not "!ACTUAL_SHA!"=="!UPD_SHA256!" ( echo [Junie] Checksum mismatch, skipping update 1>&2 echo [Junie] Expected: !UPD_SHA256! 1>&2 echo [Junie] Got: !ACTUAL_SHA! 1>&2 + call :log_upgrade "checksum: FAIL expected=!UPD_SHA256! got=!ACTUAL_SHA! -- dropping manifest and archive" del /f /q "!CLAIMED_UPDATE!" 2>nul del /f /q "!UPD_ZIP!" 2>nul goto :resolve_version ) + call :log_upgrade "checksum: OK" ) :: Extract to a staging dir on the same filesystem as %VERSIONS_DIR%, validate @@ -441,9 +475,11 @@ if exist "!UPD_STAGING!" rmdir /s /q "!UPD_STAGING!" 2>nul mkdir "!UPD_STAGING!" 2>nul echo [Junie] Applying pending update to version !UPD_VERSION!... 1>&2 +call :log_upgrade "extract: starting Expand-Archive src=!UPD_ZIP! dst=!UPD_STAGING!" powershell -NoProfile -Command "Expand-Archive -Path '!UPD_ZIP!' -DestinationPath '!UPD_STAGING!' -Force" 2>nul if errorlevel 1 ( echo [Junie] Failed to extract update; preserving for retry 1>&2 + call :log_upgrade "extract: FAIL (Expand-Archive non-zero); preserving manifest and archive for retry" rmdir /s /q "!UPD_STAGING!" 2>nul rename "!CLAIMED_UPDATE!" "pending-update.json" 2>nul goto :resolve_version @@ -452,6 +488,7 @@ if errorlevel 1 ( :: Validate extracted binary if not exist "!UPD_STAGING!\junie\junie.exe" ( echo [Junie] Extracted payload missing junie\junie.exe; preserving for retry 1>&2 + call :log_upgrade "validate: FAIL (junie\junie.exe missing in payload); preserving manifest and archive for retry" rmdir /s /q "!UPD_STAGING!" 2>nul rename "!CLAIMED_UPDATE!" "pending-update.json" 2>nul goto :resolve_version @@ -462,6 +499,7 @@ if exist "!UPD_TARGET!" rename "!UPD_TARGET!" "!UPD_OLD_NAME!" 2>nul move "!UPD_STAGING!" "!UPD_TARGET!" >nul 2>nul if errorlevel 1 ( echo [Junie] Failed to install new version; preserving for retry 1>&2 + call :log_upgrade "swap: FAIL (could not move staging into place); rolled back and preserving manifest for retry" if exist "!UPD_OLD!" if not exist "!UPD_TARGET!" rename "!UPD_OLD!" "!UPD_VERSION!" 2>nul rmdir /s /q "!UPD_STAGING!" 2>nul rename "!CLAIMED_UPDATE!" "pending-update.json" 2>nul @@ -477,6 +515,7 @@ if exist "!UPD_OLD!" rmdir /s /q "!UPD_OLD!" 2>nul :: Cleanup downloaded zip and manifest only after a successful swap del /f /q "!UPD_ZIP!" 2>nul del /f /q "!CLAIMED_UPDATE!" 2>nul +call :log_upgrade "apply: SUCCESS updated to version !UPD_VERSION!" echo [Junie] Updated to version !UPD_VERSION! 1>&2 :: === Resolve Version === @@ -543,6 +582,18 @@ for %%A in (%*) do ( :: Export this shim's own absolute path (%~f0) so the binary can refresh it in :: place during auto-update (see ShimUpdater on the binary side). endlocal & set "EJ_RUNNER_PWD=%EJ_RUNNER_PWD%" & set "JUNIE_DATA=%JUNIE_DATA%" & set "JUNIE_SHIM_PATH=%~f0" & "%JUNIE_EXE%" %FILTERED_ARGS% + +:: Jump past the subroutines to end of file, preserving junie.exe's exit code. +goto :eof + +:: === Subroutines === + +:log_upgrade +:: Append a timestamped, session-tagged line to the persistent upgrade log. +:: Best-effort: a failure to write (e.g. read-only profile) must never break +:: launching, so the error is swallowed. %~1 is the (already-expanded) message. +echo [%DATE% %TIME%] [sid=%LOG_SID%] %~1>> "%UPGRADE_LOG%" 2>nul +exit /b 0 '@ [System.IO.File]::WriteAllText($SHIM_PATH, $SHIM_CONTENT) diff --git a/install-experimental.ps1 b/install-experimental.ps1 index 23f04d33..df8dc7f3 100644 --- a/install-experimental.ps1 +++ b/install-experimental.ps1 @@ -221,6 +221,17 @@ set "CURRENT_FILE=%JUNIE_DATA%\current" set "PENDING_UPDATE=%UPDATES_DIR%\pending-update.json" set "PROCESSING=%UPDATES_DIR%\pending-update.json.processing" +:: Persistent upgrade log (mirrors the POSIX shim). Lives under +:: %USERPROFILE%\.junie\logs -- separate from the data dir so a reinstall that +:: wipes ~/.local/share/junie does not lose update history. Always appended, +:: never truncated. Override the directory with JUNIE_LOG_DIR. A per-invocation +:: session id (LOG_SID) is stamped on every line so one launch can be followed. +set "UPGRADE_LOG_DIR=%USERPROFILE%\.junie\logs" +if defined JUNIE_LOG_DIR set "UPGRADE_LOG_DIR=%JUNIE_LOG_DIR%" +set "UPGRADE_LOG=%UPGRADE_LOG_DIR%\upgrade.log" +set "LOG_SID=%RANDOM%%RANDOM%" +if not exist "%UPGRADE_LOG_DIR%" mkdir "%UPGRADE_LOG_DIR%" 2>nul + :: === Channel Switching (one-shot) === :: :: `junie --eap` (also --nightly / --release / --experimental, or --channel=) @@ -369,6 +380,7 @@ if exist "%PENDING_UPDATE%" ( for /f "usebackq delims=" %%V in (`powershell -NoProfile -Command "try { $c = Get-Content -Raw -LiteralPath '%PENDING_UPDATE%' -ErrorAction Stop; if ([string]::IsNullOrWhiteSpace($c)) { 'bad' } else { $j = $c | ConvertFrom-Json; if ($j.version -and $j.zipPath) { 'ok' } else { 'bad' } } } catch { 'bad' }"`) do set "PEND_OK=%%V" if /i not "!PEND_OK!"=="ok" ( echo [Junie] Removing invalid update artifact: %PENDING_UPDATE% 1>&2 + call :log_upgrade "sanitize: removing invalid update artifact pending-update.json" del /f /q "%PENDING_UPDATE%" 2>nul ) ) @@ -377,6 +389,7 @@ if exist "!PROCESSING!" ( for /f "usebackq delims=" %%V in (`powershell -NoProfile -Command "try { $c = Get-Content -Raw -LiteralPath '!PROCESSING!' -ErrorAction Stop; if ([string]::IsNullOrWhiteSpace($c)) { 'bad' } else { $j = $c | ConvertFrom-Json; if ($j.version -and $j.zipPath) { 'ok' } else { 'bad' } } } catch { 'bad' }"`) do set "PROC_OK=%%V" if /i not "!PROC_OK!"=="ok" ( echo [Junie] Removing invalid update artifact: !PROCESSING! 1>&2 + call :log_upgrade "sanitize: removing invalid update artifact pending-update.json.processing" del /f /q "!PROCESSING!" 2>nul ) ) @@ -392,39 +405,60 @@ if errorlevel 1 ( goto :resolve_version ) set "CLAIMED_UPDATE=%UPDATES_DIR%\pending-update.json.processing" +call :log_upgrade "apply: claimed pending update for processing" :: Parse the claimed manifest using PowerShell (reliable JSON parsing) for /f "usebackq delims=" %%V in (`powershell -NoProfile -Command "(Get-Content '!CLAIMED_UPDATE!' | ConvertFrom-Json).version"`) do set "UPD_VERSION=%%V" for /f "usebackq delims=" %%Z in (`powershell -NoProfile -Command "(Get-Content '!CLAIMED_UPDATE!' | ConvertFrom-Json).zipPath"`) do set "UPD_ZIP=%%Z" for /f "usebackq delims=" %%S in (`powershell -NoProfile -Command "(Get-Content '!CLAIMED_UPDATE!' | ConvertFrom-Json).sha256"`) do set "UPD_SHA256=%%S" +call :log_upgrade "apply: manifest parsed version=!UPD_VERSION! zipPath=!UPD_ZIP! sha256=!UPD_SHA256!" if not defined UPD_VERSION ( echo [Junie] Invalid pending update manifest, skipping 1>&2 + call :log_upgrade "apply: invalid manifest (missing version); dropping manifest" del /f /q "!CLAIMED_UPDATE!" 2>nul goto :resolve_version ) if not defined UPD_ZIP ( echo [Junie] Invalid pending update manifest, skipping 1>&2 + call :log_upgrade "apply: invalid manifest (missing zipPath); dropping manifest" del /f /q "!CLAIMED_UPDATE!" 2>nul goto :resolve_version ) if not exist "!UPD_ZIP!" ( echo [Junie] Update file not found: !UPD_ZIP! 1>&2 + call :log_upgrade "apply: update archive not found at !UPD_ZIP!; dropping manifest" del /f /q "!CLAIMED_UPDATE!" 2>nul goto :resolve_version ) :: Verify SHA-256 checksum if defined UPD_SHA256 ( + call :log_upgrade "checksum: starting SHA-256 validation expected=!UPD_SHA256!" + set "ACTUAL_SHA=" for /f "usebackq delims=" %%H in (`powershell -NoProfile -Command "(Get-FileHash '!UPD_ZIP!' -Algorithm SHA256).Hash.ToLower()"`) do set "ACTUAL_SHA=%%H" + if not defined ACTUAL_SHA ( + :: Get-FileHash produced no output -- the zip could not be read/hashed, + :: almost always because it is still locked or being scanned by antivirus. + :: This is a transient condition, NOT corruption: PRESERVE the manifest and + :: zip and retry on the next launch. Deleting them here (as a plain mismatch + :: would) makes the binary re-download and re-fail every startup -- an + :: endless update loop that never recovers. + echo [Junie] Could not read update zip ^(locked or AV scan?^); will retry next launch 1>&2 + call :log_upgrade "checksum: UNREADABLE -- Get-FileHash returned no output; preserving manifest and archive for retry" + rename "!CLAIMED_UPDATE!" "pending-update.json" 2>nul + goto :resolve_version + ) if /i not "!ACTUAL_SHA!"=="!UPD_SHA256!" ( echo [Junie] Checksum mismatch, skipping update 1>&2 echo [Junie] Expected: !UPD_SHA256! 1>&2 echo [Junie] Got: !ACTUAL_SHA! 1>&2 + call :log_upgrade "checksum: FAIL expected=!UPD_SHA256! got=!ACTUAL_SHA! -- dropping manifest and archive" del /f /q "!CLAIMED_UPDATE!" 2>nul del /f /q "!UPD_ZIP!" 2>nul goto :resolve_version ) + call :log_upgrade "checksum: OK" ) :: Extract to a staging dir on the same filesystem as %VERSIONS_DIR%, validate @@ -441,9 +475,11 @@ if exist "!UPD_STAGING!" rmdir /s /q "!UPD_STAGING!" 2>nul mkdir "!UPD_STAGING!" 2>nul echo [Junie] Applying pending update to version !UPD_VERSION!... 1>&2 +call :log_upgrade "extract: starting Expand-Archive src=!UPD_ZIP! dst=!UPD_STAGING!" powershell -NoProfile -Command "Expand-Archive -Path '!UPD_ZIP!' -DestinationPath '!UPD_STAGING!' -Force" 2>nul if errorlevel 1 ( echo [Junie] Failed to extract update; preserving for retry 1>&2 + call :log_upgrade "extract: FAIL (Expand-Archive non-zero); preserving manifest and archive for retry" rmdir /s /q "!UPD_STAGING!" 2>nul rename "!CLAIMED_UPDATE!" "pending-update.json" 2>nul goto :resolve_version @@ -452,6 +488,7 @@ if errorlevel 1 ( :: Validate extracted binary if not exist "!UPD_STAGING!\junie\junie.exe" ( echo [Junie] Extracted payload missing junie\junie.exe; preserving for retry 1>&2 + call :log_upgrade "validate: FAIL (junie\junie.exe missing in payload); preserving manifest and archive for retry" rmdir /s /q "!UPD_STAGING!" 2>nul rename "!CLAIMED_UPDATE!" "pending-update.json" 2>nul goto :resolve_version @@ -462,6 +499,7 @@ if exist "!UPD_TARGET!" rename "!UPD_TARGET!" "!UPD_OLD_NAME!" 2>nul move "!UPD_STAGING!" "!UPD_TARGET!" >nul 2>nul if errorlevel 1 ( echo [Junie] Failed to install new version; preserving for retry 1>&2 + call :log_upgrade "swap: FAIL (could not move staging into place); rolled back and preserving manifest for retry" if exist "!UPD_OLD!" if not exist "!UPD_TARGET!" rename "!UPD_OLD!" "!UPD_VERSION!" 2>nul rmdir /s /q "!UPD_STAGING!" 2>nul rename "!CLAIMED_UPDATE!" "pending-update.json" 2>nul @@ -477,6 +515,7 @@ if exist "!UPD_OLD!" rmdir /s /q "!UPD_OLD!" 2>nul :: Cleanup downloaded zip and manifest only after a successful swap del /f /q "!UPD_ZIP!" 2>nul del /f /q "!CLAIMED_UPDATE!" 2>nul +call :log_upgrade "apply: SUCCESS updated to version !UPD_VERSION!" echo [Junie] Updated to version !UPD_VERSION! 1>&2 :: === Resolve Version === @@ -543,6 +582,18 @@ for %%A in (%*) do ( :: Export this shim's own absolute path (%~f0) so the binary can refresh it in :: place during auto-update (see ShimUpdater on the binary side). endlocal & set "EJ_RUNNER_PWD=%EJ_RUNNER_PWD%" & set "JUNIE_DATA=%JUNIE_DATA%" & set "JUNIE_SHIM_PATH=%~f0" & "%JUNIE_EXE%" %FILTERED_ARGS% + +:: Jump past the subroutines to end of file, preserving junie.exe's exit code. +goto :eof + +:: === Subroutines === + +:log_upgrade +:: Append a timestamped, session-tagged line to the persistent upgrade log. +:: Best-effort: a failure to write (e.g. read-only profile) must never break +:: launching, so the error is swallowed. %~1 is the (already-expanded) message. +echo [%DATE% %TIME%] [sid=%LOG_SID%] %~1>> "%UPGRADE_LOG%" 2>nul +exit /b 0 '@ [System.IO.File]::WriteAllText($SHIM_PATH, $SHIM_CONTENT) diff --git a/install-nightly.ps1 b/install-nightly.ps1 index 0c551147..b9bb87ab 100644 --- a/install-nightly.ps1 +++ b/install-nightly.ps1 @@ -221,6 +221,17 @@ set "CURRENT_FILE=%JUNIE_DATA%\current" set "PENDING_UPDATE=%UPDATES_DIR%\pending-update.json" set "PROCESSING=%UPDATES_DIR%\pending-update.json.processing" +:: Persistent upgrade log (mirrors the POSIX shim). Lives under +:: %USERPROFILE%\.junie\logs -- separate from the data dir so a reinstall that +:: wipes ~/.local/share/junie does not lose update history. Always appended, +:: never truncated. Override the directory with JUNIE_LOG_DIR. A per-invocation +:: session id (LOG_SID) is stamped on every line so one launch can be followed. +set "UPGRADE_LOG_DIR=%USERPROFILE%\.junie\logs" +if defined JUNIE_LOG_DIR set "UPGRADE_LOG_DIR=%JUNIE_LOG_DIR%" +set "UPGRADE_LOG=%UPGRADE_LOG_DIR%\upgrade.log" +set "LOG_SID=%RANDOM%%RANDOM%" +if not exist "%UPGRADE_LOG_DIR%" mkdir "%UPGRADE_LOG_DIR%" 2>nul + :: === Channel Switching (one-shot) === :: :: `junie --eap` (also --nightly / --release / --experimental, or --channel=) @@ -369,6 +380,7 @@ if exist "%PENDING_UPDATE%" ( for /f "usebackq delims=" %%V in (`powershell -NoProfile -Command "try { $c = Get-Content -Raw -LiteralPath '%PENDING_UPDATE%' -ErrorAction Stop; if ([string]::IsNullOrWhiteSpace($c)) { 'bad' } else { $j = $c | ConvertFrom-Json; if ($j.version -and $j.zipPath) { 'ok' } else { 'bad' } } } catch { 'bad' }"`) do set "PEND_OK=%%V" if /i not "!PEND_OK!"=="ok" ( echo [Junie] Removing invalid update artifact: %PENDING_UPDATE% 1>&2 + call :log_upgrade "sanitize: removing invalid update artifact pending-update.json" del /f /q "%PENDING_UPDATE%" 2>nul ) ) @@ -377,6 +389,7 @@ if exist "!PROCESSING!" ( for /f "usebackq delims=" %%V in (`powershell -NoProfile -Command "try { $c = Get-Content -Raw -LiteralPath '!PROCESSING!' -ErrorAction Stop; if ([string]::IsNullOrWhiteSpace($c)) { 'bad' } else { $j = $c | ConvertFrom-Json; if ($j.version -and $j.zipPath) { 'ok' } else { 'bad' } } } catch { 'bad' }"`) do set "PROC_OK=%%V" if /i not "!PROC_OK!"=="ok" ( echo [Junie] Removing invalid update artifact: !PROCESSING! 1>&2 + call :log_upgrade "sanitize: removing invalid update artifact pending-update.json.processing" del /f /q "!PROCESSING!" 2>nul ) ) @@ -392,39 +405,60 @@ if errorlevel 1 ( goto :resolve_version ) set "CLAIMED_UPDATE=%UPDATES_DIR%\pending-update.json.processing" +call :log_upgrade "apply: claimed pending update for processing" :: Parse the claimed manifest using PowerShell (reliable JSON parsing) for /f "usebackq delims=" %%V in (`powershell -NoProfile -Command "(Get-Content '!CLAIMED_UPDATE!' | ConvertFrom-Json).version"`) do set "UPD_VERSION=%%V" for /f "usebackq delims=" %%Z in (`powershell -NoProfile -Command "(Get-Content '!CLAIMED_UPDATE!' | ConvertFrom-Json).zipPath"`) do set "UPD_ZIP=%%Z" for /f "usebackq delims=" %%S in (`powershell -NoProfile -Command "(Get-Content '!CLAIMED_UPDATE!' | ConvertFrom-Json).sha256"`) do set "UPD_SHA256=%%S" +call :log_upgrade "apply: manifest parsed version=!UPD_VERSION! zipPath=!UPD_ZIP! sha256=!UPD_SHA256!" if not defined UPD_VERSION ( echo [Junie] Invalid pending update manifest, skipping 1>&2 + call :log_upgrade "apply: invalid manifest (missing version); dropping manifest" del /f /q "!CLAIMED_UPDATE!" 2>nul goto :resolve_version ) if not defined UPD_ZIP ( echo [Junie] Invalid pending update manifest, skipping 1>&2 + call :log_upgrade "apply: invalid manifest (missing zipPath); dropping manifest" del /f /q "!CLAIMED_UPDATE!" 2>nul goto :resolve_version ) if not exist "!UPD_ZIP!" ( echo [Junie] Update file not found: !UPD_ZIP! 1>&2 + call :log_upgrade "apply: update archive not found at !UPD_ZIP!; dropping manifest" del /f /q "!CLAIMED_UPDATE!" 2>nul goto :resolve_version ) :: Verify SHA-256 checksum if defined UPD_SHA256 ( + call :log_upgrade "checksum: starting SHA-256 validation expected=!UPD_SHA256!" + set "ACTUAL_SHA=" for /f "usebackq delims=" %%H in (`powershell -NoProfile -Command "(Get-FileHash '!UPD_ZIP!' -Algorithm SHA256).Hash.ToLower()"`) do set "ACTUAL_SHA=%%H" + if not defined ACTUAL_SHA ( + :: Get-FileHash produced no output -- the zip could not be read/hashed, + :: almost always because it is still locked or being scanned by antivirus. + :: This is a transient condition, NOT corruption: PRESERVE the manifest and + :: zip and retry on the next launch. Deleting them here (as a plain mismatch + :: would) makes the binary re-download and re-fail every startup -- an + :: endless update loop that never recovers. + echo [Junie] Could not read update zip ^(locked or AV scan?^); will retry next launch 1>&2 + call :log_upgrade "checksum: UNREADABLE -- Get-FileHash returned no output; preserving manifest and archive for retry" + rename "!CLAIMED_UPDATE!" "pending-update.json" 2>nul + goto :resolve_version + ) if /i not "!ACTUAL_SHA!"=="!UPD_SHA256!" ( echo [Junie] Checksum mismatch, skipping update 1>&2 echo [Junie] Expected: !UPD_SHA256! 1>&2 echo [Junie] Got: !ACTUAL_SHA! 1>&2 + call :log_upgrade "checksum: FAIL expected=!UPD_SHA256! got=!ACTUAL_SHA! -- dropping manifest and archive" del /f /q "!CLAIMED_UPDATE!" 2>nul del /f /q "!UPD_ZIP!" 2>nul goto :resolve_version ) + call :log_upgrade "checksum: OK" ) :: Extract to a staging dir on the same filesystem as %VERSIONS_DIR%, validate @@ -441,9 +475,11 @@ if exist "!UPD_STAGING!" rmdir /s /q "!UPD_STAGING!" 2>nul mkdir "!UPD_STAGING!" 2>nul echo [Junie] Applying pending update to version !UPD_VERSION!... 1>&2 +call :log_upgrade "extract: starting Expand-Archive src=!UPD_ZIP! dst=!UPD_STAGING!" powershell -NoProfile -Command "Expand-Archive -Path '!UPD_ZIP!' -DestinationPath '!UPD_STAGING!' -Force" 2>nul if errorlevel 1 ( echo [Junie] Failed to extract update; preserving for retry 1>&2 + call :log_upgrade "extract: FAIL (Expand-Archive non-zero); preserving manifest and archive for retry" rmdir /s /q "!UPD_STAGING!" 2>nul rename "!CLAIMED_UPDATE!" "pending-update.json" 2>nul goto :resolve_version @@ -452,6 +488,7 @@ if errorlevel 1 ( :: Validate extracted binary if not exist "!UPD_STAGING!\junie\junie.exe" ( echo [Junie] Extracted payload missing junie\junie.exe; preserving for retry 1>&2 + call :log_upgrade "validate: FAIL (junie\junie.exe missing in payload); preserving manifest and archive for retry" rmdir /s /q "!UPD_STAGING!" 2>nul rename "!CLAIMED_UPDATE!" "pending-update.json" 2>nul goto :resolve_version @@ -462,6 +499,7 @@ if exist "!UPD_TARGET!" rename "!UPD_TARGET!" "!UPD_OLD_NAME!" 2>nul move "!UPD_STAGING!" "!UPD_TARGET!" >nul 2>nul if errorlevel 1 ( echo [Junie] Failed to install new version; preserving for retry 1>&2 + call :log_upgrade "swap: FAIL (could not move staging into place); rolled back and preserving manifest for retry" if exist "!UPD_OLD!" if not exist "!UPD_TARGET!" rename "!UPD_OLD!" "!UPD_VERSION!" 2>nul rmdir /s /q "!UPD_STAGING!" 2>nul rename "!CLAIMED_UPDATE!" "pending-update.json" 2>nul @@ -477,6 +515,7 @@ if exist "!UPD_OLD!" rmdir /s /q "!UPD_OLD!" 2>nul :: Cleanup downloaded zip and manifest only after a successful swap del /f /q "!UPD_ZIP!" 2>nul del /f /q "!CLAIMED_UPDATE!" 2>nul +call :log_upgrade "apply: SUCCESS updated to version !UPD_VERSION!" echo [Junie] Updated to version !UPD_VERSION! 1>&2 :: === Resolve Version === @@ -543,6 +582,18 @@ for %%A in (%*) do ( :: Export this shim's own absolute path (%~f0) so the binary can refresh it in :: place during auto-update (see ShimUpdater on the binary side). endlocal & set "EJ_RUNNER_PWD=%EJ_RUNNER_PWD%" & set "JUNIE_DATA=%JUNIE_DATA%" & set "JUNIE_SHIM_PATH=%~f0" & "%JUNIE_EXE%" %FILTERED_ARGS% + +:: Jump past the subroutines to end of file, preserving junie.exe's exit code. +goto :eof + +:: === Subroutines === + +:log_upgrade +:: Append a timestamped, session-tagged line to the persistent upgrade log. +:: Best-effort: a failure to write (e.g. read-only profile) must never break +:: launching, so the error is swallowed. %~1 is the (already-expanded) message. +echo [%DATE% %TIME%] [sid=%LOG_SID%] %~1>> "%UPGRADE_LOG%" 2>nul +exit /b 0 '@ [System.IO.File]::WriteAllText($SHIM_PATH, $SHIM_CONTENT) diff --git a/install.ps1 b/install.ps1 index 9507bf44..7a1948cd 100644 --- a/install.ps1 +++ b/install.ps1 @@ -221,6 +221,17 @@ set "CURRENT_FILE=%JUNIE_DATA%\current" set "PENDING_UPDATE=%UPDATES_DIR%\pending-update.json" set "PROCESSING=%UPDATES_DIR%\pending-update.json.processing" +:: Persistent upgrade log (mirrors the POSIX shim). Lives under +:: %USERPROFILE%\.junie\logs -- separate from the data dir so a reinstall that +:: wipes ~/.local/share/junie does not lose update history. Always appended, +:: never truncated. Override the directory with JUNIE_LOG_DIR. A per-invocation +:: session id (LOG_SID) is stamped on every line so one launch can be followed. +set "UPGRADE_LOG_DIR=%USERPROFILE%\.junie\logs" +if defined JUNIE_LOG_DIR set "UPGRADE_LOG_DIR=%JUNIE_LOG_DIR%" +set "UPGRADE_LOG=%UPGRADE_LOG_DIR%\upgrade.log" +set "LOG_SID=%RANDOM%%RANDOM%" +if not exist "%UPGRADE_LOG_DIR%" mkdir "%UPGRADE_LOG_DIR%" 2>nul + :: === Channel Switching (one-shot) === :: :: `junie --eap` (also --nightly / --release / --experimental, or --channel=) @@ -369,6 +380,7 @@ if exist "%PENDING_UPDATE%" ( for /f "usebackq delims=" %%V in (`powershell -NoProfile -Command "try { $c = Get-Content -Raw -LiteralPath '%PENDING_UPDATE%' -ErrorAction Stop; if ([string]::IsNullOrWhiteSpace($c)) { 'bad' } else { $j = $c | ConvertFrom-Json; if ($j.version -and $j.zipPath) { 'ok' } else { 'bad' } } } catch { 'bad' }"`) do set "PEND_OK=%%V" if /i not "!PEND_OK!"=="ok" ( echo [Junie] Removing invalid update artifact: %PENDING_UPDATE% 1>&2 + call :log_upgrade "sanitize: removing invalid update artifact pending-update.json" del /f /q "%PENDING_UPDATE%" 2>nul ) ) @@ -377,6 +389,7 @@ if exist "!PROCESSING!" ( for /f "usebackq delims=" %%V in (`powershell -NoProfile -Command "try { $c = Get-Content -Raw -LiteralPath '!PROCESSING!' -ErrorAction Stop; if ([string]::IsNullOrWhiteSpace($c)) { 'bad' } else { $j = $c | ConvertFrom-Json; if ($j.version -and $j.zipPath) { 'ok' } else { 'bad' } } } catch { 'bad' }"`) do set "PROC_OK=%%V" if /i not "!PROC_OK!"=="ok" ( echo [Junie] Removing invalid update artifact: !PROCESSING! 1>&2 + call :log_upgrade "sanitize: removing invalid update artifact pending-update.json.processing" del /f /q "!PROCESSING!" 2>nul ) ) @@ -392,39 +405,60 @@ if errorlevel 1 ( goto :resolve_version ) set "CLAIMED_UPDATE=%UPDATES_DIR%\pending-update.json.processing" +call :log_upgrade "apply: claimed pending update for processing" :: Parse the claimed manifest using PowerShell (reliable JSON parsing) for /f "usebackq delims=" %%V in (`powershell -NoProfile -Command "(Get-Content '!CLAIMED_UPDATE!' | ConvertFrom-Json).version"`) do set "UPD_VERSION=%%V" for /f "usebackq delims=" %%Z in (`powershell -NoProfile -Command "(Get-Content '!CLAIMED_UPDATE!' | ConvertFrom-Json).zipPath"`) do set "UPD_ZIP=%%Z" for /f "usebackq delims=" %%S in (`powershell -NoProfile -Command "(Get-Content '!CLAIMED_UPDATE!' | ConvertFrom-Json).sha256"`) do set "UPD_SHA256=%%S" +call :log_upgrade "apply: manifest parsed version=!UPD_VERSION! zipPath=!UPD_ZIP! sha256=!UPD_SHA256!" if not defined UPD_VERSION ( echo [Junie] Invalid pending update manifest, skipping 1>&2 + call :log_upgrade "apply: invalid manifest (missing version); dropping manifest" del /f /q "!CLAIMED_UPDATE!" 2>nul goto :resolve_version ) if not defined UPD_ZIP ( echo [Junie] Invalid pending update manifest, skipping 1>&2 + call :log_upgrade "apply: invalid manifest (missing zipPath); dropping manifest" del /f /q "!CLAIMED_UPDATE!" 2>nul goto :resolve_version ) if not exist "!UPD_ZIP!" ( echo [Junie] Update file not found: !UPD_ZIP! 1>&2 + call :log_upgrade "apply: update archive not found at !UPD_ZIP!; dropping manifest" del /f /q "!CLAIMED_UPDATE!" 2>nul goto :resolve_version ) :: Verify SHA-256 checksum if defined UPD_SHA256 ( + call :log_upgrade "checksum: starting SHA-256 validation expected=!UPD_SHA256!" + set "ACTUAL_SHA=" for /f "usebackq delims=" %%H in (`powershell -NoProfile -Command "(Get-FileHash '!UPD_ZIP!' -Algorithm SHA256).Hash.ToLower()"`) do set "ACTUAL_SHA=%%H" + if not defined ACTUAL_SHA ( + :: Get-FileHash produced no output -- the zip could not be read/hashed, + :: almost always because it is still locked or being scanned by antivirus. + :: This is a transient condition, NOT corruption: PRESERVE the manifest and + :: zip and retry on the next launch. Deleting them here (as a plain mismatch + :: would) makes the binary re-download and re-fail every startup -- an + :: endless update loop that never recovers. + echo [Junie] Could not read update zip ^(locked or AV scan?^); will retry next launch 1>&2 + call :log_upgrade "checksum: UNREADABLE -- Get-FileHash returned no output; preserving manifest and archive for retry" + rename "!CLAIMED_UPDATE!" "pending-update.json" 2>nul + goto :resolve_version + ) if /i not "!ACTUAL_SHA!"=="!UPD_SHA256!" ( echo [Junie] Checksum mismatch, skipping update 1>&2 echo [Junie] Expected: !UPD_SHA256! 1>&2 echo [Junie] Got: !ACTUAL_SHA! 1>&2 + call :log_upgrade "checksum: FAIL expected=!UPD_SHA256! got=!ACTUAL_SHA! -- dropping manifest and archive" del /f /q "!CLAIMED_UPDATE!" 2>nul del /f /q "!UPD_ZIP!" 2>nul goto :resolve_version ) + call :log_upgrade "checksum: OK" ) :: Extract to a staging dir on the same filesystem as %VERSIONS_DIR%, validate @@ -441,9 +475,11 @@ if exist "!UPD_STAGING!" rmdir /s /q "!UPD_STAGING!" 2>nul mkdir "!UPD_STAGING!" 2>nul echo [Junie] Applying pending update to version !UPD_VERSION!... 1>&2 +call :log_upgrade "extract: starting Expand-Archive src=!UPD_ZIP! dst=!UPD_STAGING!" powershell -NoProfile -Command "Expand-Archive -Path '!UPD_ZIP!' -DestinationPath '!UPD_STAGING!' -Force" 2>nul if errorlevel 1 ( echo [Junie] Failed to extract update; preserving for retry 1>&2 + call :log_upgrade "extract: FAIL (Expand-Archive non-zero); preserving manifest and archive for retry" rmdir /s /q "!UPD_STAGING!" 2>nul rename "!CLAIMED_UPDATE!" "pending-update.json" 2>nul goto :resolve_version @@ -452,6 +488,7 @@ if errorlevel 1 ( :: Validate extracted binary if not exist "!UPD_STAGING!\junie\junie.exe" ( echo [Junie] Extracted payload missing junie\junie.exe; preserving for retry 1>&2 + call :log_upgrade "validate: FAIL (junie\junie.exe missing in payload); preserving manifest and archive for retry" rmdir /s /q "!UPD_STAGING!" 2>nul rename "!CLAIMED_UPDATE!" "pending-update.json" 2>nul goto :resolve_version @@ -462,6 +499,7 @@ if exist "!UPD_TARGET!" rename "!UPD_TARGET!" "!UPD_OLD_NAME!" 2>nul move "!UPD_STAGING!" "!UPD_TARGET!" >nul 2>nul if errorlevel 1 ( echo [Junie] Failed to install new version; preserving for retry 1>&2 + call :log_upgrade "swap: FAIL (could not move staging into place); rolled back and preserving manifest for retry" if exist "!UPD_OLD!" if not exist "!UPD_TARGET!" rename "!UPD_OLD!" "!UPD_VERSION!" 2>nul rmdir /s /q "!UPD_STAGING!" 2>nul rename "!CLAIMED_UPDATE!" "pending-update.json" 2>nul @@ -477,6 +515,7 @@ if exist "!UPD_OLD!" rmdir /s /q "!UPD_OLD!" 2>nul :: Cleanup downloaded zip and manifest only after a successful swap del /f /q "!UPD_ZIP!" 2>nul del /f /q "!CLAIMED_UPDATE!" 2>nul +call :log_upgrade "apply: SUCCESS updated to version !UPD_VERSION!" echo [Junie] Updated to version !UPD_VERSION! 1>&2 :: === Resolve Version === @@ -543,6 +582,18 @@ for %%A in (%*) do ( :: Export this shim's own absolute path (%~f0) so the binary can refresh it in :: place during auto-update (see ShimUpdater on the binary side). endlocal & set "EJ_RUNNER_PWD=%EJ_RUNNER_PWD%" & set "JUNIE_DATA=%JUNIE_DATA%" & set "JUNIE_SHIM_PATH=%~f0" & "%JUNIE_EXE%" %FILTERED_ARGS% + +:: Jump past the subroutines to end of file, preserving junie.exe's exit code. +goto :eof + +:: === Subroutines === + +:log_upgrade +:: Append a timestamped, session-tagged line to the persistent upgrade log. +:: Best-effort: a failure to write (e.g. read-only profile) must never break +:: launching, so the error is swallowed. %~1 is the (already-expanded) message. +echo [%DATE% %TIME%] [sid=%LOG_SID%] %~1>> "%UPGRADE_LOG%" 2>nul +exit /b 0 '@ [System.IO.File]::WriteAllText($SHIM_PATH, $SHIM_CONTENT) diff --git a/templates/junie.shim.bat b/templates/junie.shim.bat index a023519d..1ce93cdc 100644 --- a/templates/junie.shim.bat +++ b/templates/junie.shim.bat @@ -24,6 +24,17 @@ set "CURRENT_FILE=%JUNIE_DATA%\current" set "PENDING_UPDATE=%UPDATES_DIR%\pending-update.json" set "PROCESSING=%UPDATES_DIR%\pending-update.json.processing" +:: Persistent upgrade log (mirrors the POSIX shim). Lives under +:: %USERPROFILE%\.junie\logs -- separate from the data dir so a reinstall that +:: wipes ~/.local/share/junie does not lose update history. Always appended, +:: never truncated. Override the directory with JUNIE_LOG_DIR. A per-invocation +:: session id (LOG_SID) is stamped on every line so one launch can be followed. +set "UPGRADE_LOG_DIR=%USERPROFILE%\.junie\logs" +if defined JUNIE_LOG_DIR set "UPGRADE_LOG_DIR=%JUNIE_LOG_DIR%" +set "UPGRADE_LOG=%UPGRADE_LOG_DIR%\upgrade.log" +set "LOG_SID=%RANDOM%%RANDOM%" +if not exist "%UPGRADE_LOG_DIR%" mkdir "%UPGRADE_LOG_DIR%" 2>nul + :: === Channel Switching (one-shot) === :: :: `junie --eap` (also --nightly / --release / --experimental, or --channel=) @@ -172,6 +183,7 @@ if exist "%PENDING_UPDATE%" ( for /f "usebackq delims=" %%V in (`powershell -NoProfile -Command "try { $c = Get-Content -Raw -LiteralPath '%PENDING_UPDATE%' -ErrorAction Stop; if ([string]::IsNullOrWhiteSpace($c)) { 'bad' } else { $j = $c | ConvertFrom-Json; if ($j.version -and $j.zipPath) { 'ok' } else { 'bad' } } } catch { 'bad' }"`) do set "PEND_OK=%%V" if /i not "!PEND_OK!"=="ok" ( echo [Junie] Removing invalid update artifact: %PENDING_UPDATE% 1>&2 + call :log_upgrade "sanitize: removing invalid update artifact pending-update.json" del /f /q "%PENDING_UPDATE%" 2>nul ) ) @@ -180,6 +192,7 @@ if exist "!PROCESSING!" ( for /f "usebackq delims=" %%V in (`powershell -NoProfile -Command "try { $c = Get-Content -Raw -LiteralPath '!PROCESSING!' -ErrorAction Stop; if ([string]::IsNullOrWhiteSpace($c)) { 'bad' } else { $j = $c | ConvertFrom-Json; if ($j.version -and $j.zipPath) { 'ok' } else { 'bad' } } } catch { 'bad' }"`) do set "PROC_OK=%%V" if /i not "!PROC_OK!"=="ok" ( echo [Junie] Removing invalid update artifact: !PROCESSING! 1>&2 + call :log_upgrade "sanitize: removing invalid update artifact pending-update.json.processing" del /f /q "!PROCESSING!" 2>nul ) ) @@ -195,39 +208,60 @@ if errorlevel 1 ( goto :resolve_version ) set "CLAIMED_UPDATE=%UPDATES_DIR%\pending-update.json.processing" +call :log_upgrade "apply: claimed pending update for processing" :: Parse the claimed manifest using PowerShell (reliable JSON parsing) for /f "usebackq delims=" %%V in (`powershell -NoProfile -Command "(Get-Content '!CLAIMED_UPDATE!' | ConvertFrom-Json).version"`) do set "UPD_VERSION=%%V" for /f "usebackq delims=" %%Z in (`powershell -NoProfile -Command "(Get-Content '!CLAIMED_UPDATE!' | ConvertFrom-Json).zipPath"`) do set "UPD_ZIP=%%Z" for /f "usebackq delims=" %%S in (`powershell -NoProfile -Command "(Get-Content '!CLAIMED_UPDATE!' | ConvertFrom-Json).sha256"`) do set "UPD_SHA256=%%S" +call :log_upgrade "apply: manifest parsed version=!UPD_VERSION! zipPath=!UPD_ZIP! sha256=!UPD_SHA256!" if not defined UPD_VERSION ( echo [Junie] Invalid pending update manifest, skipping 1>&2 + call :log_upgrade "apply: invalid manifest (missing version); dropping manifest" del /f /q "!CLAIMED_UPDATE!" 2>nul goto :resolve_version ) if not defined UPD_ZIP ( echo [Junie] Invalid pending update manifest, skipping 1>&2 + call :log_upgrade "apply: invalid manifest (missing zipPath); dropping manifest" del /f /q "!CLAIMED_UPDATE!" 2>nul goto :resolve_version ) if not exist "!UPD_ZIP!" ( echo [Junie] Update file not found: !UPD_ZIP! 1>&2 + call :log_upgrade "apply: update archive not found at !UPD_ZIP!; dropping manifest" del /f /q "!CLAIMED_UPDATE!" 2>nul goto :resolve_version ) :: Verify SHA-256 checksum if defined UPD_SHA256 ( + call :log_upgrade "checksum: starting SHA-256 validation expected=!UPD_SHA256!" + set "ACTUAL_SHA=" for /f "usebackq delims=" %%H in (`powershell -NoProfile -Command "(Get-FileHash '!UPD_ZIP!' -Algorithm SHA256).Hash.ToLower()"`) do set "ACTUAL_SHA=%%H" + if not defined ACTUAL_SHA ( + :: Get-FileHash produced no output -- the zip could not be read/hashed, + :: almost always because it is still locked or being scanned by antivirus. + :: This is a transient condition, NOT corruption: PRESERVE the manifest and + :: zip and retry on the next launch. Deleting them here (as a plain mismatch + :: would) makes the binary re-download and re-fail every startup -- an + :: endless update loop that never recovers. + echo [Junie] Could not read update zip ^(locked or AV scan?^); will retry next launch 1>&2 + call :log_upgrade "checksum: UNREADABLE -- Get-FileHash returned no output; preserving manifest and archive for retry" + rename "!CLAIMED_UPDATE!" "pending-update.json" 2>nul + goto :resolve_version + ) if /i not "!ACTUAL_SHA!"=="!UPD_SHA256!" ( echo [Junie] Checksum mismatch, skipping update 1>&2 echo [Junie] Expected: !UPD_SHA256! 1>&2 echo [Junie] Got: !ACTUAL_SHA! 1>&2 + call :log_upgrade "checksum: FAIL expected=!UPD_SHA256! got=!ACTUAL_SHA! -- dropping manifest and archive" del /f /q "!CLAIMED_UPDATE!" 2>nul del /f /q "!UPD_ZIP!" 2>nul goto :resolve_version ) + call :log_upgrade "checksum: OK" ) :: Extract to a staging dir on the same filesystem as %VERSIONS_DIR%, validate @@ -244,9 +278,11 @@ if exist "!UPD_STAGING!" rmdir /s /q "!UPD_STAGING!" 2>nul mkdir "!UPD_STAGING!" 2>nul echo [Junie] Applying pending update to version !UPD_VERSION!... 1>&2 +call :log_upgrade "extract: starting Expand-Archive src=!UPD_ZIP! dst=!UPD_STAGING!" powershell -NoProfile -Command "Expand-Archive -Path '!UPD_ZIP!' -DestinationPath '!UPD_STAGING!' -Force" 2>nul if errorlevel 1 ( echo [Junie] Failed to extract update; preserving for retry 1>&2 + call :log_upgrade "extract: FAIL (Expand-Archive non-zero); preserving manifest and archive for retry" rmdir /s /q "!UPD_STAGING!" 2>nul rename "!CLAIMED_UPDATE!" "pending-update.json" 2>nul goto :resolve_version @@ -255,6 +291,7 @@ if errorlevel 1 ( :: Validate extracted binary if not exist "!UPD_STAGING!\junie\junie.exe" ( echo [Junie] Extracted payload missing junie\junie.exe; preserving for retry 1>&2 + call :log_upgrade "validate: FAIL (junie\junie.exe missing in payload); preserving manifest and archive for retry" rmdir /s /q "!UPD_STAGING!" 2>nul rename "!CLAIMED_UPDATE!" "pending-update.json" 2>nul goto :resolve_version @@ -265,6 +302,7 @@ if exist "!UPD_TARGET!" rename "!UPD_TARGET!" "!UPD_OLD_NAME!" 2>nul move "!UPD_STAGING!" "!UPD_TARGET!" >nul 2>nul if errorlevel 1 ( echo [Junie] Failed to install new version; preserving for retry 1>&2 + call :log_upgrade "swap: FAIL (could not move staging into place); rolled back and preserving manifest for retry" if exist "!UPD_OLD!" if not exist "!UPD_TARGET!" rename "!UPD_OLD!" "!UPD_VERSION!" 2>nul rmdir /s /q "!UPD_STAGING!" 2>nul rename "!CLAIMED_UPDATE!" "pending-update.json" 2>nul @@ -280,6 +318,7 @@ if exist "!UPD_OLD!" rmdir /s /q "!UPD_OLD!" 2>nul :: Cleanup downloaded zip and manifest only after a successful swap del /f /q "!UPD_ZIP!" 2>nul del /f /q "!CLAIMED_UPDATE!" 2>nul +call :log_upgrade "apply: SUCCESS updated to version !UPD_VERSION!" echo [Junie] Updated to version !UPD_VERSION! 1>&2 :: === Resolve Version === @@ -346,3 +385,15 @@ for %%A in (%*) do ( :: Export this shim's own absolute path (%~f0) so the binary can refresh it in :: place during auto-update (see ShimUpdater on the binary side). endlocal & set "EJ_RUNNER_PWD=%EJ_RUNNER_PWD%" & set "JUNIE_DATA=%JUNIE_DATA%" & set "JUNIE_SHIM_PATH=%~f0" & "%JUNIE_EXE%" %FILTERED_ARGS% + +:: Jump past the subroutines to end of file, preserving junie.exe's exit code. +goto :eof + +:: === Subroutines === + +:log_upgrade +:: Append a timestamped, session-tagged line to the persistent upgrade log. +:: Best-effort: a failure to write (e.g. read-only profile) must never break +:: launching, so the error is swallowed. %~1 is the (already-expanded) message. +echo [%DATE% %TIME%] [sid=%LOG_SID%] %~1>> "%UPGRADE_LOG%" 2>nul +exit /b 0