8 lines
20 KiB
JSON
8 lines
20 KiB
JSON
{
|
||
"index": 334,
|
||
"slug": "editorial-2018-09-field-windows-dev-env",
|
||
"title": "Windows: как доказать, что «работает на моей машине» связано с окружением",
|
||
"excerpt": "Одинаковая команда может запускать разные файлы и получать разный вывод. Разбираем порядок проверки PATH, приоритета PowerShell, версии инструмента и кодировки без переустановки среды.",
|
||
"contentHtml": "<p>Симптом знакомый: один и тот же commit и одна команда проходят на машине коллеги, но падают на вашей. Иногда команда запускается, но показывает другую версию инструмента. Иногда сборка верна, а лог в консоли превращается в нечитаемый текст. Цена ошибки начинается не с исправления, а с поспешной реакции. Переустановка Node, очистка кеша и правка системного <code>PATH</code> меняют сразу несколько условий. После этого трудно восстановить исходную причину.</p>\n<p>Тезис статьи простой: имя команды не доказывает, какой файл и какой процесс её выполняет. В PowerShell на результат влияют приоритет команд, функции и alias, порядок каталогов в <code>PATH</code>, наследование переменных новым процессом и отдельный слой кодировки консоли. Поэтому диагностика должна сначала зафиксировать наблюдения, затем проверить одну гипотезу обратимым действием.</p>\n<h2>Сначала исключите различия проекта</h2>\n<p>Сравнение окружений имеет смысл только при одинаковом входе. Зафиксируйте commit, состояние рабочей копии, lock-файл, каталог запуска и точный текст команды. Если на одной машине изменён <code>package-lock.json</code>, установлен другой набор зависимостей или команда запущена из другого каталога, это уже самостоятельная причина. Не смешивайте её с вопросом о Windows.</p>\n<pre><code>git rev-parse --short HEAD\ngit status --short\nGet-Location\nnode --version\nnpm --version\nnpm run build</code></pre>\n<p>Этот фрагмент — учебный шаблон. Название команды проекта и набор проверок зависят от репозитория. Сохраните полный вывод на обеих машинах. Секреты, токены и личные части путей перед передачей другому человеку удалите.</p>\n<h2>Механизм выбора команды</h2>\n<p>PowerShell ищет команду не по абстрактному имени, а по правилам приоритета. Функция или alias может скрыть приложение. Если выбрано приложение, PowerShell ищет исполняемый файл в каталогах из переменной <code>PATH</code>. Первый подходящий каталог имеет значение. Переменная в текущем процессе не обязана совпадать с тем, что вы изменили в системных настройках несколько минут назад.</p>\n<p>Сначала покажите все кандидаты и тип найденного объекта. Затем покажите фактическую версию. Эти команды отвечают на разные вопросы:</p>\n<pre><code>Get-Command node -All |\n Select-Object CommandType, Name, Version, Definition |\n Format-Table -AutoSize\n\nwhere.exe node\nnode --version\n$env:Path -split ';'</code></pre>\n<p><code>Get-Command</code> показывает, что выберет текущая сессия PowerShell. <code>where.exe</code> помогает увидеть исполняемые файлы, которые находятся в путях поиска Windows. Если первый результат — <code>Function</code> или <code>Alias</code>, список файлов не объясняет поведение команды. Если результаты указывают на разные <code>node.exe</code>, сравните путь и версию. Если путь и версия совпали, прекратите менять <code>PATH</code> и переходите к следующему факту.</p>\n<h2>Учебный пример: два node.exe</h2>\n<p>Представим две машины с одним репозиторием. На машине A <code>Get-Command node -All</code> первым показывает <code>C:\\Tools\\node-18\\node.exe</code>. На машине B первым идёт <code>C:\\Program Files\\nodejs\\node.exe</code>. Это не означает, что машина A или B настроена правильно. Сначала нужно посмотреть, какую версию требует проект, и сопоставить её с <code>node --version</code>.</p>\n<table><thead><tr><th>Поле</th><th>Машина A</th><th>Машина B</th><th>Вывод</th></tr></thead><tbody><tr><td>Первый кандидат</td><td><code>C:\\Tools\\node-18\\node.exe</code></td><td><code>C:\\Program Files\\nodejs\\node.exe</code></td><td>Одинаковое имя команды ведёт к разным файлам</td></tr><tr><td>Версия</td><td>Учебное значение <code>v18.x</code></td><td>Учебное значение <code>v20.x</code></td><td>Версия может менять поведение сборки</td></tr><tr><td>Порядок PATH</td><td>Каталог Tools стоит раньше</td><td>Каталог Tools отсутствует</td><td>Различие объясняет выбор, но не требование проекта</td></tr><tr><td>Исходная команда</td><td>Учебно завершается ошибкой</td><td>Учебно проходит</td><td>Нужна проверка гипотезы, а не удаление среды</td></tr></tbody></table>\n<p>Значения в таблице условные. Это пример способа рассуждать, а не отчёт о production-системе. Если проект требует другую версию, источник требования должен находиться в его README, lock-файле, менеджере версий или принятой инструкции установки.</p>\n<h2>Снимок окружения без лишнего шума</h2>\n<p>Полный дамп среды часто содержит слишком много данных. Для сравнения достаточно сохранить версию PowerShell, кодовую страницу, настройки вывода, <code>PATHEXT</code>, элементы <code>PATH</code> и кандидатов нужных команд. Важен порядок элементов. Превращайте каждую папку в отдельную строку, чтобы отличие было видно в обычном diff.</p>\n<pre><code>$snapshot = [ordered]@{\n powershell = $PSVersionTable.PSVersion.ToString()\n codePage = (chcp)\n outputEncoding = [Console]::OutputEncoding.WebName\n path = @($env:Path -split ';')\n commands = @(Get-Command node -All | ForEach-Object {\n [ordered]@{\n type = $_.CommandType.ToString()\n name = $_.Name\n definition = $_.Definition\n version = $_.Version.ToString()\n }\n })\n}\n$snapshot | ConvertTo-Json -Depth 5</code></pre>\n<p>В реальном скрипте обработайте отсутствие версии и отсутствие кандидата. Не записывайте в общий файл весь набор переменных без фильтра. Значения <code>PATH</code> могут раскрыть имена пользователей, внутренние каталоги и служебные адреса. Для учебного запуска достаточно вывести JSON в файл и сравнить очищенные копии.</p>\n<figure><img src=\"/assets/editorial/2018/windows-dev-env-diff-2018.svg\" alt=\"Схема сравнения окружения двух Windows-машин: одинаковая команда, снимки, проверка PATH и версии, обратимый опыт в новом PowerShell\"><figcaption>Диагностический маршрут: одинаковый вход, два очищенных снимка, одно отличие, обратимый опыт и повтор исходной команды.</figcaption></figure>\n<h2>Как не спутать похожие симптомы</h2>\n<table><thead><tr><th>Симптом</th><th>Причина</th><th>Проверка</th><th>Действие</th></tr></thead><tbody><tr><td><code>node --version</code> возвращает старую версию</td><td>Другой файл оказался первым в <code>PATH</code></td><td><code>Get-Command node -All</code>, <code>where.exe node</code></td><td>Временно поставить одобренный каталог первым в текущем процессе</td></tr><tr><td><code>where.exe</code> показывает файлы, но PowerShell ведёт себя иначе</td><td>Команду перехватывает функция или alias</td><td>Посмотреть <code>CommandType</code>; сравнить сессии с профилем и без него</td><td>Открыть новый PowerShell с <code>-NoProfile</code></td></tr><tr><td>Только одна консоль показывает битый текст</td><td>Различается кодовая страница или кодировка вывода</td><td><code>chcp</code>, <code>[Console]::OutputEncoding</code></td><td>Повторить одну команду в новой консоли; не менять PATH</td></tr><tr><td>После правки переменной результат прежний</td><td>Старый процесс унаследовал прежнее значение</td><td>Проверить новый процесс и его <code>$env:Path</code></td><td>Закрыть старое окно, повторить проверку в новом</td></tr><tr><td>Путь и версия совпали, сборка всё ещё падает</td><td>Причина лежит в зависимостях, правах, сети или конфигурации</td><td>Lock-файл, полный лог, доступ к каталогу и сетевой запрос</td><td>Остановить изменения PATH и расследовать следующий слой</td></tr></tbody></table>\n<h2>Обратимая проверка гипотезы</h2>\n<p>Если сравнение указывает на порядок <code>PATH</code>, меняйте только процессную переменную в новом PowerShell. Такой эксперимент не редактирует системную конфигурацию и исчезает после закрытия окна. Путь ниже условный. Подставляйте каталог, который назван в документации проекта или одобренном способе установки.</p>\n<pre><code>$requiredNode = 'C:\\Tools\\node-18'\n$nodePath = Join-Path $requiredNode 'node.exe'\nif (-not (Test-Path -LiteralPath $nodePath)) {\n throw \"Не найден учебный node.exe: $nodePath\"\n}\n\n$env:Path = $requiredNode + ';' + $env:Path\nGet-Command node -All |\n Select-Object CommandType, Definition, Version |\n Format-Table -AutoSize\nnode --version\n\n# Здесь повторяется та же команда, что дала исходную ошибку.\nnpm run build</code></pre>\n<p>Если версия изменилась и исходная команда стала проходить, гипотеза о выборе бинарника получила подтверждение. Это ещё не готовое постоянное исправление. Зафиксируйте требуемую версию в проекте и выберите согласованный способ поставки. Если версия не изменилась, не переносите каталог в пользовательский или системный <code>PATH</code>. Если команда всё равно падает, путь был не причиной или существует дополнительная причина.</p>\n<h2>Кодировка — отдельная проверка</h2>\n<p><code>chcp</code> показывает активную кодовую страницу консоли. Команды, запущенные после её изменения, могут получить новое значение, а уже работающие процессы сохраняют прежнее. Это не делает <code>chcp 65001</code> универсальным лечением. Программа может читать файл в другой кодировке, задавать собственный вывод или писать результат не в консоль.</p>\n<p>Проверяйте один и тот же текст в одинаковой команде. Сравните <code>chcp</code>, <code>[Console]::OutputEncoding</code>, способ записи файла и редактор, который открывает файл. Если путь и версия совпадают, а ломается только сохранённый файл, расследуйте кодировку файла. Если ломается только окно терминала, расследуйте консоль. Не меняйте два слоя одновременно.</p>\n<h2>Порядок действий</h2>\n<ol><li>Зафиксируйте commit, lock-файл, каталог запуска, команду и полный текст ошибки.</li><li>Повторите команду на обеих машинах без предварительной правки настроек.</li><li>Снимите версию PowerShell, кодовую страницу, кодировку вывода, <code>PATH</code>, <code>PATHEXT</code> и всех кандидатов нужного инструмента.</li><li>Удалите секреты и персональные пути из снимков, затем сравните одинаковые поля.</li><li>Выберите одно отличие, которое прямо связано с симптомом: кандидат, версия, процесс или кодировка.</li><li>Проверьте отличие в новом PowerShell с <code>-NoProfile</code> и временным изменением только текущего <code>$env:Path</code>, если это гипотеза о пути.</li><li>Повторите исходную команду и сохраните результат проверки.</li><li>Только после подтверждения внесите постоянное изменение в README, менеджер версий или согласованный установщик.</li></ol>\n<h2>Ограничения и отрицательный путь</h2>\n<p>Одинаковый путь к бинарнику не гарантирует одинаковую сборку. Различия могут быть в разрядности, DLL, правах доступа, сертификате прокси, сетевом маршруте, кеше, антивирусе, окончаниях строк и содержимом рабочей копии. После совпадения пути и версии это не повод продолжать менять окружение. Зафиксируйте следующий наблюдаемый симптом и перейдите к нему.</p>\n<p>Не отключайте защиту, политику запуска или антивирус ради проверки «на всякий случай». Не скачивайте исполняемый файл из случайного каталога. Не копируйте чужой полный <code>PATH</code> поверх своего. Если временный опыт не изменил исходный результат, отрицательный вывод полезен: выбранная гипотеза не подтверждена, а доказательства сохранены.</p>\n<h2>Проверяемый критерий готовности</h2>\n<p>Разбор завершён, когда другой разработчик может повторить команду из того же commit и получить тот же результат на новой сессии. Для этого остаются четыре проверяемых факта: указан выбранный бинарник и его версия; описан источник требования к версии; команда проходит после чистого запуска или зафиксирован следующий отдельный отказ; постоянное изменение не зависит от ручной настройки конкретного компьютера.</p>\n<p>Фраза «на моей машине работает» после такого разбора превращается в проверяемое утверждение. У неё есть команда, вход, процесс, путь поиска и результат. Если утверждение не подтверждается, следующий шаг выбирают по наблюдаемому отличию, а не по числу переустановленных инструментов.</p>\n<h2>Проверяемые источники</h2><ul><li><a href=\"https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_command_precedence\" target=\"_blank\" rel=\"noopener\">Microsoft Learn: about_Command_Precedence</a> — правила выбора команды в PowerShell.</li><li><a href=\"https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_environment_variables\" target=\"_blank\" rel=\"noopener\">Microsoft Learn: about_Environment_Variables</a> — области действия и наследование переменных среды.</li><li><a href=\"https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/chcp\" target=\"_blank\" rel=\"noopener\">Microsoft Learn: chcp</a> — активная кодовая страница консоли.</li></ul>"
|
||
}
|