@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 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. **Creates** the IndexNow verification file at `public/<key>.txt`
91
- 6. **Parses** all `<loc>` URLs from the sitemap XML
92
- 7. **Submits** URLs in batches (default 100) to the IndexNow API
93
- 8. **Reports** results with per-chunk success/failure details
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
- | `dryRun` | `boolean` | `false` | Preview without submitting |
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; // Total URLs extracted from sitemap
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.ts",
32
- "next-sitemap.config.mjs",
34
+ "next-sitemap.config.js",
33
35
  "next-sitemap.config.cjs",
34
- "next-sitemap.config.js"
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, and submits all URLs to the IndexNow API in chunks.
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", (v) => Number.parseInt(v, 10), 100).option("-d, --dry-run", "Preview URLs without submitting to the IndexNow API").addHelpText("after", `
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
- const siteUrl = opts.siteUrl ?? parsedConfig.siteUrl;
686
- const urlValidation = validateSiteUrl(siteUrl);
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
- checkMark(false, "Site URL", urlValidation.error);
689
- console.log(chalk.red(`\n ${urlValidation.error}`));
690
- process.exit(1);
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
- const key = opts.key ?? process.env.INDEXNOW_KEY ?? "91c80f732f4e4e5b80b4c02a7e8c9e9c";
695
- if (opts.key) checkMark(true, "API key provided", "via --key option");
696
- else if (process.env.INDEXNOW_KEY) checkMark(true, "API key resolved", "via INDEXNOW_KEY env");
697
- else checkMark(true, "API key resolved", "using built-in default key");
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
- checkMark(true, "Key verification file", existsSync(resolve(process.cwd(), "public", `${key}.txt`)) ? `${key}.txt exists` : `${key}.txt created`);
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: `${siteUrl}/${key}.txt`
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 sitemapResult = await readSitemap(opts.sitemap ?? resolveSitemapPath(parsedConfig.outDir));
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) spinner.stop();
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.1",
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": "^5.6.2",
58
+ "chalk": "^6.0.0",
59
59
  "commander": "^15.0.0",
60
60
  "log-symbols": "^7.0.1",
61
61
  "ora": "^9.4.1",