astro-indexnow 2.3.4 โ†’ 2.3.6

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.4
9
+ > **Current release:** v2.3.6
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**
@@ -31,6 +43,7 @@ Instead of waiting for crawlers, your site tells search engines exactly which UR
31
43
  - ๐Ÿงช Optional `dryRun` plans submissions without sending requests
32
44
  - ๐Ÿ—ฃ Optional `logMode` controls verbosity with `quiet`, `normal`, and `verbose`
33
45
  - ๐Ÿ“ฆ Optional `batchSize` lets you lower the submit batch size for testing
46
+ - ๐Ÿ” Optional retry controls let you tune transient failure handling
34
47
  - ๐Ÿงช Fully **CI/CD and Docker safe**
35
48
  - ๐Ÿ”’ No secrets prompted, stored, or mutated
36
49
  - ๐Ÿงฉ No client-side or runtime code added
@@ -124,6 +137,20 @@ Using environment variables is **strongly recommended** for CI/CD.
124
137
  This release is backwards compatible with earlier supported package versions.
125
138
  Older npm versions are not deprecated by default; they remain supported until a future major release introduces a breaking change.
126
139
 
140
+ ## Migration Notes
141
+
142
+ ### From v1
143
+
144
+ - v1 submitted all generated pages after each build
145
+ - v2 and later add cache-based changed-only submissions
146
+ - if you upgrade from v1, the first build may submit more URLs because the cache starts empty
147
+
148
+ ### From v2
149
+
150
+ - v2.1 added `cacheDir`
151
+ - v2.3 added `submissionMode`, `dryRun`, `logMode`, `batchSize`, and retry controls
152
+ - the current build-time behavior remains compatible with static and hybrid builds
153
+
127
154
  ---
128
155
 
129
156
  ## How It Works
@@ -142,6 +169,23 @@ This ensures:
142
169
  - No unnecessary API calls
143
170
  - Accurate change detection based on real output
144
171
 
172
+ ## What Gets Submitted
173
+
174
+ Only generated HTML pages are scanned and considered for submission.
175
+
176
+ Included:
177
+ - generated `index.html` pages
178
+ - static pages emitted into the final build output
179
+
180
+ Ignored:
181
+ - assets
182
+ - RSS or XML feeds
183
+ - JavaScript bundles
184
+ - images
185
+ - any other non-HTML outputs
186
+
187
+ This integration does not inspect source files directly. It only works from final build output.
188
+
145
189
  ---
146
190
 
147
191
  ## State & Cache File (Important)
@@ -306,6 +350,33 @@ indexnow({
306
350
 
307
351
  ---
308
352
 
353
+ ### `retryAttempts` (optional)
354
+
355
+ Type: `number`
356
+ Default: `3`
357
+
358
+ Controls how many times a failed batch submission is retried.
359
+
360
+ ---
361
+
362
+ ### `retryBaseDelayMs` (optional)
363
+
364
+ Type: `number`
365
+ Default: `1000`
366
+
367
+ Controls the starting delay for exponential backoff between retries.
368
+
369
+ ---
370
+
371
+ ### `retryMaxDelayMs` (optional)
372
+
373
+ Type: `number`
374
+ Default: `8000`
375
+
376
+ Controls the maximum backoff delay between retries.
377
+
378
+ ---
379
+
309
380
  ## IndexNow Limits & Batching
310
381
 
311
382
  - IndexNow allows **up to 10,000 URLs per request**
@@ -321,6 +392,8 @@ indexnow({
321
392
  - No prompts or side effects
322
393
  - No reliance on Git history
323
394
  - Deterministic output-based change detection
395
+ - Intended for static and hybrid builds, not SSR request-time submission
396
+ - Build-time only; use a webhook or server-side flow for SSR updates
324
397
 
325
398
  ---
326
399
 
package/dist/index.d.ts CHANGED
@@ -8,6 +8,9 @@ export interface IndexNowOptions {
8
8
  logMode?: "quiet" | "normal" | "verbose";
9
9
  submissionMode?: "changed" | "all";
10
10
  batchSize?: number;
11
+ retryAttempts?: number;
12
+ retryBaseDelayMs?: number;
13
+ retryMaxDelayMs?: number;
11
14
  }
12
15
  export default function indexNow(options?: IndexNowOptions): AstroIntegration;
13
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;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,CAAC,OAAO,UAAU,QAAQ,CAC9B,OAAO,GAAE,eAAoB,GAC5B,gBAAgB,CA0PlB"}
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,CAmUlB"}
package/dist/index.js CHANGED
@@ -14,6 +14,15 @@ export default function indexNow(options = {}) {
14
14
  const batchSize = typeof options.batchSize === "number" && options.batchSize > 0
15
15
  ? Math.floor(options.batchSize)
16
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;
17
26
  /* =========================================================
18
27
  Helpers
19
28
  ========================================================= */
@@ -57,6 +66,9 @@ export default function indexNow(options = {}) {
57
66
  }
58
67
  return chunks;
59
68
  }
69
+ function sleep(ms) {
70
+ return new Promise((resolve) => setTimeout(resolve, ms));
71
+ }
60
72
  function isQuiet() {
61
73
  return options.logMode === "quiet";
62
74
  }
@@ -75,6 +87,47 @@ export default function indexNow(options = {}) {
75
87
  if (isVerbose())
76
88
  logger.info(message);
77
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
+ }
78
131
  /* =========================================================
79
132
  Integration
80
133
  ========================================================= */
@@ -116,12 +169,15 @@ export default function indexNow(options = {}) {
116
169
  if (entry.isDirectory())
117
170
  walk(fullPath);
118
171
  if (entry.isFile() && entry.name === "index.html") {
172
+ // Astro emits one index.html per generated route, so this is the
173
+ // canonical build artifact to hash and submit.
119
174
  const relativePath = path
120
175
  .relative(outDir, fullPath)
121
176
  .replace(/index\.html$/, "")
122
177
  .replace(/\\/g, "/");
123
178
  const url = site + "/" + relativePath.replace(/^\/+/, "");
124
179
  const hash = hashFile(fullPath);
180
+ // Cache by URL because IndexNow submits URLs, not filesystem paths.
125
181
  nextCache[url] = hash;
126
182
  if (previousCache[url] !== hash) {
127
183
  changedUrls.push(url);
@@ -130,6 +186,8 @@ export default function indexNow(options = {}) {
130
186
  }
131
187
  }
132
188
  walk(outDir);
189
+ const scannedCount = Object.keys(nextCache).length;
190
+ const changedCount = changedUrls.length;
133
191
  logVerbose(logger, "page diff:");
134
192
  for (const url of Object.keys(nextCache)) {
135
193
  const state = previousCache[url] === nextCache[url]
@@ -148,7 +206,9 @@ export default function indexNow(options = {}) {
148
206
  return;
149
207
  }
150
208
  const batches = chunk(urlsToSubmit, batchSize);
209
+ const batchCount = batches.length;
151
210
  logInfo(logger, `submitting ${urlsToSubmit.length} URL(s) in ${batches.length} batch(es) [mode=${options.submissionMode ?? "changed"}, batchSize=${batchSize}]`);
211
+ logInfo(logger, `summary: scanned=${scannedCount}, changed=${changedCount}, batched=${batchCount}, submitted=${urlsToSubmit.length}`);
152
212
  if (isVerbose()) {
153
213
  logVerbose(logger, "planned URL list:");
154
214
  for (const url of urlsToSubmit) {
@@ -159,30 +219,22 @@ export default function indexNow(options = {}) {
159
219
  logInfo(logger, "dry run enabled, skipping submission");
160
220
  return;
161
221
  }
222
+ let anyBatchFailed = false;
162
223
  for (let i = 0; i < batches.length; i++) {
163
224
  const batch = batches[i];
164
225
  logVerbose(logger, `submitting batch ${i + 1}/${batches.length} (${batch.length} URLs)`);
165
- try {
166
- const response = await fetch(INDEXNOW_ENDPOINT, {
167
- method: "POST",
168
- headers: { "Content-Type": "application/json" },
169
- body: JSON.stringify({
170
- host: new URL(site).host,
171
- key: options.key,
172
- keyLocation: `${site}/${options.key}.txt`,
173
- urlList: batch,
174
- }),
175
- });
176
- if (!response.ok) {
177
- logWarn(logger, `batch ${i + 1} failed (${response.status})`);
178
- }
179
- }
180
- catch {
181
- logWarn(logger, `batch ${i + 1} submission failed (network error)`);
226
+ const batchSucceeded = await submitBatch(logger, batch, i + 1);
227
+ if (!batchSucceeded) {
228
+ anyBatchFailed = true;
182
229
  }
183
230
  }
184
231
  saveCache(logger, nextCache);
185
- logInfo(logger, `IndexNow submission complete`);
232
+ if (anyBatchFailed) {
233
+ logWarn(logger, `IndexNow submission failed`);
234
+ }
235
+ else {
236
+ logInfo(logger, `IndexNow submission complete`);
237
+ }
186
238
  },
187
239
  },
188
240
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "astro-indexnow",
3
- "version": "2.3.4",
3
+ "version": "2.3.6",
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": [