@vijayhardaha/next-indexnow 1.0.1 β 1.2.0
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 +25 -14
- package/dist/cli.js +279 -31
- package/dist/cli.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -29,6 +29,10 @@ npx next-indexnow --key my-api-key
|
|
|
29
29
|
# Use a custom sitemap path
|
|
30
30
|
npx next-indexnow --sitemap ./public/sitemap.xml
|
|
31
31
|
|
|
32
|
+
# Submit only a slice of the sitemap (avoid bulk submission)
|
|
33
|
+
npx next-indexnow -o 0 -l 50
|
|
34
|
+
npx next-indexnow -o 50 -l 50
|
|
35
|
+
|
|
32
36
|
# Preview URLs without submitting
|
|
33
37
|
npx next-indexnow --dry-run
|
|
34
38
|
```
|
|
@@ -76,7 +80,9 @@ All 10 urls submitted to IndexNow successfully! π₯³
|
|
|
76
80
|
| `--site-url <url>` | Site URL (e.g. `https://example.com`). Overrides config value |
|
|
77
81
|
| `--key <key>` | IndexNow API key. Falls back to `INDEXNOW_KEY` env variable or a built-in default |
|
|
78
82
|
| `--sitemap <path>` | Path to the sitemap XML file |
|
|
79
|
-
| `--chunk-size <number>` | URLs per submission batch (default: 100)
|
|
83
|
+
| `--chunk-size <number>` | URLs per submission batch (positive integer, default: 100) |
|
|
84
|
+
| `-o, --offset <number>` | Skip this many URLs from the start of the sitemap |
|
|
85
|
+
| `-l, --limit <number>` | Submit at most this many URLs (after offset) |
|
|
80
86
|
| `-d, --dry-run` | Preview URLs without submitting to the IndexNow API |
|
|
81
87
|
| `-h, --help` | Show help |
|
|
82
88
|
| `--version` | Show version |
|
|
@@ -85,12 +91,15 @@ All 10 urls submitted to IndexNow successfully! π₯³
|
|
|
85
91
|
|
|
86
92
|
1. **Validates** your Next.js project (`next.config.*` must exist)
|
|
87
93
|
2. **Checks** the `.next` build directory exists and is not empty
|
|
88
|
-
3. **Reads** `next-sitemap.config.*` to extract `siteUrl` and `outDir`
|
|
94
|
+
3. **Reads** `next-sitemap.config.*` to extract `siteUrl` and `outDir` (`.js` wins over `.cjs`)
|
|
89
95
|
4. **Resolves** the API key from CLI option, `INDEXNOW_KEY` env variable, or a built-in default key
|
|
90
|
-
5. **
|
|
91
|
-
6. **
|
|
92
|
-
7. **
|
|
93
|
-
8. **
|
|
96
|
+
5. **Prompts** for the site URL when the configured one is unreachable, and for the API key when falling back to default
|
|
97
|
+
6. **Creates** the IndexNow verification file at `public/<key>.txt`
|
|
98
|
+
7. **Parses** all `<loc>` URLs from the sitemap XML
|
|
99
|
+
8. **Applies** the `-o, --offset` / `-l, --limit` range to the parsed URLs
|
|
100
|
+
9. **Confirms** before submitting (skipped on `--dry-run` and non-TTY stdin)
|
|
101
|
+
10. **Submits** URLs in batches (default 100) to the IndexNow API with a live progress counter
|
|
102
|
+
11. **Reports** results with per-chunk success/failure details
|
|
94
103
|
|
|
95
104
|
### Environment Variables
|
|
96
105
|
|
|
@@ -114,19 +123,21 @@ console.log(`Found ${result.urlsFound} URLs`);
|
|
|
114
123
|
|
|
115
124
|
### `NextIndexnowOptions`
|
|
116
125
|
|
|
117
|
-
| Option | Type | Default | Description
|
|
118
|
-
| ----------- | --------- | ---------------- |
|
|
119
|
-
| `siteUrl` | `string` | β | Site URL (overrides config)
|
|
120
|
-
| `key` | `string` | built-in default | IndexNow API key
|
|
121
|
-
| `sitemap` | `string` | β | Custom sitemap path
|
|
122
|
-
| `chunkSize` | `number` | `100` | URLs per submission batch
|
|
123
|
-
| `
|
|
126
|
+
| Option | Type | Default | Description |
|
|
127
|
+
| ----------- | --------- | ---------------- | --------------------------------- |
|
|
128
|
+
| `siteUrl` | `string` | β | Site URL (overrides config) |
|
|
129
|
+
| `key` | `string` | built-in default | IndexNow API key |
|
|
130
|
+
| `sitemap` | `string` | β | Custom sitemap path |
|
|
131
|
+
| `chunkSize` | `number` | `100` | URLs per submission batch |
|
|
132
|
+
| `offset` | `number` | β | URLs to skip from the start |
|
|
133
|
+
| `limit` | `number` | β | Max URLs to submit (after offset) |
|
|
134
|
+
| `dryRun` | `boolean` | `false` | Preview without submitting |
|
|
124
135
|
|
|
125
136
|
### `NextIndexnowResult`
|
|
126
137
|
|
|
127
138
|
```typescript
|
|
128
139
|
interface NextIndexnowResult {
|
|
129
|
-
urlsFound: number; //
|
|
140
|
+
urlsFound: number; // URLs selected after offset/limit
|
|
130
141
|
urlsSubmitted: number; // Successfully submitted URLs
|
|
131
142
|
urlsFailed: number; // URLs that failed to submit
|
|
132
143
|
chunks: SubmissionResult[];
|
package/dist/cli.js
CHANGED
|
@@ -3,10 +3,11 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from
|
|
|
3
3
|
import { resolve } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import chalk from "chalk";
|
|
6
|
-
import { Command } from "commander";
|
|
6
|
+
import { Command, InvalidArgumentError } from "commander";
|
|
7
7
|
import logSymbols from "log-symbols";
|
|
8
8
|
import ora from "ora";
|
|
9
9
|
import xml2js from "xml2js";
|
|
10
|
+
import { createInterface } from "node:readline";
|
|
10
11
|
//#region src/constants.ts
|
|
11
12
|
/**
|
|
12
13
|
* Constants and configuration defaults for the IndexNow CLI tool.
|
|
@@ -19,6 +20,8 @@ var INDEXNOW_API_URL = "https://api.indexnow.org/indexnow";
|
|
|
19
20
|
var DEFAULT_SITEMAP_DIR = "public";
|
|
20
21
|
/** IndexNow key verification filename extension. */
|
|
21
22
|
var KEY_FILE_EXTENSION = ".txt";
|
|
23
|
+
/** Default IndexNow API key β used when neither --key option nor INDEXNOW_KEY env var is provided. */
|
|
24
|
+
var DEFAULT_INDEXNOW_KEY = "91c80f732f4e4e5b80b4c02a7e8c9e9c";
|
|
22
25
|
/** Conventional Next.js config filenames to detect a Next.js project. */
|
|
23
26
|
var NEXT_CONFIG_FILES = [
|
|
24
27
|
"next.config.ts",
|
|
@@ -26,12 +29,12 @@ var NEXT_CONFIG_FILES = [
|
|
|
26
29
|
"next.config.cjs",
|
|
27
30
|
"next.config.js"
|
|
28
31
|
];
|
|
29
|
-
/** Conventional next-sitemap config filenames. */
|
|
32
|
+
/** Conventional next-sitemap config filenames, ordered by priority (.js first, .ts last). */
|
|
30
33
|
var NEXT_SITEMAP_CONFIG_FILES = [
|
|
31
|
-
"next-sitemap.config.
|
|
32
|
-
"next-sitemap.config.mjs",
|
|
34
|
+
"next-sitemap.config.js",
|
|
33
35
|
"next-sitemap.config.cjs",
|
|
34
|
-
"next-sitemap.config.
|
|
36
|
+
"next-sitemap.config.mjs",
|
|
37
|
+
"next-sitemap.config.ts"
|
|
35
38
|
];
|
|
36
39
|
/** Directories that indicate a Next.js build has been run. */
|
|
37
40
|
var NEXT_BUILD_DIRS = [".next"];
|
|
@@ -43,6 +46,29 @@ var NEXT_BUILD_DIRS = [".next"];
|
|
|
43
46
|
* @module utils
|
|
44
47
|
*/
|
|
45
48
|
/**
|
|
49
|
+
* Fetch the site root with HEAD to verify the domain is reachable.
|
|
50
|
+
*
|
|
51
|
+
* @param {string} url - Absolute site URL with http:// or https:// scheme.
|
|
52
|
+
*
|
|
53
|
+
* @returns {ValidationResult} Valid if HEAD responds with status < 500; invalid otherwise.
|
|
54
|
+
*/
|
|
55
|
+
async function validateSiteReachable(url) {
|
|
56
|
+
try {
|
|
57
|
+
const response = await fetch(url, { method: "HEAD" });
|
|
58
|
+
if (response.status >= 500) return {
|
|
59
|
+
valid: false,
|
|
60
|
+
error: `Site unreachable: ${url} responded with ${response.status}.`
|
|
61
|
+
};
|
|
62
|
+
return { valid: true };
|
|
63
|
+
} catch (error) {
|
|
64
|
+
/* v8 ignore next 3 */
|
|
65
|
+
return {
|
|
66
|
+
valid: false,
|
|
67
|
+
error: `Cannot reach site: ${error instanceof Error ? error.message : String(error)}`
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
46
72
|
* Validate that a URL string is a valid site domain with http:// or https:// scheme.
|
|
47
73
|
*
|
|
48
74
|
* Accepts standard domains (example.com), subdomains, and localhost with ports.
|
|
@@ -223,6 +249,27 @@ function resolveSitemapPath(outDir, sitemapFile) {
|
|
|
223
249
|
return resolve(process.cwd(), dir, file);
|
|
224
250
|
}
|
|
225
251
|
/**
|
|
252
|
+
* Apply offset and limit options to a list of URLs.
|
|
253
|
+
*
|
|
254
|
+
* Skips the first `offset` URLs and keeps at most `limit` URLs from the
|
|
255
|
+
* remaining list. Omitted options leave the corresponding bound unlimited.
|
|
256
|
+
*
|
|
257
|
+
* @param {string[]} urls - The full list of URLs extracted from the sitemap.
|
|
258
|
+
* @param {number} [offset] - Number of URLs to skip from the start.
|
|
259
|
+
* @param {number} [limit] - Maximum number of URLs to keep (after offset).
|
|
260
|
+
*
|
|
261
|
+
* @returns {string[]} The sliced list of URLs to submit.
|
|
262
|
+
*
|
|
263
|
+
* @throws {Error} If offset or limit is a negative or non-integer value.
|
|
264
|
+
*/
|
|
265
|
+
function applyOffsetLimit(urls, offset, limit) {
|
|
266
|
+
if (offset !== void 0 && (!Number.isInteger(offset) || offset < 0)) throw new Error("offset must be a non-negative integer");
|
|
267
|
+
if (limit !== void 0 && (!Number.isInteger(limit) || limit < 0)) throw new Error("limit must be a non-negative integer");
|
|
268
|
+
const start = offset ?? 0;
|
|
269
|
+
const end = limit === void 0 ? void 0 : start + limit;
|
|
270
|
+
return urls.slice(start, end);
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
226
273
|
* Fetch XML content from a remote sitemap URL.
|
|
227
274
|
*
|
|
228
275
|
* @param {string} url - The remote sitemap URL to fetch.
|
|
@@ -464,7 +511,8 @@ async function submitAllUrls(urls, siteHost, key, keyLocation, chunkSize, onProg
|
|
|
464
511
|
onProgress?.({
|
|
465
512
|
batch: batchNum,
|
|
466
513
|
totalBatches: totalChunks,
|
|
467
|
-
urlCount: chunk.length
|
|
514
|
+
urlCount: chunk.length,
|
|
515
|
+
previousResult: chunks.length > 0 ? chunks[chunks.length - 1] : void 0
|
|
468
516
|
});
|
|
469
517
|
const result = await submitUrls(siteHost, key, keyLocation, chunk);
|
|
470
518
|
chunks.push(result);
|
|
@@ -479,19 +527,21 @@ async function submitAllUrls(urls, siteHost, key, keyLocation, chunkSize, onProg
|
|
|
479
527
|
* Run the IndexNow submission process.
|
|
480
528
|
*
|
|
481
529
|
* Validates the environment (Next.js project, .next dir, sitemap config),
|
|
482
|
-
* reads the sitemap,
|
|
530
|
+
* reads the sitemap, applies the offset/limit range, and submits the
|
|
531
|
+
* selected URLs to the IndexNow API in chunks.
|
|
483
532
|
*
|
|
484
|
-
* @param {NextIndexnowOptions} options - CLI options (siteUrl, key, sitemap, chunkSize, dryRun, onProgress).
|
|
533
|
+
* @param {NextIndexnowOptions} options - CLI options (siteUrl, key, sitemap, chunkSize, offset, limit, dryRun, onProgress).
|
|
485
534
|
*
|
|
486
535
|
* @returns {Promise<NextIndexnowResult>} Aggregate result with per-chunk details.
|
|
487
536
|
*/
|
|
488
537
|
async function run(options = {}) {
|
|
489
538
|
const startTime = Date.now();
|
|
490
539
|
const chunkSize = options.chunkSize ?? 100;
|
|
540
|
+
if (!Number.isInteger(chunkSize) || chunkSize < 1) throw new Error("chunkSize must be a positive integer");
|
|
491
541
|
const parsedConfig = validateEnvironment();
|
|
492
542
|
const { siteUrl, siteHost } = resolveSiteConfig(options, parsedConfig);
|
|
493
543
|
const { key, keyLocation } = resolveKeyValue(options, siteUrl);
|
|
494
|
-
const urls = await loadSitemapUrls(options, parsedConfig);
|
|
544
|
+
const urls = applyOffsetLimit(await loadSitemapUrls(options, parsedConfig), options.offset, options.limit);
|
|
495
545
|
if (options.dryRun) return {
|
|
496
546
|
urlsFound: urls.length,
|
|
497
547
|
urlsSubmitted: 0,
|
|
@@ -509,6 +559,108 @@ async function run(options = {}) {
|
|
|
509
559
|
};
|
|
510
560
|
}
|
|
511
561
|
//#endregion
|
|
562
|
+
//#region src/prompts.ts
|
|
563
|
+
/**
|
|
564
|
+
* Interactive prompt helpers for the IndexNow CLI.
|
|
565
|
+
*
|
|
566
|
+
* Uses node:readline to read single-line input from a TTY. When stdin is
|
|
567
|
+
* not a TTY (piped/CI input), all prompts auto-resolve to the default value
|
|
568
|
+
* so scripted usage is never blocked.
|
|
569
|
+
*
|
|
570
|
+
* @module prompts
|
|
571
|
+
*/
|
|
572
|
+
/**
|
|
573
|
+
* Normalize a user-entered site URL by prepending https:// when no scheme is given.
|
|
574
|
+
*
|
|
575
|
+
* @param {string} input - Raw input from the user.
|
|
576
|
+
*
|
|
577
|
+
* @returns {string} URL with http:// or https:// scheme.
|
|
578
|
+
*/
|
|
579
|
+
function normalizeSiteUrl(input) {
|
|
580
|
+
const trimmed = input.trim();
|
|
581
|
+
if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) return trimmed;
|
|
582
|
+
return `https://${trimmed}`;
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Read a single line of user input from stdin.
|
|
586
|
+
*
|
|
587
|
+
* Non-TTY stdin resolves immediately to the empty string so callers can
|
|
588
|
+
* fall back to their default value.
|
|
589
|
+
*
|
|
590
|
+
* @param {string} question - The prompt text shown to the user.
|
|
591
|
+
*
|
|
592
|
+
* @returns {Promise<string>} The trimmed user input, or empty string when not a TTY.
|
|
593
|
+
*/
|
|
594
|
+
function readLine(question) {
|
|
595
|
+
if (!process.stdin.isTTY) return Promise.resolve("");
|
|
596
|
+
const rl = createInterface({
|
|
597
|
+
input: process.stdin,
|
|
598
|
+
output: process.stdout
|
|
599
|
+
});
|
|
600
|
+
return new Promise((resolveInput) => {
|
|
601
|
+
rl.question(question, (answer) => {
|
|
602
|
+
rl.close();
|
|
603
|
+
resolveInput(answer.trim());
|
|
604
|
+
});
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
/**
|
|
608
|
+
* Prompt the user for a Y/n confirmation, defaulting to Y on empty input.
|
|
609
|
+
*
|
|
610
|
+
* Non-TTY stdin auto-confirms (`true`) so piped/CI usage is not blocked.
|
|
611
|
+
*
|
|
612
|
+
* @returns {Promise<boolean>} `true` when the user confirms (or stdin is non-interactive), `false` otherwise.
|
|
613
|
+
*/
|
|
614
|
+
function confirm() {
|
|
615
|
+
if (!process.stdin.isTTY) return Promise.resolve(true);
|
|
616
|
+
const rl = createInterface({
|
|
617
|
+
input: process.stdin,
|
|
618
|
+
output: process.stdout
|
|
619
|
+
});
|
|
620
|
+
return new Promise((resolvePrompt) => {
|
|
621
|
+
rl.question(`${chalk.cyan("?")} Continue? ${chalk.dim("(Y/n)")} `, (answer) => {
|
|
622
|
+
rl.close();
|
|
623
|
+
const normalized = answer.trim().toLowerCase();
|
|
624
|
+
resolvePrompt(normalized === "" || normalized === "y" || normalized === "yes");
|
|
625
|
+
});
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
/**
|
|
629
|
+
* Prompt the user for a site URL until a valid one is entered.
|
|
630
|
+
*
|
|
631
|
+
* Accepts https://, http://, or a bare domain. Bare domains get https://
|
|
632
|
+
* prepended automatically. Each entered value is validated for syntax and
|
|
633
|
+
* re-prompted on failure. Caller can additionally validate reachability.
|
|
634
|
+
*
|
|
635
|
+
* @returns {Promise<string>} The normalized, syntactically valid site URL.
|
|
636
|
+
*/
|
|
637
|
+
async function promptSiteUrl() {
|
|
638
|
+
while (true) {
|
|
639
|
+
const normalized = normalizeSiteUrl(await readLine(`${chalk.cyan("?")} Enter site URL ${chalk.dim("(https://example.com)")}: `));
|
|
640
|
+
const validation = validateSiteUrl(normalized);
|
|
641
|
+
if (validation.valid) return normalized;
|
|
642
|
+
console.log(chalk.red(` ${validation.error}`));
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
/**
|
|
646
|
+
* Prompt the user to choose between the built-in default IndexNow key and a custom key.
|
|
647
|
+
*
|
|
648
|
+
* Y (or empty) selects the default; n prompts for a custom key (loops until non-empty).
|
|
649
|
+
*
|
|
650
|
+
* @param {string} defaultKey - The built-in default key string.
|
|
651
|
+
*
|
|
652
|
+
* @returns {Promise<string>} The chosen key (default or user-entered).
|
|
653
|
+
*/
|
|
654
|
+
async function promptApiKey(defaultKey) {
|
|
655
|
+
const normalized = (await readLine(`${chalk.cyan("?")} Use default IndexNow key? ${chalk.dim("(Y/n)")} `)).toLowerCase();
|
|
656
|
+
if (normalized === "" || normalized === "y" || normalized === "yes") return defaultKey;
|
|
657
|
+
while (true) {
|
|
658
|
+
const custom = await readLine(`${chalk.cyan("?")} Enter your IndexNow API key: `);
|
|
659
|
+
if (custom.length > 0) return custom;
|
|
660
|
+
console.log(chalk.red(" API key cannot be empty."));
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
//#endregion
|
|
512
664
|
//#region src/bin/cli.ts
|
|
513
665
|
/**
|
|
514
666
|
* next-indexnow β CLI entry point.
|
|
@@ -523,6 +675,7 @@ async function run(options = {}) {
|
|
|
523
675
|
* next-indexnow --site-url https://example.com
|
|
524
676
|
* next-indexnow --key my-api-key
|
|
525
677
|
* next-indexnow --sitemap ./public/sitemap-0.xml
|
|
678
|
+
* next-indexnow -o 100 -l 50
|
|
526
679
|
* next-indexnow --dry-run
|
|
527
680
|
* next-indexnow --help
|
|
528
681
|
*/
|
|
@@ -535,6 +688,32 @@ ${chalk.white("ββββββ βββββββββββββββ
|
|
|
535
688
|
${chalk.white("ββββββ ββββββββββββ βββββββββββ ββββββ βββββ βββββββ ββββββββ")}`;
|
|
536
689
|
var SEPARATOR = chalk.dim("=".repeat(69));
|
|
537
690
|
/**
|
|
691
|
+
* Parse a non-negative integer CLI option value.
|
|
692
|
+
*
|
|
693
|
+
* @param {string} value - Raw string value from the command line.
|
|
694
|
+
*
|
|
695
|
+
* @returns {number} The parsed integer.
|
|
696
|
+
*
|
|
697
|
+
* @throws {InvalidArgumentError} If the value is not a non-negative integer.
|
|
698
|
+
*/
|
|
699
|
+
function parseNonNegativeInt(value) {
|
|
700
|
+
if (!/^\d+$/.test(value)) throw new InvalidArgumentError("must be a non-negative integer");
|
|
701
|
+
return Number.parseInt(value, 10);
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Parse a positive integer CLI option value.
|
|
705
|
+
*
|
|
706
|
+
* @param {string} value - Raw string value from the command line.
|
|
707
|
+
*
|
|
708
|
+
* @returns {number} The parsed integer.
|
|
709
|
+
*
|
|
710
|
+
* @throws {InvalidArgumentError} If the value is not a positive integer.
|
|
711
|
+
*/
|
|
712
|
+
function parsePositiveInt(value) {
|
|
713
|
+
if (!/^[1-9]\d*$/.test(value)) throw new InvalidArgumentError("must be a positive integer");
|
|
714
|
+
return Number.parseInt(value, 10);
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
538
717
|
* Print a checkmark or error icon with a status label and optional detail.
|
|
539
718
|
*
|
|
540
719
|
* @param {boolean} valid - Whether the check passed.
|
|
@@ -581,12 +760,13 @@ var pkgPathDist = resolve(__filename, "..", "..", "package.json");
|
|
|
581
760
|
var pkgPath = existsSync(pkgPathDist) ? pkgPathDist : pkgPathSrc;
|
|
582
761
|
var pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
583
762
|
var program = new Command();
|
|
584
|
-
program.name("next-indexnow").description("Submit Next.js sitemap URLs to the IndexNow API for faster search engine indexing").version(pkg.version).option("--site-url <url>", "The site URL (e.g. https://example.com). Overrides next-sitemap.config value.").option("--key <key>", "IndexNow API key. Falls back to INDEXNOW_KEY environment variable or a built-in default key.").option("--sitemap <path>", "Path to the sitemap XML file").option("--chunk-size <number>", "URLs per submission batch", (
|
|
763
|
+
program.name("next-indexnow").description("Submit Next.js sitemap URLs to the IndexNow API for faster search engine indexing").version(pkg.version).option("--site-url <url>", "The site URL (e.g. https://example.com). Overrides next-sitemap.config value.").option("--key <key>", "IndexNow API key. Falls back to INDEXNOW_KEY environment variable or a built-in default key.").option("--sitemap <path>", "Path to the sitemap XML file").option("--chunk-size <number>", "URLs per submission batch (positive integer)", parsePositiveInt, 100).option("-o, --offset <number>", "Skip this many URLs from the start of the sitemap", parseNonNegativeInt).option("-l, --limit <number>", "Submit at most this many URLs (after offset)", parseNonNegativeInt).option("-d, --dry-run", "Preview URLs without submitting to the IndexNow API").addHelpText("after", `
|
|
585
764
|
Examples:
|
|
586
765
|
$ next-indexnow Submit URLs using settings from next-sitemap.config
|
|
587
766
|
$ next-indexnow --site-url https://example.com Override the site URL
|
|
588
767
|
$ next-indexnow --key my-api-key Provide IndexNow API key
|
|
589
768
|
$ next-indexnow --sitemap ./public/sitemap.xml Use a custom sitemap path
|
|
769
|
+
$ next-indexnow -o 100 -l 50 Submit a slice of the sitemap
|
|
590
770
|
$ next-indexnow --dry-run Preview URLs without submitting
|
|
591
771
|
$ next-indexnow --help Show this help message
|
|
592
772
|
`).parse(process.argv);
|
|
@@ -638,9 +818,7 @@ function displayResults(result) {
|
|
|
638
818
|
case "yellow":
|
|
639
819
|
coloredValue = chalk.yellow(value);
|
|
640
820
|
break;
|
|
641
|
-
default:
|
|
642
|
-
coloredValue = value;
|
|
643
|
-
break;
|
|
821
|
+
default: coloredValue = value;
|
|
644
822
|
}
|
|
645
823
|
console.log(`${logSymbols.success} ${chalk.bold(label)}: ${coloredValue}`);
|
|
646
824
|
}
|
|
@@ -648,13 +826,17 @@ function displayResults(result) {
|
|
|
648
826
|
/**
|
|
649
827
|
* Run validation checks and display checkmarks, returning parsed state.
|
|
650
828
|
*
|
|
829
|
+
* When the resolved site URL is unreachable or the API key falls back to
|
|
830
|
+
* the built-in default, the user is prompted interactively (skipped when
|
|
831
|
+
* stdin is not a TTY).
|
|
832
|
+
*
|
|
651
833
|
* @param {object} opts - CLI options for site URL and API key.
|
|
652
834
|
* @param {string} [opts.siteUrl] - Override site URL from --site-url option.
|
|
653
835
|
* @param {string} [opts.key] - Override API key from --key option.
|
|
654
836
|
*
|
|
655
837
|
* @returns {{ parsedConfig: NextSitemapConfig; siteUrl: string; siteHost: string; key: string; keyLocation: string }} Resolved configuration values.
|
|
656
838
|
*/
|
|
657
|
-
function runValidationChecks(opts) {
|
|
839
|
+
async function runValidationChecks(opts) {
|
|
658
840
|
const projectCheck = validateNextProject();
|
|
659
841
|
if (!projectCheck.valid) {
|
|
660
842
|
checkMark(false, "Next.js project", projectCheck.error);
|
|
@@ -682,37 +864,64 @@ function runValidationChecks(opts) {
|
|
|
682
864
|
console.log(chalk.red(`\n Could not parse "siteUrl" from ${sitemapConfigCheck.filePath}.`));
|
|
683
865
|
process.exit(1);
|
|
684
866
|
}
|
|
685
|
-
|
|
686
|
-
|
|
867
|
+
let siteUrl = opts.siteUrl ?? parsedConfig.siteUrl;
|
|
868
|
+
let urlValidation = validateSiteUrl(siteUrl);
|
|
869
|
+
if (!urlValidation.valid) checkMark(false, "Site URL", urlValidation.error);
|
|
870
|
+
else {
|
|
871
|
+
const reachable = await validateSiteReachable(siteUrl);
|
|
872
|
+
if (!reachable.valid) {
|
|
873
|
+
checkMark(false, "Site URL reachable", reachable.error);
|
|
874
|
+
urlValidation = {
|
|
875
|
+
valid: false,
|
|
876
|
+
error: reachable.error
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
}
|
|
687
880
|
if (!urlValidation.valid) {
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
881
|
+
const prompted = await promptSiteUrl();
|
|
882
|
+
const reachable = await validateSiteReachable(prompted);
|
|
883
|
+
if (!reachable.valid) {
|
|
884
|
+
console.log(chalk.red(` ${reachable.error}`));
|
|
885
|
+
process.exit(1);
|
|
886
|
+
}
|
|
887
|
+
siteUrl = prompted;
|
|
691
888
|
}
|
|
692
889
|
const siteHost = new URL(siteUrl).host;
|
|
693
890
|
checkMark(true, "Site URL resolved", siteHost);
|
|
694
|
-
|
|
695
|
-
if (opts.key)
|
|
696
|
-
|
|
697
|
-
|
|
891
|
+
let key;
|
|
892
|
+
if (opts.key) {
|
|
893
|
+
key = opts.key;
|
|
894
|
+
checkMark(true, "API key provided", "via --key option");
|
|
895
|
+
} else if (process.env.INDEXNOW_KEY) {
|
|
896
|
+
key = process.env.INDEXNOW_KEY;
|
|
897
|
+
checkMark(true, "API key resolved", "via INDEXNOW_KEY env");
|
|
898
|
+
} else {
|
|
899
|
+
key = await promptApiKey(DEFAULT_INDEXNOW_KEY);
|
|
900
|
+
checkMark(true, "API key resolved", key === "91c80f732f4e4e5b80b4c02a7e8c9e9c" ? "using built-in default key" : "using custom key");
|
|
901
|
+
}
|
|
698
902
|
const keyFileCheck = ensureKeyFile(key);
|
|
699
903
|
if (!keyFileCheck.valid) {
|
|
700
904
|
checkMark(false, "Key file", keyFileCheck.error);
|
|
701
905
|
console.log(chalk.red(`\n ${keyFileCheck.error}`));
|
|
702
906
|
process.exit(1);
|
|
703
907
|
}
|
|
704
|
-
|
|
908
|
+
const keyFilePath = resolve(process.cwd(), "public", `${key}.txt`);
|
|
909
|
+
checkMark(true, "Key verification file", existsSync(keyFilePath) ? `${key}.txt exists` : `${key}.txt created`);
|
|
910
|
+
const keyLocation = `${siteUrl}/${key}.txt`;
|
|
705
911
|
return {
|
|
706
912
|
parsedConfig,
|
|
707
913
|
siteUrl,
|
|
708
914
|
siteHost,
|
|
709
915
|
key,
|
|
710
|
-
keyLocation
|
|
916
|
+
keyLocation
|
|
711
917
|
};
|
|
712
918
|
}
|
|
713
919
|
/**
|
|
714
920
|
* Read sitemap and display checkmark with URL count.
|
|
715
921
|
*
|
|
922
|
+
* Shows a lightweight ora spinner during the read to indicate progress on
|
|
923
|
+
* large sitemaps (10k+ URLs can take several seconds to fetch and parse).
|
|
924
|
+
*
|
|
716
925
|
* @param {object} opts - CLI options for sitemap path.
|
|
717
926
|
* @param {string} [opts.sitemap] - Custom sitemap path from --sitemap option.
|
|
718
927
|
* @param {NextSitemapConfig} parsedConfig - Parsed sitemap config for default outDir.
|
|
@@ -720,7 +929,22 @@ function runValidationChecks(opts) {
|
|
|
720
929
|
* @returns {Promise<string[]>} The list of URLs extracted from the sitemap.
|
|
721
930
|
*/
|
|
722
931
|
async function loadSitemapCheck(opts, parsedConfig) {
|
|
723
|
-
const
|
|
932
|
+
const sitemapPath = opts.sitemap ?? resolveSitemapPath(parsedConfig.outDir);
|
|
933
|
+
const loader = ora({
|
|
934
|
+
color: "cyan",
|
|
935
|
+
text: "Loading sitemapβ¦",
|
|
936
|
+
discardStdin: false
|
|
937
|
+
});
|
|
938
|
+
loader.start();
|
|
939
|
+
let sitemapResult;
|
|
940
|
+
try {
|
|
941
|
+
sitemapResult = await readSitemap(sitemapPath);
|
|
942
|
+
} catch (error) {
|
|
943
|
+
loader.stop();
|
|
944
|
+
checkMark(false, "Sitemap", error instanceof Error ? error.message : String(error));
|
|
945
|
+
process.exit(1);
|
|
946
|
+
}
|
|
947
|
+
loader.stop();
|
|
724
948
|
if (!sitemapResult.valid) {
|
|
725
949
|
checkMark(false, "Sitemap", sitemapResult.error);
|
|
726
950
|
console.log(chalk.red(`\n ${sitemapResult.error}`));
|
|
@@ -744,30 +968,54 @@ async function main() {
|
|
|
744
968
|
console.log(chalk.yellow(`next-indexnow: v${pkg.version}`));
|
|
745
969
|
console.log(SEPARATOR);
|
|
746
970
|
console.log("");
|
|
747
|
-
const { parsedConfig } = runValidationChecks({
|
|
971
|
+
const { parsedConfig } = await runValidationChecks({
|
|
748
972
|
siteUrl: opts.siteUrl,
|
|
749
973
|
key: opts.key
|
|
750
974
|
});
|
|
751
|
-
await loadSitemapCheck({ sitemap: opts.sitemap }, parsedConfig);
|
|
975
|
+
const sitemapUrls = await loadSitemapCheck({ sitemap: opts.sitemap }, parsedConfig);
|
|
976
|
+
if (opts.offset !== void 0 || opts.limit !== void 0) {
|
|
977
|
+
const selected = applyOffsetLimit(sitemapUrls, opts.offset, opts.limit);
|
|
978
|
+
const rangeParts = [];
|
|
979
|
+
if (opts.offset !== void 0) rangeParts.push(`offset ${opts.offset}`);
|
|
980
|
+
if (opts.limit !== void 0) rangeParts.push(`limit ${opts.limit}`);
|
|
981
|
+
checkMark(true, "URL range applied", `${selected.length} of ${sitemapUrls.length} URLs (${rangeParts.join(", ")})`);
|
|
982
|
+
}
|
|
752
983
|
console.log("");
|
|
984
|
+
if (!opts.dryRun) {
|
|
985
|
+
if (!await confirm()) {
|
|
986
|
+
console.log(chalk.yellow("Aborted by user."));
|
|
987
|
+
process.exit(0);
|
|
988
|
+
}
|
|
989
|
+
}
|
|
753
990
|
spinner = ora({
|
|
754
991
|
color: "cyan",
|
|
755
992
|
discardStdin: false
|
|
756
993
|
});
|
|
994
|
+
let submittedSoFar = 0;
|
|
995
|
+
let failedSoFar = 0;
|
|
757
996
|
const result = await run({
|
|
758
997
|
siteUrl: opts.siteUrl,
|
|
759
998
|
key: opts.key,
|
|
760
999
|
sitemap: opts.sitemap,
|
|
761
1000
|
chunkSize: opts.chunkSize,
|
|
1001
|
+
offset: opts.offset,
|
|
1002
|
+
limit: opts.limit,
|
|
762
1003
|
dryRun: opts.dryRun ?? false,
|
|
763
|
-
onProgress: ({ batch, totalBatches, urlCount }) => {
|
|
1004
|
+
onProgress: ({ batch, totalBatches, urlCount, previousResult }) => {
|
|
1005
|
+
if (previousResult) {
|
|
1006
|
+
if (previousResult.success) submittedSoFar += previousResult.count;
|
|
1007
|
+
else failedSoFar += previousResult.count;
|
|
1008
|
+
}
|
|
764
1009
|
if (spinner) {
|
|
765
|
-
spinner.text = `Submitting batch ${batch}/${totalBatches} (${urlCount} URLs)`;
|
|
1010
|
+
spinner.text = `Submitting batch ${batch}/${totalBatches} (${urlCount} URLs) ${chalk.green(`β ${submittedSoFar}`)} ${chalk.red(`β ${failedSoFar}`)}`;
|
|
766
1011
|
if (!spinner.isSpinning) spinner.start();
|
|
767
1012
|
}
|
|
768
1013
|
}
|
|
769
1014
|
});
|
|
770
|
-
if (spinner?.isSpinning)
|
|
1015
|
+
if (spinner?.isSpinning) {
|
|
1016
|
+
spinner.text = `Submitting complete ${chalk.green(`β ${result.urlsSubmitted}`)} ${chalk.red(`β ${result.urlsFailed}`)}`;
|
|
1017
|
+
spinner.stop();
|
|
1018
|
+
}
|
|
771
1019
|
console.log(SEPARATOR);
|
|
772
1020
|
console.log(`${logSymbols.success} Submission Completed π`);
|
|
773
1021
|
console.log(SEPARATOR);
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","names":[],"sources":["../src/constants.ts","../src/utils.ts","../src/index.ts","../src/bin/cli.ts"],"sourcesContent":["/**\n * Constants and configuration defaults for the IndexNow CLI tool.\n *\n * @module constants\n */\n\n/** IndexNow API endpoint for URL submission. */\nexport const INDEXNOW_API_URL = 'https://api.indexnow.org/indexnow';\n\n/** Number of URLs to submit per API request. */\nexport const CHUNK_SIZE = 100;\n\n/** Default sitemap index filename (generated by next-sitemap). */\nexport const DEFAULT_SITEMAP_FILE = 'sitemap.xml';\n\n/** Sitemap output directory (relative to project root or configured outDir). */\nexport const DEFAULT_SITEMAP_DIR = 'public';\n\n/** IndexNow key verification filename extension. */\nexport const KEY_FILE_EXTENSION = '.txt';\n\n/** Default IndexNow API key β used when neither --key option nor INDEXNOW_KEY env var is provided. */\nexport const DEFAULT_INDEXNOW_KEY = '91c80f732f4e4e5b80b4c02a7e8c9e9c';\n\n/** Conventional Next.js config filenames to detect a Next.js project. */\nexport const NEXT_CONFIG_FILES = ['next.config.ts', 'next.config.mjs', 'next.config.cjs', 'next.config.js'];\n\n/** Conventional next-sitemap config filenames. */\nexport const NEXT_SITEMAP_CONFIG_FILES = [\n 'next-sitemap.config.ts',\n 'next-sitemap.config.mjs',\n 'next-sitemap.config.cjs',\n 'next-sitemap.config.js',\n];\n\n/** Directories that indicate a Next.js build has been run. */\nexport const NEXT_BUILD_DIRS = ['.next'];\n","/**\n * Validation and helper functions for the IndexNow CLI.\n *\n * @module utils\n */\n\nimport { existsSync, readFileSync, mkdirSync, writeFileSync, readdirSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nimport xml2js from 'xml2js';\n\nimport {\n INDEXNOW_API_URL,\n NEXT_CONFIG_FILES,\n NEXT_SITEMAP_CONFIG_FILES,\n NEXT_BUILD_DIRS,\n KEY_FILE_EXTENSION,\n DEFAULT_SITEMAP_DIR,\n DEFAULT_SITEMAP_FILE,\n} from './constants.ts';\nimport type { NextSitemapConfig, ValidationResult, SubmissionResult } from './types.ts';\n\n/**\n * Validate that a URL string is a valid site domain with http:// or https:// scheme.\n *\n * Accepts standard domains (example.com), subdomains, and localhost with ports.\n *\n * @param {string} url - The URL string to validate.\n *\n * @returns {ValidationResult} Valid result on success, or invalid with an error message.\n */\nexport function validateSiteUrl(url: string): ValidationResult {\n if (!url || typeof url !== 'string') {\n return { valid: false, error: 'Site URL is required.' };\n }\n\n if (!url.startsWith('http://') && !url.startsWith('https://')) {\n return { valid: false, error: 'Site URL must start with http:// or https://' };\n }\n\n try {\n const parsed = new URL(url);\n\n /* v8 ignore next 3 */\n if (!parsed.hostname) {\n return { valid: false, error: 'Site URL must have a valid hostname.' };\n }\n\n // Reject URLs with path, query, or fragment beyond a single trailing slash\n /* v8 ignore next 2 */\n if ((parsed.pathname !== '/' && parsed.pathname !== '') || parsed.search || parsed.hash) {\n return { valid: false, error: 'Site URL should be a domain root (e.g. https://example.com).' };\n }\n } catch {\n return { valid: false, error: `Invalid URL: \"${url}\". Please provide a valid URL.` };\n }\n\n return { valid: true };\n}\n\n/**\n * Check if the current working directory is a Next.js project by looking for\n * a next.config.* file.\n *\n * @returns {ValidationResult} Valid result if a Next.js config file is found.\n */\nexport function validateNextProject(): ValidationResult {\n for (const configFile of NEXT_CONFIG_FILES) {\n if (existsSync(resolve(process.cwd(), configFile))) {\n return { valid: true };\n }\n }\n\n return { valid: false, error: 'No next.config.* file found. This command must be run from a Next.js project root.' };\n}\n\n/**\n * Check if the `.next` build directory exists and contains files.\n *\n * @returns {ValidationResult} Valid result if `.next` exists and is not empty.\n */\nexport function validateDotNext(): ValidationResult {\n for (const dir of NEXT_BUILD_DIRS) {\n const dotNextPath = resolve(process.cwd(), dir);\n\n if (!existsSync(dotNextPath)) {\n return { valid: false, error: `\"${dir}\" directory not found. Run \"next build\" first.` };\n }\n\n try {\n const entries = readdirSync(dotNextPath);\n if (entries.length === 0) {\n return { valid: false, error: `\"${dir}\" directory is empty. Run \"next build\" first.` };\n }\n } catch {\n /* v8 ignore next */\n return { valid: false, error: `Cannot read \"${dir}\" directory.` };\n }\n }\n\n return { valid: true };\n}\n\n/**\n * Check if a next-sitemap config file exists and is not empty.\n *\n * Searches for next-sitemap.config.{ts,mjs,cjs,js} in the current directory.\n *\n * @returns {ValidationResult & { filePath?: string }} Valid result with the config file path if found.\n */\nexport function validateNextSitemapConfig(): ValidationResult & { filePath?: string } {\n for (const configFile of NEXT_SITEMAP_CONFIG_FILES) {\n const configPath = resolve(process.cwd(), configFile);\n\n if (existsSync(configPath)) {\n const content = readFileSync(configPath, 'utf-8').trim();\n\n if (content.length === 0) {\n return { valid: false, error: `\"${configFile}\" exists but is empty.` };\n }\n\n return { valid: true, filePath: configPath };\n }\n }\n\n return { valid: false, error: 'No next-sitemap.config.* file found. Create one to configure your sitemap.' };\n}\n\n/**\n * Read the next-sitemap config file and extract the `siteUrl` and optional `outDir`.\n *\n * Parses the file to extract `siteUrl` from the config object, handling both\n * inline string literals (`siteUrl: 'https://...'`) and variable references\n * (`const siteDomain = 'https://...'; siteUrl: siteDomain`). Avoids the\n * complexity of dynamic module loading for mixed CJS/ESM configs.\n *\n * @param {string} configPath - Absolute path to the next-sitemap config file.\n *\n * @returns {NextSitemapConfig | null} Parsed config, or null if siteUrl could not be extracted.\n */\nexport function readSitemapConfig(configPath: string): NextSitemapConfig | null {\n try {\n const content = readFileSync(configPath, 'utf-8');\n\n // Build a map of const variable names to their string literal values\n const constMap = new Map<string, string>();\n for (const match of content.matchAll(/const\\s+(\\w+)\\s*=\\s*['\"]([^'\"]+)['\"]/g)) {\n constMap.set(match[1]!, match[2]!);\n }\n\n // Try inline string literal first, then variable reference lookup\n const siteUrlMatch = content.match(/siteUrl:\\s*['\"]([^'\"]+)['\"]/);\n const siteUrlVarMatch = content.match(/siteUrl:\\s*(\\w+)/);\n\n const siteUrl = siteUrlMatch?.[1] ?? (siteUrlVarMatch?.[1] ? constMap.get(siteUrlVarMatch[1]) : undefined);\n\n if (!siteUrl) {\n return null;\n }\n\n // Extract optional outDir value (inline string literal or variable)\n const outDirMatch = content.match(/outDir:\\s*['\"]([^'\"]+)['\"]/);\n const outDirVarMatch = content.match(/outDir:\\s*(\\w+)/);\n const outDir = outDirMatch?.[1] ?? (outDirVarMatch?.[1] ? constMap.get(outDirVarMatch[1]) : undefined);\n\n return { siteUrl, outDir };\n } catch {\n return null;\n }\n}\n\n/**\n * Ensure the IndexNow verification key file exists at public/<key>.txt with the\n * correct content. Creates the file and directory if they don't exist.\n *\n * @param {string} key - The IndexNow API key.\n *\n * @returns {ValidationResult} Valid result if the key file exists or was created successfully.\n */\nexport function ensureKeyFile(key: string): ValidationResult {\n if (!key || typeof key !== 'string') {\n return { valid: false, error: 'IndexNow API key is required.' };\n }\n\n const publicDir = resolve(process.cwd(), DEFAULT_SITEMAP_DIR);\n const keyFilePath = resolve(publicDir, `${key}${KEY_FILE_EXTENSION}`);\n\n try {\n if (!existsSync(publicDir)) {\n mkdirSync(publicDir, { recursive: true });\n }\n\n if (existsSync(keyFilePath)) {\n const existingContent = readFileSync(keyFilePath, 'utf-8').trim();\n\n if (existingContent !== key) {\n writeFileSync(keyFilePath, key, 'utf-8');\n }\n } else {\n writeFileSync(keyFilePath, key, 'utf-8');\n }\n\n return { valid: true };\n } catch (error) {\n /* v8 ignore next 3 */\n return {\n valid: false,\n error: `Failed to create key file: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n}\n\n/**\n * Resolve the sitemap file path.\n *\n * Uses the configured outDir (from next-sitemap.config) or falls back to\n * the default `public/` directory.\n *\n * @param {string} [outDir] - Custom output directory from next-sitemap config.\n * @param {string} [sitemapFile] - Custom sitemap filename. Defaults to sitemap-0.xml.\n *\n * @returns {string} Absolute path to the sitemap file.\n */\nexport function resolveSitemapPath(outDir?: string, sitemapFile?: string): string {\n const dir = outDir ?? DEFAULT_SITEMAP_DIR;\n const file = sitemapFile ?? DEFAULT_SITEMAP_FILE;\n return resolve(process.cwd(), dir, file);\n}\n\n/**\n * Fetch XML content from a remote sitemap URL.\n *\n * @param {string} url - The remote sitemap URL to fetch.\n *\n * @returns {Promise<string>} The XML content of the sitemap.\n *\n * @throws {Error} If the fetch fails or the response is not OK.\n */\nasync function fetchSitemapXml(url: string): Promise<string> {\n const response = await fetch(url);\n\n if (!response.ok) {\n throw new Error(`Failed to fetch sitemap: ${url} (${response.status})`);\n }\n\n return response.text();\n}\n\n/**\n * Read and parse a sitemap XML file, extracting all `<loc>` URLs.\n *\n * Supports both regular sitemaps (`<urlset>`) and sitemap index files\n * (`<sitemapindex>`). For sitemap index files, each referenced sub-sitemap\n * is fetched and parsed to collect all URLs.\n *\n * @param {string} sitemapPath - Absolute path to the sitemap XML file.\n *\n * @returns {Promise<ValidationResult & { urls?: string[] }>} Valid result with URL list, or invalid with error.\n */\nexport async function readSitemap(sitemapPath: string): Promise<ValidationResult & { urls?: string[] }> {\n let content: string;\n\n try {\n content = await readFileSync(sitemapPath, 'utf-8');\n } catch {\n return { valid: false, error: `Sitemap file not found: ${sitemapPath}` };\n }\n\n let parsed: { urlset?: { url?: Array<{ loc?: string[] }> }; sitemapindex?: { sitemap?: Array<{ loc?: string[] }> } };\n\n try {\n parsed = await xml2js.parseStringPromise(content);\n } catch {\n return { valid: false, error: 'Failed to parse sitemap XML. Ensure the file is valid XML.' };\n }\n\n // Check if this is a sitemap index file\n if (parsed.sitemapindex) {\n const subSitemapUrls = (parsed.sitemapindex.sitemap ?? [])\n .map((entry) => entry.loc?.[0])\n .filter(Boolean) as string[];\n\n if (subSitemapUrls.length === 0) {\n return { valid: false, error: 'No sub-sitemap URLs found in the sitemap index.' };\n }\n\n const allUrls: string[] = [];\n\n for (const subSitemapUrl of subSitemapUrls) {\n try {\n const subContent = await fetchSitemapXml(subSitemapUrl);\n const subParsed: { urlset?: { url?: Array<{ loc?: string[] }> } } = await xml2js.parseStringPromise(subContent);\n\n const urls = subParsed.urlset?.url?.map((entry) => entry.loc?.[0]).filter(Boolean) as string[] | undefined;\n\n if (urls && urls.length > 0) {\n allUrls.push(...urls);\n }\n } catch {\n // Skip failed sub-sitemaps silently\n }\n }\n\n if (allUrls.length === 0) {\n return { valid: false, error: 'No URLs found in any of the sub-sitemaps.' };\n }\n\n return { valid: true, urls: allUrls };\n }\n\n // Regular sitemap\n const urls = parsed.urlset?.url?.map((entry) => entry.loc?.[0]).filter(Boolean) as string[] | undefined;\n\n if (!urls || urls.length === 0) {\n return { valid: false, error: 'No URLs found in the sitemap.' };\n }\n\n return { valid: true, urls };\n}\n\n/**\n * Submit a batch of URLs to the IndexNow API.\n *\n * @param {string} host - The site hostname (e.g. example.com).\n * @param {string} key - The IndexNow API key.\n * @param {string} keyLocation - Public URL of the key verification file.\n * @param {string[]} urlList - URLs to submit in this batch.\n *\n * @returns {Promise<SubmissionResult>} The submission result for this batch.\n */\nexport async function submitUrls(\n host: string,\n key: string,\n keyLocation: string,\n urlList: string[]\n): Promise<SubmissionResult> {\n try {\n const response = await fetch(INDEXNOW_API_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ host, key, keyLocation, urlList }),\n });\n\n if (response.ok) {\n return { count: urlList.length, success: true };\n }\n\n const errorText = await response.text();\n return { count: urlList.length, success: false, error: errorText };\n } catch (error) {\n /* v8 ignore next 4 */\n return {\n count: urlList.length,\n success: false,\n error: `Network error: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n}\n","/**\n * Core orchestration for the IndexNow CLI tool.\n *\n * Coordinates validation, sitemap parsing, and URL submission to the\n * IndexNow API for faster search engine indexing.\n *\n * @module next-indexnow\n */\n\nimport { CHUNK_SIZE, DEFAULT_INDEXNOW_KEY } from './constants.ts';\nimport type {\n NextIndexnowOptions,\n NextIndexnowResult,\n NextSitemapConfig,\n SubmissionResult,\n IndexnowProgress,\n} from './types.ts';\nimport {\n validateSiteUrl,\n validateNextProject,\n validateDotNext,\n validateNextSitemapConfig,\n readSitemapConfig,\n ensureKeyFile,\n resolveSitemapPath,\n readSitemap,\n submitUrls,\n} from './utils.ts';\n\n/**\n * Validate basic project structure: Next.js project and .next build directory.\n *\n * @throws {Error} If the project is not a Next.js project or .next is missing/empty.\n */\nfunction validateBasicEnvironment(): void {\n const projectCheck = validateNextProject();\n if (!projectCheck.valid) {\n throw new Error(projectCheck.error);\n }\n\n const dotNextCheck = validateDotNext();\n if (!dotNextCheck.valid) {\n throw new Error(dotNextCheck.error);\n }\n}\n\n/**\n * Validate that a sitemap config file exists and parse its contents.\n *\n * @returns {NextSitemapConfig} The parsed sitemap config (siteUrl + optional outDir).\n *\n * @throws {Error} If no config file is found, it is empty, or siteUrl cannot be parsed.\n */\nfunction validateSitemapSetup(): NextSitemapConfig {\n const sitemapConfigCheck = validateNextSitemapConfig();\n if (!sitemapConfigCheck.valid) {\n throw new Error(sitemapConfigCheck.error!);\n }\n\n const parsedConfig = readSitemapConfig(sitemapConfigCheck.filePath!);\n if (!parsedConfig) {\n throw new Error(`Could not parse \"siteUrl\" from ${sitemapConfigCheck.filePath}.`);\n }\n\n return parsedConfig;\n}\n\n/**\n * Validate that the current directory is a Next.js project with a build\n * artifact and a sitemap config file.\n *\n * @returns {NextSitemapConfig} The parsed sitemap config (siteUrl + optional outDir).\n *\n * @throws {Error} If any validation check fails.\n */\nfunction validateEnvironment(): NextSitemapConfig {\n validateBasicEnvironment();\n return validateSitemapSetup();\n}\n\n/**\n * Resolve the effective siteUrl from options (takes precedence) or config file,\n * validate it, and extract the hostname.\n *\n * @param {NextIndexnowOptions} options - CLI options.\n * @param {NextSitemapConfig} parsedConfig - Parsed sitemap config.\n *\n * @returns {{ siteUrl: string; siteHost: string }} The resolved site URL and host.\n *\n * @throws {Error} If the site URL is invalid.\n */\nfunction resolveSiteConfig(\n options: NextIndexnowOptions,\n parsedConfig: NextSitemapConfig\n): { siteUrl: string; siteHost: string } {\n const siteUrl = options.siteUrl ?? parsedConfig.siteUrl;\n\n const urlValidation = validateSiteUrl(siteUrl);\n if (!urlValidation.valid) {\n throw new Error(urlValidation.error);\n }\n\n return { siteUrl, siteHost: new URL(siteUrl).host };\n}\n\n/**\n * Resolve the IndexNow API key from options or the INDEXNOW_KEY env var,\n * and ensure the verification key file exists on disk.\n *\n * Falls back to a hard-coded default key when neither the --key option nor\n * the INDEXNOW_KEY environment variable is provided.\n *\n * @param {NextIndexnowOptions} options - CLI options.\n * @param {string} siteUrl - The resolved site URL (for keyLocation).\n *\n * @returns {{ key: string; keyLocation: string }} The resolved key and its public URL.\n *\n * @throws {Error} If the key file cannot be created.\n */\nfunction resolveKeyValue(options: NextIndexnowOptions, siteUrl: string): { key: string; keyLocation: string } {\n const key = options.key ?? process.env.INDEXNOW_KEY ?? DEFAULT_INDEXNOW_KEY;\n\n const keyFileCheck = ensureKeyFile(key);\n if (!keyFileCheck.valid) {\n throw new Error(keyFileCheck.error);\n }\n\n return { key, keyLocation: `${siteUrl}/${key}.txt` };\n}\n\n/**\n * Resolve the sitemap path (from options or default) and read all URLs.\n *\n * @param {NextIndexnowOptions} options - CLI options.\n * @param {NextSitemapConfig} parsedConfig - Parsed sitemap config (for outDir).\n *\n * @returns {Promise<string[]>} The list of URLs found in the sitemap.\n *\n * @throws {Error} If the sitemap cannot be read or contains no URLs.\n */\nasync function loadSitemapUrls(options: NextIndexnowOptions, parsedConfig: NextSitemapConfig): Promise<string[]> {\n const sitemapPath = options.sitemap ?? resolveSitemapPath(parsedConfig.outDir);\n\n const sitemapResult = await readSitemap(sitemapPath);\n if (!sitemapResult.valid) {\n throw new Error(sitemapResult.error!);\n }\n\n return sitemapResult.urls!;\n}\n\n/**\n * Submit all URLs to the IndexNow API in chunks and aggregate the results.\n *\n * @param {string[]} urls - All URLs to submit.\n * @param {string} siteHost - The site hostname.\n * @param {string} key - The IndexNow API key.\n * @param {string} keyLocation - Public URL of the key verification file.\n * @param {number} chunkSize - Maximum URLs per submission batch.\n * @param {IndexnowProgress} [onProgress] - Callback invoked before each batch submission.\n *\n * @returns {Promise<Pick<NextIndexnowResult, 'urlsSubmitted' | 'urlsFailed' | 'chunks'>>} Aggregate submission counts and per-chunk details.\n */\nasync function submitAllUrls(\n urls: string[],\n siteHost: string,\n key: string,\n keyLocation: string,\n chunkSize: number,\n onProgress?: IndexnowProgress\n): Promise<Pick<NextIndexnowResult, 'urlsSubmitted' | 'urlsFailed' | 'chunks'>> {\n const chunks: SubmissionResult[] = [];\n const totalChunks = Math.ceil(urls.length / chunkSize);\n\n for (let i = 0; i < urls.length; i += chunkSize) {\n const batchNum = Math.floor(i / chunkSize) + 1;\n const chunk = urls.slice(i, i + chunkSize);\n\n onProgress?.({ batch: batchNum, totalBatches: totalChunks, urlCount: chunk.length });\n\n const result = await submitUrls(siteHost, key, keyLocation, chunk);\n chunks.push(result);\n }\n\n const urlsSubmitted = chunks.filter((c) => c.success).reduce((sum, c) => sum + c.count, 0);\n const urlsFailed = chunks.filter((c) => !c.success).reduce((sum, c) => sum + c.count, 0);\n\n return { urlsSubmitted, urlsFailed, chunks };\n}\n\n/**\n * Run the IndexNow submission process.\n *\n * Validates the environment (Next.js project, .next dir, sitemap config),\n * reads the sitemap, and submits all URLs to the IndexNow API in chunks.\n *\n * @param {NextIndexnowOptions} options - CLI options (siteUrl, key, sitemap, chunkSize, dryRun, onProgress).\n *\n * @returns {Promise<NextIndexnowResult>} Aggregate result with per-chunk details.\n */\nexport async function run(options: NextIndexnowOptions = {}): Promise<NextIndexnowResult> {\n const startTime = Date.now();\n const chunkSize = options.chunkSize ?? CHUNK_SIZE;\n\n // 1. Validate environment & parse sitemap config\n const parsedConfig = validateEnvironment();\n\n // 2. Resolve siteUrl & key\n const { siteUrl, siteHost } = resolveSiteConfig(options, parsedConfig);\n const { key, keyLocation } = resolveKeyValue(options, siteUrl);\n\n // 3. Read sitemap\n const urls = await loadSitemapUrls(options, parsedConfig);\n\n // 4. Dry-run short-circuit\n if (options.dryRun) {\n return { urlsFound: urls.length, urlsSubmitted: 0, urlsFailed: 0, chunks: [], durationMs: Date.now() - startTime };\n }\n\n // 5. Submit URLs in chunks\n const { urlsSubmitted, urlsFailed, chunks } = await submitAllUrls(\n urls,\n siteHost,\n key,\n keyLocation,\n chunkSize,\n options.onProgress\n );\n\n return { urlsFound: urls.length, urlsSubmitted, urlsFailed, chunks, durationMs: Date.now() - startTime };\n}\n","#!/usr/bin/env node\n\n/**\n * next-indexnow β CLI entry point.\n *\n * Parses command-line arguments with commander, validates the environment,\n * reads the Next.js sitemap, and submits URLs to the IndexNow API.\n *\n * @module cli\n *\n * Usage:\n * next-indexnow\n * next-indexnow --site-url https://example.com\n * next-indexnow --key my-api-key\n * next-indexnow --sitemap ./public/sitemap-0.xml\n * next-indexnow --dry-run\n * next-indexnow --help\n */\n\nimport { readFileSync, existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport chalk from 'chalk';\nimport { Command } from 'commander';\nimport logSymbols from 'log-symbols';\nimport ora from 'ora';\n\nimport { DEFAULT_INDEXNOW_KEY } from '../constants.ts';\nimport { run } from '../index.ts';\nimport type { NextIndexnowResult, NextSitemapConfig } from '../types.ts';\nimport {\n validateNextProject,\n validateDotNext,\n validateNextSitemapConfig,\n readSitemapConfig,\n validateSiteUrl,\n ensureKeyFile,\n resolveSitemapPath,\n readSitemap,\n} from '../utils.ts';\n\n// ββ ASCII Banner βββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n\nconst BANNER = `\n${chalk.white('βββββββ ββββββββββ βββββββββββ βββββββ βββ βββββββ βββ βββ')}\n${chalk.white('ββββββββ ββββββββββββββββββββββββββββββββ βββββββββββββββ βββ')}\n${chalk.white('βββββββββ ββββββ βββββββββ ββββββ ββββββ ββββββ ββββββ ββ βββ')}\n${chalk.white('ββββββββββββββββ βββββββββ ββββββ βββββββββββββ βββββββββββββ')}\n${chalk.white('ββββββ ββββββββββββββββββββββββββ ββββββ βββββββββββββββββββββββββ')}\n${chalk.white('ββββββ ββββββββββββ βββββββββββ ββββββ βββββ βββββββ ββββββββ')}`;\n\nconst SEPARATOR = chalk.dim('='.repeat(69));\n\n// ββ Helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n\n/**\n * Print a checkmark or error icon with a status label and optional detail.\n *\n * @param {boolean} valid - Whether the check passed.\n * @param {string} label - The status label text.\n * @param {string} [detail] - Optional detail text shown after a colon.\n */\nfunction checkMark(valid: boolean, label: string, detail?: string): void {\n const icon = valid ? logSymbols.success : logSymbols.error;\n const msg = detail ? `${label}: ${detail}` : label;\n console.log(`${icon} ${msg}`);\n}\n\n/**\n * Print the summary footer message after a submission run.\n *\n * @param {NextIndexnowResult} result - The result from the IndexNow submission run.\n * @param {boolean} dryRun - Whether this was a dry-run preview.\n */\nfunction printFooter(result: NextIndexnowResult, dryRun: boolean): void {\n console.log('');\n if (dryRun) {\n console.log(chalk.green(`Dry-run complete. ${result.urlsFound} urls would be submitted. π`));\n } else if (result.urlsFailed === 0) {\n console.log(chalk.green(`All ${result.urlsSubmitted} urls submitted to IndexNow successfully! π₯³`));\n } else {\n console.log(\n chalk.yellow(\n `${result.urlsSubmitted}/${result.urlsFound} urls submitted successfully (${result.urlsFailed} failed).`\n )\n );\n }\n}\n\n/**\n * Print detailed failure information for failed submission batches.\n *\n * @param {NextIndexnowResult} result - The result from the IndexNow submission run.\n */\nfunction printFailures(result: NextIndexnowResult): void {\n if (result.urlsFailed === 0) return;\n\n console.error(chalk.red.bold('Failed submissions:'));\n for (const chunk of result.chunks) {\n if (!chunk.success) {\n console.error(chalk.red(` ${logSymbols.error} ${chunk.error}`));\n }\n }\n}\n\n// ββ Ctrl+C handling ββββββββββββββββββββββββββββββββββββββββββββββββββββ\n\nlet spinner: ReturnType<typeof ora> | null = null;\n\nprocess.on('SIGINT', () => {\n if (spinner?.isSpinning) {\n spinner.stop();\n }\n console.log('');\n console.log(chalk.yellow('Process aborted by user.'));\n process.exit(130);\n});\n\nconst __filename = fileURLToPath(import.meta.url);\nconst pkgPathSrc = resolve(__filename, '..', '..', '..', 'package.json');\nconst pkgPathDist = resolve(__filename, '..', '..', 'package.json');\nconst pkgPath = existsSync(pkgPathDist) ? pkgPathDist : pkgPathSrc;\n\n// Read version from package.json\nconst pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as { version: string };\n\nconst program = new Command();\n\nprogram\n .name('next-indexnow')\n .description('Submit Next.js sitemap URLs to the IndexNow API for faster search engine indexing')\n .version(pkg.version)\n .option('--site-url <url>', 'The site URL (e.g. https://example.com). Overrides next-sitemap.config value.')\n .option('--key <key>', 'IndexNow API key. Falls back to INDEXNOW_KEY environment variable or a built-in default key.')\n .option('--sitemap <path>', 'Path to the sitemap XML file')\n .option('--chunk-size <number>', 'URLs per submission batch', (v) => Number.parseInt(v, 10), 100)\n .option('-d, --dry-run', 'Preview URLs without submitting to the IndexNow API')\n .addHelpText(\n 'after',\n `\nExamples:\n $ next-indexnow Submit URLs using settings from next-sitemap.config\n $ next-indexnow --site-url https://example.com Override the site URL\n $ next-indexnow --key my-api-key Provide IndexNow API key\n $ next-indexnow --sitemap ./public/sitemap.xml Use a custom sitemap path\n $ next-indexnow --dry-run Preview URLs without submitting\n $ next-indexnow --help Show this help message\n `\n )\n .parse(process.argv);\n\n/**\n * Log submission results as checkmark-style lines.\n *\n * @param {NextIndexnowResult} result - The result from the IndexNow submission run.\n */\nfunction displayResults(result: NextIndexnowResult): void {\n const duration = (result.durationMs / 1000).toFixed(2);\n\n const items = [\n ['URLs found', String(result.urlsFound), result.urlsFound > 0 ? ('blue' as const) : ('dim' as const)],\n ['URLs submitted', String(result.urlsSubmitted), result.urlsSubmitted > 0 ? ('green' as const) : ('dim' as const)],\n ['URLs failed', String(result.urlsFailed), result.urlsFailed > 0 ? ('red' as const) : ('dim' as const)],\n ['Duration', `${duration}s`, result.durationMs > 0 ? ('yellow' as const) : ('dim' as const)],\n ];\n\n console.log('');\n for (const [label, value, color] of items) {\n let coloredValue: string;\n switch (color) {\n case 'red':\n coloredValue = chalk.red(value);\n break;\n case 'dim':\n coloredValue = chalk.dim(value);\n break;\n case 'green':\n coloredValue = chalk.green(value);\n break;\n case 'blue':\n coloredValue = chalk.blue(value);\n break;\n case 'yellow':\n coloredValue = chalk.yellow(value);\n break;\n default:\n coloredValue = value;\n break;\n }\n console.log(`${logSymbols.success} ${chalk.bold(label)}: ${coloredValue}`);\n }\n}\n\n/**\n * Run validation checks and display checkmarks, returning parsed state.\n *\n * @param {object} opts - CLI options for site URL and API key.\n * @param {string} [opts.siteUrl] - Override site URL from --site-url option.\n * @param {string} [opts.key] - Override API key from --key option.\n *\n * @returns {{ parsedConfig: NextSitemapConfig; siteUrl: string; siteHost: string; key: string; keyLocation: string }} Resolved configuration values.\n */\nfunction runValidationChecks(opts: { siteUrl?: string; key?: string }): {\n parsedConfig: NextSitemapConfig;\n siteUrl: string;\n siteHost: string;\n key: string;\n keyLocation: string;\n} {\n // 1. Next.js project check\n const projectCheck = validateNextProject();\n if (!projectCheck.valid) {\n checkMark(false, 'Next.js project', projectCheck.error);\n console.log(chalk.red(`\\n ${projectCheck.error}`));\n process.exit(1);\n }\n checkMark(true, 'Next.js config found');\n\n // 2. Build directory check\n const dotNextCheck = validateDotNext();\n if (!dotNextCheck.valid) {\n checkMark(false, 'Build directory', dotNextCheck.error);\n console.log(chalk.red(`\\n ${dotNextCheck.error}`));\n process.exit(1);\n }\n checkMark(true, 'Build directory exists (.next)');\n\n // 3. Sitemap config check\n const sitemapConfigCheck = validateNextSitemapConfig();\n if (!sitemapConfigCheck.valid) {\n checkMark(false, 'Sitemap config', sitemapConfigCheck.error);\n console.log(chalk.red(`\\n ${sitemapConfigCheck.error}`));\n process.exit(1);\n }\n checkMark(true, 'Sitemap config found', sitemapConfigCheck.filePath);\n\n // 4. Parse sitemap config\n const parsedConfig = readSitemapConfig(sitemapConfigCheck.filePath!);\n if (!parsedConfig) {\n checkMark(false, 'Site URL', 'Could not parse siteUrl from config');\n console.log(chalk.red(`\\n Could not parse \"siteUrl\" from ${sitemapConfigCheck.filePath}.`));\n process.exit(1);\n }\n\n // 5. Validate site URL\n const siteUrl = opts.siteUrl ?? parsedConfig.siteUrl;\n const urlValidation = validateSiteUrl(siteUrl);\n if (!urlValidation.valid) {\n checkMark(false, 'Site URL', urlValidation.error);\n console.log(chalk.red(`\\n ${urlValidation.error}`));\n process.exit(1);\n }\n const siteHost = new URL(siteUrl).host;\n checkMark(true, 'Site URL resolved', siteHost);\n\n // 6. Resolve API key\n const key = opts.key ?? process.env.INDEXNOW_KEY ?? DEFAULT_INDEXNOW_KEY;\n if (opts.key) {\n checkMark(true, 'API key provided', 'via --key option');\n } else if (process.env.INDEXNOW_KEY) {\n checkMark(true, 'API key resolved', 'via INDEXNOW_KEY env');\n } else {\n checkMark(true, 'API key resolved', 'using built-in default key');\n }\n\n // 7. Ensure key file\n const keyFileCheck = ensureKeyFile(key);\n if (!keyFileCheck.valid) {\n checkMark(false, 'Key file', keyFileCheck.error);\n console.log(chalk.red(`\\n ${keyFileCheck.error}`));\n process.exit(1);\n }\n const keyFilePath = resolve(process.cwd(), 'public', `${key}.txt`);\n const keyExists = existsSync(keyFilePath);\n checkMark(true, 'Key verification file', keyExists ? `${key}.txt exists` : `${key}.txt created`);\n\n const keyLocation = `${siteUrl}/${key}.txt`;\n\n return { parsedConfig, siteUrl, siteHost, key, keyLocation };\n}\n\n/**\n * Read sitemap and display checkmark with URL count.\n *\n * @param {object} opts - CLI options for sitemap path.\n * @param {string} [opts.sitemap] - Custom sitemap path from --sitemap option.\n * @param {NextSitemapConfig} parsedConfig - Parsed sitemap config for default outDir.\n *\n * @returns {Promise<string[]>} The list of URLs extracted from the sitemap.\n */\nasync function loadSitemapCheck(opts: { sitemap?: string }, parsedConfig: NextSitemapConfig): Promise<string[]> {\n const sitemapPath = opts.sitemap ?? resolveSitemapPath(parsedConfig.outDir);\n\n const sitemapResult = await readSitemap(sitemapPath);\n if (!sitemapResult.valid) {\n checkMark(false, 'Sitemap', sitemapResult.error);\n console.log(chalk.red(`\\n ${sitemapResult.error}`));\n process.exit(1);\n }\n\n const urlCount = sitemapResult.urls!.length;\n checkMark(true, 'Sitemap loaded', `${urlCount} URLs found`);\n\n return sitemapResult.urls!;\n}\n\n/**\n * Main CLI entry point.\n *\n * Displays the ASCII banner, runs validation checks with checkmarks,\n * submits URLs to the IndexNow API, and shows results with a footer.\n */\nexport async function main(): Promise<void> {\n const opts = program.optsWithGlobals();\n\n // 1. Show banner + separator + version\n console.log(BANNER);\n console.log('');\n console.log(SEPARATOR);\n console.log(chalk.yellow(`next-indexnow: v${pkg.version}`));\n console.log(SEPARATOR);\n console.log('');\n\n // 2. Run validation checks with checkmarks\n const { parsedConfig } = runValidationChecks({ siteUrl: opts.siteUrl, key: opts.key });\n await loadSitemapCheck({ sitemap: opts.sitemap }, parsedConfig);\n\n console.log('');\n\n // 3. Submit URLs with progress spinner\n spinner = ora({ color: 'cyan', discardStdin: false });\n\n const result = await run({\n siteUrl: opts.siteUrl,\n key: opts.key,\n sitemap: opts.sitemap,\n chunkSize: opts.chunkSize,\n dryRun: opts.dryRun ?? false,\n onProgress: ({ batch, totalBatches, urlCount }) => {\n if (spinner) {\n spinner.text = `Submitting batch ${batch}/${totalBatches} (${urlCount} URLs)`;\n if (!spinner.isSpinning) {\n spinner.start();\n }\n }\n },\n });\n\n if (spinner?.isSpinning) {\n spinner.stop();\n }\n console.log(SEPARATOR);\n console.log(`${logSymbols.success} Submission Completed π`);\n console.log(SEPARATOR);\n\n // 4. Show results\n displayResults(result);\n\n // 5. Show footer\n printFooter(result, opts.dryRun);\n\n // 6. Show failure details at end\n printFailures(result);\n\n // 7. Exit with appropriate code\n if (result.urlsFailed > 0) {\n process.exit(1);\n }\n}\n\nmain().catch((error) => {\n console.error(chalk.red('Error:'), error instanceof Error ? error.message : String(error));\n process.exit(1);\n});\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.js","names":[],"sources":["../src/constants.ts","../src/utils.ts","../src/index.ts","../src/prompts.ts","../src/bin/cli.ts"],"sourcesContent":["/**\n * Constants and configuration defaults for the IndexNow CLI tool.\n *\n * @module constants\n */\n\n/** IndexNow API endpoint for URL submission. */\nexport const INDEXNOW_API_URL = 'https://api.indexnow.org/indexnow';\n\n/** Number of URLs to submit per API request. */\nexport const CHUNK_SIZE = 100;\n\n/** Default sitemap index filename (generated by next-sitemap). */\nexport const DEFAULT_SITEMAP_FILE = 'sitemap.xml';\n\n/** Sitemap output directory (relative to project root or configured outDir). */\nexport const DEFAULT_SITEMAP_DIR = 'public';\n\n/** IndexNow key verification filename extension. */\nexport const KEY_FILE_EXTENSION = '.txt';\n\n/** Default IndexNow API key β used when neither --key option nor INDEXNOW_KEY env var is provided. */\nexport const DEFAULT_INDEXNOW_KEY = '91c80f732f4e4e5b80b4c02a7e8c9e9c';\n\n/** Conventional Next.js config filenames to detect a Next.js project. */\nexport const NEXT_CONFIG_FILES = ['next.config.ts', 'next.config.mjs', 'next.config.cjs', 'next.config.js'];\n\n/** Conventional next-sitemap config filenames, ordered by priority (.js first, .ts last). */\nexport const NEXT_SITEMAP_CONFIG_FILES = [\n 'next-sitemap.config.js',\n 'next-sitemap.config.cjs',\n 'next-sitemap.config.mjs',\n 'next-sitemap.config.ts',\n];\n\n/** Directories that indicate a Next.js build has been run. */\nexport const NEXT_BUILD_DIRS = ['.next'];\n","/**\n * Validation and helper functions for the IndexNow CLI.\n *\n * @module utils\n */\n\nimport { existsSync, readFileSync, mkdirSync, writeFileSync, readdirSync } from 'node:fs';\nimport { resolve } from 'node:path';\n\nimport xml2js from 'xml2js';\n\nimport {\n INDEXNOW_API_URL,\n NEXT_CONFIG_FILES,\n NEXT_SITEMAP_CONFIG_FILES,\n NEXT_BUILD_DIRS,\n KEY_FILE_EXTENSION,\n DEFAULT_SITEMAP_DIR,\n DEFAULT_SITEMAP_FILE,\n} from './constants.ts';\nimport type { NextSitemapConfig, ValidationResult, SubmissionResult } from './types.ts';\n\n/**\n * Fetch the site root with HEAD to verify the domain is reachable.\n *\n * @param {string} url - Absolute site URL with http:// or https:// scheme.\n *\n * @returns {ValidationResult} Valid if HEAD responds with status < 500; invalid otherwise.\n */\nexport async function validateSiteReachable(url: string): Promise<ValidationResult> {\n try {\n const response = await fetch(url, { method: 'HEAD' });\n if (response.status >= 500) {\n return { valid: false, error: `Site unreachable: ${url} responded with ${response.status}.` };\n }\n return { valid: true };\n } catch (error) {\n /* v8 ignore next 3 */\n return { valid: false, error: `Cannot reach site: ${error instanceof Error ? error.message : String(error)}` };\n }\n}\n\n/**\n * Validate that a URL string is a valid site domain with http:// or https:// scheme.\n *\n * Accepts standard domains (example.com), subdomains, and localhost with ports.\n *\n * @param {string} url - The URL string to validate.\n *\n * @returns {ValidationResult} Valid result on success, or invalid with an error message.\n */\nexport function validateSiteUrl(url: string): ValidationResult {\n if (!url || typeof url !== 'string') {\n return { valid: false, error: 'Site URL is required.' };\n }\n\n if (!url.startsWith('http://') && !url.startsWith('https://')) {\n return { valid: false, error: 'Site URL must start with http:// or https://' };\n }\n\n try {\n const parsed = new URL(url);\n\n /* v8 ignore next 3 */\n if (!parsed.hostname) {\n return { valid: false, error: 'Site URL must have a valid hostname.' };\n }\n\n // Reject URLs with path, query, or fragment beyond a single trailing slash\n /* v8 ignore next 2 */\n if ((parsed.pathname !== '/' && parsed.pathname !== '') || parsed.search || parsed.hash) {\n return { valid: false, error: 'Site URL should be a domain root (e.g. https://example.com).' };\n }\n } catch {\n return { valid: false, error: `Invalid URL: \"${url}\". Please provide a valid URL.` };\n }\n\n return { valid: true };\n}\n\n/**\n * Check if the current working directory is a Next.js project by looking for\n * a next.config.* file.\n *\n * @returns {ValidationResult} Valid result if a Next.js config file is found.\n */\nexport function validateNextProject(): ValidationResult {\n for (const configFile of NEXT_CONFIG_FILES) {\n if (existsSync(resolve(process.cwd(), configFile))) {\n return { valid: true };\n }\n }\n\n return { valid: false, error: 'No next.config.* file found. This command must be run from a Next.js project root.' };\n}\n\n/**\n * Check if the `.next` build directory exists and contains files.\n *\n * @returns {ValidationResult} Valid result if `.next` exists and is not empty.\n */\nexport function validateDotNext(): ValidationResult {\n for (const dir of NEXT_BUILD_DIRS) {\n const dotNextPath = resolve(process.cwd(), dir);\n\n if (!existsSync(dotNextPath)) {\n return { valid: false, error: `\"${dir}\" directory not found. Run \"next build\" first.` };\n }\n\n try {\n const entries = readdirSync(dotNextPath);\n if (entries.length === 0) {\n return { valid: false, error: `\"${dir}\" directory is empty. Run \"next build\" first.` };\n }\n } catch {\n /* v8 ignore next */\n return { valid: false, error: `Cannot read \"${dir}\" directory.` };\n }\n }\n\n return { valid: true };\n}\n\n/**\n * Check if a next-sitemap config file exists and is not empty.\n *\n * Searches for next-sitemap.config.{ts,mjs,cjs,js} in the current directory.\n *\n * @returns {ValidationResult & { filePath?: string }} Valid result with the config file path if found.\n */\nexport function validateNextSitemapConfig(): ValidationResult & { filePath?: string } {\n for (const configFile of NEXT_SITEMAP_CONFIG_FILES) {\n const configPath = resolve(process.cwd(), configFile);\n\n if (existsSync(configPath)) {\n const content = readFileSync(configPath, 'utf-8').trim();\n\n if (content.length === 0) {\n return { valid: false, error: `\"${configFile}\" exists but is empty.` };\n }\n\n return { valid: true, filePath: configPath };\n }\n }\n\n return { valid: false, error: 'No next-sitemap.config.* file found. Create one to configure your sitemap.' };\n}\n\n/**\n * Read the next-sitemap config file and extract the `siteUrl` and optional `outDir`.\n *\n * Parses the file to extract `siteUrl` from the config object, handling both\n * inline string literals (`siteUrl: 'https://...'`) and variable references\n * (`const siteDomain = 'https://...'; siteUrl: siteDomain`). Avoids the\n * complexity of dynamic module loading for mixed CJS/ESM configs.\n *\n * @param {string} configPath - Absolute path to the next-sitemap config file.\n *\n * @returns {NextSitemapConfig | null} Parsed config, or null if siteUrl could not be extracted.\n */\nexport function readSitemapConfig(configPath: string): NextSitemapConfig | null {\n try {\n const content = readFileSync(configPath, 'utf-8');\n\n // Build a map of const variable names to their string literal values\n const constMap = new Map<string, string>();\n for (const match of content.matchAll(/const\\s+(\\w+)\\s*=\\s*['\"]([^'\"]+)['\"]/g)) {\n constMap.set(match[1]!, match[2]!);\n }\n\n // Try inline string literal first, then variable reference lookup\n const siteUrlMatch = content.match(/siteUrl:\\s*['\"]([^'\"]+)['\"]/);\n const siteUrlVarMatch = content.match(/siteUrl:\\s*(\\w+)/);\n\n const siteUrl = siteUrlMatch?.[1] ?? (siteUrlVarMatch?.[1] ? constMap.get(siteUrlVarMatch[1]) : undefined);\n\n if (!siteUrl) {\n return null;\n }\n\n // Extract optional outDir value (inline string literal or variable)\n const outDirMatch = content.match(/outDir:\\s*['\"]([^'\"]+)['\"]/);\n const outDirVarMatch = content.match(/outDir:\\s*(\\w+)/);\n const outDir = outDirMatch?.[1] ?? (outDirVarMatch?.[1] ? constMap.get(outDirVarMatch[1]) : undefined);\n\n return { siteUrl, outDir };\n } catch {\n return null;\n }\n}\n\n/**\n * Ensure the IndexNow verification key file exists at public/<key>.txt with the\n * correct content. Creates the file and directory if they don't exist.\n *\n * @param {string} key - The IndexNow API key.\n *\n * @returns {ValidationResult} Valid result if the key file exists or was created successfully.\n */\nexport function ensureKeyFile(key: string): ValidationResult {\n if (!key || typeof key !== 'string') {\n return { valid: false, error: 'IndexNow API key is required.' };\n }\n\n const publicDir = resolve(process.cwd(), DEFAULT_SITEMAP_DIR);\n const keyFilePath = resolve(publicDir, `${key}${KEY_FILE_EXTENSION}`);\n\n try {\n if (!existsSync(publicDir)) {\n mkdirSync(publicDir, { recursive: true });\n }\n\n if (existsSync(keyFilePath)) {\n const existingContent = readFileSync(keyFilePath, 'utf-8').trim();\n\n if (existingContent !== key) {\n writeFileSync(keyFilePath, key, 'utf-8');\n }\n } else {\n writeFileSync(keyFilePath, key, 'utf-8');\n }\n\n return { valid: true };\n } catch (error) {\n /* v8 ignore next 3 */\n return {\n valid: false,\n error: `Failed to create key file: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n}\n\n/**\n * Resolve the sitemap file path.\n *\n * Uses the configured outDir (from next-sitemap.config) or falls back to\n * the default `public/` directory.\n *\n * @param {string} [outDir] - Custom output directory from next-sitemap config.\n * @param {string} [sitemapFile] - Custom sitemap filename. Defaults to sitemap-0.xml.\n *\n * @returns {string} Absolute path to the sitemap file.\n */\nexport function resolveSitemapPath(outDir?: string, sitemapFile?: string): string {\n const dir = outDir ?? DEFAULT_SITEMAP_DIR;\n const file = sitemapFile ?? DEFAULT_SITEMAP_FILE;\n return resolve(process.cwd(), dir, file);\n}\n\n/**\n * Apply offset and limit options to a list of URLs.\n *\n * Skips the first `offset` URLs and keeps at most `limit` URLs from the\n * remaining list. Omitted options leave the corresponding bound unlimited.\n *\n * @param {string[]} urls - The full list of URLs extracted from the sitemap.\n * @param {number} [offset] - Number of URLs to skip from the start.\n * @param {number} [limit] - Maximum number of URLs to keep (after offset).\n *\n * @returns {string[]} The sliced list of URLs to submit.\n *\n * @throws {Error} If offset or limit is a negative or non-integer value.\n */\nexport function applyOffsetLimit(urls: string[], offset?: number, limit?: number): string[] {\n if (offset !== undefined && (!Number.isInteger(offset) || offset < 0)) {\n throw new Error('offset must be a non-negative integer');\n }\n\n if (limit !== undefined && (!Number.isInteger(limit) || limit < 0)) {\n throw new Error('limit must be a non-negative integer');\n }\n\n const start = offset ?? 0;\n const end = limit === undefined ? undefined : start + limit;\n\n return urls.slice(start, end);\n}\n\n/**\n * Fetch XML content from a remote sitemap URL.\n *\n * @param {string} url - The remote sitemap URL to fetch.\n *\n * @returns {Promise<string>} The XML content of the sitemap.\n *\n * @throws {Error} If the fetch fails or the response is not OK.\n */\nasync function fetchSitemapXml(url: string): Promise<string> {\n const response = await fetch(url);\n\n if (!response.ok) {\n throw new Error(`Failed to fetch sitemap: ${url} (${response.status})`);\n }\n\n return response.text();\n}\n\n/**\n * Read and parse a sitemap XML file, extracting all `<loc>` URLs.\n *\n * Supports both regular sitemaps (`<urlset>`) and sitemap index files\n * (`<sitemapindex>`). For sitemap index files, each referenced sub-sitemap\n * is fetched and parsed to collect all URLs.\n *\n * @param {string} sitemapPath - Absolute path to the sitemap XML file.\n *\n * @returns {Promise<ValidationResult & { urls?: string[] }>} Valid result with URL list, or invalid with error.\n */\nexport async function readSitemap(sitemapPath: string): Promise<ValidationResult & { urls?: string[] }> {\n let content: string;\n\n try {\n content = await readFileSync(sitemapPath, 'utf-8');\n } catch {\n return { valid: false, error: `Sitemap file not found: ${sitemapPath}` };\n }\n\n let parsed: { urlset?: { url?: Array<{ loc?: string[] }> }; sitemapindex?: { sitemap?: Array<{ loc?: string[] }> } };\n\n try {\n parsed = await xml2js.parseStringPromise(content);\n } catch {\n return { valid: false, error: 'Failed to parse sitemap XML. Ensure the file is valid XML.' };\n }\n\n // Check if this is a sitemap index file\n if (parsed.sitemapindex) {\n const subSitemapUrls = (parsed.sitemapindex.sitemap ?? [])\n .map((entry) => entry.loc?.[0])\n .filter(Boolean) as string[];\n\n if (subSitemapUrls.length === 0) {\n return { valid: false, error: 'No sub-sitemap URLs found in the sitemap index.' };\n }\n\n const allUrls: string[] = [];\n\n for (const subSitemapUrl of subSitemapUrls) {\n try {\n const subContent = await fetchSitemapXml(subSitemapUrl);\n const subParsed: { urlset?: { url?: Array<{ loc?: string[] }> } } = await xml2js.parseStringPromise(subContent);\n\n const urls = subParsed.urlset?.url?.map((entry) => entry.loc?.[0]).filter(Boolean) as string[] | undefined;\n\n if (urls && urls.length > 0) {\n allUrls.push(...urls);\n }\n } catch {\n // Skip failed sub-sitemaps silently\n }\n }\n\n if (allUrls.length === 0) {\n return { valid: false, error: 'No URLs found in any of the sub-sitemaps.' };\n }\n\n return { valid: true, urls: allUrls };\n }\n\n // Regular sitemap\n const urls = parsed.urlset?.url?.map((entry) => entry.loc?.[0]).filter(Boolean) as string[] | undefined;\n\n if (!urls || urls.length === 0) {\n return { valid: false, error: 'No URLs found in the sitemap.' };\n }\n\n return { valid: true, urls };\n}\n\n/**\n * Submit a batch of URLs to the IndexNow API.\n *\n * @param {string} host - The site hostname (e.g. example.com).\n * @param {string} key - The IndexNow API key.\n * @param {string} keyLocation - Public URL of the key verification file.\n * @param {string[]} urlList - URLs to submit in this batch.\n *\n * @returns {Promise<SubmissionResult>} The submission result for this batch.\n */\nexport async function submitUrls(\n host: string,\n key: string,\n keyLocation: string,\n urlList: string[]\n): Promise<SubmissionResult> {\n try {\n const response = await fetch(INDEXNOW_API_URL, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ host, key, keyLocation, urlList }),\n });\n\n if (response.ok) {\n return { count: urlList.length, success: true };\n }\n\n const errorText = await response.text();\n return { count: urlList.length, success: false, error: errorText };\n } catch (error) {\n /* v8 ignore next 4 */\n return {\n count: urlList.length,\n success: false,\n error: `Network error: ${error instanceof Error ? error.message : String(error)}`,\n };\n }\n}\n","/**\n * Core orchestration for the IndexNow CLI tool.\n *\n * Coordinates validation, sitemap parsing, and URL submission to the\n * IndexNow API for faster search engine indexing.\n *\n * @module next-indexnow\n */\n\nimport { CHUNK_SIZE, DEFAULT_INDEXNOW_KEY } from './constants.ts';\nimport type {\n NextIndexnowOptions,\n NextIndexnowResult,\n NextSitemapConfig,\n SubmissionResult,\n IndexnowProgress,\n} from './types.ts';\nimport {\n validateSiteUrl,\n validateNextProject,\n validateDotNext,\n validateNextSitemapConfig,\n readSitemapConfig,\n ensureKeyFile,\n resolveSitemapPath,\n readSitemap,\n submitUrls,\n applyOffsetLimit,\n} from './utils.ts';\n\n/**\n * Validate basic project structure: Next.js project and .next build directory.\n *\n * @throws {Error} If the project is not a Next.js project or .next is missing/empty.\n */\nfunction validateBasicEnvironment(): void {\n const projectCheck = validateNextProject();\n if (!projectCheck.valid) {\n throw new Error(projectCheck.error);\n }\n\n const dotNextCheck = validateDotNext();\n if (!dotNextCheck.valid) {\n throw new Error(dotNextCheck.error);\n }\n}\n\n/**\n * Validate that a sitemap config file exists and parse its contents.\n *\n * @returns {NextSitemapConfig} The parsed sitemap config (siteUrl + optional outDir).\n *\n * @throws {Error} If no config file is found, it is empty, or siteUrl cannot be parsed.\n */\nfunction validateSitemapSetup(): NextSitemapConfig {\n const sitemapConfigCheck = validateNextSitemapConfig();\n if (!sitemapConfigCheck.valid) {\n throw new Error(sitemapConfigCheck.error!);\n }\n\n const parsedConfig = readSitemapConfig(sitemapConfigCheck.filePath!);\n if (!parsedConfig) {\n throw new Error(`Could not parse \"siteUrl\" from ${sitemapConfigCheck.filePath}.`);\n }\n\n return parsedConfig;\n}\n\n/**\n * Validate that the current directory is a Next.js project with a build\n * artifact and a sitemap config file.\n *\n * @returns {NextSitemapConfig} The parsed sitemap config (siteUrl + optional outDir).\n *\n * @throws {Error} If any validation check fails.\n */\nfunction validateEnvironment(): NextSitemapConfig {\n validateBasicEnvironment();\n return validateSitemapSetup();\n}\n\n/**\n * Resolve the effective siteUrl from options (takes precedence) or config file,\n * validate it, and extract the hostname.\n *\n * @param {NextIndexnowOptions} options - CLI options.\n * @param {NextSitemapConfig} parsedConfig - Parsed sitemap config.\n *\n * @returns {{ siteUrl: string; siteHost: string }} The resolved site URL and host.\n *\n * @throws {Error} If the site URL is invalid.\n */\nfunction resolveSiteConfig(\n options: NextIndexnowOptions,\n parsedConfig: NextSitemapConfig\n): { siteUrl: string; siteHost: string } {\n const siteUrl = options.siteUrl ?? parsedConfig.siteUrl;\n\n const urlValidation = validateSiteUrl(siteUrl);\n if (!urlValidation.valid) {\n throw new Error(urlValidation.error);\n }\n\n return { siteUrl, siteHost: new URL(siteUrl).host };\n}\n\n/**\n * Resolve the IndexNow API key from options or the INDEXNOW_KEY env var,\n * and ensure the verification key file exists on disk.\n *\n * Falls back to a hard-coded default key when neither the --key option nor\n * the INDEXNOW_KEY environment variable is provided.\n *\n * @param {NextIndexnowOptions} options - CLI options.\n * @param {string} siteUrl - The resolved site URL (for keyLocation).\n *\n * @returns {{ key: string; keyLocation: string }} The resolved key and its public URL.\n *\n * @throws {Error} If the key file cannot be created.\n */\nfunction resolveKeyValue(options: NextIndexnowOptions, siteUrl: string): { key: string; keyLocation: string } {\n const key = options.key ?? process.env.INDEXNOW_KEY ?? DEFAULT_INDEXNOW_KEY;\n\n const keyFileCheck = ensureKeyFile(key);\n if (!keyFileCheck.valid) {\n throw new Error(keyFileCheck.error);\n }\n\n return { key, keyLocation: `${siteUrl}/${key}.txt` };\n}\n\n/**\n * Resolve the sitemap path (from options or default) and read all URLs.\n *\n * @param {NextIndexnowOptions} options - CLI options.\n * @param {NextSitemapConfig} parsedConfig - Parsed sitemap config (for outDir).\n *\n * @returns {Promise<string[]>} The list of URLs found in the sitemap.\n *\n * @throws {Error} If the sitemap cannot be read or contains no URLs.\n */\nasync function loadSitemapUrls(options: NextIndexnowOptions, parsedConfig: NextSitemapConfig): Promise<string[]> {\n const sitemapPath = options.sitemap ?? resolveSitemapPath(parsedConfig.outDir);\n\n const sitemapResult = await readSitemap(sitemapPath);\n if (!sitemapResult.valid) {\n throw new Error(sitemapResult.error!);\n }\n\n return sitemapResult.urls!;\n}\n\n/**\n * Submit all URLs to the IndexNow API in chunks and aggregate the results.\n *\n * @param {string[]} urls - All URLs to submit.\n * @param {string} siteHost - The site hostname.\n * @param {string} key - The IndexNow API key.\n * @param {string} keyLocation - Public URL of the key verification file.\n * @param {number} chunkSize - Maximum URLs per submission batch.\n * @param {IndexnowProgress} [onProgress] - Callback invoked before each batch submission.\n *\n * @returns {Promise<Pick<NextIndexnowResult, 'urlsSubmitted' | 'urlsFailed' | 'chunks'>>} Aggregate submission counts and per-chunk details.\n */\nasync function submitAllUrls(\n urls: string[],\n siteHost: string,\n key: string,\n keyLocation: string,\n chunkSize: number,\n onProgress?: IndexnowProgress\n): Promise<Pick<NextIndexnowResult, 'urlsSubmitted' | 'urlsFailed' | 'chunks'>> {\n const chunks: SubmissionResult[] = [];\n const totalChunks = Math.ceil(urls.length / chunkSize);\n\n for (let i = 0; i < urls.length; i += chunkSize) {\n const batchNum = Math.floor(i / chunkSize) + 1;\n const chunk = urls.slice(i, i + chunkSize);\n\n onProgress?.({\n batch: batchNum,\n totalBatches: totalChunks,\n urlCount: chunk.length,\n previousResult: chunks.length > 0 ? chunks[chunks.length - 1] : undefined,\n });\n\n const result = await submitUrls(siteHost, key, keyLocation, chunk);\n chunks.push(result);\n }\n\n const urlsSubmitted = chunks.filter((c) => c.success).reduce((sum, c) => sum + c.count, 0);\n const urlsFailed = chunks.filter((c) => !c.success).reduce((sum, c) => sum + c.count, 0);\n\n return { urlsSubmitted, urlsFailed, chunks };\n}\n\n/**\n * Run the IndexNow submission process.\n *\n * Validates the environment (Next.js project, .next dir, sitemap config),\n * reads the sitemap, applies the offset/limit range, and submits the\n * selected URLs to the IndexNow API in chunks.\n *\n * @param {NextIndexnowOptions} options - CLI options (siteUrl, key, sitemap, chunkSize, offset, limit, dryRun, onProgress).\n *\n * @returns {Promise<NextIndexnowResult>} Aggregate result with per-chunk details.\n */\nexport async function run(options: NextIndexnowOptions = {}): Promise<NextIndexnowResult> {\n const startTime = Date.now();\n const chunkSize = options.chunkSize ?? CHUNK_SIZE;\n\n if (!Number.isInteger(chunkSize) || chunkSize < 1) {\n throw new Error('chunkSize must be a positive integer');\n }\n\n // 1. Validate environment & parse sitemap config\n const parsedConfig = validateEnvironment();\n\n // 2. Resolve siteUrl & key\n const { siteUrl, siteHost } = resolveSiteConfig(options, parsedConfig);\n const { key, keyLocation } = resolveKeyValue(options, siteUrl);\n\n // 3. Read sitemap and apply offset/limit range\n const urls = applyOffsetLimit(await loadSitemapUrls(options, parsedConfig), options.offset, options.limit);\n\n // 4. Dry-run short-circuit\n if (options.dryRun) {\n return { urlsFound: urls.length, urlsSubmitted: 0, urlsFailed: 0, chunks: [], durationMs: Date.now() - startTime };\n }\n\n // 5. Submit URLs in chunks\n const { urlsSubmitted, urlsFailed, chunks } = await submitAllUrls(\n urls,\n siteHost,\n key,\n keyLocation,\n chunkSize,\n options.onProgress\n );\n\n return { urlsFound: urls.length, urlsSubmitted, urlsFailed, chunks, durationMs: Date.now() - startTime };\n}\n","/**\n * Interactive prompt helpers for the IndexNow CLI.\n *\n * Uses node:readline to read single-line input from a TTY. When stdin is\n * not a TTY (piped/CI input), all prompts auto-resolve to the default value\n * so scripted usage is never blocked.\n *\n * @module prompts\n */\n\nimport { createInterface } from 'node:readline';\n\nimport chalk from 'chalk';\n\nimport type { ValidationResult } from './types.ts';\nimport { validateSiteUrl } from './utils.ts';\n\n/**\n * Normalize a user-entered site URL by prepending https:// when no scheme is given.\n *\n * @param {string} input - Raw input from the user.\n *\n * @returns {string} URL with http:// or https:// scheme.\n */\nfunction normalizeSiteUrl(input: string): string {\n const trimmed = input.trim();\n if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) {\n return trimmed;\n }\n return `https://${trimmed}`;\n}\n\n/**\n * Read a single line of user input from stdin.\n *\n * Non-TTY stdin resolves immediately to the empty string so callers can\n * fall back to their default value.\n *\n * @param {string} question - The prompt text shown to the user.\n *\n * @returns {Promise<string>} The trimmed user input, or empty string when not a TTY.\n */\nexport function readLine(question: string): Promise<string> {\n if (!process.stdin.isTTY) {\n return Promise.resolve('');\n }\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n return new Promise((resolveInput) => {\n rl.question(question, (answer) => {\n rl.close();\n resolveInput(answer.trim());\n });\n });\n}\n\n/**\n * Prompt the user for a Y/n confirmation, defaulting to Y on empty input.\n *\n * Non-TTY stdin auto-confirms (`true`) so piped/CI usage is not blocked.\n *\n * @returns {Promise<boolean>} `true` when the user confirms (or stdin is non-interactive), `false` otherwise.\n */\nexport function confirm(): Promise<boolean> {\n if (!process.stdin.isTTY) {\n return Promise.resolve(true);\n }\n\n const rl = createInterface({ input: process.stdin, output: process.stdout });\n return new Promise((resolvePrompt) => {\n rl.question(`${chalk.cyan('?')} Continue? ${chalk.dim('(Y/n)')} `, (answer) => {\n rl.close();\n const normalized = answer.trim().toLowerCase();\n resolvePrompt(normalized === '' || normalized === 'y' || normalized === 'yes');\n });\n });\n}\n\n/**\n * Prompt the user for a site URL until a valid one is entered.\n *\n * Accepts https://, http://, or a bare domain. Bare domains get https://\n * prepended automatically. Each entered value is validated for syntax and\n * re-prompted on failure. Caller can additionally validate reachability.\n *\n * @returns {Promise<string>} The normalized, syntactically valid site URL.\n */\nexport async function promptSiteUrl(): Promise<string> {\n while (true) {\n const input = await readLine(`${chalk.cyan('?')} Enter site URL ${chalk.dim('(https://example.com)')}: `);\n const normalized = normalizeSiteUrl(input);\n const validation: ValidationResult = validateSiteUrl(normalized);\n if (validation.valid) {\n return normalized;\n }\n console.log(chalk.red(` ${validation.error}`));\n }\n}\n\n/**\n * Prompt the user to choose between the built-in default IndexNow key and a custom key.\n *\n * Y (or empty) selects the default; n prompts for a custom key (loops until non-empty).\n *\n * @param {string} defaultKey - The built-in default key string.\n *\n * @returns {Promise<string>} The chosen key (default or user-entered).\n */\nexport async function promptApiKey(defaultKey: string): Promise<string> {\n const choice = await readLine(`${chalk.cyan('?')} Use default IndexNow key? ${chalk.dim('(Y/n)')} `);\n const normalized = choice.toLowerCase();\n\n if (normalized === '' || normalized === 'y' || normalized === 'yes') {\n return defaultKey;\n }\n\n while (true) {\n const custom = await readLine(`${chalk.cyan('?')} Enter your IndexNow API key: `);\n if (custom.length > 0) {\n return custom;\n }\n console.log(chalk.red(' API key cannot be empty.'));\n }\n}\n","#!/usr/bin/env node\n\n/**\n * next-indexnow β CLI entry point.\n *\n * Parses command-line arguments with commander, validates the environment,\n * reads the Next.js sitemap, and submits URLs to the IndexNow API.\n *\n * @module cli\n *\n * Usage:\n * next-indexnow\n * next-indexnow --site-url https://example.com\n * next-indexnow --key my-api-key\n * next-indexnow --sitemap ./public/sitemap-0.xml\n * next-indexnow -o 100 -l 50\n * next-indexnow --dry-run\n * next-indexnow --help\n */\n\nimport { readFileSync, existsSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport chalk from 'chalk';\nimport { Command, InvalidArgumentError } from 'commander';\nimport logSymbols from 'log-symbols';\nimport ora from 'ora';\n\nimport { DEFAULT_INDEXNOW_KEY } from '../constants.ts';\nimport { run } from '../index.ts';\nimport { confirm, promptApiKey, promptSiteUrl } from '../prompts.ts';\nimport type { NextIndexnowResult, NextSitemapConfig } from '../types.ts';\nimport {\n validateNextProject,\n validateDotNext,\n validateNextSitemapConfig,\n readSitemapConfig,\n validateSiteUrl,\n ensureKeyFile,\n resolveSitemapPath,\n readSitemap,\n applyOffsetLimit,\n validateSiteReachable,\n} from '../utils.ts';\n\n// ββ ASCII Banner βββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n\nconst BANNER = `\n${chalk.white('βββββββ ββββββββββ βββββββββββ βββββββ βββ βββββββ βββ βββ')}\n${chalk.white('ββββββββ ββββββββββββββββββββββββββββββββ βββββββββββββββ βββ')}\n${chalk.white('βββββββββ ββββββ βββββββββ ββββββ ββββββ ββββββ ββββββ ββ βββ')}\n${chalk.white('ββββββββββββββββ βββββββββ ββββββ βββββββββββββ βββββββββββββ')}\n${chalk.white('ββββββ ββββββββββββββββββββββββββ ββββββ βββββββββββββββββββββββββ')}\n${chalk.white('ββββββ ββββββββββββ βββββββββββ ββββββ βββββ βββββββ ββββββββ')}`;\n\nconst SEPARATOR = chalk.dim('='.repeat(69));\n\n// ββ Helpers ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ\n\n/**\n * Parse a non-negative integer CLI option value.\n *\n * @param {string} value - Raw string value from the command line.\n *\n * @returns {number} The parsed integer.\n *\n * @throws {InvalidArgumentError} If the value is not a non-negative integer.\n */\nfunction parseNonNegativeInt(value: string): number {\n if (!/^\\d+$/.test(value)) {\n throw new InvalidArgumentError('must be a non-negative integer');\n }\n return Number.parseInt(value, 10);\n}\n\n/**\n * Parse a positive integer CLI option value.\n *\n * @param {string} value - Raw string value from the command line.\n *\n * @returns {number} The parsed integer.\n *\n * @throws {InvalidArgumentError} If the value is not a positive integer.\n */\nfunction parsePositiveInt(value: string): number {\n if (!/^[1-9]\\d*$/.test(value)) {\n throw new InvalidArgumentError('must be a positive integer');\n }\n return Number.parseInt(value, 10);\n}\n\n/**\n * Print a checkmark or error icon with a status label and optional detail.\n *\n * @param {boolean} valid - Whether the check passed.\n * @param {string} label - The status label text.\n * @param {string} [detail] - Optional detail text shown after a colon.\n */\nfunction checkMark(valid: boolean, label: string, detail?: string): void {\n const icon = valid ? logSymbols.success : logSymbols.error;\n const msg = detail ? `${label}: ${detail}` : label;\n console.log(`${icon} ${msg}`);\n}\n\n/**\n * Print the summary footer message after a submission run.\n *\n * @param {NextIndexnowResult} result - The result from the IndexNow submission run.\n * @param {boolean} dryRun - Whether this was a dry-run preview.\n */\nfunction printFooter(result: NextIndexnowResult, dryRun: boolean): void {\n console.log('');\n if (dryRun) {\n console.log(chalk.green(`Dry-run complete. ${result.urlsFound} urls would be submitted. π`));\n } else if (result.urlsFailed === 0) {\n console.log(chalk.green(`All ${result.urlsSubmitted} urls submitted to IndexNow successfully! π₯³`));\n } else {\n console.log(\n chalk.yellow(\n `${result.urlsSubmitted}/${result.urlsFound} urls submitted successfully (${result.urlsFailed} failed).`\n )\n );\n }\n}\n\n/**\n * Print detailed failure information for failed submission batches.\n *\n * @param {NextIndexnowResult} result - The result from the IndexNow submission run.\n */\nfunction printFailures(result: NextIndexnowResult): void {\n if (result.urlsFailed === 0) return;\n\n console.error(chalk.red.bold('Failed submissions:'));\n for (const chunk of result.chunks) {\n if (!chunk.success) {\n console.error(chalk.red(` ${logSymbols.error} ${chunk.error}`));\n }\n }\n}\n\n// ββ Ctrl+C handling ββββββββββββββββββββββββββββββββββββββββββββββββββββ\n\nlet spinner: ReturnType<typeof ora> | null = null;\n\nprocess.on('SIGINT', () => {\n if (spinner?.isSpinning) {\n spinner.stop();\n }\n console.log('');\n console.log(chalk.yellow('Process aborted by user.'));\n process.exit(130);\n});\n\nconst __filename = fileURLToPath(import.meta.url);\nconst pkgPathSrc = resolve(__filename, '..', '..', '..', 'package.json');\nconst pkgPathDist = resolve(__filename, '..', '..', 'package.json');\nconst pkgPath = existsSync(pkgPathDist) ? pkgPathDist : pkgPathSrc;\n\n// Read version from package.json\nconst pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')) as { version: string };\n\nconst program = new Command();\n\nprogram\n .name('next-indexnow')\n .description('Submit Next.js sitemap URLs to the IndexNow API for faster search engine indexing')\n .version(pkg.version)\n .option('--site-url <url>', 'The site URL (e.g. https://example.com). Overrides next-sitemap.config value.')\n .option('--key <key>', 'IndexNow API key. Falls back to INDEXNOW_KEY environment variable or a built-in default key.')\n .option('--sitemap <path>', 'Path to the sitemap XML file')\n .option('--chunk-size <number>', 'URLs per submission batch (positive integer)', parsePositiveInt, 100)\n .option('-o, --offset <number>', 'Skip this many URLs from the start of the sitemap', parseNonNegativeInt)\n .option('-l, --limit <number>', 'Submit at most this many URLs (after offset)', parseNonNegativeInt)\n .option('-d, --dry-run', 'Preview URLs without submitting to the IndexNow API')\n .addHelpText(\n 'after',\n `\nExamples:\n $ next-indexnow Submit URLs using settings from next-sitemap.config\n $ next-indexnow --site-url https://example.com Override the site URL\n $ next-indexnow --key my-api-key Provide IndexNow API key\n $ next-indexnow --sitemap ./public/sitemap.xml Use a custom sitemap path\n $ next-indexnow -o 100 -l 50 Submit a slice of the sitemap\n $ next-indexnow --dry-run Preview URLs without submitting\n $ next-indexnow --help Show this help message\n `\n )\n .parse(process.argv);\n\n/**\n * Log submission results as checkmark-style lines.\n *\n * @param {NextIndexnowResult} result - The result from the IndexNow submission run.\n */\nfunction displayResults(result: NextIndexnowResult): void {\n const duration = (result.durationMs / 1000).toFixed(2);\n\n const items = [\n ['URLs found', String(result.urlsFound), result.urlsFound > 0 ? ('blue' as const) : ('dim' as const)],\n ['URLs submitted', String(result.urlsSubmitted), result.urlsSubmitted > 0 ? ('green' as const) : ('dim' as const)],\n ['URLs failed', String(result.urlsFailed), result.urlsFailed > 0 ? ('red' as const) : ('dim' as const)],\n ['Duration', `${duration}s`, result.durationMs > 0 ? ('yellow' as const) : ('dim' as const)],\n ];\n\n console.log('');\n for (const [label, value, color] of items) {\n let coloredValue: string;\n switch (color) {\n case 'red':\n coloredValue = chalk.red(value);\n break;\n case 'dim':\n coloredValue = chalk.dim(value);\n break;\n case 'green':\n coloredValue = chalk.green(value);\n break;\n case 'blue':\n coloredValue = chalk.blue(value);\n break;\n case 'yellow':\n coloredValue = chalk.yellow(value);\n break;\n default:\n coloredValue = value;\n break;\n }\n console.log(`${logSymbols.success} ${chalk.bold(label)}: ${coloredValue}`);\n }\n}\n\n/**\n * Run validation checks and display checkmarks, returning parsed state.\n *\n * When the resolved site URL is unreachable or the API key falls back to\n * the built-in default, the user is prompted interactively (skipped when\n * stdin is not a TTY).\n *\n * @param {object} opts - CLI options for site URL and API key.\n * @param {string} [opts.siteUrl] - Override site URL from --site-url option.\n * @param {string} [opts.key] - Override API key from --key option.\n *\n * @returns {{ parsedConfig: NextSitemapConfig; siteUrl: string; siteHost: string; key: string; keyLocation: string }} Resolved configuration values.\n */\n// fallow-ignore-next-line complexity\nasync function runValidationChecks(opts: {\n siteUrl?: string;\n key?: string;\n}): Promise<{ parsedConfig: NextSitemapConfig; siteUrl: string; siteHost: string; key: string; keyLocation: string }> {\n // 1. Next.js project check\n const projectCheck = validateNextProject();\n if (!projectCheck.valid) {\n checkMark(false, 'Next.js project', projectCheck.error);\n console.log(chalk.red(`\\n ${projectCheck.error}`));\n process.exit(1);\n }\n checkMark(true, 'Next.js config found');\n\n // 2. Build directory check\n const dotNextCheck = validateDotNext();\n if (!dotNextCheck.valid) {\n checkMark(false, 'Build directory', dotNextCheck.error);\n console.log(chalk.red(`\\n ${dotNextCheck.error}`));\n process.exit(1);\n }\n checkMark(true, 'Build directory exists (.next)');\n\n // 3. Sitemap config check\n const sitemapConfigCheck = validateNextSitemapConfig();\n if (!sitemapConfigCheck.valid) {\n checkMark(false, 'Sitemap config', sitemapConfigCheck.error);\n console.log(chalk.red(`\\n ${sitemapConfigCheck.error}`));\n process.exit(1);\n }\n checkMark(true, 'Sitemap config found', sitemapConfigCheck.filePath);\n\n // 4. Parse sitemap config\n const parsedConfig = readSitemapConfig(sitemapConfigCheck.filePath!);\n if (!parsedConfig) {\n checkMark(false, 'Site URL', 'Could not parse siteUrl from config');\n console.log(chalk.red(`\\n Could not parse \"siteUrl\" from ${sitemapConfigCheck.filePath}.`));\n process.exit(1);\n }\n\n // 5. Resolve site URL β interactive prompt if validation or fetch test fails\n let siteUrl = opts.siteUrl ?? parsedConfig.siteUrl;\n let urlValidation = validateSiteUrl(siteUrl);\n\n if (!urlValidation.valid) {\n checkMark(false, 'Site URL', urlValidation.error);\n } else {\n const reachable = await validateSiteReachable(siteUrl);\n if (!reachable.valid) {\n checkMark(false, 'Site URL reachable', reachable.error);\n urlValidation = { valid: false, error: reachable.error };\n }\n }\n\n if (!urlValidation.valid) {\n const prompted = await promptSiteUrl();\n const reachable = await validateSiteReachable(prompted);\n if (!reachable.valid) {\n console.log(chalk.red(` ${reachable.error}`));\n process.exit(1);\n }\n siteUrl = prompted;\n }\n\n const siteHost = new URL(siteUrl).host;\n checkMark(true, 'Site URL resolved', siteHost);\n\n // 6. Resolve API key β prompt user when falling back to built-in default\n let key: string;\n if (opts.key) {\n key = opts.key;\n checkMark(true, 'API key provided', 'via --key option');\n } else if (process.env.INDEXNOW_KEY) {\n key = process.env.INDEXNOW_KEY;\n checkMark(true, 'API key resolved', 'via INDEXNOW_KEY env');\n } else {\n key = await promptApiKey(DEFAULT_INDEXNOW_KEY);\n checkMark(\n true,\n 'API key resolved',\n key === DEFAULT_INDEXNOW_KEY ? 'using built-in default key' : 'using custom key'\n );\n }\n\n // 7. Ensure key file\n const keyFileCheck = ensureKeyFile(key);\n if (!keyFileCheck.valid) {\n checkMark(false, 'Key file', keyFileCheck.error);\n console.log(chalk.red(`\\n ${keyFileCheck.error}`));\n process.exit(1);\n }\n const keyFilePath = resolve(process.cwd(), 'public', `${key}.txt`);\n const keyExists = existsSync(keyFilePath);\n checkMark(true, 'Key verification file', keyExists ? `${key}.txt exists` : `${key}.txt created`);\n\n const keyLocation = `${siteUrl}/${key}.txt`;\n\n return { parsedConfig, siteUrl, siteHost, key, keyLocation };\n}\n\n/**\n * Read sitemap and display checkmark with URL count.\n *\n * Shows a lightweight ora spinner during the read to indicate progress on\n * large sitemaps (10k+ URLs can take several seconds to fetch and parse).\n *\n * @param {object} opts - CLI options for sitemap path.\n * @param {string} [opts.sitemap] - Custom sitemap path from --sitemap option.\n * @param {NextSitemapConfig} parsedConfig - Parsed sitemap config for default outDir.\n *\n * @returns {Promise<string[]>} The list of URLs extracted from the sitemap.\n */\nasync function loadSitemapCheck(opts: { sitemap?: string }, parsedConfig: NextSitemapConfig): Promise<string[]> {\n const sitemapPath = opts.sitemap ?? resolveSitemapPath(parsedConfig.outDir);\n\n const loader = ora({ color: 'cyan', text: 'Loading sitemapβ¦', discardStdin: false });\n loader.start();\n\n let sitemapResult;\n try {\n sitemapResult = await readSitemap(sitemapPath);\n } catch (error) {\n loader.stop();\n checkMark(false, 'Sitemap', error instanceof Error ? error.message : String(error));\n process.exit(1);\n }\n\n loader.stop();\n\n if (!sitemapResult.valid) {\n checkMark(false, 'Sitemap', sitemapResult.error);\n console.log(chalk.red(`\\n ${sitemapResult.error}`));\n process.exit(1);\n }\n\n const urlCount = sitemapResult.urls!.length;\n checkMark(true, 'Sitemap loaded', `${urlCount} URLs found`);\n\n return sitemapResult.urls!;\n}\n\n/**\n * Main CLI entry point.\n *\n * Displays the ASCII banner, runs validation checks with checkmarks,\n * submits URLs to the IndexNow API, and shows results with a footer.\n */\nexport async function main(): Promise<void> {\n const opts = program.optsWithGlobals();\n\n // 1. Show banner + separator + version\n console.log(BANNER);\n console.log('');\n console.log(SEPARATOR);\n console.log(chalk.yellow(`next-indexnow: v${pkg.version}`));\n console.log(SEPARATOR);\n console.log('');\n\n // 2. Run validation checks with checkmarks\n const { parsedConfig } = await runValidationChecks({ siteUrl: opts.siteUrl, key: opts.key });\n const sitemapUrls = await loadSitemapCheck({ sitemap: opts.sitemap }, parsedConfig);\n\n // 2b. Show applied offset/limit range when provided\n if (opts.offset !== undefined || opts.limit !== undefined) {\n const selected = applyOffsetLimit(sitemapUrls, opts.offset, opts.limit);\n const rangeParts: string[] = [];\n if (opts.offset !== undefined) rangeParts.push(`offset ${opts.offset}`);\n if (opts.limit !== undefined) rangeParts.push(`limit ${opts.limit}`);\n checkMark(true, 'URL range applied', `${selected.length} of ${sitemapUrls.length} URLs (${rangeParts.join(', ')})`);\n }\n\n console.log('');\n\n // 2c. Confirm before submitting (skip for dry-run)\n if (!opts.dryRun) {\n const ok = await confirm();\n if (!ok) {\n console.log(chalk.yellow('Aborted by user.'));\n process.exit(0);\n }\n }\n\n // 3. Submit URLs with progress spinner + live counter\n spinner = ora({ color: 'cyan', discardStdin: false });\n\n let submittedSoFar = 0;\n let failedSoFar = 0;\n\n const result = await run({\n siteUrl: opts.siteUrl,\n key: opts.key,\n sitemap: opts.sitemap,\n chunkSize: opts.chunkSize,\n offset: opts.offset,\n limit: opts.limit,\n dryRun: opts.dryRun ?? false,\n onProgress: ({ batch, totalBatches, urlCount, previousResult }) => {\n if (previousResult) {\n if (previousResult.success) {\n submittedSoFar += previousResult.count;\n } else {\n failedSoFar += previousResult.count;\n }\n }\n\n if (spinner) {\n spinner.text = `Submitting batch ${batch}/${totalBatches} (${urlCount} URLs) ${chalk.green(`β ${submittedSoFar}`)} ${chalk.red(`β ${failedSoFar}`)}`;\n if (!spinner.isSpinning) {\n spinner.start();\n }\n }\n },\n });\n\n if (spinner?.isSpinning) {\n // Reflect the final batch's totals before stopping so the spinner\n // text matches the upcoming summary.\n spinner.text = `Submitting complete ${chalk.green(`β ${result.urlsSubmitted}`)} ${chalk.red(`β ${result.urlsFailed}`)}`;\n spinner.stop();\n }\n console.log(SEPARATOR);\n console.log(`${logSymbols.success} Submission Completed π`);\n console.log(SEPARATOR);\n\n // 4. Show results\n displayResults(result);\n\n // 5. Show footer\n printFooter(result, opts.dryRun);\n\n // 6. Show failure details at end\n printFailures(result);\n\n // 7. Exit with appropriate code\n if (result.urlsFailed > 0) {\n process.exit(1);\n }\n}\n\nmain().catch((error) => {\n console.error(chalk.red('Error:'), error instanceof Error ? error.message : String(error));\n process.exit(1);\n});\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vijayhardaha/next-indexnow",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "CLI tool to submit Next.js sitemaps to the IndexNow API for faster search engine indexing",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "vite",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
},
|
|
56
56
|
"license": "MIT",
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"chalk": "^
|
|
58
|
+
"chalk": "^6.0.0",
|
|
59
59
|
"commander": "^15.0.0",
|
|
60
60
|
"log-symbols": "^7.0.1",
|
|
61
61
|
"ora": "^9.4.1",
|