@gscdump/cli 0.37.3 → 0.37.4

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.
@@ -0,0 +1,203 @@
1
+ import { OUTPUT_ARGS, applyOutputMode, logger, readUrlList } from "./utils.mjs";
2
+ import { createCommandContext } from "./context.mjs";
3
+ import { gscErrorHandler } from "./error-handler.mjs";
4
+ import process from "node:process";
5
+ import { defineCommand } from "citty";
6
+ import { batchInspectUrls, fetchSitemapUrls } from "gscdump/api";
7
+ function verdictTone(verdict) {
8
+ if (verdict === "PASS") return "\x1B[32m";
9
+ if (verdict === "NEUTRAL" || verdict === "PARTIAL") return "\x1B[33m";
10
+ if (verdict === "FAIL") return "\x1B[31m";
11
+ return "\x1B[90m";
12
+ }
13
+ function colorVerdict(verdict) {
14
+ return `${verdictTone(verdict)}${verdict || "N/A"}\x1B[0m`;
15
+ }
16
+ function printInspection(url, inspection) {
17
+ const indexStatus = inspection?.indexStatusResult;
18
+ console.log();
19
+ console.log(` \x1B[1mURL:\x1B[0m ${url}`);
20
+ console.log();
21
+ console.log(` \x1B[1mIndex status\x1B[0m`);
22
+ console.log(` Verdict: ${colorVerdict(indexStatus?.verdict)}`);
23
+ if (indexStatus?.coverageState) console.log(` Coverage: ${indexStatus.coverageState}`);
24
+ if (indexStatus?.indexingState) console.log(` Indexing: ${indexStatus.indexingState}`);
25
+ if (indexStatus?.lastCrawlTime) console.log(` Last Crawl: ${indexStatus.lastCrawlTime}`);
26
+ if (indexStatus?.crawledAs) console.log(` Crawled As: ${indexStatus.crawledAs}`);
27
+ if (indexStatus?.robotsTxtState) console.log(` Robots.txt: ${indexStatus.robotsTxtState}`);
28
+ if (indexStatus?.pageFetchState) console.log(` Page Fetch: ${indexStatus.pageFetchState}`);
29
+ if (indexStatus?.googleCanonical) console.log(` Google Canon: ${indexStatus.googleCanonical}`);
30
+ if (indexStatus?.userCanonical) console.log(` User Canon: ${indexStatus.userCanonical}`);
31
+ if (indexStatus?.sitemap?.length) {
32
+ console.log(` Sitemaps:`);
33
+ for (const sm of indexStatus.sitemap) console.log(` \x1B[90m└─\x1B[0m ${sm}`);
34
+ }
35
+ if (indexStatus?.referringUrls?.length) {
36
+ const shown = indexStatus.referringUrls.slice(0, 5);
37
+ console.log(` Referring URLs (${indexStatus.referringUrls.length}):`);
38
+ for (const r of shown) console.log(` \x1B[90m└─\x1B[0m ${r}`);
39
+ if (indexStatus.referringUrls.length > shown.length) console.log(` \x1B[90m… ${indexStatus.referringUrls.length - shown.length} more\x1B[0m`);
40
+ }
41
+ const rich = inspection?.richResultsResult;
42
+ if (rich) {
43
+ console.log();
44
+ console.log(` \x1B[1mRich results\x1B[0m`);
45
+ console.log(` Verdict: ${colorVerdict(rich.verdict)}`);
46
+ if (rich.detectedItems?.length) for (const group of rich.detectedItems) {
47
+ const count = group.items?.length ?? 0;
48
+ console.log(` ${group.richResultType ?? "unknown"}: ${count} item${count === 1 ? "" : "s"}`);
49
+ for (const item of group.items ?? []) if (item.issues?.length) for (const issue of item.issues) {
50
+ const sev = issue.severity === "ERROR" ? "\x1B[31m" : "\x1B[33m";
51
+ console.log(` ${sev}${issue.severity ?? "?"}\x1B[0m ${item.name ?? ""}: ${issue.issueMessage ?? ""}`);
52
+ }
53
+ }
54
+ }
55
+ const amp = inspection?.ampResult;
56
+ if (amp) {
57
+ console.log();
58
+ console.log(` \x1B[1mAMP\x1B[0m`);
59
+ console.log(` Verdict: ${colorVerdict(amp.verdict)}`);
60
+ if (amp.ampUrl) console.log(` AMP URL: ${amp.ampUrl}`);
61
+ if (amp.ampIndexStatusVerdict) console.log(` Index Verdict: ${amp.ampIndexStatusVerdict}`);
62
+ if (amp.indexingState) console.log(` Indexing: ${amp.indexingState}`);
63
+ if (amp.robotsTxtState) console.log(` Robots.txt: ${amp.robotsTxtState}`);
64
+ if (amp.pageFetchState) console.log(` Page Fetch: ${amp.pageFetchState}`);
65
+ if (amp.lastCrawlTime) console.log(` Last Crawl: ${amp.lastCrawlTime}`);
66
+ if (amp.issues?.length) for (const issue of amp.issues) {
67
+ const sev = issue.severity === "ERROR" ? "\x1B[31m" : "\x1B[33m";
68
+ console.log(` ${sev}${issue.severity ?? "?"}\x1B[0m ${issue.issueMessage ?? ""}`);
69
+ }
70
+ }
71
+ const mobile = inspection?.mobileUsabilityResult;
72
+ if (mobile) {
73
+ console.log();
74
+ console.log(` \x1B[1mMobile usability\x1B[0m`);
75
+ console.log(` Verdict: ${colorVerdict(mobile.verdict)}`);
76
+ if (mobile.issues?.length) for (const issue of mobile.issues) console.log(` \x1B[33m${issue.issueType ?? "?"}\x1B[0m ${issue.message ?? ""}`);
77
+ }
78
+ if (inspection?.inspectionResultLink) {
79
+ console.log();
80
+ console.log(` \x1B[90mOpen in Search Console:\x1B[0m \x1B[36m${inspection.inspectionResultLink}\x1B[0m`);
81
+ }
82
+ console.log();
83
+ }
84
+ const batchCommand = defineCommand({
85
+ meta: {
86
+ name: "batch",
87
+ description: "Inspect many URLs from a file or stdin (one URL per line)"
88
+ },
89
+ args: {
90
+ ...OUTPUT_ARGS,
91
+ "site": {
92
+ type: "string",
93
+ alias: "s",
94
+ description: "Site URL (defaults to config.defaultSite or prompt)"
95
+ },
96
+ "urls": {
97
+ type: "positional",
98
+ required: false,
99
+ description: "URLs (or use --file/--from-sitemap/stdin)"
100
+ },
101
+ "file": {
102
+ type: "string",
103
+ alias: "f",
104
+ description: "File with URLs (one per line)"
105
+ },
106
+ "from-sitemap": {
107
+ type: "string",
108
+ description: "Sitemap URL (or sitemap index) to pull URLs from"
109
+ },
110
+ "delay-ms": {
111
+ type: "string",
112
+ default: "200",
113
+ description: "Delay between requests"
114
+ },
115
+ "concurrency": {
116
+ type: "string",
117
+ alias: "c",
118
+ default: "1",
119
+ description: "Concurrent in-flight requests"
120
+ }
121
+ },
122
+ async run({ args }) {
123
+ const { json, quiet } = applyOutputMode(args);
124
+ const urls = args["from-sitemap"] ? await fetchSitemapUrls(String(args["from-sitemap"])).catch((e) => {
125
+ logger.error(`Sitemap fetch failed: ${e.message}`);
126
+ process.exit(1);
127
+ }) : await readUrlList(args);
128
+ if (urls.length === 0) {
129
+ logger.error("No URLs provided. Pass URLs as args, --file, --from-sitemap, or stdin.");
130
+ process.exit(1);
131
+ }
132
+ const ctx = await createCommandContext({ needsAuth: true });
133
+ const siteUrl = await ctx.resolveSite(args.site ? String(args.site) : void 0);
134
+ const delayMs = Number.parseInt(String(args["delay-ms"]), 10);
135
+ const concurrency = Math.max(1, Number.parseInt(String(args.concurrency), 10) || 1);
136
+ if (!quiet) logger.info(`Inspecting ${urls.length} URLs ...`);
137
+ const results = await batchInspectUrls(ctx.client, siteUrl, urls, {
138
+ delayMs,
139
+ concurrency,
140
+ onProgress: quiet ? void 0 : (r, i, total) => logger.info(`[${i + 1}/${total}] ${r.url} ${r.isIndexed ? "PASS" : "FAIL"}`)
141
+ }).catch(gscErrorHandler);
142
+ if (json) {
143
+ const flattened = results.map((r) => {
144
+ const indexStatus = r.inspection?.indexStatusResult;
145
+ return {
146
+ url: r.url,
147
+ verdict: indexStatus?.verdict || null,
148
+ coverageState: indexStatus?.coverageState || null,
149
+ indexingState: indexStatus?.indexingState || null,
150
+ lastCrawlTime: indexStatus?.lastCrawlTime || null,
151
+ isIndexed: r.isIndexed,
152
+ raw: r.inspection
153
+ };
154
+ });
155
+ console.log(JSON.stringify(flattened, null, 2));
156
+ return;
157
+ }
158
+ const indexed = results.filter((r) => r.isIndexed).length;
159
+ if (!quiet) logger.success(`Inspected ${results.length} URLs (${indexed} indexed, ${results.length - indexed} not)`);
160
+ }
161
+ });
162
+ const inspectCommand = defineCommand({
163
+ meta: {
164
+ name: "inspect",
165
+ description: "Inspect URL indexing status (single URL; use `inspect batch` for many)"
166
+ },
167
+ args: {
168
+ ...OUTPUT_ARGS,
169
+ site: {
170
+ type: "string",
171
+ alias: "s",
172
+ description: "Site URL (defaults to config.defaultSite or prompt)"
173
+ },
174
+ url: {
175
+ type: "positional",
176
+ required: true,
177
+ description: "URL to inspect"
178
+ }
179
+ },
180
+ subCommands: { batch: batchCommand },
181
+ async run({ args }) {
182
+ const { json } = applyOutputMode(args);
183
+ const ctx = await createCommandContext({ needsAuth: true });
184
+ const siteUrl = await ctx.resolveSite(args.site ? String(args.site) : void 0);
185
+ const result = await ctx.client.inspect(siteUrl, args.url).catch(gscErrorHandler);
186
+ const inspection = result?.inspectionResult;
187
+ const indexStatus = inspection?.indexStatusResult;
188
+ if (json) {
189
+ console.log(JSON.stringify({
190
+ url: args.url,
191
+ verdict: indexStatus?.verdict || null,
192
+ coverageState: indexStatus?.coverageState || null,
193
+ indexingState: indexStatus?.indexingState || null,
194
+ lastCrawlTime: indexStatus?.lastCrawlTime || null,
195
+ isIndexed: indexStatus?.verdict === "PASS",
196
+ raw: result
197
+ }, null, 2));
198
+ return;
199
+ }
200
+ printInspection(args.url, inspection);
201
+ }
202
+ });
203
+ export { inspectCommand };