Справочник
Архитектура
У каждого поддерживаемого формата конфига (JSON/JSONC, YAML, JS/TS) свой адаптер, выбираемый по расширению файла. JSON-адаптер — тонкая обёртка над point-edit API jsonc-parser. YAML-адаптер построен на изменяемой модели документа из пакета yaml. JS/TS-адаптер построен на ts-morph и читает и пишет только литеральные значения (строки, числа, булевы, null, а также массивы/объекты, целиком построенные из них) — динамическое выражение (spread, вызов функции, импортированная переменная) остаётся нетронутым, LintSync сообщает, что не может безопасно его изменить, вместо того чтобы угадывать. Поэтому же JS/TS-адаптер умеет читать настоящий flat config ESLint в виде массива — при условии, что объект конфига является последним элементом массива (именно так выглядит реальный flat config с раскрытыми через spread базовыми конфигами).
Трёхстороннее сравнение в sync, по каждому управляемому ключу, сводится к следующему:
- Значение в файле равно последнему применённому значению из манифеста (ключ не трогали) → если оно при этом отличается от пресета, это изменение к применению; иначе просто обновляется учёт в манифесте.
- Значение в файле уже равно значению пресета (вы независимо сделали ту же правку, либо ничего не менялось) → писать нечего.
- Всё остальное → конфликт.
Конфликт по любому ключу прерывает весь пакет ожидающих изменений для этого инструмента — файл не пишется наполовину. Wildcard-ы в managedKeys (rules.*) совпадают ровно с одним сегментом пути в этой позиции — не рекурсивный и не многосегментный glob — а набор проверяемых кандидатов-ключей это объединение того, что реально есть в файле, и того, что определяет сам пресет, поэтому правило, добавленное более новой версией пресета, показывается в status/sync ещё до того, как оно появится в файле проекта.
Известное ограничение: YAML-адаптер не разворачивает merge-ключи (<<: *anchor). Управляемый ключ, заданный только через смёрженный якорь, читается как отсутствующий, а не как его реальное смёрженное значение — из-за этого sync может решить, что ключ нужно добавить, хотя он уже фактически задан через якорь.
Сравнение
В отличие от шэрингового конфига ESLint, опубликованного отдельным npm-пакетом (eslint-config-*, @org/eslint-config), LintSync вообще не добавляет зависимость через extends — он записывает правила пресета прямо в ваш собственный конфиг, так что на выходе — обычный, полностью видимый конфиг, который можно прочитать и поправить руками, а не непрозрачный extends: ['@org/eslint-config'], указывающий на чужой пакет. Плата за это — ровно то, вокруг чего построен LintSync: у обычного шэрингового конфига нет способа отличить «базовый конфиг поменял это правило» от «я сам поменял это правило» после того как оба смёрджились в один итоговый конфиг — именно это различие и сохраняют манифест и система конфликтов.
Это также не менеджер общего конфига для одного монорепозитория (ничто здесь не предполагает workspace или подъём конфига из корня) — его пакетный режим нацелен на папку из независимых друг от друга проектов, у каждого свои файлы конфига, а не на один workspace с одним общим расположением конфига.
Разработка
git clone https://github.com/macrulezru/lintsync.git
cd lintsync
npm install
npm run build # tsc -> dist/
npm test # vitest
npm run lint # eslint .
npm run format # prettier --check .
npm run typecheck # tsc --noEmitЛицензия
MIT — см. LICENSE.