@gscdump/cli 2.1.0 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -27,250 +27,254 @@ async function readUrlList(opts) {
27
27
  for await (const chunk of process.stdin) chunks.push(chunk);
28
28
  return Buffer.concat(chunks).toString("utf8").split("\n").map((l) => l.trim()).filter(Boolean);
29
29
  }
30
- const entitiesCommand = defineCommand({
30
+ const inspectSubCommand = defineCommand({
31
31
  meta: {
32
- name: "entities",
33
- description: "Manage local entity snapshots (URL inspections and indexing metadata)"
32
+ name: "inspect",
33
+ description: "Run URL Inspection for a list of URLs and persist results to the local entity store"
34
34
  },
35
- subCommands: {
36
- inspect: defineCommand({
37
- meta: {
38
- name: "inspect",
39
- description: "Run URL Inspection for a list of URLs and persist results to the local entity store"
40
- },
41
- args: {
42
- site: {
43
- type: "string",
44
- alias: "s",
45
- description: "Site URL (e.g., sc-domain:example.com); defaults to config.defaultSite or prompt"
46
- },
47
- file: {
48
- type: "string",
49
- alias: "f",
50
- description: "Path to a file with one URL per line. If omitted, reads from stdin."
51
- },
52
- limit: {
53
- type: "string",
54
- description: `Max URLs to inspect this run (default: ${INSPECTION_QPD_PER_PROPERTY}, the per-property GSC daily quota)`
55
- },
56
- concurrency: {
57
- type: "string",
58
- alias: "c",
59
- default: "4",
60
- description: "Concurrent in-flight inspect calls (default: 4)"
61
- },
62
- ...OUTPUT_ARGS
63
- },
64
- async run({ args }) {
65
- const { json, quiet } = applyOutputMode(args);
66
- const ctx = await createCommandContext({
67
- needsAuth: true,
68
- needsStore: true
35
+ args: {
36
+ site: {
37
+ type: "string",
38
+ alias: "s",
39
+ description: "Site URL (e.g., sc-domain:example.com); defaults to config.defaultSite or prompt"
40
+ },
41
+ file: {
42
+ type: "string",
43
+ alias: "f",
44
+ description: "Path to a file with one URL per line. If omitted, reads from stdin."
45
+ },
46
+ limit: {
47
+ type: "string",
48
+ description: `Max URLs to inspect this run (default: ${INSPECTION_QPD_PER_PROPERTY}, the per-property GSC daily quota)`
49
+ },
50
+ concurrency: {
51
+ type: "string",
52
+ alias: "c",
53
+ default: "4",
54
+ description: "Concurrent in-flight inspect calls (default: 4)"
55
+ },
56
+ ...OUTPUT_ARGS
57
+ },
58
+ async run({ args }) {
59
+ const { json, quiet } = applyOutputMode(args);
60
+ const ctx = await createCommandContext({
61
+ needsAuth: true,
62
+ needsStore: true
63
+ });
64
+ const client = ctx.client;
65
+ const store = ctx.store;
66
+ const siteUrl = await ctx.resolveSite(args.site ? String(args.site) : void 0);
67
+ const limit = args.limit ? Number.parseInt(String(args.limit), 10) : INSPECTION_QPD_PER_PROPERTY;
68
+ const concurrency = Math.max(1, Number.parseInt(String(args.concurrency), 10) || 4);
69
+ const urls = (await readUrlList({ file: args.file ? String(args.file) : void 0 })).slice(0, limit);
70
+ if (urls.length === 0) {
71
+ logger.warn("No URLs to inspect.");
72
+ return;
73
+ }
74
+ if (urls.length === limit && limit < INSPECTION_QPD_PER_PROPERTY) logger.info(`Capping at --limit ${limit}`);
75
+ if (urls.length === INSPECTION_QPD_PER_PROPERTY) logger.info(`Hit per-property daily inspection quota (${INSPECTION_QPD_PER_PROPERTY}); remaining URLs will be queued for tomorrow.`);
76
+ const inspector = createInspectionStore({ dataSource: store.dataSource });
77
+ let completed = 0;
78
+ let failed = 0;
79
+ const records = [];
80
+ const failures = [];
81
+ await runWithConcurrency(urls, concurrency, async (url) => {
82
+ const result = await client.inspect(siteUrl, url).catch((err) => err);
83
+ if (result instanceof Error) {
84
+ failed++;
85
+ failures.push({
86
+ url,
87
+ error: result.message
69
88
  });
70
- const client = ctx.client;
71
- const store = ctx.store;
72
- const siteUrl = await ctx.resolveSite(args.site ? String(args.site) : void 0);
73
- const limit = args.limit ? Number.parseInt(String(args.limit), 10) : INSPECTION_QPD_PER_PROPERTY;
74
- const concurrency = Math.max(1, Number.parseInt(String(args.concurrency), 10) || 4);
75
- const urls = (await readUrlList({ file: args.file ? String(args.file) : void 0 })).slice(0, limit);
76
- if (urls.length === 0) {
77
- logger.warn("No URLs to inspect.");
78
- return;
79
- }
80
- if (urls.length === limit && limit < INSPECTION_QPD_PER_PROPERTY) logger.info(`Capping at --limit ${limit}`);
81
- if (urls.length === INSPECTION_QPD_PER_PROPERTY) logger.info(`Hit per-property daily inspection quota (${INSPECTION_QPD_PER_PROPERTY}); remaining URLs will be queued for tomorrow.`);
82
- const inspector = createInspectionStore({ dataSource: store.dataSource });
83
- let completed = 0;
84
- let failed = 0;
85
- const records = [];
86
- const failures = [];
87
- await runWithConcurrency(urls, concurrency, async (url) => {
88
- const result = await client.inspect(siteUrl, url).catch((err) => err);
89
- if (result instanceof Error) {
90
- failed++;
91
- failures.push({
92
- url,
93
- error: result.message
94
- });
95
- } else {
96
- const ix = result.inspectionResult;
97
- const indexStatus = ix?.indexStatusResult;
98
- records.push({
99
- url,
100
- inspectedAt: (/* @__PURE__ */ new Date()).toISOString(),
101
- indexStatus: indexStatus?.verdict ?? void 0,
102
- lastCrawlTime: indexStatus?.lastCrawlTime ?? void 0,
103
- googleCanonical: indexStatus?.googleCanonical ?? void 0,
104
- userCanonical: indexStatus?.userCanonical ?? void 0,
105
- coverageState: indexStatus?.coverageState ?? void 0,
106
- robotsTxtState: indexStatus?.robotsTxtState ?? void 0,
107
- indexingState: indexStatus?.indexingState ?? void 0,
108
- pageFetchState: indexStatus?.pageFetchState ?? void 0,
109
- mobileUsabilityVerdict: ix?.mobileUsabilityResult?.verdict ?? void 0,
110
- richResultsVerdict: ix?.richResultsResult?.verdict ?? void 0,
111
- raw: ix
112
- });
113
- }
114
- completed++;
115
- if (!quiet) process.stdout.write(`\r${progressBar(completed, urls.length, `${url.slice(0, 60)}`)}`);
89
+ } else {
90
+ const ix = result.inspectionResult;
91
+ const indexStatus = ix?.indexStatusResult;
92
+ records.push({
93
+ url,
94
+ inspectedAt: (/* @__PURE__ */ new Date()).toISOString(),
95
+ indexStatus: indexStatus?.verdict ?? void 0,
96
+ lastCrawlTime: indexStatus?.lastCrawlTime ?? void 0,
97
+ googleCanonical: indexStatus?.googleCanonical ?? void 0,
98
+ userCanonical: indexStatus?.userCanonical ?? void 0,
99
+ coverageState: indexStatus?.coverageState ?? void 0,
100
+ robotsTxtState: indexStatus?.robotsTxtState ?? void 0,
101
+ indexingState: indexStatus?.indexingState ?? void 0,
102
+ pageFetchState: indexStatus?.pageFetchState ?? void 0,
103
+ mobileUsabilityVerdict: ix?.mobileUsabilityResult?.verdict ?? void 0,
104
+ richResultsVerdict: ix?.richResultsResult?.verdict ?? void 0,
105
+ raw: ix
116
106
  });
117
- if (!quiet) process.stdout.write("\n");
118
- await inspector.appendHistory({
119
- userId: store.userId,
120
- siteId: store.siteIdFor(siteUrl)
121
- }, records);
122
- if (json) console.log(JSON.stringify({
123
- site: siteUrl,
124
- inspected: records.length,
125
- failed,
126
- failures,
127
- records
128
- }, null, 2));
129
- else if (!quiet) {
130
- logger.success(`Inspected ${records.length}/${urls.length} URL(s)`);
131
- if (failed > 0) {
132
- logger.warn(`${failed} failed:`);
133
- for (const f of failures.slice(0, 5)) console.log(` ${f.url}: ${f.error}`);
134
- if (failures.length > 5) console.log(` ... and ${failures.length - 5} more`);
135
- }
136
- }
137
- if (failed > 0) process.exit(1);
138
107
  }
139
- }),
140
- show: defineCommand({
141
- meta: {
142
- name: "show",
143
- description: "Print the latest inspection record for a URL from the local entity store"
144
- },
145
- args: {
146
- ...OUTPUT_ARGS,
147
- site: {
148
- type: "string",
149
- alias: "s",
150
- description: "Site URL (defaults to config.defaultSite or prompt)"
151
- },
152
- url: {
153
- type: "positional",
154
- required: true,
155
- description: "URL to look up"
156
- }
157
- },
158
- async run({ args }) {
159
- const { json } = applyOutputMode(args);
160
- const ctx = await createCommandContext({
161
- needsAuth: true,
162
- needsStore: true
108
+ completed++;
109
+ if (!quiet) process.stdout.write(`\r${progressBar(completed, urls.length, `${url.slice(0, 60)}`)}`);
110
+ });
111
+ if (!quiet) process.stdout.write("\n");
112
+ await inspector.appendHistory({
113
+ userId: store.userId,
114
+ siteId: store.siteIdFor(siteUrl)
115
+ }, records);
116
+ if (json) console.log(JSON.stringify({
117
+ site: siteUrl,
118
+ inspected: records.length,
119
+ failed,
120
+ failures,
121
+ records
122
+ }, null, 2));
123
+ else if (!quiet) {
124
+ logger.success(`Inspected ${records.length}/${urls.length} URL(s)`);
125
+ if (failed > 0) {
126
+ logger.warn(`${failed} failed:`);
127
+ for (const f of failures.slice(0, 5)) console.log(` ${f.url}: ${f.error}`);
128
+ if (failures.length > 5) console.log(` ... and ${failures.length - 5} more`);
129
+ }
130
+ }
131
+ if (failed > 0) process.exit(1);
132
+ }
133
+ });
134
+ const showSubCommand = defineCommand({
135
+ meta: {
136
+ name: "show",
137
+ description: "Print the latest inspection record for a URL from the local entity store"
138
+ },
139
+ args: {
140
+ ...OUTPUT_ARGS,
141
+ site: {
142
+ type: "string",
143
+ alias: "s",
144
+ description: "Site URL (defaults to config.defaultSite or prompt)"
145
+ },
146
+ url: {
147
+ type: "positional",
148
+ required: true,
149
+ description: "URL to look up"
150
+ }
151
+ },
152
+ async run({ args }) {
153
+ const { json } = applyOutputMode(args);
154
+ const ctx = await createCommandContext({
155
+ needsAuth: true,
156
+ needsStore: true
157
+ });
158
+ const store = ctx.store;
159
+ const siteUrl = await ctx.resolveSite(args.site ? String(args.site) : void 0);
160
+ const record = await findLatestInspection(createInspectionStore({ dataSource: store.dataSource }), {
161
+ userId: store.userId,
162
+ siteId: store.siteIdFor(siteUrl)
163
+ }, String(args.url));
164
+ if (!record) {
165
+ logger.warn(`No inspection record for ${args.url}`);
166
+ process.exit(1);
167
+ }
168
+ if (json) {
169
+ console.log(JSON.stringify(record, null, 2));
170
+ return;
171
+ }
172
+ console.log();
173
+ console.log(` \x1B[1m${record.url}\x1B[0m`);
174
+ console.log(` Inspected: ${record.inspectedAt}`);
175
+ if (record.indexStatus) console.log(` Index: ${record.indexStatus}`);
176
+ if (record.lastCrawlTime) console.log(` Last crawl: ${record.lastCrawlTime}`);
177
+ if (record.googleCanonical) console.log(` Canonical: ${record.googleCanonical}`);
178
+ if (record.coverageState) console.log(` Coverage: ${record.coverageState}`);
179
+ if (record.richResultsVerdict) console.log(` Rich results: ${record.richResultsVerdict}`);
180
+ console.log();
181
+ }
182
+ });
183
+ const indexingSnapshotSubCommand = defineCommand({
184
+ meta: {
185
+ name: "snapshot",
186
+ description: "Fetch Indexing API metadata (latest update/remove per URL) and persist to the local entity store"
187
+ },
188
+ args: {
189
+ site: {
190
+ type: "string",
191
+ alias: "s",
192
+ description: "Site URL (e.g., sc-domain:example.com); defaults to config.defaultSite or prompt"
193
+ },
194
+ file: {
195
+ type: "string",
196
+ alias: "f",
197
+ description: "Path to a file with one URL per line. If omitted, reads from stdin."
198
+ },
199
+ concurrency: {
200
+ type: "string",
201
+ alias: "c",
202
+ default: "4",
203
+ description: "Concurrent in-flight getMetadata calls (default: 4)"
204
+ },
205
+ ...OUTPUT_ARGS
206
+ },
207
+ async run({ args }) {
208
+ const { quiet } = applyOutputMode(args);
209
+ const ctx = await createCommandContext({
210
+ needsAuth: true,
211
+ needsStore: true
212
+ });
213
+ const client = ctx.client;
214
+ const store = ctx.store;
215
+ const siteUrl = await ctx.resolveSite(args.site ? String(args.site) : void 0);
216
+ const concurrency = Math.max(1, Number.parseInt(String(args.concurrency), 10) || 4);
217
+ const urls = await readUrlList({ file: args.file ? String(args.file) : void 0 });
218
+ if (urls.length === 0) {
219
+ logger.warn("No URLs to fetch metadata for.");
220
+ return;
221
+ }
222
+ const records = [];
223
+ const failures = [];
224
+ let completed = 0;
225
+ await runWithConcurrency(urls, concurrency, async (url) => {
226
+ const result = await client.indexing.getMetadata(url).catch((err) => err);
227
+ if (result instanceof Error) {
228
+ if (INDEXING_NOT_FOUND_RE.test(result.message)) records.push({
229
+ url,
230
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString()
163
231
  });
164
- const store = ctx.store;
165
- const siteUrl = await ctx.resolveSite(args.site ? String(args.site) : void 0);
166
- const record = await findLatestInspection(createInspectionStore({ dataSource: store.dataSource }), {
167
- userId: store.userId,
168
- siteId: store.siteIdFor(siteUrl)
169
- }, String(args.url));
170
- if (!record) {
171
- logger.warn(`No inspection record for ${args.url}`);
172
- process.exit(1);
173
- }
174
- if (json) {
175
- console.log(JSON.stringify(record, null, 2));
176
- return;
177
- }
178
- console.log();
179
- console.log(` \x1B[1m${record.url}\x1B[0m`);
180
- console.log(` Inspected: ${record.inspectedAt}`);
181
- if (record.indexStatus) console.log(` Index: ${record.indexStatus}`);
182
- if (record.lastCrawlTime) console.log(` Last crawl: ${record.lastCrawlTime}`);
183
- if (record.googleCanonical) console.log(` Canonical: ${record.googleCanonical}`);
184
- if (record.coverageState) console.log(` Coverage: ${record.coverageState}`);
185
- if (record.mobileUsabilityVerdict) console.log(` Mobile: ${record.mobileUsabilityVerdict}`);
186
- if (record.richResultsVerdict) console.log(` Rich results: ${record.richResultsVerdict}`);
187
- console.log();
232
+ else failures.push({
233
+ url,
234
+ error: result.message
235
+ });
236
+ } else records.push({
237
+ url,
238
+ capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
239
+ latestUpdateAt: result.latestUpdate?.notifyTime ?? void 0,
240
+ latestRemoveAt: result.latestRemove?.notifyTime ?? void 0,
241
+ raw: result
242
+ });
243
+ completed++;
244
+ if (!quiet) process.stdout.write(`\r${progressBar(completed, urls.length, url.slice(0, 60))}`);
245
+ });
246
+ if (!quiet) process.stdout.write("\n");
247
+ await createIndexingMetadataStore({ dataSource: store.dataSource }).writeBatch({
248
+ userId: store.userId,
249
+ siteId: store.siteIdFor(siteUrl)
250
+ }, records);
251
+ if (!quiet) {
252
+ logger.success(`Captured metadata for ${records.length}/${urls.length} URL(s)`);
253
+ if (failures.length > 0) {
254
+ logger.warn(`${failures.length} failed:`);
255
+ for (const f of failures.slice(0, 5)) console.log(` ${f.url}: ${f.error}`);
256
+ if (failures.length > 5) console.log(` ... and ${failures.length - 5} more`);
188
257
  }
189
- }),
190
- indexing: defineCommand({
191
- meta: {
192
- name: "indexing",
193
- description: "Snapshot Indexing API metadata per URL"
194
- },
195
- subCommands: { snapshot: defineCommand({
196
- meta: {
197
- name: "snapshot",
198
- description: "Fetch Indexing API metadata (latest update/remove per URL) and persist to the local entity store"
199
- },
200
- args: {
201
- site: {
202
- type: "string",
203
- alias: "s",
204
- description: "Site URL (e.g., sc-domain:example.com); defaults to config.defaultSite or prompt"
205
- },
206
- file: {
207
- type: "string",
208
- alias: "f",
209
- description: "Path to a file with one URL per line. If omitted, reads from stdin."
210
- },
211
- concurrency: {
212
- type: "string",
213
- alias: "c",
214
- default: "4",
215
- description: "Concurrent in-flight getMetadata calls (default: 4)"
216
- },
217
- ...OUTPUT_ARGS
218
- },
219
- async run({ args }) {
220
- const { quiet } = applyOutputMode(args);
221
- const ctx = await createCommandContext({
222
- needsAuth: true,
223
- needsStore: true
224
- });
225
- const client = ctx.client;
226
- const store = ctx.store;
227
- const siteUrl = await ctx.resolveSite(args.site ? String(args.site) : void 0);
228
- const concurrency = Math.max(1, Number.parseInt(String(args.concurrency), 10) || 4);
229
- const urls = await readUrlList({ file: args.file ? String(args.file) : void 0 });
230
- if (urls.length === 0) {
231
- logger.warn("No URLs to fetch metadata for.");
232
- return;
233
- }
234
- const records = [];
235
- const failures = [];
236
- let completed = 0;
237
- await runWithConcurrency(urls, concurrency, async (url) => {
238
- const result = await client.indexing.getMetadata(url).catch((err) => err);
239
- if (result instanceof Error) if (INDEXING_NOT_FOUND_RE.test(result.message)) records.push({
240
- url,
241
- capturedAt: (/* @__PURE__ */ new Date()).toISOString()
242
- });
243
- else failures.push({
244
- url,
245
- error: result.message
246
- });
247
- else records.push({
248
- url,
249
- capturedAt: (/* @__PURE__ */ new Date()).toISOString(),
250
- latestUpdateAt: result.latestUpdate?.notifyTime ?? void 0,
251
- latestRemoveAt: result.latestRemove?.notifyTime ?? void 0,
252
- raw: result
253
- });
254
- completed++;
255
- if (!quiet) process.stdout.write(`\r${progressBar(completed, urls.length, url.slice(0, 60))}`);
256
- });
257
- if (!quiet) process.stdout.write("\n");
258
- await createIndexingMetadataStore({ dataSource: store.dataSource }).writeBatch({
259
- userId: store.userId,
260
- siteId: store.siteIdFor(siteUrl)
261
- }, records);
262
- if (!quiet) {
263
- logger.success(`Captured metadata for ${records.length}/${urls.length} URL(s)`);
264
- if (failures.length > 0) {
265
- logger.warn(`${failures.length} failed:`);
266
- for (const f of failures.slice(0, 5)) console.log(` ${f.url}: ${f.error}`);
267
- if (failures.length > 5) console.log(` ... and ${failures.length - 5} more`);
268
- }
269
- }
270
- if (failures.length > 0) process.exit(1);
271
- }
272
- }) }
273
- })
258
+ }
259
+ if (failures.length > 0) process.exit(1);
260
+ }
261
+ });
262
+ const indexingSubCommand = defineCommand({
263
+ meta: {
264
+ name: "indexing",
265
+ description: "Snapshot Indexing API metadata per URL"
266
+ },
267
+ subCommands: { snapshot: indexingSnapshotSubCommand }
268
+ });
269
+ const entitiesCommand = defineCommand({
270
+ meta: {
271
+ name: "entities",
272
+ description: "Manage local entity snapshots (URL inspections and indexing metadata)"
273
+ },
274
+ subCommands: {
275
+ inspect: inspectSubCommand,
276
+ show: showSubCommand,
277
+ indexing: indexingSubCommand
274
278
  }
275
279
  });
276
280
  export { entitiesCommand };