Repository navigation
[bug] Windows: пробел или %VAR% в имени файла ломают конвертацию, и это рапортуется как «скорее всего скан → OCR» #3
Description
Что произошло
На Windows convert.js не конвертирует файл, в имени которого есть пробел — в том числе пример из справки самого скрипта (node convert.js a.pdf b.xlsx "отчёт за март.pptx").
Причина: spawnSync('npx', ['-y', '@firecrawl/anydoc', f, '-o', out], { shell: isWin }). Под shell: true аргументы уходят в cmd.exe одной строкой, cmd режет её по пробелам, и anydoc получает второй «вход».
Тот же корень даёт второй случай: имя с %VAR% (например отчёт %TEMP% март.docx) — cmd раскрывает переменную, и в anydoc уходит уже другой путь (io error: The filename, directory name, or volume label syntax is incorrect. (os error 123)).
Отдельно, и по-моему важнее самого бага: исход категоризируется неверно. Оба случая печатаются как ПРОПУСК с советом «Пропущенные, скорее всего, сканы или PDF-картинки без текстового слоя — прогони их через OCR», хотя файл идеально читаемый. А process.exit(fail > 0 ? 1 : 0) даёт exit 0, потому что это skip, а не fail. В пакетном режиме по папке такой файл молча теряется при статусе «успех», а пользователя отправляют на бессмысленный OCR.
Как воспроизвести
Команда:
node skills/doc2md/scripts/convert.js "a b.docx"Ожидал: OK
Получил:
ПРОПУСК C:\...\a b.docx — anydoc: one document per invocation: unexpected second input 'b.docx'
Готово: 0 конвертировано, 1 пропущено (не читается/зашифровано/скан), 0 ошибок вызова
Пропущенные, скорее всего, сканы или PDF-картинки без текстового слоя — прогони их через OCR (скилл pdf, шаг «сделать PDF searchable») и повтори.
Кириллица тут не при чём — проверял раздельно, данные.csv и отчёт.docx без пробела конвертируются нормально. Ломает именно пробел.
Окружение
- ОС и версия: Windows 11 Enterprise 26200, проверял и из
cmd.exe, и из PowerShell - Node.js (
node -v): v26.2.0, npm 11.13.0 - Версия обёртки:
skills/doc2md/scripts/convert.js(кросс-платформенная Node-версия), клон main от 06.08.2026 - anydoc: через
npx -y @firecrawl/anydoc, версия 0.1.6 (глобально не ставил)
Секция «Протестировано» в SKILL.md просит перепроверить и дополнить её при первом боевом запуске на Windows — этим и занимаюсь. Вывод «convert.js написан без shell-специфичного синтаксиса … но это рассуждение о корректности, а не проверка» оказался ровно в точку.
Что помогло у меня — не настаиваю на способе
-
Квотировать аргументы под
shell: true— лечит пробелы, но не лечит%VAR%: раскрытие переменных на командной строке cmd не экранируется. -
Корневой фикс — убрать
cmd.exeиз цепочки.shell: trueнужен только чтобы нашёлсяnpx.cmd; вместо этого можно позватьnpx-cli.jsнапрямую тем же node, без shell:const npxCli = path.join(path.dirname(process.execPath), 'node_modules', 'npm', 'bin', 'npx-cli.js'); spawnSync(process.execPath, [npxCli, '-y', '@firecrawl/anydoc', f, '-o', out]);
После этого у меня прошли все имена:
a b.docx,отчёт за март.docx,a&b^c%TEMP% d.docx— результат байт-в-байт совпал с эталоном. Оговорка: путь кnpx-cli.js— деталь раскладки npm-бандла, а не публичный контракт, и на nvm-windows/Volta он может лежать не рядом сnode.exe. -
Глубже, и это, кажется, интереснее всего для самого скилла: у
@firecrawl/anydocесть официальный Node API. В установленном 0.1.6 естьtoMarkdown(path),toMarkdownBytes,toDocument,formatFromPathи типизированныйConvertErrorCode(unsupported | malformed | encrypted | resourceLimit | missingPart | io), таргетx86_64-pc-windows-msvc. Если звать его вместо CLI:- исчезает
cmd.exeи вместе с ним весь класс проблем с именами — чинить нечего, потому что ломаться больше нечему; - исчезают накладные
npxна каждый файл. Замерил у себя: через npx ~1.02 с на файл (5 тривиальных CSV = 5.11 с) при собственной медиане anydoc ~4.4 мс; после перехода на API 20 файлов конвертируются за 111 мс. На пачке это порядка 100×, а пакетная обработка — ровно то, ради чего скилл и нужен; - вместо угадывания по exit-коду приходит точная причина, и
malformed documentбольше не советует OCR. Сейчас, например, лок-файл Word~$договор.docxпопадает в обработку и рапортуется как «скорее всего скан».
Я в итоге так и сделал у себя и был неправ, когда сначала полез чинить обёртку вокруг CLI: правильный ход оказался не «квотировать лучше», а не звать cmd вообще.
- исчезает
Две мелочи, если пригодятся
-oпоследним аргументом молча игнорируется:outDir = argv[++i]даётundefined, дальшеif (outDir)ложно → всё пишется рядом с исходниками, без предупреждения, exit 0. Человек думает, что собрал результат в отдельную папку.- При
-oи рекурсивном обходе имя выхода строится изpath.basename(f), поэтому2025\акт.pdfи2026\акт.pdfдают одинакт.pdf.md— второй молча перезаписывает первый, оба со статусомOK. Это тот же класс, что уже поправлен дляотчёт.csv/отчёт.docx(сохранением расширения), только по другой оси — по подпапкам.
Спасибо за скилл: идея «сконвертировать пачку до того, как её начнёт читать агент» оказалась ровно тем, что было нужно, и basename + .md против коллизий, и отсечение циклов по симлинкам — приятно аккуратные детали.
Activity
ilyautov commentedon Oct 7, 2026
Исправлено в #4 и слито в main. Windows CI с настоящим anydoc прошёл для имени с пробелом и
%TEMP%, а также для двух одноимённых файлов из разных папок. Повреждённый документ теперь даёт ошибку и ненулевой код пакета;-oбез папки тоже отклоняется.