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 +74 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +70 -18
- 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.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
|
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;IACnC,SAAS,CAAC,EAAE,MAAM,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,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
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
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.
|
|
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": [
|