Реестр пакетов
Реестр — это обычный репозиторий GitHub. Каждая папка верхнего уровня — это
пакет, и в ней лежит один ketch.toml, который его описывает:
ketch-registry/
├── README.md ← не пакет: нет ketch.toml
├── fd/
│ └── ketch.toml
├── jq/
│ └── ketch.toml
└── ripgrep/
└── ketch.toml
Имя папки и есть имя пакета — именно с ним сопоставляется ketch install <name>.
Нет индексного файла, который надо держать в согласии с содержимым, а
папка без ketch.toml просто не является пакетом, поэтому в репозитории
рядом с пакетами могут лежать README, лицензия и конфигурация CI.
Файл пакета
# ripgrep/ketch.toml
source = "github:BurntSushi/ripgrep"
description = "Recursively search directories for a regex pattern"
homepage = "https://github.com/BurntSushi/ripgrep"
bin = [{ name = "rg" }]
provides = ["rg"]
source — единственное обязательное поле. Остальные — те же поля, что принимает
пользовательский манифест в ~/.ketch/manifests/: MANIFESTS.md содержит
полную схему, а crates/ketch-core/src/builtin.toml — рабочий пример каждого поля.
name можно указать, но оно должно совпадать с именем папки — пакет, который
расходится со своей папкой, был бы недоступен под именем, которое объявляет папка,
поэтому ketch отклоняет его, а не индексирует тихо дважды.
Что проверяет ketch
Запись реестра — это код, написанный кем-то другим, который в итоге создаёт файлы на диске незнакомого человека, поэтому её проверяют, прежде чем ей доверять:
| Отклоняется | Почему |
|---|---|
неизвестный ключ (binary = … вместо bin = …) | ключ с опечаткой, который молча игнорируется, устанавливает не то и нигде не жалуется |
источник local: | запись реестра должна называть релиз, который может скачать кто угодно, а не путь на диске одной машины |
name, расходящееся с папкой | пакет был бы недоступен под именем, которое объявляет его папка |
name или bin.name, непригодные как имя файла | name становится каталогом в хранилище, а bin.name — ссылкой в ~/.ketch/bin; ../../.zshrc — не бинарник |
запись bin.path или extra_paths, содержащая .. | пути отсчитываются от распакованного содержимого и должны оставаться внутри него |
запись bin без name и без path | она не описывает ничего, на что можно поставить ссылку |
запись provides с пробельными символами | её никто не сможет набрать |
strip_prefix больше 8 | каждый уровень — это листинг каталога содержимого, и ни один реальный архив не вкладывает обёртки так глубоко |
Папка, не прошедшая проверку, на машине, где реестр уже есть, отображается в отчёте и пропускается,
так что одна плохая запись никогда не ломает остальную копию.
ketch update также предупреждает, когда два пакета претендуют на одно имя: каждая папка
корректна сама по себе, но разрешаться будет только одна из них. Клиенты действуют
по принципу best-effort в отношении уже опубликованного реестра: они предупреждают и пропускают, но
не отвергают всё дерево.
До слияния те же проблемы фатальны. ketch registry validate превращает
ошибки разбора, ошибки валидации и конфликты имён или алиасов в проваленный
запуск, чтобы они не могли попасть в реестр.
Manifest::validate применяется и к ~/.ketch/manifests/*.toml.
Записи реестра должны дополнительно пройти правила про local: и имя папки из
таблицы выше — правила, которых пользовательские манифесты никогда не видят, — поэтому манифест, который
устанавливает локально, не обязательно можно внести в реестр как есть.
Использование
ketch update # загрузить реестр в ~/.ketch/registry
ketch registry status # возраст и источник локальной копии; без сети
ketch search fd # искать в нём, а также на GitHub
ketch install fd # установить по имени
ketch doctor # строка реестра включает число пакетов, возраст и ревизию
Ничто не загружает реестр неявно: имя, которое разрешается сегодня, продолжит
разрешаться офлайн и завтра, а ketch install owner/repo реестр вообще никогда не нужен.
ketch update — единственный способ обновления. ketch registry status и ketch doctor читают локальную копию и registry.meta.toml (ревизию источника и
время загрузки, хранятся в корне ketch рядом с манифестами) и никогда
не открывают сетевых соединений.
Рабочий процесс авторов и мейнтейнеров
Точная команда валидации, запускаемая из корня репозитория реестра (или из любого дерева с такой же структурой):
ketch registry validate .
Именно она должна пройти до слияния: каждый ketch.toml разобран, сверен
с именем своей папки, отклонён, если source равен local:, пропущен через
Manifest::validate, а конфликты имён и алиасов проваливают
запуск.
Чтобы ещё и офлайн установить записи, которые меняет пул-реквест, положите по одному локальному ассету на пакет в каталог фикстур — файл с именем пакета или папку с этим именем, в которой ровно один файл, — и назовите пакеты:
ketch registry validate . --fixture ./ci/fixtures --changed ripgrep --changed fd
Без --changed устанавливается каждый пакет, у которого есть фикстура. --changed
без --fixture — ошибка. Установка выполняется во временном корне ketch
из файла фикстуры, поэтому источник github: в записи никогда не обращается к
сети. Отсутствующий бинарник, неверный bin.path или архив, который не
распаковывается, проваливают запуск так же, как конфликт.
Совместимость
- Локальная валидация (
ketch registry validate) работает по принципу fail-closed. Некорректные или конфликтующие записи проваливают запуск, поэтому их нельзя отправить в upstream. - Клиенты (
ketch update, поиск, установка по имени) действуют по принципу best-effort в отношении уже опубликованного плохого реестра: они предупреждают, пропускают сломанную папку и продолжают разрешать остальное. Они никогда не загружают реестр как побочный эффект status или doctor. - Установка из фикстуры — дополнительное доказательство для изменённых записей. Она не заменяет проверку конфликтов по всему дереву.
От CI для реестра намеренно отказались (plan.md F2): репозиторий ketch-registry
удалил свой единственный workflow (коммит 5a9bbd6, "no CI is wanted in
this repo"), так что в upstream нет workflow, куда это можно встроить. Проверяйте локально
перед отправкой, из корня клона реестра (или из любого дерева с такой же
структурой):
ketch registry validate .
Добавьте ту же проверку как pre-push хук, чтобы ничего непроверенного не покинуло машину, — из корня клона реестра:
cat > .git/hooks/pre-push <<'EOF'
#!/bin/sh
# Отклонить push, если дерево реестра не проходит валидацию.
ketch registry validate . || exit 1
EOF
chmod +x .git/hooks/pre-push
registry push выполняет те же проверки отдельного пакета для файла, который собирается
отправить. Он не сканирует остальной реестр на конфликты имён — это делает только
registry validate. Он не выполняет офлайн-установку из фикстуры.
Добавление пакета
ketch.toml в корне проекта — это тот же файл, который лежал бы в его папке в реестре,
поэтому его добавление — одна команда, запускаемая из этого корня:
KETCH_GITHUB_TOKEN=... ketch registry push # загрузить, сравнить, затем пул-реквест
ketch registry push --yes # ответить на вопрос заранее
ketch registry push --dry-run # что было бы отправлено, офлайн
ketch registry push --file path/to/ketch.toml # файл в другом месте
ketch registry push --registry someone/their-registry # реестр, отличный от стандартного
После того как файл прошёл проверки, registry push загружает текущую копию
<name>/ketch.toml из ветки по умолчанию реестра, и что происходит
дальше, зависит от её содержимого:
| Копия в реестре | Что делает registry push |
|---|---|
| не существует | сразу открывает пул-реквест |
| совпадает байт в байт | сообщает unchanged и ничего не открывает |
| отличается | печатает unified diff — копия реестра как старая сторона, локальный файл как новая — и открывает пул-реквест с обновлением только после ответа да/нет |
Затем он кладёт файл в <name>/ketch.toml в ветке с именем ketch/<name> и открывает пул-реквест
в ветку по умолчанию реестра. При наличии прав на push в
реестр ветка создаётся там; без них — в форке реестра
под вашей учётной записью, который при необходимости создаётся. Повторная отправка обновляет ту же ветку
и присоединяется к уже открытому пул-реквесту, а не открывает второй.
--yes отвечает на вопрос заранее — для скриптов и CI, где
некого спросить, или когда вы уже знаете, что покажет дифф. --dry-run
показывает, что было бы отправлено, не обращаясь к сети.
name можно не указывать в файле — тогда пакет называется по папке, в которой
он лежит, по тому же правилу, что применяет реестр. Файл отправляется ровно в том виде, в каком
написан, включая комментарии.
Токен — тот же, что используют все остальные команды (KETCH_GITHUB_TOKEN или
github_token в config.toml); ему нужна область repo, а для
fine-grained токена — права на contents и pull requests в реестре и, если
участвует форк, право его создать.
Приоритет
Имя разрешается по порядку:
~/.ketch/manifests/<name>.toml— ваши собственные манифесты- загруженный реестр
- реестр, вкомпилированный в бинарник (
crates/ketch-core/src/builtin.toml) - вывод из
owner/repo
Так что локальный манифест всегда побеждает, а пакет, курируемый в реестре, побеждает более старую копию, вшитую в ту сборку ketch, которую вы сейчас запускаете.
Использование другого реестра
export KETCH_REGISTRY=someone/their-registry
или в ~/.ketch/config.toml:
registry = "someone/their-registry"
По умолчанию используется pyrlyn/ketch-registry. Принимается только owner/repo;
загружается ветка репозитория по умолчанию.