Реестр пакетов

From docs/ru/REGISTRY.md at 4e43843 View on GitHub

Реестр — это обычный репозиторий 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 в реестре и, если участвует форк, право его создать.

Приоритет

Имя разрешается по порядку:

  1. ~/.ketch/manifests/<name>.toml — ваши собственные манифесты
  2. загруженный реестр
  3. реестр, вкомпилированный в бинарник (crates/ketch-core/src/builtin.toml)
  4. вывод из owner/repo

Так что локальный манифест всегда побеждает, а пакет, курируемый в реестре, побеждает более старую копию, вшитую в ту сборку ketch, которую вы сейчас запускаете.

Использование другого реестра

export KETCH_REGISTRY=someone/their-registry

или в ~/.ketch/config.toml:

registry = "someone/their-registry"

По умолчанию используется pyrlyn/ketch-registry. Принимается только owner/repo; загружается ветка репозитория по умолчанию.