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'sexports/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'senvironment: '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 по всему проекту, запускаемый дважды, а не по одному файлу или изолированному блоку.