Справочник
Архитектура
Единый CLI-раннер на commander с одной подкомандой на команду — каждая подкоманда самодостаточна: свой парсинг флагов, свой модуль с ядром логики, свой рендерер отчёта. Общий для всех четырнадцати код — три вещи: модуль вывода (баннер, цвета, выровненные колонки для списков файлов/находок, единообразный рендеринг unified diff), модель безопасности --dry-run/-y/--diff у команд, которые пишут на диск, и — для каждой команды, где это применимо, — принцип «реальный AST, не регэксп»: разбор кода идёт через ts.createSourceFile/сканер TypeScript Compiler API, exports-doctor резолвит пути через readdir посегментно (не через existsSync, который на Windows/Mac соврёт про регистр), readme-check компилирует один виртуальный код-блок через полноценную ts.Program с кастомным CompilerHost, а stale-ts-ignore применяет тот же приём уже ко всему реальному проекту, дважды — больше всего вычислений среди всех команд здесь. scripts-check — осознанное исключение: проза README и YAML CI-конфигов — не код для разбора, так что она сканирует текст и честно об этом заявляет, не выдавая себя за точность, которой у неё нет; orphan-tests вообще не смотрит на содержимое source, а проверяет существование файлов на диске.
full-check — единственная команда, которая вообще не является отдельной диагностикой: она напрямую вызывает собственные программные run*()-функции остальных тринадцати (каждая уже экспортирована из корня пакета ровно для такой композиции) и никогда не передаёт ту опцию, что позволила бы команде реально что-то записать на диск. Каждый вызов обёрнут по отдельности, так что падение одной команды не срывает весь остальной прогон. Её интерактивный вопрос (только про stale-ts-ignore, только в настоящем терминале) сознательно сделан одним-единственным question()-вызовом без цикла повторного запроса — реальное тестирование показало, что ВТОРОЙ question() на том же интерфейсе readline/promises может молча потерять ответ и зависнуть навсегда, если во входном потоке уже буферизовано больше одной строки — настоящая особенность пайпнутого/неинтерактивного ввода, не баг конкретно этого кода; один вопрос с явным дефолтом на любой нераспознанный ответ обходит весь этот класс сбоя, а не рискует наткнуться на него.
Загрузчик tsconfig.json (автоопределение через ts.findConfigFile, реальные опции компилятора через ts.parseJsonConfigFileContent) живёт в src/utils/load-tsconfig.ts, не внутри какой-то одной команды — изначально был приватным внутри readme-check, вынесен в общую инфраструктуру, как только та же логика резолва понадобилась и stale-ts-ignore (плюс реальный список файлов проекта, который самому readme-check для проверки одного блока никогда не был нужен).
Граф импортов (разбор import/export, резолв относительных спецификаторов, обнаружение pnpm/npm/yarn-воркспейса) живёт в src/utils/, не внутри какой-то одной команды — изначально был частью dead-exports, вынесен в общую инфраструктуру, как только понадобился второму потребителю (unused-deps/circular-imports), а не заранее. Тот же принцип применён и к поиску настоящих комментариев (маскирование каждого строкового/шаблонного/regex/JSX-текстового участка перед сканом на токены комментариев, чтобы ////* внутри него никогда не спутался с настоящим комментарием) — изначально был частью strip-comments, переехал в src/utils/, как только понадобился и todo-report.
Юмор и цвет в выводе — не украшение ради украшения, а часть одного и того же модуля вывода: баннер и поздравление при чистом результате рандомизируются из общего пула фраз, само оформление находок остаётся сухим и легко сканируемым независимо от настроения баннера, а цвет автоматически выключается вне реального интерактивного терминала (CI, NO_COLOR, TERM=dumb, пайп) поверх явных --quiet/--plain; --color и FORCE_COLOR включают его обратно. Каждый отчёт собирает оформление через один небольшой модуль стилей (src/format/style.ts), а не пишет escape-коды сам, так что палитра меняется в одном месте, а текст отчёта одинаков с цветом и без него. См. Вывод и цвета.
Сравнение
Не дублирует LintSync (синхронизация конфигов ESLint/Prettier/Stylelint между проектами) и не дублирует Polyrepo (версии, релизы, публикация, рассинхрон зависимостей между локальными репозиториями) — оба уже покрывают свою нишу, и в DevToolz сознательно не попало ничего из их области. Не замена и полноценному линтеру: ESLint/Biome настраиваются один раз на весь проект и держат в голове десятки правил сразу, а каждая команда DevToolz — узкая, самодостаточная проверка ровно одной вещи, без конфигурации, которую можно запустить разово прямо перед коммитом или встроить в CI отдельным шагом, не устанавливая и не поддерживая общий конфиг.
Разработка
git clone https://github.com/macrulezru/devtoolz.git
cd devtoolz
npm install
npm testТесты — сквозные, на реальных временных директориях (mkdtempSync + реальные файлы на диске), а не на моках AST — именно эта дисциплина не раз ловила настоящие баги на реальной файловой системе, включая специфичные для Windows (регистро-нечувствительность, обратные слэши в путях, которые TypeScript внутри всегда нормализует к прямым).
npm run build # tsc -> dist/
npm run typecheck # tsc --noEmit
npm run lint # eslint .
npm run format # prettier --check .Лицензия
MIT — см. LICENSE.