Skip to content

DevToolz ​

Набор самостоятельных CLI-команд для рутинных задач разработки — тех, что иначе делаются наполовину забытым регэкспом или руками, прямо перед коммитом. Один бинарник, одна подкоманда на задачу, каждая безопасна по умолчанию: сначала превью, -y — чтобы реально что-то записать.

Команды для изображений image-hash и image-batch переехали в отдельный пакет MediaToolz в DevToolz 0.5.0. Здесь обе только печатают, куда они переехали, и завершаются с кодом 2.

Возможности ​

  • strip-comments — убирает //, /* */, /** */ и <!-- --> из исходников на месте. --keep-jsdoc оставляет /** */ прямо над экспортируемым объявлением нетронутым, чтобы не терять подсказки в IDE; всё остальное уходит. Понимает .vue-файлы — <script> через тот же парсер, <!-- --> внутри <template> — построчным сканом.
  • console-strip — убирает оставленные по ошибке console.log/console.debug/debugger. console.warn/console.error по умолчанию не трогаются — это часто легитимное продакшен-логирование, а не отладочный мусор (--methods переопределяет список). Удаляет только вызов, который является отдельным выражением целиком — то, что является частью более сложного выражения или сидит внутри тела if/while/for без фигурных скобок, оставляется на месте и перечисляется отдельно, без угадывания.
  • dead-exports — находит именованные экспорты, которые нигде в проекте не импортируются. Понимает, что «мёртвый» на самом деле значит для публикуемой библиотеки: собственные публичные точки входа пакета (автоопределяются по package.json's exports/main/module/bin) по умолчанию исключены — неиспользуемость внутри репозитория не то же самое, что неиспользуемость вообще, в этом и весь смысл экспорта. Прослеживает цепочки ре-экспортов (export { x } from './y', export * from './y') до места, где символ реально объявлен, и автоопределяет pnpm/npm/yarn-воркспейс, так что импорт вашего экспорта соседним пакетом тоже не читается как мёртвый. --strict проверяет и точки входа, когда это действительно нужно.
  • case-check — находит импорты, чей регистр не совпадает с реальным именем файла на диске. Windows и macOS по умолчанию не различают регистр, так что import './foo' при реальном Foo.ts работает без проблем ровно до тех пор, пока не доедет до Linux CI. Проверяется каждый сегмент пути, не только имя файла; резолвятся и алиасы tsconfig.json (@/...). --fix переписывает несовпадающий специфайер на реальный регистр (алиас-резолвленные исключены — см. страницу команды).
  • unused-deps — находит зависимости в package.json, на которые нет ни одного импорта, и зеркально — «фантомные» зависимости: пакет реально импортируется, но нигде не объявлен. Знает про обычные способы использования пакета без прямого импорта — вызов из scripts/lint-staged по имени бинарника, упоминание в конфиг-файле (vitest.config.ts's environment: 'happy-dom') — и отдельно про пакеты, которые структурно никогда не появятся текстом нигде (@types/*, typescript, postcss/sass).
  • circular-imports — находит циклы импортов (A → B → … → A) в собственном коде — то, что в ESM иногда молча даёт undefined в рантайме. Показывает каждый цикл полной цепочкой, не только первый найденный. Цикл, полностью состоящий из import type, не рантайм-баг и скрыт по умолчанию — --include-types показывает и такие тоже, явно помеченными.
  • exports-doctor — резолвит каждый путь, заявленный в package.json (main/module/types/typings/bin/exports, включая вложенные condition-объекты, subpath-карты и fallback-массивы), против того, что реально лежит на диске. Ловит несуществующий файл, файл с другим регистром, bin-запись без #!/usr/bin/env node, и — самое коварное — subpath, чьи рантайм-условия резолвятся штатно, но types для него нигде не объявлены вообще, так что TypeScript-потребитель молча остаётся без подсказок типов.
  • readme-check — вытаскивает ts-блоки кода из README/доков и реально тайпчекает их через TypeScript Compiler API против собственного tsconfig.json пакета — ничего не выполняется, только компилируется. Виртуальный файл живёт в корне пакета, так что резолвятся и относительные импорты, и self-reference импорт по собственному опубликованному имени пакета (import { x } from 'my-package') — ровно так же, как у настоящего потребителя.
  • empty-catch — находит catch-блоки, которые ничего не делают с ошибкой, или делают настолько мало, что она по факту проглатывается: полностью пустые, или тело, состоящее только из вызовов console.*, без throw, без записи в переменную внешней области видимости, без осмысленного return. Комментарий внутри блока освобождает находку от отчёта — тот же принцип, что у ESLint-правила no-empty для документированных пустых блоков.
  • todo-report — сводка по TODO/FIXME/HACK-комментариям в проекте — file:line плюс сам текст заметки, включая обёрнутую на несколько строк // или многострочный /* */-блок, склеенную в одну читаемую строку. --tags настраивает список меток; --max <n> поднимает планку с «падать на любой находке» до заданного числа — так уже накопленные заметки не блокируют сборку, а падать команда начинает, только когда их становится больше.
  • scripts-check — сверяет scripts из package.json с README/доками и .github/workflows/*.yml: упомянутый, но не объявленный скрипт (реально сломанная ссылка), и, как более слабый сигнал, объявленный скрипт, который нигде не задокументирован. Зарезервированные npm-именами жизненного цикла скрипты исключены из второго направления; шаг CI со своим working-directory: понимается как принадлежащий другому package.json.
  • orphan-tests — находит тестовые файлы, у которых пропал исходник, тест остался зелёным и ничего реального больше не проверяет. Надёжна только при простом соглашении об именовании: совмещённый вариант (включая сестринскую __tests__/tests-директорию на уровень глубже), либо явная зеркальная раскладка --source-dir/--test-dir.
  • stale-ts-ignore — находит // @ts-ignore, который больше ничего не подавляет. Запускает реальный тайпчек всего проекта дважды — один раз как есть, второй раз со всеми директивами замаскированными — и сравнивает дельту на месте каждой из них; самая дорогая команда здесь, поэтому предупреждает об этом перед началом работы и вообще не запускает дорогую часть, если в проекте нет ни одной @ts-ignore. .vue-файлы получают изолированную проверку по одному файлу — тот же приём, что readme-check использует для своих виртуальных файлов.
  • ui — запускает локальный веб-интерфейс и открывает его в браузере, чтобы команды выше можно было выполнять формами, таблицами и графами вместо флагов. Проверки пакета в одном месте с обзором по всем сразу, каждая находка открывается рядом с кодом с подсветкой синтаксиса, циклы импорта рисуются графом; очистка кода с diff, который вы подтверждаете по файлам, и откатом. Сервер слушает только этот компьютер, под одноразовым токеном, и ничего не пишется без явного выбора.
  • full-check — прогоняет все тринадцать команд выше за один проход, каждую в её уже безопасном режиме, и показывает одну сводную таблицу вместо тринадцати отчётов. Поскольку stale-ts-ignore — единственная команда, способная заметно замедлить обычный запуск, devtoolz full-check без флагов в настоящем терминале сначала спрашивает про неё; --skip stale-ts-ignore пропускает и вопрос, и саму команду.

strip-comments, console-strip и case-check используют одну и ту же модель безопасности: --dry-run (или просто запуск без --dry-run и -y) только показывает превью, -y/--yes обязателен, чтобы реально что-то записать, --diff показывает настоящий unified diff по каждому файлу. dead-exports, unused-deps, circular-imports, exports-doctor, readme-check, empty-catch, todo-report, scripts-check, orphan-tests, stale-ts-ignore и full-check — только для чтения, вообще ничего не пишут — превью не нужно. Каждая команда поддерживает --json для машиночитаемого вывода.

Вывод и цвета ​

Каждая команда раскрашивает вывод одной общей палитрой: пути к файлам — голубым, а их :строка:столбец приглушены, имена и виды находок — жёлтым, заголовок «N problems found» — жирным красным, то, что команда сделала бы, — жирным жёлтым, а то, что сделала, — жирным зелёным, подсказки приглушены, а упомянутые в них флаги (-y, --dry-run) выделены голубым, ✔/✖ в таблице full-check — зелёным и красным. Диффы, как и раньше, красно-зелёные.

Цвет включён в настоящем интерактивном терминале и сам выключается, когда вывод перенаправлен в пайп, когда задан CI или NO_COLOR, или при TERM=dumb. Переопределяют это четыре вещи:

  • --color (или FORCE_COLOR=1) включает цвет всё равно — для devtoolz … --color | less -R или CI-лога, который отображает ANSI.
  • --plain выключает сразу цвет, баннер и поздравление.
  • --quiet убирает только баннер и вывод чистого прогона; цвет остаётся.
  • --json печатает отчёт как данные, вообще без оформления.

Текст одинаков с цветом и без него — добавляются только escape-коды, поэтому цветной лог читается нормально и после их удаления.

Как это работает ​

Реальный разбор AST через TypeScript Compiler API там, где проверка этого требует, а не угадывание по регэкспу — строка, шаблонный литерал или содержимое регулярного выражения, которое просто содержит искомый текст, никогда не затрагивается. exports-doctor и readme-check смотрят на пакет так же, как смотрел бы настоящий потребитель: первая сверяет заявленные в package.json пути с реальным диском, вторая типчекает примеры из документации против собственной сборки пакета — обе ловят именно тот класс расхождений между тем, что написано, и тем, что реально работает, который иначе всплывает только у кого-то снаружи. scripts-check — осознанное исключение: она сканирует прозу README и текст YAML, а не разбирает их, и честно так и заявляет, не выдавая себя за AST-точность, которой у неё нет. stale-ts-ignore заходит в реальную компиляцию дальше всех остальных команд здесь — настоящий ts.Program по всему проекту, запускаемый дважды, а не по одному файлу или изолированному блоку.