From adf6750d2884154a104ab1be310075342aefc7ed Mon Sep 17 00:00:00 2001 From: "E.Gavrilov" Date: Fri, 4 Sep 2026 00:20:23 +0300 Subject: [PATCH] editorial: refine Windows environment article 334 --- editorial/agent-rewrites/334.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 меняют сразу несколько условий. После этого трудно восстановить исходную причину.

\n

Тезис статьи простой: имя команды не доказывает, какой файл и какой процесс её выполняет. В PowerShell на результат влияют приоритет команд, функции и alias, порядок каталогов в PATH, наследование переменных новым процессом и отдельный слой кодировки консоли. Поэтому диагностика должна сначала зафиксировать наблюдения, затем проверить одну гипотезу обратимым действием.

\n

Сначала исключите различия проекта

\n

Сравнение окружений имеет смысл только при одинаковом входе. Зафиксируйте commit, состояние рабочей копии, lock-файл, каталог запуска и точный текст команды. Если на одной машине изменён package-lock.json, установлен другой набор зависимостей или команда запущена из другого каталога, это уже самостоятельная причина. Не смешивайте её с вопросом о Windows.

\n
git rev-parse --short HEAD\ngit status --short\nGet-Location\nnode --version\nnpm --version\nnpm run build
\n

Этот фрагмент — учебный шаблон. Название команды проекта и набор проверок зависят от репозитория. Сохраните полный вывод на обеих машинах. Секреты, токены и личные части путей перед передачей другому человеку удалите.

\n

Механизм выбора команды

\n

PowerShell ищет команду не по абстрактному имени, а по правилам приоритета. Функция или alias может скрыть приложение. Если выбрано приложение, PowerShell ищет исполняемый файл в каталогах из переменной PATH. Первый подходящий каталог имеет значение. Переменная в текущем процессе не обязана совпадать с тем, что вы изменили в системных настройках несколько минут назад.

\n

Сначала покажите все кандидаты и тип найденного объекта. Затем покажите фактическую версию. Эти команды отвечают на разные вопросы:

\n
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 ';'
\n

Get-Command показывает, что выберет текущая сессия PowerShell. where.exe помогает увидеть исполняемые файлы, которые находятся в путях поиска Windows. Если первый результат — Function или Alias, список файлов не объясняет поведение команды. Если результаты указывают на разные node.exe, сравните путь и версию. Если путь и версия совпали, прекратите менять PATH и переходите к следующему факту.

\n

Учебный пример: два node.exe

\n

Представим две машины с одним репозиторием. На машине A Get-Command node -All первым показывает C:\\Tools\\node-18\\node.exe. На машине B первым идёт C:\\Program Files\\nodejs\\node.exe. Это не означает, что машина A или B настроена правильно. Сначала нужно посмотреть, какую версию требует проект, и сопоставить её с node --version.

\n
ПолеМашина AМашина BВывод
Первый кандидатC:\\Tools\\node-18\\node.exeC:\\Program Files\\nodejs\\node.exeОдинаковое имя команды ведёт к разным файлам
ВерсияУчебное значение v18.xУчебное значение v20.xВерсия может менять поведение сборки
Порядок PATHКаталог Tools стоит раньшеКаталог Tools отсутствуетРазличие объясняет выбор, но не требование проекта
Исходная командаУчебно завершается ошибкойУчебно проходитНужна проверка гипотезы, а не удаление среды
\n

Значения в таблице условные. Это пример способа рассуждать, а не отчёт о production-системе. Если проект требует другую версию, источник требования должен находиться в его README, lock-файле, менеджере версий или принятой инструкции установки.

\n

Снимок окружения без лишнего шума

\n

Полный дамп среды часто содержит слишком много данных. Для сравнения достаточно сохранить версию PowerShell, кодовую страницу, настройки вывода, PATHEXT, элементы PATH и кандидатов нужных команд. Важен порядок элементов. Превращайте каждую папку в отдельную строку, чтобы отличие было видно в обычном diff.

\n
$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 в файл и сравнить очищенные копии.

\n
\"Схема
Диагностический маршрут: одинаковый вход, два очищенных снимка, одно отличие, обратимый опыт и повтор исходной команды.
\n

Как не спутать похожие симптомы

\n
СимптомПричинаПроверкаДействие
node --version возвращает старую версиюДругой файл оказался первым в PATHGet-Command node -All, where.exe nodeВременно поставить одобренный каталог первым в текущем процессе
where.exe показывает файлы, но PowerShell ведёт себя иначеКоманду перехватывает функция или aliasПосмотреть CommandType; сравнить сессии с профилем и без негоОткрыть новый PowerShell с -NoProfile
Только одна консоль показывает битый текстРазличается кодовая страница или кодировка выводаchcp, [Console]::OutputEncodingПовторить одну команду в новой консоли; не менять PATH
После правки переменной результат прежнийСтарый процесс унаследовал прежнее значениеПроверить новый процесс и его $env:PathЗакрыть старое окно, повторить проверку в новом
Путь и версия совпали, сборка всё ещё падаетПричина лежит в зависимостях, правах, сети или конфигурацииLock-файл, полный лог, доступ к каталогу и сетевой запросОстановить изменения PATH и расследовать следующий слой
\n

Обратимая проверка гипотезы

\n

Если сравнение указывает на порядок PATH, меняйте только процессную переменную в новом PowerShell. Такой эксперимент не редактирует системную конфигурацию и исчезает после закрытия окна. Путь ниже условный. Подставляйте каталог, который назван в документации проекта или одобренном способе установки.

\n
$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. Если команда всё равно падает, путь был не причиной или существует дополнительная причина.

\n

Кодировка — отдельная проверка

\n

chcp показывает активную кодовую страницу консоли. Команды, запущенные после её изменения, могут получить новое значение, а уже работающие процессы сохраняют прежнее. Это не делает chcp 65001 универсальным лечением. Программа может читать файл в другой кодировке, задавать собственный вывод или писать результат не в консоль.

\n

Проверяйте один и тот же текст в одинаковой команде. Сравните chcp, [Console]::OutputEncoding, способ записи файла и редактор, который открывает файл. Если путь и версия совпадают, а ломается только сохранённый файл, расследуйте кодировку файла. Если ломается только окно терминала, расследуйте консоль. Не меняйте два слоя одновременно.

\n

Порядок действий

\n
  1. Зафиксируйте commit, lock-файл, каталог запуска, команду и полный текст ошибки.
  2. Повторите команду на обеих машинах без предварительной правки настроек.
  3. Снимите версию PowerShell, кодовую страницу, кодировку вывода, PATH, PATHEXT и всех кандидатов нужного инструмента.
  4. Удалите секреты и персональные пути из снимков, затем сравните одинаковые поля.
  5. Выберите одно отличие, которое прямо связано с симптомом: кандидат, версия, процесс или кодировка.
  6. Проверьте отличие в новом PowerShell с -NoProfile и временным изменением только текущего $env:Path, если это гипотеза о пути.
  7. Повторите исходную команду и сохраните результат проверки.
  8. Только после подтверждения внесите постоянное изменение в README, менеджер версий или согласованный установщик.
\n

Ограничения и отрицательный путь

\n

Одинаковый путь к бинарнику не гарантирует одинаковую сборку. Различия могут быть в разрядности, DLL, правах доступа, сертификате прокси, сетевом маршруте, кеше, антивирусе, окончаниях строк и содержимом рабочей копии. После совпадения пути и версии это не повод продолжать менять окружение. Зафиксируйте следующий наблюдаемый симптом и перейдите к нему.

\n

Не отключайте защиту, политику запуска или антивирус ради проверки «на всякий случай». Не скачивайте исполняемый файл из случайного каталога. Не копируйте чужой полный PATH поверх своего. Если временный опыт не изменил исходный результат, отрицательный вывод полезен: выбранная гипотеза не подтверждена, а доказательства сохранены.

\n

Проверяемый критерий готовности

\n

Разбор завершён, когда другой разработчик может повторить команду из того же commit и получить тот же результат на новой сессии. Для этого остаются четыре проверяемых факта: указан выбранный бинарник и его версия; описан источник требования к версии; команда проходит после чистого запуска или зафиксирован следующий отдельный отказ; постоянное изменение не зависит от ручной настройки конкретного компьютера.

\n

Фраза «на моей машине работает» после такого разбора превращается в проверяемое утверждение. У неё есть команда, вход, процесс, путь поиска и результат. Если утверждение не подтверждается, следующий шаг выбирают по наблюдаемому отличию, а не по числу переустановленных инструментов.

\n

Проверяемые источники

" + "contentHtml": "

Симптом знакомый: один и тот же commit и одна команда проходят на машине коллеги, но падают на вашей. Иногда команда запускается, но показывает другую версию инструмента. Иногда сборка верна, а лог в консоли превращается в нечитаемый текст. Цена ошибки начинается не с исправления, а с поспешной реакции. Переустановка Node, очистка кеша и правка системного PATH меняют сразу несколько условий. После этого трудно восстановить исходную причину.

\n

Имя команды не показывает, какой файл и какой процесс её выполняет. В PowerShell на результат влияют приоритет команд, функции и alias, порядок каталогов в PATH, наследование переменных новым процессом и отдельный слой кодировки консоли. Поэтому сначала зафиксируйте наблюдения, а затем проверьте одну гипотезу обратимым действием.

\n

Сначала исключите различия проекта

\n

Сравнение окружений имеет смысл только при одинаковом входе. Зафиксируйте commit, состояние рабочей копии, lock-файл, каталог запуска и точный текст команды. Если на одной машине изменён package-lock.json, установлен другой набор зависимостей или команда запущена из другого каталога, это уже самостоятельная причина. Не смешивайте её с вопросом о Windows.

\n
git rev-parse --short HEAD\ngit status --short\nGet-Location\nnode --version\nnpm --version\nnpm run build
\n

Этот фрагмент — учебный шаблон. Название команды проекта и набор проверок зависят от репозитория. Сохраните полный вывод на обеих машинах. Секреты, токены и личные части путей перед передачей другому человеку удалите.

\n

Механизм выбора команды

\n

PowerShell ищет команду не по абстрактному имени, а по правилам приоритета. Функция или alias может скрыть приложение. Если выбрано приложение, PowerShell ищет исполняемый файл в каталогах из переменной PATH. Первый подходящий каталог имеет значение. Переменная в текущем процессе не обязана совпадать с тем, что вы изменили в системных настройках несколько минут назад.

\n

Сначала покажите все кандидаты и тип найденного объекта. Затем покажите фактическую версию. Эти команды отвечают на разные вопросы:

\n
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 ';'
\n

Get-Command показывает, что выберет текущая сессия PowerShell. where.exe помогает увидеть исполняемые файлы, которые находятся в путях поиска Windows. Если первый результат — Function или Alias, список файлов не объясняет поведение команды. Если результаты указывают на разные node.exe, сравните путь и версию. Если путь и версия совпали, прекратите менять PATH и переходите к следующему факту.

\n

Учебный пример: два node.exe

\n

Представим две машины с одним репозиторием. На машине A Get-Command node -All первым показывает C:\\Tools\\node-18\\node.exe. На машине B первым идёт C:\\Program Files\\nodejs\\node.exe. Это не означает, что машина A или B настроена правильно. Сначала нужно посмотреть, какую версию требует проект, и сопоставить её с node --version.

\n
ПолеМашина AМашина BВывод
Первый кандидатC:\\Tools\\node-18\\node.exeC:\\Program Files\\nodejs\\node.exeОдинаковое имя команды ведёт к разным файлам
ВерсияУчебное значение v18.xУчебное значение v20.xВерсия может менять поведение сборки
Порядок PATHКаталог Tools стоит раньшеКаталог Tools отсутствуетРазличие объясняет выбор, но не требование проекта
Исходная командаУчебно завершается ошибкойУчебно проходитНужна проверка гипотезы, а не удаление среды
\n

Значения в таблице условные. Это пример способа рассуждать, а не отчёт о production-системе. Если проект требует другую версию, источник требования должен находиться в его README, lock-файле, менеджере версий или принятой инструкции установки.

\n

Снимок окружения без лишнего шума

\n

Полный дамп среды часто содержит слишком много данных. Для сравнения достаточно сохранить версию PowerShell, кодовую страницу, настройки вывода, PATHEXT, элементы PATH и кандидатов нужных команд. Важен порядок элементов. Превращайте каждую папку в отдельную строку, чтобы отличие было видно в обычном diff.

\n
$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 завершит проверку ошибкой — это полезный отдельный результат. Сохраните снимки в разные файлы и сравните очищенные копии:

\\n
$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 могут раскрыть имена пользователей, внутренние каталоги и служебные адреса. Перед сравнением удалите секреты и персональные пути, но не меняйте порядок элементов.

\n
\"Схема
Диагностический маршрут: одинаковый вход, два очищенных снимка, одно отличие, обратимый опыт и повтор исходной команды.
\n

Как не спутать похожие симптомы

\n
СимптомПричинаПроверкаДействие
node --version возвращает старую версиюДругой файл оказался первым в PATHGet-Command node -All, where.exe nodeВременно поставить одобренный каталог первым в текущем процессе
where.exe показывает файлы, но PowerShell ведёт себя иначеКоманду перехватывает функция или aliasПосмотреть CommandType; сравнить сессии с профилем и без негоОткрыть новый PowerShell с -NoProfile
Только одна консоль показывает битый текстРазличается кодовая страница или кодировка выводаchcp, [Console]::OutputEncodingПовторить одну команду в новой консоли; не менять PATH
После правки переменной результат прежнийСтарый процесс унаследовал прежнее значениеПроверить новый процесс и его $env:PathЗакрыть старое окно, повторить проверку в новом
Путь и версия совпали, сборка всё ещё падаетПричина лежит в зависимостях, правах, сети или конфигурацииLock-файл, полный лог, доступ к каталогу и сетевой запросОстановить изменения PATH и расследовать следующий слой
\n

Обратимая проверка гипотезы

\n

Если сравнение указывает на порядок PATH, меняйте только процессную переменную в новом PowerShell. Такой эксперимент не редактирует системную конфигурацию и исчезает после закрытия окна. Путь ниже условный. Подставляйте каталог, который назван в документации проекта или одобренном способе установки.

\n
$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. Если команда всё равно падает, путь был не причиной или существует дополнительная причина.

\n

Кодировка — отдельная проверка

\n

chcp показывает активную кодовую страницу консоли. Программы, запущенные после её изменения, могут получить новое значение, а уже работающие процессы сохраняют прежнее. Это не делает chcp 65001 универсальным лечением. Программа может читать файл в другой кодировке, задавать собственный вывод или писать результат не в консоль.

\n

Проверяйте один и тот же текст в одинаковой команде. Сравните chcp, [Console]::OutputEncoding, способ записи файла и редактор, который открывает файл. Если путь и версия совпадают, а ломается только сохранённый файл, расследуйте кодировку файла. Если ломается только окно терминала, расследуйте консоль. Не меняйте два слоя одновременно.

\n

Порядок действий

\n
  1. Зафиксируйте commit, lock-файл, каталог запуска, команду и полный текст ошибки.
  2. Повторите команду на обеих машинах без предварительной правки настроек.
  3. Снимите версию PowerShell, кодовую страницу, кодировку вывода, PATH, PATHEXT и всех кандидатов нужного инструмента.
  4. Удалите секреты и персональные пути из снимков, затем сравните одинаковые поля.
  5. Выберите одно отличие, которое прямо связано с симптомом: кандидат, версия, процесс или кодировка.
  6. Проверьте отличие в новом PowerShell с -NoProfile и временным изменением только текущего $env:Path, если это гипотеза о пути.
  7. Повторите исходную команду и сохраните результат проверки.
  8. Только после подтверждения внесите постоянное изменение в README, менеджер версий или согласованный установщик.
\n

Ограничения и отрицательный путь

\n

Одинаковый путь к бинарнику не гарантирует одинаковую сборку. Различия могут быть в разрядности, DLL, правах доступа, сертификате прокси, сетевом маршруте, кеше, антивирусе, окончаниях строк и содержимом рабочей копии. После совпадения пути и версии это не повод продолжать менять окружение. Зафиксируйте следующий наблюдаемый симптом и перейдите к нему.

\n

Не отключайте защиту, политику запуска или антивирус ради проверки «на всякий случай». Не скачивайте исполняемый файл из случайного каталога. Не копируйте чужой полный PATH поверх своего. Если временный опыт не изменил исходный результат, отрицательный вывод полезен: выбранная гипотеза не подтверждена, а доказательства сохранены.

\n

Проверяемый критерий готовности

\n

Разбор завершён, когда другой разработчик может повторить команду из того же commit и получить тот же результат на новой сессии. Для этого остаются четыре проверяемых факта: указан выбранный бинарник и его версия; описан источник требования к версии; команда проходит после чистого запуска или зафиксирован следующий отдельный отказ; постоянное изменение не зависит от ручной настройки конкретного компьютера.

\n

Фраза «на моей машине работает» после такого разбора превращается в проверяемое утверждение. У неё есть команда, вход, процесс, путь поиска и результат. Если утверждение не подтверждается, следующий шаг выбирают по наблюдаемому отличию, а не по числу переустановленных инструментов.

\n

Проверяемые источники

" }