@gscdump/cli 3.8.0 → 4.0.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 +113 -30
- package/bin/gscdump.mjs +21 -1
- package/dist/analysis-local.mjs +172 -53
- package/dist/auth-state.mjs +21 -6
- package/dist/auth.mjs +27 -18
- package/dist/bing-data.mjs +6 -2
- package/dist/bing-hosted.mjs +1 -1
- package/dist/cli-args.mjs +8 -0
- package/dist/cli.d.mts +5 -1
- package/dist/cli.mjs +41 -10
- package/dist/cloud-google.mjs +8 -1
- package/dist/command-meta.mjs +4 -4
- package/dist/command-registry.mjs +86 -10
- package/dist/commands/analyze.mjs +84 -43
- package/dist/commands/auth.mjs +46 -37
- package/dist/commands/bing.mjs +3 -3
- package/dist/commands/compact.mjs +12 -8
- package/dist/commands/config.mjs +14 -14
- package/dist/commands/doctor.mjs +26 -26
- package/dist/commands/dump.mjs +203 -105
- package/dist/commands/entities.mjs +10 -113
- package/dist/commands/gc.mjs +4 -3
- package/dist/commands/indexing-urls.mjs +185 -0
- package/dist/commands/indexing.mjs +12 -8
- package/dist/commands/init.mjs +77 -18
- package/dist/commands/inspect.mjs +97 -110
- package/dist/commands/mcp.mjs +37 -57
- package/dist/commands/papercut.mjs +1 -1
- package/dist/commands/profile.mjs +9 -17
- package/dist/commands/query.mjs +219 -210
- package/dist/commands/report.mjs +68 -85
- package/dist/commands/rollups.mjs +9 -6
- package/dist/commands/sitemaps.mjs +51 -104
- package/dist/commands/sites.mjs +40 -24
- package/dist/commands/stats.mjs +14 -17
- package/dist/commands/store-purge.mjs +9 -5
- package/dist/commands/store.mjs +0 -12
- package/dist/commands/sync.mjs +774 -296
- package/dist/config.mjs +7 -4
- package/dist/context.mjs +51 -21
- package/dist/coverage.mjs +251 -0
- package/dist/dump-bing.mjs +71 -0
- package/dist/dump-writers.mjs +246 -0
- package/dist/error-handler.mjs +108 -38
- package/dist/filters.mjs +125 -0
- package/dist/hosted-site.mjs +91 -0
- package/dist/inspect-urls.mjs +76 -0
- package/dist/inspection-record.mjs +93 -0
- package/dist/local-entities.mjs +658 -0
- package/dist/mcp/errors.mjs +71 -18
- package/dist/mcp/handlers/diagnostics.mjs +15 -12
- package/dist/mcp/handlers/reports.mjs +33 -42
- package/dist/mcp/server/index.mjs +80 -38
- package/dist/mcp/types.mjs +7 -7
- package/dist/package.mjs +1 -1
- package/dist/quota-ledger.mjs +370 -0
- package/dist/render/analysis.mjs +8 -1
- package/dist/render/report.mjs +8 -1
- package/dist/request-pacer.mjs +29 -0
- package/dist/route.mjs +295 -0
- package/dist/sitemap.mjs +4 -0
- package/dist/sql-views.mjs +117 -0
- package/dist/store-sites.mjs +134 -0
- package/dist/sync-plan.mjs +129 -0
- package/dist/sync-run.mjs +96 -0
- package/dist/table-sources.mjs +57 -0
- package/dist/token-info.mjs +54 -0
- package/dist/utils.mjs +28 -19
- package/dist/window.mjs +159 -0
- package/package.json +17 -14
- package/skills/gscdump/SKILL.md +167 -37
- package/dist/commands/export.mjs +0 -76
- package/dist/native-duckdb.mjs +0 -46
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { formatQuotaStop, quotaStopOf } from "./quota-ledger.mjs";
|
|
2
|
+
import { classifyError } from "gscdump/errors";
|
|
3
|
+
import { URL_INSPECTION_QUOTA, describeInspectionError } from "gscdump/indexing";
|
|
4
|
+
function checkInspectionBatch(urls) {
|
|
5
|
+
if (urls.length <= URL_INSPECTION_QUOTA.perDay) return { kind: "ok" };
|
|
6
|
+
const perDay = URL_INSPECTION_QUOTA.perDay.toLocaleString("en-US");
|
|
7
|
+
return {
|
|
8
|
+
kind: "too-many",
|
|
9
|
+
message: `You passed ${urls.length.toLocaleString("en-US")} URLs. Google allows ${perDay} URL inspections per day for each property. Inspect at most ${perDay} URLs in one run.`
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
async function inspectUrls(input) {
|
|
13
|
+
const intervalMs = input.intervalMs ?? 120;
|
|
14
|
+
const sleep = input.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
15
|
+
const now = input.now ?? Date.now;
|
|
16
|
+
const outcomes = [];
|
|
17
|
+
let lastStart = null;
|
|
18
|
+
for (let i = 0; i < input.urls.length; i++) {
|
|
19
|
+
const url = input.urls[i];
|
|
20
|
+
if (!input.inProperty(url)) {
|
|
21
|
+
const outcome = {
|
|
22
|
+
kind: "failed",
|
|
23
|
+
url,
|
|
24
|
+
error: "The URL is outside this Site."
|
|
25
|
+
};
|
|
26
|
+
outcomes.push(outcome);
|
|
27
|
+
await input.onOutcome(outcome);
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (lastStart !== null) {
|
|
31
|
+
const wait = lastStart + intervalMs - now();
|
|
32
|
+
if (wait > 0) await sleep(wait);
|
|
33
|
+
}
|
|
34
|
+
lastStart = now();
|
|
35
|
+
const settled = await input.inspect(url).then((result) => ({
|
|
36
|
+
ok: true,
|
|
37
|
+
result
|
|
38
|
+
}), (error) => ({
|
|
39
|
+
ok: false,
|
|
40
|
+
error
|
|
41
|
+
}));
|
|
42
|
+
if (!settled.ok) {
|
|
43
|
+
const quota = quotaStopOf(settled.error);
|
|
44
|
+
if (quota) return {
|
|
45
|
+
outcomes,
|
|
46
|
+
stopped: {
|
|
47
|
+
reason: formatQuotaStop(quota),
|
|
48
|
+
remaining: input.urls.length - i
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
if (classifyError(settled.error).kind === "rate-limited") return {
|
|
52
|
+
outcomes,
|
|
53
|
+
stopped: {
|
|
54
|
+
reason: describeInspectionError(settled.error),
|
|
55
|
+
remaining: input.urls.length - i
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const outcome = settled.ok ? {
|
|
60
|
+
kind: "inspected",
|
|
61
|
+
url,
|
|
62
|
+
result: settled.result
|
|
63
|
+
} : {
|
|
64
|
+
kind: "failed",
|
|
65
|
+
url,
|
|
66
|
+
error: describeInspectionError(settled.error)
|
|
67
|
+
};
|
|
68
|
+
outcomes.push(outcome);
|
|
69
|
+
await input.onOutcome(outcome);
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
outcomes,
|
|
73
|
+
stopped: null
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export { checkInspectionBatch, inspectUrls };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { inspectionPolicy } from "@gscdump/engine";
|
|
2
|
+
const INSPECTION_QPD_PER_PROPERTY = 2e3;
|
|
3
|
+
const DAY_MS = 864e5;
|
|
4
|
+
const STATE_FIELDS = [
|
|
5
|
+
"indexStatus",
|
|
6
|
+
"coverageState",
|
|
7
|
+
"robotsTxtState",
|
|
8
|
+
"indexingState",
|
|
9
|
+
"pageFetchState",
|
|
10
|
+
"googleCanonical"
|
|
11
|
+
];
|
|
12
|
+
function verdictOf(indexStatus) {
|
|
13
|
+
return indexStatus === "PASS" || indexStatus === "FAIL" ? indexStatus : "NEUTRAL";
|
|
14
|
+
}
|
|
15
|
+
function scheduleOf(record) {
|
|
16
|
+
if (record.raw?.schedule) return record.raw.schedule;
|
|
17
|
+
const at = Date.parse(record.inspectedAt);
|
|
18
|
+
return inspectionPolicy(verdictOf(record.indexStatus)).initial(Number.isFinite(at) ? at : 0);
|
|
19
|
+
}
|
|
20
|
+
function toInspectionRecord(input) {
|
|
21
|
+
const { url, result, inspectedAt, previous } = input;
|
|
22
|
+
const indexStatus = result?.indexStatusResult;
|
|
23
|
+
const record = {
|
|
24
|
+
url,
|
|
25
|
+
inspectedAt: inspectedAt.toISOString(),
|
|
26
|
+
indexStatus: indexStatus?.verdict ?? void 0,
|
|
27
|
+
lastCrawlTime: indexStatus?.lastCrawlTime ?? void 0,
|
|
28
|
+
googleCanonical: indexStatus?.googleCanonical ?? void 0,
|
|
29
|
+
userCanonical: indexStatus?.userCanonical ?? void 0,
|
|
30
|
+
coverageState: indexStatus?.coverageState ?? void 0,
|
|
31
|
+
robotsTxtState: indexStatus?.robotsTxtState ?? void 0,
|
|
32
|
+
indexingState: indexStatus?.indexingState ?? void 0,
|
|
33
|
+
pageFetchState: indexStatus?.pageFetchState ?? void 0,
|
|
34
|
+
mobileUsabilityVerdict: result?.mobileUsabilityResult?.verdict ?? void 0,
|
|
35
|
+
richResultsVerdict: result?.richResultsResult?.verdict ?? void 0
|
|
36
|
+
};
|
|
37
|
+
const policy = inspectionPolicy(verdictOf(record.indexStatus));
|
|
38
|
+
const at = inspectedAt.getTime();
|
|
39
|
+
const schedule = previous ? policy.observe(scheduleOf(previous), {
|
|
40
|
+
changed: STATE_FIELDS.some((field) => (previous[field] ?? null) !== (record[field] ?? null)),
|
|
41
|
+
at
|
|
42
|
+
}) : policy.initial(at);
|
|
43
|
+
record.raw = {
|
|
44
|
+
...result,
|
|
45
|
+
schedule
|
|
46
|
+
};
|
|
47
|
+
return record;
|
|
48
|
+
}
|
|
49
|
+
function recentMonths(now) {
|
|
50
|
+
const month = (at) => new Date(at).toISOString().slice(0, 7);
|
|
51
|
+
return [.../* @__PURE__ */ new Set([month(now.getTime() - DAY_MS), month(now.getTime())])];
|
|
52
|
+
}
|
|
53
|
+
function latestByUrl(records) {
|
|
54
|
+
const latest = /* @__PURE__ */ new Map();
|
|
55
|
+
for (const record of records) {
|
|
56
|
+
const prior = latest.get(record.url);
|
|
57
|
+
if (!prior || record.inspectedAt > prior.inspectedAt) latest.set(record.url, record);
|
|
58
|
+
}
|
|
59
|
+
return latest;
|
|
60
|
+
}
|
|
61
|
+
function planInspections(input) {
|
|
62
|
+
const now = input.now.getTime();
|
|
63
|
+
const since = now - DAY_MS;
|
|
64
|
+
const usedToday = input.recent.filter((record) => {
|
|
65
|
+
const at = Date.parse(record.inspectedAt);
|
|
66
|
+
return at > since && at <= now;
|
|
67
|
+
}).length;
|
|
68
|
+
const quotaLeft = Math.max(0, INSPECTION_QPD_PER_PROPERTY - usedToday);
|
|
69
|
+
const latest = input.latest;
|
|
70
|
+
const fresh = [];
|
|
71
|
+
const due = [];
|
|
72
|
+
for (const url of new Set(input.candidates)) {
|
|
73
|
+
const record = latest.get(url);
|
|
74
|
+
if (!record) {
|
|
75
|
+
fresh.push(url);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
const schedule = scheduleOf(record);
|
|
79
|
+
if (inspectionPolicy(verdictOf(record.indexStatus)).isDue(schedule, now)) due.push({
|
|
80
|
+
url,
|
|
81
|
+
nextAt: schedule.nextAt
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
due.sort((a, b) => a.nextAt - b.nextAt);
|
|
85
|
+
const ordered = [...fresh, ...due.map((entry) => entry.url)];
|
|
86
|
+
const budget = Math.max(0, Math.min(input.limit, quotaLeft));
|
|
87
|
+
return {
|
|
88
|
+
urls: ordered.slice(0, budget),
|
|
89
|
+
deferred: Math.max(0, ordered.length - budget),
|
|
90
|
+
quotaLeft
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
export { INSPECTION_QPD_PER_PROPERTY, latestByUrl, planInspections, recentMonths, scheduleOf, toInspectionRecord };
|