Архитектура¶
indexnow/
├── action.yml # определение composite GitHub Action (inputs/outputs/steps)
├── scripts/ # шаговые скрипты action'а
│ ├── resolve-version.sh # input → ref action'а → "latest"
│ ├── detect-platform.sh # RUNNER_OS/ARCH → имена архивов goreleaser
│ ├── install.sh # скачать + sha256-проверить + распаковать в tool cache
│ ├── preflight.sh # проверка «ровно один источник URL»
│ └── run.sh # сборка argv для submit, парс JSON, outputs + step summary
├── cmd/indexnow/main.go # cobra-обвязка, ENV/config-defaults, exit-code-обвязка
└── internal/
├── cli/ # логика в тестируемой форме (без cobra)
│ ├── submit.go # RunSubmit, сбор URL, fan-out по эндпоинтам, рендеринг, fail-on
│ ├── keygen.go # RunKeygen, запись hosted key-файла
│ ├── verify.go # RunVerify, проверка hosted-ключа
│ └── errors.go
├── client/ # HTTP-клиент IndexNow
│ ├── client.go # Submit / SubmitBatch, retry-loop, marshal payload
│ ├── endpoints.go # резолв алиасов в URL (одиночный и comma-separated)
│ ├── retry.go # математика backoff / jitter
│ └── errors.go
├── config/config.go # опциональный yaml-конфиг (XDG-путь по умолчанию или --config)
└── sitemap/sitemap.go # потоковый парсер urlset / sitemapindex
Разделение слоёв¶
cmd/indexnowдержит только cobra и OS-обвязку: флаги, сигналы, мердж flag > env > config > default, маппингcli.Exit*в process exit code.internal/cliдержит поведение CLI в тестируемом виде:RunSubmit(ctx, opts, stdin, stdout, stderr, factory) int, плюсRunKeygenиRunVerifyв той же форме. Вход —io.Reader, выход —io.Writer, HTTP-клиент инжектится черезSubmitterFactory, поэтому тестам не нужна сеть.internal/clientдержит wire-формат IndexNow и retry-политику. Безопасен для конкурентного использования; конфигурируется черезclient.Config.internal/configдержит yaml-файл: строгий декод (неизвестные поля отвергаются), дефолтный путь$XDG_CONFIG_HOME/indexnow/config.yamlи различение «нет дефолтного файла — не ошибка, нет явного--config— ошибка».internal/sitemapдержит wire-формат sitemap.org. Матчинг идёт по local name элемента, поэтому документы безxmlnsтоже парсятся; поток идёт черезencoding/xml, так что sitemap на 50 МБ / 50 000 записей не грузится в память целиком. Рекурсия по<sitemapindex>ограниченаMaxDepth = 5, посещённые источники дедуплицируются — самоссылающиеся индексы завершаются корректно.action.yml+scripts/оборачивают релизный бинарь, а не переизобретают логику: action резолвит версию, сверяет checksum и вызываетindexnow submit --output json.
Fan-out по эндпоинтам¶
--endpoint резолвится в список; каждый элемент получает свою goroutine и свой client.Client, батч URL'ов у всех общий. Результаты возвращаются как один endpointBatch на эндпоинт, в порядке входа — вывод детерминирован независимо от того, кто ответил первым. Wall-time равен времени самого медленного эндпоинта, а не сумме.
Retry-политика¶
Клиент ретраит HTTP 429, 5xx и transport-ошибки. Backoff экспоненциальный с jitter'ом, ограничен BaseBackoff и MaxBackoff. Заголовок Retry-After парсится и как секунды, и как HTTP-date. Каждый Result несёт итоговый StatusCode, число Attempts, URL батча и итоговую ошибку (если есть).
Батчинг¶
SubmitBatch режет вход на куски по MaxBatchSize = 10000 (лимит протокола) и отдаёт по одному Result на HTTP-вызов, в порядке отправки.