Skip to content

Проверка пакета ​

Две команды, которые смотрят на пакет так же, как смотрел бы настоящий потребитель, а не как смотрит его собственная сборка: exports-doctor сверяет пути, заявленные в package.json, с тем, что реально лежит на диске, readme-check тайпчекает код-примеры из README/доков против собственной сборки пакета. Обе — только для чтения, ничего не пишут и не выполняют исходный код примеров, только компилируют/резолвят пути.

exports-doctor ​

Резолвит каждый путь, который package.json заявляет (main/module/types/typings/bin/exports, включая вложенные condition-объекты, subpath-карты и fallback-массивы), против того, что реально лежит на диске.

bash
devtoolz exports-doctor [dir] [options]

Команда читает один package.json в указанной директории (по умолчанию — текущая), а не обходит дерево файлов — поэтому у неё нет --cwd/--ext/--ignore, только [dir].

Реальный вывод — пакет, где bin-файл забыл про shebang, а ./cdn-subpath резолвится в рантайме, но у него нигде не объявлены типы:

🧰 devtoolz — chores, automated

Checked widget-kit's declared exports.

2 problems found:
  bin.widget-kit    ./dist/cli.js        missing '#!/usr/bin/env node' — won't run as a bin
  exports["./cdn"]  ./dist/cdn/index.js  runtime resolves fine, but no types declared for this entry at all

Второй случай — самый коварный из всех: import из pkg/cdn реально сработает у любого потребителя, сборка пакета пройдёт без единой ошибки, а TypeScript-подсказок для этого пути не будет ни у кого — ни ошибки, ни предупреждения, просто тихая пустота вместо типов. Ловится только сверкой с тем, что реально объявлено под тем же ключом exports.

Несовпадение регистра — та же механика, что у case-check, только для полей package.json, а не для импортов исходников:

🧰 devtoolz, reporting for duty

Checked widget-kit's declared exports.

1 problem found:
  main  ./dist/index.js  wrong case — real path is ./dist/Index.js

Что проверяется ​

  • missing — путь не резолвится вообще, ни при каком регистре.
  • case-mismatch — резолвится, но не тем регистром — показывается реальный путь на диске.
  • missing-shebang — bin-файл существует, но не начинается с #!/usr/bin/env node, так что не запустится как исполняемый.
  • types-missing — у subpath'а из exports есть хотя бы одно рантайм-условие (import/require/...), которое резолвится нормально, но среди условий того же subpath'а нигде не объявлен types. Не срабатывает на простом строковом значении без condition-объекта вовсе — для такой формы TypeScript и так подхватывает соседний .d.ts по конвенции, как у main.

Опции ​

[dir] ​

Директория пакета для проверки (позиционный аргумент, по умолчанию — .).

Пример:

bash
devtoolz exports-doctor                  # package.json в текущей директории
devtoolz exports-doctor path/to/package  # конкретная директория
devtoolz exports-doctor --json           # машиночитаемый вывод, для CI

readme-check ​

Вытаскивает ts-блоки кода из README/доков и реально тайпчекает их через TypeScript Compiler API против собственного tsconfig.json пакета.

bash
devtoolz readme-check [dir] [options]

Виртуальный файл, в который подставляется код блока, кладётся прямо в корень пакета (рядом с package.json) — поэтому резолвятся и относительные импорты, и self-reference импорт по собственному опубликованному имени пакета (import { x } from 'my-package'), в точности как у настоящего потребителя, если moduleResolution пакета это поддерживает (bundler/node16/nodenext). Ничего не выполняется — только компилируется.

Реальный вывод — README с двумя примерами: рабочим и намеренно неверным, оба импортируют пакет по его собственному опубликованному имени:

🧰 devtoolz

Typechecked 2 code blocks across 1 file.

1 problem found:
  README.md:13:7  ts  Type 'string' is not assignable to type 'number'.

Первый блок (const widget = createWidget('main'); console.log(widget.label)) в отчёте не появляется — он реально компилируется. Второй, const count: number = createWidget('main').label, компилируется тоже, но label — строка, а не число, поэтому и находка.

Каждый блок проверяется изолированно ​

Блок проверяется сам по себе, без общего контекста с соседними блоками того же файла. Это осознанный выбор, а не упрощение: док-сниппет часто представляет собой фрагмент, который опирается на переменную, заданную где-то в окружающей прозе или в другом, несвязанном примере («внутри вашего компонента:», «имея экземпляр router, ...») — в изоляции это выглядит как неопределённое имя, но не обязательно является реальной ошибкой в доке. Поэтому следующие классы диагностик TypeScript намеренно не показываются как находки:

  • Cannot find name и его вариант «did you mean» — неопределённое имя, скорее всего унаследованное из внешнего по отношению к блоку контекста;
  • Cannot find module './x' для ОТНОСИТЕЛЬНОГО пути — почти всегда гипотетический файл в проекте читателя («ваш App.vue»), а не то, что могло бы существовать в самом пакете;
  • «X is declared but its value is never read» — док-фрагмент регулярно объявляет обработчик, вызываемый не в этом же блоке (из шаблона, из другого примера);
  • ошибка резолва self-reference-импорта именно из-за того, что СОБСТВЕННЫЙ tsconfig.json пакета не включает moduleResolution: bundler/node16/nodenext — это ограничение окружения самого пакета, а не ошибка в доке.

Cannot find module 'pkg-name' для ГОЛОГО имени пакета (self-reference) при этом остаётся находкой — это и есть тот самый исторически ценный случай, ради которого команда существует.

Опции ​

--file <path> ​

Markdown-файл для проверки, относительно [dir] (можно указать несколько раз). По умолчанию — README.md.

--lang <list> ​

Через запятую, языки фенс-блоков для проверки. По умолчанию — только ts. tsx намеренно не включён по умолчанию — Vue-пакеты обычно не настраивают JSX/React-типы, так что tsx-блок там почти всегда иллюстративный псевдокод, который сам пакет не смог бы скомпилировать в принципе; добавляйте явно через --lang ts,tsx для пакета, который реально настраивает JSX.

--tsconfig <path> ​

tsconfig.json, из которого читать compiler options (по умолчанию автоопределяется в [dir]).

Пример:

bash
devtoolz readme-check                          # ts-блоки в ./README.md
devtoolz readme-check --file docs/guide.md      # ещё один документ вместо/вместе с README
devtoolz readme-check --lang ts,tsx             # включить и tsx-блоки тоже