WinClean v2.21: self-update targeting and honest exit codes - #11
Conversation
… роняет прогон MyAI-3y5i (P1). Проверка обновлений спрашивала "есть ли копия из галереи где-нибудь на машине", а ответ использовался так, будто это был ответ на вопрос "является ли выполняемый файл той самой копией". Оба условия штатно истинны одновременно: install.ps1 (с 2.15) ставит копию в %ProgramFiles%\WinClean, куда смотрит ярлык, а старая копия из Install-Script остаётся в Documents. Update-Script обновлял копию в Documents, печатал "Update complete! Please run WinClean again", выходил с кодом 0 - и ярлык продолжал запускать нетронутый старый файл. Бесконечно, без единой ошибки. Теперь выполняемый файл сравнивается с местом установки провайдера, и авто-обновление предлагается только когда это один и тот же файл. Остальным называется способ, который реально к ним применим. Ревью (16 раундов Codex + 4 агента) вскрыло, что задача шире исходной: - совет для не-галерейных копий предлагал Install-Script, а он СОЗДАЁТ вторую установку - то есть сам строил конфигурацию, породившую дефект; - при двух установках Update-Script нельзя нацелить (нет -Scope), поэтому авто-обновление теперь отключается с объяснением вместо мутации случайной копии. Тот же принцип, что Select-StorageSenseTask в 2.20; - весь путь был PowerShellGet-only: на машине только с PSResourceGet проверка падала, catch превращал это в "обновлений нет", ветка молча не работала. Теперь оба провайдера, и тот, что ОТВЕТИЛ при обнаружении, получает приоритет при обновлении; - Get-PSResource по умолчанию видит только CurrentUser, поэтому AllUsers-копия была невидима - и получала совет, создающий вторую установку; - обновление проверяется по версии в выполняемом файле; отказ проверки больше не неотличим от "у вас последняя версия"; - успешный путь пишет result JSON перед exit, иначе потребитель видел код 0 и отсутствующий артефакт. MyAI-iodp (P3). Отсутствие winget было ERROR, а код возврата считается только по ErrorsCount - значит машина без App Installer ВСЕГДА завершалась с кодом 1 при девяти выполненных фазах. Теперь WARNING. ERROR остаётся за случаем, когда winget есть и не отработал: провал самой проверки и необработанное исключение. Добавлено поле result JSON AppUpdatesStatus - понижение убрало единственный машиночитаемый признак "проверяли или нет". Факты, установленные экспериментом, а не рассуждением: InstalledLocation у скрипта это папка Scripts без версии; провайдеры видят установки друг друга; Select-Object -Unique регистрозависим; GetFullPath не убирает завершающий разделитель; [Version] 2.21 меньше 2.21.0; оба провайдера бросают и при "не установлено", но Get-InstalledScript без -Name возвращает пустой список - на этом и построено различение отказа от отсутствия копии. 452 -> 571 теста. Все защиты проверены мутациями (21 мутация, все пойманы).
Тот же класс, что отсутствие winget, и та же причина: код возврата считается только по ErrorsCount, поэтому машина без сети ВСЕГДА завершалась с кодом 1 - сколь бы полно ни отработала очистка. Ноутбук, обслуживаемый вне сети, рапортовал провал на каждом запуске. Обе половины фазы Updates (Windows Update и приложения) читают один мемоизованный результат проверки связности, поэтому понижены обе - оставить одну ошибкой значило бы сохранить код 1 и обессмыслить изменение. Состояние не исчезает из виду: WARNING логируется и считается, а result JSON несёт AppUpdatesStatus 'skipped-offline', который описывает всю фазу, включая половину Windows. Отдельное поле для Windows не заводилось именно поэтому. Осознанно НЕ тронуто: "PowerShell Gallery is unavailable" и соседние ветки (NuGet-провайдер, установка модуля) остаются ERROR. Это не "нет сети": проверка связности к тому моменту УЖЕ прошла, то есть сеть доказана рабочей, а не смогли получить зависимость, которую WinClean сам решил ставить. Отсутствующая предпосылка и неудавшаяся операция - разные вещи, и код возврата их различает. Разграничение проверено сторонним ревью и явно описано в docs/troubleshooting.md, где раздел про галерею теперь отсылает офлайн-читателя в раздел про связность. Доки исправлены там, где сами утверждали обратное: docs/result-json.md и docs/troubleshooting.md прямым текстом писали, что офлайн-ветка остаётся ошибкой. Заодно везде уточнено, что Updates - ОДНА фаза с двумя половинами, а не две фазы: формальные имена фаз попадают в result JSON, и прежняя формулировка читалась как два элемента. 571 -> 573 теста. Помимо двух тестов на уровень для каждой половины добавлен комбинированный: он готовит сам кеш связности, а не мокает проверку, потому что именно общий кеш и есть предпосылка, позволяющая одному полю описывать обе половины. Обе мутации (возврат любой ветки к ERROR) ловятся.
Версия поднята во всех местах: .VERSION, .RELEASENOTES, SYNOPSIS, NOTES с блоком изменений, $script:Version, диаграммы Execution Flow в обоих README. Бейджи динамические с 2.19 и правки не требуют. CHANGELOG: раздел [Unreleased] закрыт как [2.21] с датой; добавлены записи про документацию result JSON и про тесты (452 -> 573, 21 мутация). Релиз-гейт 8/10: провалены только 'дерево чисто' и 'ветка синхронна с origin' - оба про сам этот коммит. Версия 2.21 подтверждена из result JSON смоук-прогона.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0712e6e33b
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| # either can be the one present, and the one that answered discovery goes first | ||
| switch (Select-UpdateCommand -Provider $UpdateInfo.Provider) { | ||
| 'Update-Script' { Update-Script -Name WinClean -Force -ErrorAction Stop } | ||
| 'Update-PSResource' { Update-PSResource -Name WinClean -Force -TrustRepository -ErrorAction Stop } |
There was a problem hiding this comment.
Pass the matched scope to PSResourceGet updates
When the running Gallery copy is an AllUsers install and discovery selected PSResourceGet, this unscoped call does not target the file that Get-ScriptUpdateChannel just matched: Microsoft documents Update-PSResource as accepting -Scope and defaulting to CurrentUser (https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.psresourceget/update-psresource?view=powershellget-3.x#-scope). In that common admin install case, accepting the prompt either leaves the AllUsers script unchanged or creates/updates a CurrentUser copy, after which verification warns and the user still cannot self-update the running copy; carry the matched scope through detection or decline auto-update when it cannot be determined.
Useful? React with 👍 / 👎.
Что это
Релиз про честность на выходе. Две задачи, обе про то, что прогон сообщал состояние, которого не проверял.
MyAI-3y5i (P1): авто-обновление меняло не тот файл
Проверка спрашивала, есть ли копия из галереи где-нибудь на машине, а ответ использовался так, будто это ответ на вопрос «является ли выполняемый файл той самой копией». Оба условия штатно истинны одновременно:
install.ps1ставит копию в%ProgramFiles%\WinClean, куда смотрит ярлык, а старая копия изInstall-Scriptостаётся вDocuments.Update-Scriptобновлял копию в Documents, печатал «Update complete», выходил с кодом 0 - и ярлык продолжал запускать старый файл. Бесконечно, без единой ошибки.Ревью вскрыло, что задача шире исходной:
Install-Script, а он создаёт вторую установку - то есть сам строил конфигурацию, породившую дефект;Update-Scriptнацелить нечем (нет-Scope), поэтому авто-обновление теперь отключается с объяснением вместо мутации случайной копии;Get-PSResourceпо умолчанию видит толькоCurrentUser, поэтому копияAllUsersбыла невидима;MyAI-iodp, MyAI-7iej (P3): код возврата
Код возврата считается только по
ErrorsCount. Отсутствиеwinget, отсутствие сети и неудача самообновления перестали быть ошибками: это состояние окружения либо вспомогательная операция, а не отказ обслуживания.winget, который есть и не отработал, по-прежнему ошибка - как и галерея, заблокированная при рабочей сети.Новое поле result JSON
AppUpdatesStatusвозвращает машиночитаемый признак, который убрало понижение уровня.Проверки
ErrorsCount: 0, версия 2.21 подтверждена из result JSONДокументация приведена в соответствие:
docs/troubleshooting.md,docs/result-json.mdи оба README местами утверждали обратное тому, что делает код.