astro-indexnow 2.3.3 β 2.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.MD +61 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +68 -20
- package/package.json +2 -1
package/README.MD
CHANGED
|
@@ -6,11 +6,23 @@ to **IndexNow** after each build.
|
|
|
6
6
|
This package is designed for **modern CI/CD pipelines**, **Docker-based deployments**, and
|
|
7
7
|
**large static sites**, while remaining fully deterministic and explicit.
|
|
8
8
|
|
|
9
|
-
> **Current release:** v2.3.
|
|
9
|
+
> **Current release:** v2.3.5
|
|
10
10
|
> **Stateful by design. `changed` by default, `all` available. Batch-safe. CI-aware.**
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
+
## Compatibility Matrix
|
|
15
|
+
|
|
16
|
+
| Platform | Supported | Notes |
|
|
17
|
+
| --- | --- | --- |
|
|
18
|
+
| Astro | 4.x, 5.x, 6.x, 7.x | Build-time integration works in static and hybrid builds |
|
|
19
|
+
| Node.js | `>=22.12.0` | Matches Astro v7 runtime floor |
|
|
20
|
+
| Static builds | Yes | Primary supported use case |
|
|
21
|
+
| Hybrid builds | Yes | Works for prerendered output generated at build time |
|
|
22
|
+
| SSR request-time submission | No | Use a webhook or server-side helper instead |
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
14
26
|
## What is IndexNow?
|
|
15
27
|
|
|
16
28
|
[IndexNow](https://www.indexnow.org/) is a protocol supported by search engines such as **Bing**
|
|
@@ -30,6 +42,8 @@ Instead of waiting for crawlers, your site tells search engines exactly which UR
|
|
|
30
42
|
- π Optional `submissionMode` lets you choose `changed` or `all`
|
|
31
43
|
- π§ͺ Optional `dryRun` plans submissions without sending requests
|
|
32
44
|
- π£ Optional `logMode` controls verbosity with `quiet`, `normal`, and `verbose`
|
|
45
|
+
- π¦ Optional `batchSize` lets you lower the submit batch size for testing
|
|
46
|
+
- π Optional retry controls let you tune transient failure handling
|
|
33
47
|
- π§ͺ Fully **CI/CD and Docker safe**
|
|
34
48
|
- π No secrets prompted, stored, or mutated
|
|
35
49
|
- π§© No client-side or runtime code added
|
|
@@ -287,6 +301,51 @@ indexnow({
|
|
|
287
301
|
|
|
288
302
|
---
|
|
289
303
|
|
|
304
|
+
### `batchSize` (optional)
|
|
305
|
+
|
|
306
|
+
Type: `number`
|
|
307
|
+
Default: `10000`
|
|
308
|
+
|
|
309
|
+
Controls how many URLs are submitted per IndexNow request.
|
|
310
|
+
|
|
311
|
+
- The default matches IndexNowβs documented limit
|
|
312
|
+
- Lower values are useful for testing or constrained environments
|
|
313
|
+
|
|
314
|
+
```js
|
|
315
|
+
indexnow({
|
|
316
|
+
batchSize: 100,
|
|
317
|
+
})
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
---
|
|
321
|
+
|
|
322
|
+
### `retryAttempts` (optional)
|
|
323
|
+
|
|
324
|
+
Type: `number`
|
|
325
|
+
Default: `3`
|
|
326
|
+
|
|
327
|
+
Controls how many times a failed batch submission is retried.
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
331
|
+
### `retryBaseDelayMs` (optional)
|
|
332
|
+
|
|
333
|
+
Type: `number`
|
|
334
|
+
Default: `1000`
|
|
335
|
+
|
|
336
|
+
Controls the starting delay for exponential backoff between retries.
|
|
337
|
+
|
|
338
|
+
---
|
|
339
|
+
|
|
340
|
+
### `retryMaxDelayMs` (optional)
|
|
341
|
+
|
|
342
|
+
Type: `number`
|
|
343
|
+
Default: `8000`
|
|
344
|
+
|
|
345
|
+
Controls the maximum backoff delay between retries.
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
290
349
|
## IndexNow Limits & Batching
|
|
291
350
|
|
|
292
351
|
- IndexNow allows **up to 10,000 URLs per request**
|
|
@@ -302,6 +361,7 @@ indexnow({
|
|
|
302
361
|
- No prompts or side effects
|
|
303
362
|
- No reliance on Git history
|
|
304
363
|
- Deterministic output-based change detection
|
|
364
|
+
- Intended for static and hybrid builds, not SSR request-time submission
|
|
305
365
|
|
|
306
366
|
---
|
|
307
367
|
|
package/dist/index.d.ts
CHANGED
|
@@ -7,6 +7,10 @@ export interface IndexNowOptions {
|
|
|
7
7
|
dryRun?: boolean;
|
|
8
8
|
logMode?: "quiet" | "normal" | "verbose";
|
|
9
9
|
submissionMode?: "changed" | "all";
|
|
10
|
+
batchSize?: number;
|
|
11
|
+
retryAttempts?: number;
|
|
12
|
+
retryBaseDelayMs?: number;
|
|
13
|
+
retryMaxDelayMs?: number;
|
|
10
14
|
}
|
|
11
15
|
export default function indexNow(options?: IndexNowOptions): AstroIntegration;
|
|
12
16
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAM9C,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAC;IACzC,cAAc,CAAC,EAAE,SAAS,GAAG,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,OAAO,CAAC;AAM9C,MAAM,WAAW,eAAe;IAC9B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAC;IACzC,cAAc,CAAC,EAAE,SAAS,GAAG,KAAK,CAAC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,CAAC,OAAO,UAAU,QAAQ,CAC9B,OAAO,GAAE,eAAoB,GAC5B,gBAAgB,CAuTlB"}
|
package/dist/index.js
CHANGED
|
@@ -11,6 +11,18 @@ export default function indexNow(options = {}) {
|
|
|
11
11
|
: path.join(projectRoot, CACHE_FILENAME);
|
|
12
12
|
const INDEXNOW_ENDPOINT = "https://api.indexnow.org/indexnow";
|
|
13
13
|
const INDEXNOW_BATCH_SIZE = 10_000;
|
|
14
|
+
const batchSize = typeof options.batchSize === "number" && options.batchSize > 0
|
|
15
|
+
? Math.floor(options.batchSize)
|
|
16
|
+
: INDEXNOW_BATCH_SIZE;
|
|
17
|
+
const retryAttempts = typeof options.retryAttempts === "number" && options.retryAttempts > 0
|
|
18
|
+
? Math.floor(options.retryAttempts)
|
|
19
|
+
: 3;
|
|
20
|
+
const retryBaseDelayMs = typeof options.retryBaseDelayMs === "number" && options.retryBaseDelayMs > 0
|
|
21
|
+
? Math.floor(options.retryBaseDelayMs)
|
|
22
|
+
: 1000;
|
|
23
|
+
const retryMaxDelayMs = typeof options.retryMaxDelayMs === "number" && options.retryMaxDelayMs > 0
|
|
24
|
+
? Math.floor(options.retryMaxDelayMs)
|
|
25
|
+
: 8000;
|
|
14
26
|
/* =========================================================
|
|
15
27
|
Helpers
|
|
16
28
|
========================================================= */
|
|
@@ -54,6 +66,9 @@ export default function indexNow(options = {}) {
|
|
|
54
66
|
}
|
|
55
67
|
return chunks;
|
|
56
68
|
}
|
|
69
|
+
function sleep(ms) {
|
|
70
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
71
|
+
}
|
|
57
72
|
function isQuiet() {
|
|
58
73
|
return options.logMode === "quiet";
|
|
59
74
|
}
|
|
@@ -72,6 +87,47 @@ export default function indexNow(options = {}) {
|
|
|
72
87
|
if (isVerbose())
|
|
73
88
|
logger.info(message);
|
|
74
89
|
}
|
|
90
|
+
function isRetryableStatus(status) {
|
|
91
|
+
return status === 429 || (status >= 500 && status < 600);
|
|
92
|
+
}
|
|
93
|
+
async function submitBatch(logger, batch, batchIndex) {
|
|
94
|
+
for (let attempt = 1; attempt <= retryAttempts; attempt++) {
|
|
95
|
+
try {
|
|
96
|
+
const response = await fetch(INDEXNOW_ENDPOINT, {
|
|
97
|
+
method: "POST",
|
|
98
|
+
headers: { "Content-Type": "application/json" },
|
|
99
|
+
body: JSON.stringify({
|
|
100
|
+
host: new URL(site).host,
|
|
101
|
+
key: options.key,
|
|
102
|
+
keyLocation: `${site}/${options.key}.txt`,
|
|
103
|
+
urlList: batch,
|
|
104
|
+
}),
|
|
105
|
+
});
|
|
106
|
+
if (response.ok) {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
if (!isRetryableStatus(response.status) || attempt === retryAttempts) {
|
|
110
|
+
logWarn(logger, `batch ${batchIndex} failed (${response.status})`);
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
const delay = Math.min(retryBaseDelayMs * 2 ** (attempt - 1), retryMaxDelayMs) +
|
|
114
|
+
Math.floor(Math.random() * 250);
|
|
115
|
+
logWarn(logger, `batch ${batchIndex} attempt ${attempt} failed (${response.status}), retrying in ${delay}ms`);
|
|
116
|
+
await sleep(delay);
|
|
117
|
+
}
|
|
118
|
+
catch {
|
|
119
|
+
if (attempt === retryAttempts) {
|
|
120
|
+
logWarn(logger, `batch ${batchIndex} submission failed (network error)`);
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
const delay = Math.min(retryBaseDelayMs * 2 ** (attempt - 1), retryMaxDelayMs) +
|
|
124
|
+
Math.floor(Math.random() * 250);
|
|
125
|
+
logWarn(logger, `batch ${batchIndex} submission failed (network error), retrying in ${delay}ms`);
|
|
126
|
+
await sleep(delay);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
75
131
|
/* =========================================================
|
|
76
132
|
Integration
|
|
77
133
|
========================================================= */
|
|
@@ -144,8 +200,8 @@ export default function indexNow(options = {}) {
|
|
|
144
200
|
saveCache(logger, nextCache);
|
|
145
201
|
return;
|
|
146
202
|
}
|
|
147
|
-
const batches = chunk(urlsToSubmit,
|
|
148
|
-
logInfo(logger, `submitting ${urlsToSubmit.length} URL(s) in ${batches.length} batch(es) [mode=${options.submissionMode ?? "changed"}]`);
|
|
203
|
+
const batches = chunk(urlsToSubmit, batchSize);
|
|
204
|
+
logInfo(logger, `submitting ${urlsToSubmit.length} URL(s) in ${batches.length} batch(es) [mode=${options.submissionMode ?? "changed"}, batchSize=${batchSize}]`);
|
|
149
205
|
if (isVerbose()) {
|
|
150
206
|
logVerbose(logger, "planned URL list:");
|
|
151
207
|
for (const url of urlsToSubmit) {
|
|
@@ -156,30 +212,22 @@ export default function indexNow(options = {}) {
|
|
|
156
212
|
logInfo(logger, "dry run enabled, skipping submission");
|
|
157
213
|
return;
|
|
158
214
|
}
|
|
215
|
+
let anyBatchFailed = false;
|
|
159
216
|
for (let i = 0; i < batches.length; i++) {
|
|
160
217
|
const batch = batches[i];
|
|
161
218
|
logVerbose(logger, `submitting batch ${i + 1}/${batches.length} (${batch.length} URLs)`);
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
headers: { "Content-Type": "application/json" },
|
|
166
|
-
body: JSON.stringify({
|
|
167
|
-
host: new URL(site).host,
|
|
168
|
-
key: options.key,
|
|
169
|
-
keyLocation: `${site}/${options.key}.txt`,
|
|
170
|
-
urlList: batch,
|
|
171
|
-
}),
|
|
172
|
-
});
|
|
173
|
-
if (!response.ok) {
|
|
174
|
-
logWarn(logger, `batch ${i + 1} failed (${response.status})`);
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
catch {
|
|
178
|
-
logWarn(logger, `batch ${i + 1} submission failed (network error)`);
|
|
219
|
+
const batchSucceeded = await submitBatch(logger, batch, i + 1);
|
|
220
|
+
if (!batchSucceeded) {
|
|
221
|
+
anyBatchFailed = true;
|
|
179
222
|
}
|
|
180
223
|
}
|
|
181
224
|
saveCache(logger, nextCache);
|
|
182
|
-
|
|
225
|
+
if (anyBatchFailed) {
|
|
226
|
+
logWarn(logger, `IndexNow submission failed`);
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
logInfo(logger, `IndexNow submission complete`);
|
|
230
|
+
}
|
|
183
231
|
},
|
|
184
232
|
},
|
|
185
233
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "astro-indexnow",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.5",
|
|
4
4
|
"description": "Astro integration to submit pages to IndexNow automatically after build",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
],
|
|
12
12
|
"scripts": {
|
|
13
13
|
"build": "tsc",
|
|
14
|
+
"release:check": "node ./scripts/release-check.mjs",
|
|
14
15
|
"prepublishOnly": "npm run build"
|
|
15
16
|
},
|
|
16
17
|
"keywords": [
|