@vijayhardaha/next-indexnow 1.0.1 → 1.1.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 +20 -11
- package/dist/cli.js +69 -10
- package/dist/cli.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -29,6 +29,10 @@ npx next-indexnow --key my-api-key
|
|
|
29
29
|
# Use a custom sitemap path
|
|
30
30
|
npx next-indexnow --sitemap ./public/sitemap.xml
|
|
31
31
|
|
|
32
|
+
# Submit only a slice of the sitemap (avoid bulk submission)
|
|
33
|
+
npx next-indexnow -o 0 -l 50
|
|
34
|
+
npx next-indexnow -o 50 -l 50
|
|
35
|
+
|
|
32
36
|
# Preview URLs without submitting
|
|
33
37
|
npx next-indexnow --dry-run
|
|
34
38
|
```
|
|
@@ -76,7 +80,9 @@ All 10 urls submitted to IndexNow successfully! 🥳
|
|
|
76
80
|
| `--site-url <url>` | Site URL (e.g. `https://example.com`). Overrides config value |
|
|
77
81
|
| `--key <key>` | IndexNow API key. Falls back to `INDEXNOW_KEY` env variable or a built-in default |
|
|
78
82
|
| `--sitemap <path>` | Path to the sitemap XML file |
|
|
79
|
-
| `--chunk-size <number>` | URLs per submission batch (default: 100)
|
|
83
|
+
| `--chunk-size <number>` | URLs per submission batch (positive integer, default: 100) |
|
|
84
|
+
| `-o, --offset <number>` | Skip this many URLs from the start of the sitemap |
|
|
85
|
+
| `-l, --limit <number>` | Submit at most this many URLs (after offset) |
|
|
80
86
|
| `-d, --dry-run` | Preview URLs without submitting to the IndexNow API |
|
|
81
87
|
| `-h, --help` | Show help |
|
|
82
88
|
| `--version` | Show version |
|
|
@@ -89,8 +95,9 @@ All 10 urls submitted to IndexNow successfully! 🥳
|
|
|
89
95
|
4. **Resolves** the API key from CLI option, `INDEXNOW_KEY` env variable, or a built-in default key
|
|
90
96
|
5. **Creates** the IndexNow verification file at `public/<key>.txt`
|
|
91
97
|
6. **Parses** all `<loc>` URLs from the sitemap XML
|
|
92
|
-
7. **
|
|
93
|
-
8. **
|
|
98
|
+
7. **Applies** the `-o, --offset` / `-l, --limit` range to the parsed URLs
|
|
99
|
+
8. **Submits** URLs in batches (default 100) to the IndexNow API
|
|
100
|
+
9. **Reports** results with per-chunk success/failure details
|
|
94
101
|
|
|
95
102
|
### Environment Variables
|
|
96
103
|
|
|
@@ -114,19 +121,21 @@ console.log(`Found ${result.urlsFound} URLs`);
|
|
|
114
121
|
|
|
115
122
|
### `NextIndexnowOptions`
|
|
116
123
|
|
|
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
|
-
| `
|
|
124
|
+
| Option | Type | Default | Description |
|
|
125
|
+
| ----------- | --------- | ---------------- | --------------------------------- |
|
|
126
|
+
| `siteUrl` | `string` | — | Site URL (overrides config) |
|
|
127
|
+
| `key` | `string` | built-in default | IndexNow API key |
|
|
128
|
+
| `sitemap` | `string` | — | Custom sitemap path |
|
|
129
|
+
| `chunkSize` | `number` | `100` | URLs per submission batch |
|
|
130
|
+
| `offset` | `number` | — | URLs to skip from the start |
|
|
131
|
+
| `limit` | `number` | — | Max URLs to submit (after offset) |
|
|
132
|
+
| `dryRun` | `boolean` | `false` | Preview without submitting |
|
|
124
133
|
|
|
125
134
|
### `NextIndexnowResult`
|
|
126
135
|
|
|
127
136
|
```typescript
|
|
128
137
|
interface NextIndexnowResult {
|
|
129
|
-
urlsFound: number; //
|
|
138
|
+
urlsFound: number; // URLs selected after offset/limit
|
|
130
139
|
urlsSubmitted: number; // Successfully submitted URLs
|
|
131
140
|
urlsFailed: number; // URLs that failed to submit
|
|
132
141
|
chunks: SubmissionResult[];
|
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,7 @@ 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";
|
|
@@ -223,6 +223,27 @@ function resolveSitemapPath(outDir, sitemapFile) {
|
|
|
223
223
|
return resolve(process.cwd(), dir, file);
|
|
224
224
|
}
|
|
225
225
|
/**
|
|
226
|
+
* Apply offset and limit options to a list of URLs.
|
|
227
|
+
*
|
|
228
|
+
* Skips the first `offset` URLs and keeps at most `limit` URLs from the
|
|
229
|
+
* remaining list. Omitted options leave the corresponding bound unlimited.
|
|
230
|
+
*
|
|
231
|
+
* @param {string[]} urls - The full list of URLs extracted from the sitemap.
|
|
232
|
+
* @param {number} [offset] - Number of URLs to skip from the start.
|
|
233
|
+
* @param {number} [limit] - Maximum number of URLs to keep (after offset).
|
|
234
|
+
*
|
|
235
|
+
* @returns {string[]} The sliced list of URLs to submit.
|
|
236
|
+
*
|
|
237
|
+
* @throws {Error} If offset or limit is a negative or non-integer value.
|
|
238
|
+
*/
|
|
239
|
+
function applyOffsetLimit(urls, offset, limit) {
|
|
240
|
+
if (offset !== void 0 && (!Number.isInteger(offset) || offset < 0)) throw new Error("offset must be a non-negative integer");
|
|
241
|
+
if (limit !== void 0 && (!Number.isInteger(limit) || limit < 0)) throw new Error("limit must be a non-negative integer");
|
|
242
|
+
const start = offset ?? 0;
|
|
243
|
+
const end = limit === void 0 ? void 0 : start + limit;
|
|
244
|
+
return urls.slice(start, end);
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
226
247
|
* Fetch XML content from a remote sitemap URL.
|
|
227
248
|
*
|
|
228
249
|
* @param {string} url - The remote sitemap URL to fetch.
|
|
@@ -479,19 +500,21 @@ async function submitAllUrls(urls, siteHost, key, keyLocation, chunkSize, onProg
|
|
|
479
500
|
* Run the IndexNow submission process.
|
|
480
501
|
*
|
|
481
502
|
* Validates the environment (Next.js project, .next dir, sitemap config),
|
|
482
|
-
* reads the sitemap,
|
|
503
|
+
* reads the sitemap, applies the offset/limit range, and submits the
|
|
504
|
+
* selected URLs to the IndexNow API in chunks.
|
|
483
505
|
*
|
|
484
|
-
* @param {NextIndexnowOptions} options - CLI options (siteUrl, key, sitemap, chunkSize, dryRun, onProgress).
|
|
506
|
+
* @param {NextIndexnowOptions} options - CLI options (siteUrl, key, sitemap, chunkSize, offset, limit, dryRun, onProgress).
|
|
485
507
|
*
|
|
486
508
|
* @returns {Promise<NextIndexnowResult>} Aggregate result with per-chunk details.
|
|
487
509
|
*/
|
|
488
510
|
async function run(options = {}) {
|
|
489
511
|
const startTime = Date.now();
|
|
490
512
|
const chunkSize = options.chunkSize ?? 100;
|
|
513
|
+
if (!Number.isInteger(chunkSize) || chunkSize < 1) throw new Error("chunkSize must be a positive integer");
|
|
491
514
|
const parsedConfig = validateEnvironment();
|
|
492
515
|
const { siteUrl, siteHost } = resolveSiteConfig(options, parsedConfig);
|
|
493
516
|
const { key, keyLocation } = resolveKeyValue(options, siteUrl);
|
|
494
|
-
const urls = await loadSitemapUrls(options, parsedConfig);
|
|
517
|
+
const urls = applyOffsetLimit(await loadSitemapUrls(options, parsedConfig), options.offset, options.limit);
|
|
495
518
|
if (options.dryRun) return {
|
|
496
519
|
urlsFound: urls.length,
|
|
497
520
|
urlsSubmitted: 0,
|
|
@@ -523,6 +546,7 @@ async function run(options = {}) {
|
|
|
523
546
|
* next-indexnow --site-url https://example.com
|
|
524
547
|
* next-indexnow --key my-api-key
|
|
525
548
|
* next-indexnow --sitemap ./public/sitemap-0.xml
|
|
549
|
+
* next-indexnow -o 100 -l 50
|
|
526
550
|
* next-indexnow --dry-run
|
|
527
551
|
* next-indexnow --help
|
|
528
552
|
*/
|
|
@@ -535,6 +559,32 @@ ${chalk.white("██║██║ ╚████║██████╔╝█
|
|
|
535
559
|
${chalk.white("╚═╝╚═╝ ╚═══╝╚═════╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚══╝╚══╝")}`;
|
|
536
560
|
var SEPARATOR = chalk.dim("=".repeat(69));
|
|
537
561
|
/**
|
|
562
|
+
* Parse a non-negative integer CLI option value.
|
|
563
|
+
*
|
|
564
|
+
* @param {string} value - Raw string value from the command line.
|
|
565
|
+
*
|
|
566
|
+
* @returns {number} The parsed integer.
|
|
567
|
+
*
|
|
568
|
+
* @throws {InvalidArgumentError} If the value is not a non-negative integer.
|
|
569
|
+
*/
|
|
570
|
+
function parseNonNegativeInt(value) {
|
|
571
|
+
if (!/^\d+$/.test(value)) throw new InvalidArgumentError("must be a non-negative integer");
|
|
572
|
+
return Number.parseInt(value, 10);
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Parse a positive integer CLI option value.
|
|
576
|
+
*
|
|
577
|
+
* @param {string} value - Raw string value from the command line.
|
|
578
|
+
*
|
|
579
|
+
* @returns {number} The parsed integer.
|
|
580
|
+
*
|
|
581
|
+
* @throws {InvalidArgumentError} If the value is not a positive integer.
|
|
582
|
+
*/
|
|
583
|
+
function parsePositiveInt(value) {
|
|
584
|
+
if (!/^[1-9]\d*$/.test(value)) throw new InvalidArgumentError("must be a positive integer");
|
|
585
|
+
return Number.parseInt(value, 10);
|
|
586
|
+
}
|
|
587
|
+
/**
|
|
538
588
|
* Print a checkmark or error icon with a status label and optional detail.
|
|
539
589
|
*
|
|
540
590
|
* @param {boolean} valid - Whether the check passed.
|
|
@@ -581,12 +631,13 @@ var pkgPathDist = resolve(__filename, "..", "..", "package.json");
|
|
|
581
631
|
var pkgPath = existsSync(pkgPathDist) ? pkgPathDist : pkgPathSrc;
|
|
582
632
|
var pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
583
633
|
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", (
|
|
634
|
+
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
635
|
Examples:
|
|
586
636
|
$ next-indexnow Submit URLs using settings from next-sitemap.config
|
|
587
637
|
$ next-indexnow --site-url https://example.com Override the site URL
|
|
588
638
|
$ next-indexnow --key my-api-key Provide IndexNow API key
|
|
589
639
|
$ next-indexnow --sitemap ./public/sitemap.xml Use a custom sitemap path
|
|
640
|
+
$ next-indexnow -o 100 -l 50 Submit a slice of the sitemap
|
|
590
641
|
$ next-indexnow --dry-run Preview URLs without submitting
|
|
591
642
|
$ next-indexnow --help Show this help message
|
|
592
643
|
`).parse(process.argv);
|
|
@@ -638,9 +689,7 @@ function displayResults(result) {
|
|
|
638
689
|
case "yellow":
|
|
639
690
|
coloredValue = chalk.yellow(value);
|
|
640
691
|
break;
|
|
641
|
-
default:
|
|
642
|
-
coloredValue = value;
|
|
643
|
-
break;
|
|
692
|
+
default: coloredValue = value;
|
|
644
693
|
}
|
|
645
694
|
console.log(`${logSymbols.success} ${chalk.bold(label)}: ${coloredValue}`);
|
|
646
695
|
}
|
|
@@ -701,7 +750,8 @@ function runValidationChecks(opts) {
|
|
|
701
750
|
console.log(chalk.red(`\n ${keyFileCheck.error}`));
|
|
702
751
|
process.exit(1);
|
|
703
752
|
}
|
|
704
|
-
|
|
753
|
+
const keyFilePath = resolve(process.cwd(), "public", `${key}.txt`);
|
|
754
|
+
checkMark(true, "Key verification file", existsSync(keyFilePath) ? `${key}.txt exists` : `${key}.txt created`);
|
|
705
755
|
return {
|
|
706
756
|
parsedConfig,
|
|
707
757
|
siteUrl,
|
|
@@ -748,7 +798,14 @@ async function main() {
|
|
|
748
798
|
siteUrl: opts.siteUrl,
|
|
749
799
|
key: opts.key
|
|
750
800
|
});
|
|
751
|
-
await loadSitemapCheck({ sitemap: opts.sitemap }, parsedConfig);
|
|
801
|
+
const sitemapUrls = await loadSitemapCheck({ sitemap: opts.sitemap }, parsedConfig);
|
|
802
|
+
if (opts.offset !== void 0 || opts.limit !== void 0) {
|
|
803
|
+
const selected = applyOffsetLimit(sitemapUrls, opts.offset, opts.limit);
|
|
804
|
+
const rangeParts = [];
|
|
805
|
+
if (opts.offset !== void 0) rangeParts.push(`offset ${opts.offset}`);
|
|
806
|
+
if (opts.limit !== void 0) rangeParts.push(`limit ${opts.limit}`);
|
|
807
|
+
checkMark(true, "URL range applied", `${selected.length} of ${sitemapUrls.length} URLs (${rangeParts.join(", ")})`);
|
|
808
|
+
}
|
|
752
809
|
console.log("");
|
|
753
810
|
spinner = ora({
|
|
754
811
|
color: "cyan",
|
|
@@ -759,6 +816,8 @@ async function main() {
|
|
|
759
816
|
key: opts.key,
|
|
760
817
|
sitemap: opts.sitemap,
|
|
761
818
|
chunkSize: opts.chunkSize,
|
|
819
|
+
offset: opts.offset,
|
|
820
|
+
limit: opts.limit,
|
|
762
821
|
dryRun: opts.dryRun ?? false,
|
|
763
822
|
onProgress: ({ batch, totalBatches, urlCount }) => {
|
|
764
823
|
if (spinner) {
|
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/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 * 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?.({ 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, 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","#!/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 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} 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 * @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 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 // 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 offset: opts.offset,\n limit: opts.limit,\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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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vijayhardaha/next-indexnow",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "CLI tool to submit Next.js sitemaps to the IndexNow API for faster search engine indexing",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "vite",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
},
|
|
56
56
|
"license": "MIT",
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"chalk": "^
|
|
58
|
+
"chalk": "^6.0.0",
|
|
59
59
|
"commander": "^15.0.0",
|
|
60
60
|
"log-symbols": "^7.0.1",
|
|
61
61
|
"ora": "^9.4.1",
|