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 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.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
@@ -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;CACpC;AAED,MAAM,CAAC,OAAO,UAAU,QAAQ,CAC9B,OAAO,GAAE,eAAoB,GAC5B,gBAAgB,CAsPlB"}
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, INDEXNOW_BATCH_SIZE);
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
- try {
163
- const response = await fetch(INDEXNOW_ENDPOINT, {
164
- method: "POST",
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
- logInfo(logger, `IndexNow submission complete`);
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",
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": [