Repository navigation
Releases
0.15.0
The test suite of this package stays on PHPUnit ^11.5: laravel/framework 12/13 registers its error handler in a way
PHPUnit 12.5 rejects (ErrorHandler::enable(): Argument #1 must be TestCase), so the family-wide ^11.5 || ^12.0 || ^13.0
of audit 0.13 W2 does not apply here until the framework does.
Changed
- The artisan commands are the command classes of the packages;
Console\*Commandof this package is gone (wave
M, spec 19 §2.1 and §4.1 — spec 18 §9 had said artisan commands must extendIlluminate\Console\Command, which is
not so:Illuminate\Console\Application::resolve()takes any symfony/console command).indexnow:check,
indexnow:config,indexnow:submit,indexnow:submit-model,indexnow:explain,indexnow:key:generateand the
three "not installed" stubs areIndexNowKit\Console\Command\*ofindexnowkit/console0.5 (submit-modelis
SubmitSubjectsCommand, named by the vocabulary, registered through aSymfony\Component\Console\Command\LazyCommand
sophp artisannever builds it before it runs),indexnow:sitemapisIndexNowKit\Sitemap\Console\SitemapCommand
(sitemap 0.8),indexnow:historyandindexnow:statusareIndexNowKit\History\Console\HistoryCommand/
StatusCommand(history 0.4) — the same names, arguments, options, exit codes and--jsonas before, the same
classes the Symfony bundle and the Yii3 package register. The class argument staysmodel
(Artisan::call('indexnow:submit-model', ['model' => Post::class])), through theclassArgumentof the two
commands. Each command is a binding of its own:$this->app->extend(CheckCommand::class, …)replaces one, a
command of your own with the same name registered later shadows one. Whatcheckandconfigread is
Console\ConfigSource(aConsole\ConfigSourceInterface, bound). Migration: the twelve classes werefinal,
nobody extended them; code that resolved one from the container resolves the package's class instead (the names
are the same afterIndexNowKit\Console\Command\,…\Sitemap\Console\,…\History\Console\).
Check\ModelSamplerisIndexNowKit\Console\SubjectSampler(the same class four adapters carried);Console\ModelLoader
extendsIndexNowKit\Console\AbstractSubjectLoader(its constructor is unchanged;findOne()/findMany()are what is Eloquent's). Check\RouterCheckis the core'sCheck\LocalesCheck(same coderouter.locales, same texts, one class for the
three adapters that print the line; the classes it reads are the--sample-classvalues, as before). Migration:
the class was listed in docs/bc.md as a stable name; a check registered by hand becomesnew LocalesCheck($locales, $reader, fn () => [Post::class], 'router.locales', 'locale'). Decision §6.4 of spec 19: no wrapper kept.Queue\QueueDispatcheris the push onto the bus around the core'sDispatch\BatchingDispatcher(the constructor
is unchanged);Queue\SubmitUrlsJob::newId()delegates toBatchingDispatcher::newJobId(); the log lines are the
core's ({count} URL(s) queued as job {id},cannot queue {count} URL(s) (job {id}), they are lost: {error}— the
texts this adapter already printed).Url\LaravelRouteUrlResolverdecides the locale expansion, the pinned origin
and the rebase through the core'sUrl\RouteOrigin(the same behaviour, the warning text unchanged).
Check\CacheStoreProbewrites the core'sDebounceStoreCheck::PROBE_KEY(indexnowkit_check, no colon: PSR-16
reserves it) where it readindexnowkit:check.indexnow:statusdescribes the debounce store as
cache (ArrayStore)—<store> (<StoreClass>), the text ofHistory\Adapter\HistoryServices::describeStore()shared
with Yii2 and Yii3 — where it saidcache (array).Config\ConfigFactoryfolds the packages' options through
OptionalPackage::ownedOptions()/ignoredBlocks(). Thephp artisan aboutlines of an absent package come
from the core's predicate.Eloquent\IndexNowObserverresolves through the change handler, not through the facade. Its constructor now
takes aClosure(): ObjectChangeHandlerand aClosure(list<string>): voidsink instead ofIndexNowKit, so the
firstsave()of any model builds the rules, the resolver and the extractor and nothing else: the submitter, the
client, the transport, the throttle and the debounce store are built in the sink, once a save actually produced a
URL. A change handler the container cannot build is oneerrorline per process, never an exception insave().
Migration: an observer built by hand becomesIndexNowObserver::forKit($kit, $logger, $enabled, $router); the
container binding is unchanged, so applications using the trait orIndexNowKit::observe()do nothing.
Url\ObjectChangeHandleris now a binding of its own (built overAttributeReaderInterface,GuardedUrlResolver
andParamExtractor) and is handed to theIndexNowKitbinding, so replacing it changes both.Psr\Clock\ClockInterfaceis a container binding (IndexNowKit\Clock\SystemClockby default, bound only when
the application has not bound one) and reaches the throttle, the debounce store, the submitter and the command
submitter factory. BindingIndexNowKit\Testing\FrozenClockmakes the debounce window and the submission records
ofindexnowkit/historydeterministic in tests (docs/extending.md, docs/testing.md).Queue\SubmitUrlsJobcarries the attempts its batch already spent ($spentAttempts, appended to the
constructor with a default;attempt()is the batch-wide attempt number,triesis what is left of
retry.max_attempts). A partially accepted batch is re-queued as a new job whoseattempts()starts at one again,
so before thisretry.max_attemptsbounded each job instead of the batch, and a steadily shrinking batch could be
retried far more often than configured. Queued jobs from an older version keep running (spentAttemptsdefaults to 0).Check\SampleOptionsandCheck\VerifySampleCheckare removed: they were copies of the core's
IndexNowKit\Check\SampleOptionsandIndexNowKit\Check\SampleGateCheck(core 0.13), which are now bound under
their own class names. Same container mechanics, sameverify.installedlines, same--samplebehaviour.- The
resolver:container lookup no longer wraps the container's exception itself:Url\ArrayResolverLocatordoes
it for every adapter, so any failure (not only aBindingResolutionException) is now the one family text
IndexNow URL resolver "…" cannot be built by the container: ….
Added
- The
router.localesline ofindexnow:check(the core'sCheck\LocalesCheck, see "Changed"): the locales
locales: 'all'expands to, and a warning namingrouter.localeswhen a rule the command can see
(--sample-class=<FQCN>) asks for every locale while the list is empty. At run time the same situation is one
warning per process fromUrl\LaravelRouteUrlResolver, which used to collapse to a single URL without a word. - A service tagged
indexnowkit.checkthat is not aCheck\CheckInterfaceis now aConfigurationExceptionnaming
the tag when the checker is built, instead of a fatal in the middle of the check run.
Fixed
- Fixed: the package was a fatal without
indexnowkit/sitemap,indexnowkit/verifyorindexnowkit/history
(Class "IndexNowKit\Sitemap\Adapter\SitemapServices" not foundwhen the provider registered or the config was
built):Config\ConfigFactory::factory()and the provider asked the packages'*Services::package()whether the
package is installed, and those classes live in the packages. They now ask the core'sAdapter\OptionalPackage::sitemap()
/verify()/history()(core 0.13.0);Sitemap\SitemapServices::package(),Verify\VerifyServices::package()and
History\HistoryServices::package()delegate there too, sooverrideApplicationBindings()in tests is unchanged.
Same texts, same container ids. A new CI job removes the three packages and boots the application with detection
(OptionalPackagesDetectionTest). - Requires
indexnowkit/core ^0.13,indexnowkit/console ^0.5and, when installed,indexnowkit/history ^0.4(the
version cascade of wave L, spec 18: the artisan commands keep parsing over the runners, nothing changes here).
0.14.0
Changed
- The optional packages wire themselves:
Verify\VerifyServices,History\HistoryServicesandSitemap\SitemapServices
keep their container ids andregister()but build every piece throughVerify\Adapter\VerifyServices,
History\Adapter\HistoryServicesandSitemap\Adapter\SitemapServicesof the packages (verify 0.3, history 0.3,
sitemap 0.7 —conflictwith older ones); the check texts, the store and decorator constructions are no longer
copied here. What stays is Laravel's: the config repository, the cache and database managers,runningInConsole(),
the queue facts,php artisan about. - Requires
indexnowkit/verify ^0.3,indexnowkit/history ^0.3,indexnowkit/sitemap ^0.7when installed.
0.13.1
Changed
IndexNowManager::submitModels()delegates toIndexNowKit::submitEntities(); theUrlResolverInterfacebinding passes
theParamExtractorbinding as the third argument ofAttributeUrlResolver::fromConfig()(core 0.12.0).- Requires
indexnowkit/core ^0.12.
0.13.0
Changed
Queue\SubmitUrlsJobre-queues only the rejected URLs of a partially accepted batch, as a new job on the same
connection and queue with the engine's delay;release()(which replays the whole payload) is used only when every URL
was rejected. Before, aRetry-Afterlonger thandebounce.per_urlre-sent the accepted URLs too.Queue\QueueDispatcherpushes one job perbatch.max_urlsURLs: a bulk import was one job an SQS payload limit
rejected, and every URL was lost with one log line.checkgetsverify.transport;config/indexnow.phpgetsverify.time_budget.publishes()is registered in the console only, as Laravel's package conventions have it;psr/log ^1.1is gone from
the constraint (the core requires^2 || ^3).- Requires
indexnowkit/core ^0.11,indexnowkit/console ^0.4; tests againstverify ^0.2,history ^0.2,sitemap ^0.6.
0.12.0
Added
IndexNowKit\Attribute\ParamExtractorbinding:new ParamExtractor(new EloquentSubjectReader()), shared by the resolver,
the change handler, the facade andindexnow:explain. Extend it for objects neither Eloquent nor the DSL can see into:
$this->app->extend(ParamExtractor::class, fn(ParamExtractor $e) => $e->with(new CmsFieldReader())).
Changed
- Requires
indexnowkit/core ^0.10; the provider no longer calls the removed staticParamExtractor::registerReader()(the
reader is in the binding instead — the same accessors resolve the same way).
0.11.0
Added
indexnowkit/verifywiring (spec 17 §6.1).config/indexnow.phpgets theverifyblock (enabledfrom
INDEXNOW_VERIFY,redirect,non_canonical,origin_error,delay,timeout,max_redirects,max_batch,
robots_cache_ttl,user_agent); with the package andverify.enabled: truetheSubmitterInterfaceand
SubmitterFactoryInterfacebindings are extended withVerifyingSubmitter/VerifyingSubmitterFactory
(Verify\VerifyServices), sodispatch: sync, the queue job and every command verify. New bindings:
VerifyConfig,RobotsCache,VerifyServices::TRANSPORT(the pre-flight transport), the plain factory under
IndexNowKitServiceProvider::UNVERIFIED_SUBMITTER_FACTORY, the predicate underVERIFY_PACKAGE
(VerifyServices::package(false)in tests),PACKAGE_BLOCKS(the extra sections ofindexnow:config).
checklines:verify.installed,verify.dispatch(warning withdispatch: sync),verify.sample. Without the
package the block is ignored as a whole (checksays so) and--sampleis an error naming the install line.indexnow:check --sample=<url>/--sample-class=<FQCN>[:<id>](repeatable;Check\SampleOptions,
Check\VerifySampleCheck,Check\ModelSamplerover the model loader).indexnow:config --jsonprints theverifysection;php artisan abouthas aVerifyline.indexnowkit/historywiring (spec 17 §6.2).config/indexnow.phpgets thehistoryblock (storefrom
INDEXNOW_HISTORY_STORE,limit,key_prefix,pdo.dsnfromINDEXNOW_HISTORY_PDO_DSN,pdo.service,
pdo.table,retention_days); with the package andhistory.store: psr16|pdotheSubmissionStoreInterface
binding is extended (History\HistoryServices): the null store becomesPsr16SubmissionStoreover the cache store
ofdebounce.store(the default store withmemory/none) orPdoSubmissionStoreover
DB::connection(history.pdo.service)->getPdo()/ a PDO ofhistory.pdo.dsn— never both — so sync flushes, the
queue job, the commands and the verify decorator record into it; a store the application binds itself is left
alone. The table is not created (see the package'sdocs/migrations.md). New bindings:HistoryConfig,
Retry\ForbiddenCounter,HistoryRunner,StatusRunner, the predicate underIndexNowKitServiceProvider::HISTORY_PACKAGE
(HistoryServices::package(false)in tests). Commandsindexnow:history(--host,--status,--url,
--since,--limit,--json,--purge[=days]) andindexnow:status(--jsonper the package's
status.schema.json; the queue connection and queue as the adapter facts); without the package
Console\HistoryNotInstalledCommand/StatusNotInstalledCommandprint the install line and exit 1.check
lines:history.installed(without the package),history.store,history.records.indexnow:config --json
prints thehistorysection;php artisan abouthas aHistoryline.Config\ConfigFactory::factory()/create()/build()take an appended?bool $verifyInstalled = nulland
?bool $historyInstalled = null.
Changed
- Requires
indexnowkit/core ^0.9,indexnowkit/console ^0.3and (dev/suggest)indexnowkit/sitemap ^0.5,
indexnowkit/verify ^0.1,indexnowkit/history ^0.1.
0.10.0
Changed
- Requires
indexnowkit/core ^0.8,indexnowkit/console ^0.2and (dev/suggest)indexnowkit/sitemap ^0.4. Core 0.8 strips tracking parameters by default (normalizer.strip_tracking_params) and makesEqualsinparamsan error (see the core changelog).
Added
- The
indexnow:checklines of the adapter carry stable codes (core 0.8,check --json):queue.dispatch,
queue.connection,queue.driver,eloquent.enabled, plus the core'sdebounce.storeandsitemap.installed.
Listed in the core'sdocs/check-codes.md. indexnow:check --json(the report as JSON, schemadocs/check.schema.jsonofindexnowkit/console),--strict
(warnings fail the command: put it in the deploy pipeline) and a repeatable--host(console 0.2).indexnow:key:generate --forcekeeps the replaced key asINDEXNOW_PREVIOUS_KEYand refuses a second rotation while
it is set;--no-previousand--yesdecide (console 0.2).- The 403 escalation of
Clientcounts in the cache store behinddebounce.store(core 0.8; the store's
increment()makes it atomic): onecriticalline per streak for every worker.IndexNowKitServiceProvider::FAILURE_CACHE
is the container id of that?Psr\SimpleCache\CacheInterface(null withmemory/none). - Binding
Submission\SubmissionStoreInterface(NullSubmissionStoreby default): the store the submitter and the
command submitters record everyResultin (core 0.8); rebind it after the provider to keep a history. - Configuration block
normalizer(strip_tracking_params,tracking_params,trailing_slash,sort_query) in
config/indexnow.php: the canonical form of every URL (core 0.8);UrlNormalizerInterfaceis bound through
Url\UrlNormalizerFactory. Tracking parameters are stripped by default. indexnow:explain --jsonand thewhenvalues in the text output (console 0.2).indexnow:config(--json): the effective configuration with masked keys plus this adapter's own keys (console 0.2).- Results are Laravel events (spec 17 §5.7): every
Resultgoes through the event dispatcher
(Event::listen(Result::class, …), Telescope,Event::fake()), from the submitter and the command submitters alike,
through the PSR-14 bridgeEvent\EventDispatcherBridge(container idIndexNowKitServiceProvider::EVENTS). php artisan abouthas anIndexNowsection: core version, enabled/dry-run, environment, base URL, masked key,
engines, dispatch, debounce, the check command.
0.9.0
Changed
- Requires core 0.7:
Console\SubmitterFactory/Console\SubmitterFactoryInterfaceare now
IndexNowKit\Adapter\SubmitterFactory/IndexNowKit\Adapter\SubmitterFactoryInterface,Console\ResultSummaryis
IndexNowKit\Submission\ResultSummary. Application code that names them (a decorator of theSubmitterFactoryInterfacebinding) changes theuseline; nothing else. - The test suite requires
indexnowkit/testing ^0.1(require-dev): the conformance kits and the H01–H05 assertions
moved there from the core (Testing\Conformance\KeyFileAssertions,CheckOutputAssertions,ReadmeAssertions). - Requires
indexnowkit/console ^0.1: the runners and definitions the artisan commands are built on moved there from the core
with their FQCN unchanged (IndexNowKit\Console\*); Composer installs it with this package, nothing to do. Sitemap\SitemapSupport(the@internalpredicate with its static override) is gone: the provider binds an
IndexNowKit\Adapter\OptionalPackageunderIndexNowKitServiceProvider::SITEMAP_PACKAGE(SitemapServices::package()).
A test that booted without the package throughSitemapSupport::$installed = falsebinds
IndexNowKitServiceProvider::SITEMAP_PACKAGE => fn() => SitemapServices::package(false)before the provider
registers (Testbench:overrideApplicationBindings();defineEnvironment()runs afterregister()).Config\ConfigFactory::factory(),create()andbuild()take an
appended?bool $sitemapInstalled = null. Thecheckline for a configured but ignoredsitemapblock is a
warning now (it was ok). The invalid-block critical line comes fromSitemapConfig::loadOrDisabled()(same text).
Added
resources/boost/guidelines/core.blade.php: Laravel Boost picks it up onboost:install(conventions, a model
snippet, verification).
Documentation
- README: the quick-start model is
tests/Readme/Post.phpverbatim (completeuselines, thecategoryrelation the
via:rule reads, casts/defaults that make it run);ReadmeQuickstartTestcompares the README block with the file
and runs the model through the test application against the FakeTransport. - README: "Notes for AI assistants" (package, minimal complete snippet, verification, pitfalls across the adapters);
ReadmeAiNotesTestkeeps it consistent with the commands and configuration keys. - README "Operations": the production checklist first, then monitoring rules, deleted pages, what not to submit,
multi-domain and troubleshooting. docs/troubleshooting.md: "Staging submitted its URLs" and "Duplicates with
memoryand several workers". docs/multi-domain.md: www and apex, hreflang clusters throughlocales: 'all'/
locale_hosts/via:. - Russian translation: docs/troubleshooting.ru.md (linked from README.ru.md).
homepagein composer.json points at the docs site (https://indexnowkit.github.io/php/).
0.8.0
Wave 0a of docs/spec/17 with core 0.6.0. indexnow:check fails outside production_environments when a key is
configured and INDEXNOW_DRY_RUN is not set (a staging copy with the production key submits real URLs). A staging
or preview environment that submits on purpose sets INDEXNOW_DRY_RUN=0 and gets a warning instead.
Changed
- Requires
indexnowkit/core ^0.6;indexnowkit/sitemap ^0.2when installed. Laravel 12 and 13 (the badge and
CONTRIBUTING said 11 by mistake). config/indexnow.phpreads'dry_run' => env('INDEXNOW_DRY_RUN')without a cast, so an unset variable stays unset.
A config file published before 0.8.0 keeps(bool) env('INDEXNOW_DRY_RUN', false): with it, the staging case
above is a warning, not an error, until the line is changed or the file re-published
(php artisan vendor:publish --tag=indexnow-config --force).
Added
internetarchiveandamazonaccepted inengines(core 0.6).
Fixed
"App\Models\Post" is not an Eloquent modelnames the base class the command expects.
Documentation
- README: the runtime-rules snippet imports
IndexNow,IndexNowDefaults,RuleSet; the facade
Laravel\Facades\IndexNowKitversus the coreIndexNowKit\IndexNowKit; "Why this over X", "Notification, not
indexing", the issues link. docs/bc.md: config keys, env vars, artisan commands, bindings, facade,
trait, job, route. The troubleshooting table quotes the new accessor message.
0.7.0
indexnowkit/sitemap is optional again (docs/spec/16, wave C): the package suggests it instead of requiring it.
Configuration keys, command names, bindings and the facade do not change.
Changed
indexnowkit/sitemapis no longer installed automatically. If you useindexnow:sitemap, run
composer require indexnowkit/sitemap; otherwise, aftercomposer update, the command reports that the package is
missing and exits with code 1. Requiresindexnowkit/core ^0.5.1.- Without the package:
indexnow:sitemapisConsole\SitemapNotInstalledCommand(same name, every argument and
option accepted and ignored, printsindexnowkit/sitemap is not installed: composer require indexnowkit/sitemap,
exit 1);indexnow:checkprintssitemap: not installed (composer require indexnowkit/sitemap), orsitemap: not installed, the sitemap block in the configuration is ignored (…)whenconfig/indexnow.phpchanged the block from
the package defaults;Config\ConfigFactoryignores thesitemapblock as a whole (no "unknown option" warning);
SitemapConfig,SitemapSourceInterface,SitemapSpoolCheckandSitemapRunnerare not bound. Nothing is logged
at boot or on a request. - The sitemap bindings moved to
Sitemap\SitemapServices, registered only whenSitemap\SitemapSupport::installed()
holds (the predicate;@internalSitemapSupport::$installedforces it in tests). Only relevant if you reach into
the provider yourself.