Files

8 lines
20 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"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 = [string]$_.Version\n }\n })\n}\n$snapshot | ConvertTo-Json -Depth 5</code></pre>\n<p>Версия у функции, alias или другого кандидата может отсутствовать, поэтому здесь null превращается в пустую строку без вызова <code>ToString()</code>. Если команда не найдена, <code>Get-Command</code> завершит проверку ошибкой — это полезный отдельный результат. Сохраните снимки в разные файлы и сравните очищенные копии:</p>\\n<pre><code>$snapshot | ConvertTo-Json -Depth 5 | Set-Content .\\\\snapshot-a.json -Encoding utf8\\nCompare-Object (Get-Content .\\\\snapshot-a.json) (Get-Content .\\\\snapshot-b.json)</code></pre>\\n<p>Не записывайте в общий файл весь набор переменных без фильтра. Значения <code>PATH</code> могут раскрыть имена пользователей, внутренние каталоги и служебные адреса. Перед сравнением удалите секреты и персональные пути, но не меняйте порядок элементов.</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'\n$npmPath = Join-Path $requiredNode 'npm.cmd'\nforeach ($path in @($nodePath, $npmPath)) {\n if (-not (Test-Path -LiteralPath $path)) {\n throw \"Не найден учебный файл: $path\"\n }\n}\n\n$env:Path = $requiredNode + ';' + $env:Path\nGet-Command node, npm -All |\n Select-Object CommandType, Name, Definition, Version |\n Format-Table -AutoSize\n&amp; $nodePath --version\n&amp; $npmPath --version\n\n# Здесь повторяется та же команда, что дала исходную ошибку.\n&amp; $npmPath 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>"
}