diff --git a/editorial/agent-rewrites/334.json b/editorial/agent-rewrites/334.json index 5ef7053..3362fbc 100644 --- a/editorial/agent-rewrites/334.json +++ b/editorial/agent-rewrites/334.json @@ -1,7 +1,7 @@ { "index": 334, "slug": "editorial-2018-09-field-windows-dev-env", - "title": "Windows: как доказать, что «работает на моей машине» связано с окружением", + "title": "Windows: как проверить, что «работает на моей машине» связано с окружением", "excerpt": "Одинаковая команда может запускать разные файлы и получать разный вывод. Разбираем порядок проверки PATH, приоритета PowerShell, версии инструмента и кодировки без переустановки среды.", - "contentHtml": "
Симптом знакомый: один и тот же commit и одна команда проходят на машине коллеги, но падают на вашей. Иногда команда запускается, но показывает другую версию инструмента. Иногда сборка верна, а лог в консоли превращается в нечитаемый текст. Цена ошибки начинается не с исправления, а с поспешной реакции. Переустановка Node, очистка кеша и правка системного PATH меняют сразу несколько условий. После этого трудно восстановить исходную причину.
Тезис статьи простой: имя команды не доказывает, какой файл и какой процесс её выполняет. В PowerShell на результат влияют приоритет команд, функции и alias, порядок каталогов в PATH, наследование переменных новым процессом и отдельный слой кодировки консоли. Поэтому диагностика должна сначала зафиксировать наблюдения, затем проверить одну гипотезу обратимым действием.
Сравнение окружений имеет смысл только при одинаковом входе. Зафиксируйте commit, состояние рабочей копии, lock-файл, каталог запуска и точный текст команды. Если на одной машине изменён package-lock.json, установлен другой набор зависимостей или команда запущена из другого каталога, это уже самостоятельная причина. Не смешивайте её с вопросом о Windows.
git rev-parse --short HEAD\ngit status --short\nGet-Location\nnode --version\nnpm --version\nnpm run build\nЭтот фрагмент — учебный шаблон. Название команды проекта и набор проверок зависят от репозитория. Сохраните полный вывод на обеих машинах. Секреты, токены и личные части путей перед передачей другому человеку удалите.
\nPowerShell ищет команду не по абстрактному имени, а по правилам приоритета. Функция или alias может скрыть приложение. Если выбрано приложение, PowerShell ищет исполняемый файл в каталогах из переменной PATH. Первый подходящий каталог имеет значение. Переменная в текущем процессе не обязана совпадать с тем, что вы изменили в системных настройках несколько минут назад.
Сначала покажите все кандидаты и тип найденного объекта. Затем покажите фактическую версию. Эти команды отвечают на разные вопросы:
\nGet-Command node -All |\n Select-Object CommandType, Name, Version, Definition |\n Format-Table -AutoSize\n\nwhere.exe node\nnode --version\n$env:Path -split ';'\nGet-Command показывает, что выберет текущая сессия PowerShell. where.exe помогает увидеть исполняемые файлы, которые находятся в путях поиска Windows. Если первый результат — Function или Alias, список файлов не объясняет поведение команды. Если результаты указывают на разные node.exe, сравните путь и версию. Если путь и версия совпали, прекратите менять PATH и переходите к следующему факту.
Представим две машины с одним репозиторием. На машине A Get-Command node -All первым показывает C:\\Tools\\node-18\\node.exe. На машине B первым идёт C:\\Program Files\\nodejs\\node.exe. Это не означает, что машина A или B настроена правильно. Сначала нужно посмотреть, какую версию требует проект, и сопоставить её с node --version.
| Поле | Машина A | Машина B | Вывод |
|---|---|---|---|
| Первый кандидат | C:\\Tools\\node-18\\node.exe | C:\\Program Files\\nodejs\\node.exe | Одинаковое имя команды ведёт к разным файлам |
| Версия | Учебное значение v18.x | Учебное значение v20.x | Версия может менять поведение сборки |
| Порядок PATH | Каталог Tools стоит раньше | Каталог Tools отсутствует | Различие объясняет выбор, но не требование проекта |
| Исходная команда | Учебно завершается ошибкой | Учебно проходит | Нужна проверка гипотезы, а не удаление среды |
Значения в таблице условные. Это пример способа рассуждать, а не отчёт о production-системе. Если проект требует другую версию, источник требования должен находиться в его README, lock-файле, менеджере версий или принятой инструкции установки.
\nПолный дамп среды часто содержит слишком много данных. Для сравнения достаточно сохранить версию PowerShell, кодовую страницу, настройки вывода, PATHEXT, элементы PATH и кандидатов нужных команд. Важен порядок элементов. Превращайте каждую папку в отдельную строку, чтобы отличие было видно в обычном diff.
$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\nВ реальном скрипте обработайте отсутствие версии и отсутствие кандидата. Не записывайте в общий файл весь набор переменных без фильтра. Значения PATH могут раскрыть имена пользователей, внутренние каталоги и служебные адреса. Для учебного запуска достаточно вывести JSON в файл и сравнить очищенные копии.
| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
node --version возвращает старую версию | Другой файл оказался первым в PATH | Get-Command node -All, where.exe node | Временно поставить одобренный каталог первым в текущем процессе |
where.exe показывает файлы, но PowerShell ведёт себя иначе | Команду перехватывает функция или alias | Посмотреть CommandType; сравнить сессии с профилем и без него | Открыть новый PowerShell с -NoProfile |
| Только одна консоль показывает битый текст | Различается кодовая страница или кодировка вывода | chcp, [Console]::OutputEncoding | Повторить одну команду в новой консоли; не менять PATH |
| После правки переменной результат прежний | Старый процесс унаследовал прежнее значение | Проверить новый процесс и его $env:Path | Закрыть старое окно, повторить проверку в новом |
| Путь и версия совпали, сборка всё ещё падает | Причина лежит в зависимостях, правах, сети или конфигурации | Lock-файл, полный лог, доступ к каталогу и сетевой запрос | Остановить изменения PATH и расследовать следующий слой |
Если сравнение указывает на порядок PATH, меняйте только процессную переменную в новом PowerShell. Такой эксперимент не редактирует системную конфигурацию и исчезает после закрытия окна. Путь ниже условный. Подставляйте каталог, который назван в документации проекта или одобренном способе установки.
$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\nЕсли версия изменилась и исходная команда стала проходить, гипотеза о выборе бинарника получила подтверждение. Это ещё не готовое постоянное исправление. Зафиксируйте требуемую версию в проекте и выберите согласованный способ поставки. Если версия не изменилась, не переносите каталог в пользовательский или системный PATH. Если команда всё равно падает, путь был не причиной или существует дополнительная причина.
chcp показывает активную кодовую страницу консоли. Команды, запущенные после её изменения, могут получить новое значение, а уже работающие процессы сохраняют прежнее. Это не делает chcp 65001 универсальным лечением. Программа может читать файл в другой кодировке, задавать собственный вывод или писать результат не в консоль.
Проверяйте один и тот же текст в одинаковой команде. Сравните chcp, [Console]::OutputEncoding, способ записи файла и редактор, который открывает файл. Если путь и версия совпадают, а ломается только сохранённый файл, расследуйте кодировку файла. Если ломается только окно терминала, расследуйте консоль. Не меняйте два слоя одновременно.
PATH, PATHEXT и всех кандидатов нужного инструмента.-NoProfile и временным изменением только текущего $env:Path, если это гипотеза о пути.Одинаковый путь к бинарнику не гарантирует одинаковую сборку. Различия могут быть в разрядности, DLL, правах доступа, сертификате прокси, сетевом маршруте, кеше, антивирусе, окончаниях строк и содержимом рабочей копии. После совпадения пути и версии это не повод продолжать менять окружение. Зафиксируйте следующий наблюдаемый симптом и перейдите к нему.
\nНе отключайте защиту, политику запуска или антивирус ради проверки «на всякий случай». Не скачивайте исполняемый файл из случайного каталога. Не копируйте чужой полный PATH поверх своего. Если временный опыт не изменил исходный результат, отрицательный вывод полезен: выбранная гипотеза не подтверждена, а доказательства сохранены.
Разбор завершён, когда другой разработчик может повторить команду из того же commit и получить тот же результат на новой сессии. Для этого остаются четыре проверяемых факта: указан выбранный бинарник и его версия; описан источник требования к версии; команда проходит после чистого запуска или зафиксирован следующий отдельный отказ; постоянное изменение не зависит от ручной настройки конкретного компьютера.
\nФраза «на моей машине работает» после такого разбора превращается в проверяемое утверждение. У неё есть команда, вход, процесс, путь поиска и результат. Если утверждение не подтверждается, следующий шаг выбирают по наблюдаемому отличию, а не по числу переустановленных инструментов.
\nСимптом знакомый: один и тот же commit и одна команда проходят на машине коллеги, но падают на вашей. Иногда команда запускается, но показывает другую версию инструмента. Иногда сборка верна, а лог в консоли превращается в нечитаемый текст. Цена ошибки начинается не с исправления, а с поспешной реакции. Переустановка Node, очистка кеша и правка системного PATH меняют сразу несколько условий. После этого трудно восстановить исходную причину.
Имя команды не показывает, какой файл и какой процесс её выполняет. В PowerShell на результат влияют приоритет команд, функции и alias, порядок каталогов в PATH, наследование переменных новым процессом и отдельный слой кодировки консоли. Поэтому сначала зафиксируйте наблюдения, а затем проверьте одну гипотезу обратимым действием.
Сравнение окружений имеет смысл только при одинаковом входе. Зафиксируйте commit, состояние рабочей копии, lock-файл, каталог запуска и точный текст команды. Если на одной машине изменён package-lock.json, установлен другой набор зависимостей или команда запущена из другого каталога, это уже самостоятельная причина. Не смешивайте её с вопросом о Windows.
git rev-parse --short HEAD\ngit status --short\nGet-Location\nnode --version\nnpm --version\nnpm run build\nЭтот фрагмент — учебный шаблон. Название команды проекта и набор проверок зависят от репозитория. Сохраните полный вывод на обеих машинах. Секреты, токены и личные части путей перед передачей другому человеку удалите.
\nPowerShell ищет команду не по абстрактному имени, а по правилам приоритета. Функция или alias может скрыть приложение. Если выбрано приложение, PowerShell ищет исполняемый файл в каталогах из переменной PATH. Первый подходящий каталог имеет значение. Переменная в текущем процессе не обязана совпадать с тем, что вы изменили в системных настройках несколько минут назад.
Сначала покажите все кандидаты и тип найденного объекта. Затем покажите фактическую версию. Эти команды отвечают на разные вопросы:
\nGet-Command node -All |\n Select-Object CommandType, Name, Version, Definition |\n Format-Table -AutoSize\n\nwhere.exe node\nnode --version\n$env:Path -split ';'\nGet-Command показывает, что выберет текущая сессия PowerShell. where.exe помогает увидеть исполняемые файлы, которые находятся в путях поиска Windows. Если первый результат — Function или Alias, список файлов не объясняет поведение команды. Если результаты указывают на разные node.exe, сравните путь и версию. Если путь и версия совпали, прекратите менять PATH и переходите к следующему факту.
Представим две машины с одним репозиторием. На машине A Get-Command node -All первым показывает C:\\Tools\\node-18\\node.exe. На машине B первым идёт C:\\Program Files\\nodejs\\node.exe. Это не означает, что машина A или B настроена правильно. Сначала нужно посмотреть, какую версию требует проект, и сопоставить её с node --version.
| Поле | Машина A | Машина B | Вывод |
|---|---|---|---|
| Первый кандидат | C:\\Tools\\node-18\\node.exe | C:\\Program Files\\nodejs\\node.exe | Одинаковое имя команды ведёт к разным файлам |
| Версия | Учебное значение v18.x | Учебное значение v20.x | Версия может менять поведение сборки |
| Порядок PATH | Каталог Tools стоит раньше | Каталог Tools отсутствует | Различие объясняет выбор, но не требование проекта |
| Исходная команда | Учебно завершается ошибкой | Учебно проходит | Нужна проверка гипотезы, а не удаление среды |
Значения в таблице условные. Это пример способа рассуждать, а не отчёт о production-системе. Если проект требует другую версию, источник требования должен находиться в его README, lock-файле, менеджере версий или принятой инструкции установки.
\nПолный дамп среды часто содержит слишком много данных. Для сравнения достаточно сохранить версию PowerShell, кодовую страницу, настройки вывода, PATHEXT, элементы PATH и кандидатов нужных команд. Важен порядок элементов. Превращайте каждую папку в отдельную строку, чтобы отличие было видно в обычном diff.
$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\nВерсия у функции, alias или другого кандидата может отсутствовать, поэтому здесь null превращается в пустую строку без вызова ToString(). Если команда не найдена, Get-Command завершит проверку ошибкой — это полезный отдельный результат. Сохраните снимки в разные файлы и сравните очищенные копии:
$snapshot | ConvertTo-Json -Depth 5 | Set-Content .\\\\snapshot-a.json -Encoding utf8\\nCompare-Object (Get-Content .\\\\snapshot-a.json) (Get-Content .\\\\snapshot-b.json)\\nНе записывайте в общий файл весь набор переменных без фильтра. Значения PATH могут раскрыть имена пользователей, внутренние каталоги и служебные адреса. Перед сравнением удалите секреты и персональные пути, но не меняйте порядок элементов.
| Симптом | Причина | Проверка | Действие |
|---|---|---|---|
node --version возвращает старую версию | Другой файл оказался первым в PATH | Get-Command node -All, where.exe node | Временно поставить одобренный каталог первым в текущем процессе |
where.exe показывает файлы, но PowerShell ведёт себя иначе | Команду перехватывает функция или alias | Посмотреть CommandType; сравнить сессии с профилем и без него | Открыть новый PowerShell с -NoProfile |
| Только одна консоль показывает битый текст | Различается кодовая страница или кодировка вывода | chcp, [Console]::OutputEncoding | Повторить одну команду в новой консоли; не менять PATH |
| После правки переменной результат прежний | Старый процесс унаследовал прежнее значение | Проверить новый процесс и его $env:Path | Закрыть старое окно, повторить проверку в новом |
| Путь и версия совпали, сборка всё ещё падает | Причина лежит в зависимостях, правах, сети или конфигурации | Lock-файл, полный лог, доступ к каталогу и сетевой запрос | Остановить изменения PATH и расследовать следующий слой |
Если сравнение указывает на порядок PATH, меняйте только процессную переменную в новом PowerShell. Такой эксперимент не редактирует системную конфигурацию и исчезает после закрытия окна. Путь ниже условный. Подставляйте каталог, который назван в документации проекта или одобренном способе установки.
$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& $nodePath --version\n& $npmPath --version\n\n# Здесь повторяется та же команда, что дала исходную ошибку.\n& $npmPath run build\nЕсли версия изменилась и исходная команда стала проходить, гипотеза о выборе бинарника получила подтверждение. Это ещё не готовое постоянное исправление. Зафиксируйте требуемую версию в проекте и выберите согласованный способ поставки. Если версия не изменилась, не переносите каталог в пользовательский или системный PATH. Если команда всё равно падает, путь был не причиной или существует дополнительная причина.
chcp показывает активную кодовую страницу консоли. Программы, запущенные после её изменения, могут получить новое значение, а уже работающие процессы сохраняют прежнее. Это не делает chcp 65001 универсальным лечением. Программа может читать файл в другой кодировке, задавать собственный вывод или писать результат не в консоль.
Проверяйте один и тот же текст в одинаковой команде. Сравните chcp, [Console]::OutputEncoding, способ записи файла и редактор, который открывает файл. Если путь и версия совпадают, а ломается только сохранённый файл, расследуйте кодировку файла. Если ломается только окно терминала, расследуйте консоль. Не меняйте два слоя одновременно.
PATH, PATHEXT и всех кандидатов нужного инструмента.-NoProfile и временным изменением только текущего $env:Path, если это гипотеза о пути.Одинаковый путь к бинарнику не гарантирует одинаковую сборку. Различия могут быть в разрядности, DLL, правах доступа, сертификате прокси, сетевом маршруте, кеше, антивирусе, окончаниях строк и содержимом рабочей копии. После совпадения пути и версии это не повод продолжать менять окружение. Зафиксируйте следующий наблюдаемый симптом и перейдите к нему.
\nНе отключайте защиту, политику запуска или антивирус ради проверки «на всякий случай». Не скачивайте исполняемый файл из случайного каталога. Не копируйте чужой полный PATH поверх своего. Если временный опыт не изменил исходный результат, отрицательный вывод полезен: выбранная гипотеза не подтверждена, а доказательства сохранены.
Разбор завершён, когда другой разработчик может повторить команду из того же commit и получить тот же результат на новой сессии. Для этого остаются четыре проверяемых факта: указан выбранный бинарник и его версия; описан источник требования к версии; команда проходит после чистого запуска или зафиксирован следующий отдельный отказ; постоянное изменение не зависит от ручной настройки конкретного компьютера.
\nФраза «на моей машине работает» после такого разбора превращается в проверяемое утверждение. У неё есть команда, вход, процесс, путь поиска и результат. Если утверждение не подтверждается, следующий шаг выбирают по наблюдаемому отличию, а не по числу переустановленных инструментов.
\n