@gscdump/cli 4.8.0 → 5.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/dist/analysis-local.mjs +2 -2
- package/dist/auth-state.mjs +41 -6
- package/dist/auth.mjs +1 -7
- package/dist/bing-hosted.mjs +26 -5
- package/dist/cli.d.mts +0 -1
- package/dist/cli.mjs +4 -4
- package/dist/commands/analyze.mjs +2 -2
- package/dist/commands/auth.mjs +1 -1
- package/dist/commands/bing.mjs +11 -6
- package/dist/commands/doctor.mjs +12 -69
- package/dist/commands/indexing-urls.mjs +2 -2
- package/dist/commands/indexing-watch.mjs +10 -7
- package/dist/commands/init.mjs +4 -89
- package/dist/commands/mcp.mjs +1 -1
- package/dist/commands/papercut.mjs +1 -1
- package/dist/commands/profile.mjs +1 -1
- package/dist/commands/query.mjs +16 -9
- package/dist/commands/report.mjs +2 -2
- package/dist/commands/sitemaps.mjs +5 -5
- package/dist/commands/sites.mjs +10 -10
- package/dist/config.mjs +1 -1
- package/dist/context.mjs +3 -3
- package/dist/coverage.mjs +1 -3
- package/dist/environment.mjs +1 -5
- package/dist/error-handler.mjs +37 -11
- package/dist/hosted-query.mjs +23 -1
- package/dist/hosted-site.mjs +37 -17
- package/dist/mcp/errors.mjs +1 -1
- package/dist/mcp/types.mjs +1 -0
- package/dist/package.mjs +1 -1
- package/dist/render/query.mjs +1 -1
- package/dist/route.mjs +5 -9
- package/dist/runtime.d.mts +2 -0
- package/dist/runtime.mjs +1 -0
- package/dist/stop.mjs +10 -0
- package/dist/sync-plan.mjs +1 -1
- package/dist/utils.mjs +9 -5
- package/package.json +7 -7
- package/skills/gscdump/SKILL.md +36 -4
- package/dist/env-file.mjs +0 -53
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { papercutCommandMeta } from "../command-meta.mjs";
|
|
2
1
|
import { useCliRuntime } from "../runtime.mjs";
|
|
2
|
+
import { papercutCommandMeta } from "../command-meta.mjs";
|
|
3
3
|
import { OUTPUT_ARGS, applyOutputMode, logger } from "../utils.mjs";
|
|
4
4
|
import { buildPapercutBody, resolvePapercutUrl, submitPapercut } from "../papercut.mjs";
|
|
5
5
|
import process from "node:process";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { profileCommandMeta } from "../command-meta.mjs";
|
|
2
1
|
import { useCliRuntime } from "../runtime.mjs";
|
|
2
|
+
import { profileCommandMeta } from "../command-meta.mjs";
|
|
3
3
|
import { getConfigDir, setConfigDir } from "../config.mjs";
|
|
4
4
|
import { resolveCliEnvironment } from "../environment.mjs";
|
|
5
5
|
import { ACTIVE_MARKER, PROFILES_DIR, ROOT_DIR, getProfileDir, readActiveMarkerSync } from "./profile-selection.mjs";
|
package/dist/commands/query.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { queryCommandMeta } from "../command-meta.mjs";
|
|
2
1
|
import { useCliRuntime } from "../runtime.mjs";
|
|
3
2
|
import { resolveAuthentication } from "../auth-state.mjs";
|
|
3
|
+
import { queryCommandMeta } from "../command-meta.mjs";
|
|
4
4
|
import { loadConfig } from "../config.mjs";
|
|
5
|
+
import { queryHostedRows } from "../hosted-query.mjs";
|
|
5
6
|
import { terminalOutputOptions } from "../render/terminal.mjs";
|
|
6
|
-
import { ALL_SEARCH_TYPES, logger, parseSearchType, toCSV } from "../utils.mjs";
|
|
7
|
+
import { ALL_SEARCH_TYPES, logger, parseSearchType, setJsonOutput, toCSV } from "../utils.mjs";
|
|
7
8
|
import { allTables } from "../local-store.mjs";
|
|
8
9
|
import { createCommandContext, formatSiteResolution } from "../context.mjs";
|
|
9
10
|
import { decideRoute, describeStop, liveNote, readRouteState, readSiteStates, resolveReadSite, stopAtRoute } from "../route.mjs";
|
|
@@ -12,14 +13,13 @@ import { renderTable } from "../render/layout.mjs";
|
|
|
12
13
|
import { asRecord, columnsFor } from "../render/analysis.mjs";
|
|
13
14
|
import { resolveHostedSite } from "../hosted-site.mjs";
|
|
14
15
|
import { FILTER_DIMS, filterDimensions, parseFilterArgs, toLiveFilter, toLocalFilter } from "../filters.mjs";
|
|
15
|
-
import { queryHostedRows } from "../hosted-query.mjs";
|
|
16
16
|
import { renderQuery } from "../render/query.mjs";
|
|
17
17
|
import { openSqlViews, referencedTables } from "../sql-views.mjs";
|
|
18
18
|
import process from "node:process";
|
|
19
19
|
import { defineCommand } from "citty";
|
|
20
20
|
import fs from "node:fs/promises";
|
|
21
21
|
import { and, between, country, date, device, gsc, hour, page, query, searchAppearance } from "gscdump/query";
|
|
22
|
-
import { getLatestGscDate } from "gscdump/dates";
|
|
22
|
+
import { getLatestGscDate, toIsoDate } from "gscdump/dates";
|
|
23
23
|
import { cancel, isCancel, multiselect, text } from "@clack/prompts";
|
|
24
24
|
import { collectSpans } from "@gscdump/engine/profile";
|
|
25
25
|
import { inferDataset, isDatasetResolvable } from "gscdump/query/plan";
|
|
@@ -215,6 +215,7 @@ const queryCommand = defineCommand({
|
|
|
215
215
|
logger.error("Invalid --format. Use table, json, or csv.");
|
|
216
216
|
process.exit(1);
|
|
217
217
|
}
|
|
218
|
+
setJsonOutput(format === "json");
|
|
218
219
|
if (args.sql || args.schema) {
|
|
219
220
|
await runSqlMode({
|
|
220
221
|
mode: args.schema ? { kind: "schema" } : {
|
|
@@ -341,7 +342,6 @@ const queryCommand = defineCommand({
|
|
|
341
342
|
auth
|
|
342
343
|
});
|
|
343
344
|
const route = decideRoute(req, routeState);
|
|
344
|
-
const json = format === "json";
|
|
345
345
|
if (route.kind === "live") {
|
|
346
346
|
const live = await connect();
|
|
347
347
|
const siteUrl = site ?? await live.resolveSite(void 0);
|
|
@@ -406,7 +406,7 @@ const queryCommand = defineCommand({
|
|
|
406
406
|
}, null, 2));
|
|
407
407
|
return;
|
|
408
408
|
}
|
|
409
|
-
if (route.kind === "syncing" || route.kind === "prompt") stopAtRoute(route, req, routeState.auth
|
|
409
|
+
if (route.kind === "syncing" || route.kind === "prompt") stopAtRoute(route, req, routeState.auth);
|
|
410
410
|
if (dataState || aggregationType) logger.warn("--data-state / --aggregation-type are ignored without --live");
|
|
411
411
|
const siteUrl = site;
|
|
412
412
|
if (!args.quiet) logger.debug(`Querying ${siteUrl} from local Parquet store...`);
|
|
@@ -431,7 +431,7 @@ const queryCommand = defineCommand({
|
|
|
431
431
|
end: endDate
|
|
432
432
|
},
|
|
433
433
|
total: result.rows.length,
|
|
434
|
-
data: result.rows,
|
|
434
|
+
data: result.rows.map(withIsoDate),
|
|
435
435
|
meta: { source: "local" }
|
|
436
436
|
},
|
|
437
437
|
format,
|
|
@@ -547,7 +547,14 @@ function buildLocalState(dimNames, startDate, endDate, rowLimit, dimensionFilter
|
|
|
547
547
|
const dims = dimNames.map((d) => DIM_COLUMNS[d]).filter((c) => Boolean(c));
|
|
548
548
|
const dateFilter = between(date, startDate, endDate);
|
|
549
549
|
const filter = dimensionFilter ? and(dateFilter, dimensionFilter) : dateFilter;
|
|
550
|
-
|
|
550
|
+
const builder = gsc.select(...dims).where(filter);
|
|
551
|
+
return (dimNames.includes("date") ? builder.orderBy(date, "asc") : builder).limit(rowLimit).getState();
|
|
552
|
+
}
|
|
553
|
+
function withIsoDate(row) {
|
|
554
|
+
return typeof row.date === "number" ? {
|
|
555
|
+
...row,
|
|
556
|
+
date: toIsoDate(new Date(row.date))
|
|
557
|
+
} : row;
|
|
551
558
|
}
|
|
552
559
|
async function runSqlMode(opts) {
|
|
553
560
|
const ctx = await createCommandContext({ needsStore: true });
|
|
@@ -588,7 +595,7 @@ async function runSqlMode(opts) {
|
|
|
588
595
|
});
|
|
589
596
|
if (route.kind === "syncing" || route.kind === "prompt") {
|
|
590
597
|
views.close();
|
|
591
|
-
stopAtRoute(route, req, routeState.auth
|
|
598
|
+
stopAtRoute(route, req, routeState.auth);
|
|
592
599
|
}
|
|
593
600
|
}
|
|
594
601
|
}
|
package/dist/commands/report.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { reportCommandMeta } from "../command-meta.mjs";
|
|
2
2
|
import { terminalOutputOptions } from "../render/terminal.mjs";
|
|
3
|
-
import { logger, parseFetchBudget } from "../utils.mjs";
|
|
3
|
+
import { logger, parseFetchBudget, setJsonOutput } from "../utils.mjs";
|
|
4
4
|
import { COMPARISON_FLAGS, PERIOD_FLAGS, parseWindowFlags, windowFlagErrorToException } from "../window.mjs";
|
|
5
5
|
import { analysisNeeds, analyzerSources, analyzerTables, resolveAnalysisSource } from "../analysis-local.mjs";
|
|
6
6
|
import { renderCliReport } from "../render/report.mjs";
|
|
@@ -127,6 +127,7 @@ function makeReportCommand(report) {
|
|
|
127
127
|
...reportArgs
|
|
128
128
|
},
|
|
129
129
|
async run({ args }) {
|
|
130
|
+
setJsonOutput(Boolean(args.json));
|
|
130
131
|
const flags = windowFlags(args);
|
|
131
132
|
const params = buildReportParams(report, args);
|
|
132
133
|
const fetchBudget = parseFetchBudget(args["fetch-budget"]);
|
|
@@ -158,7 +159,6 @@ function makeReportCommand(report) {
|
|
|
158
159
|
const { source, siteUrl, anchor, isLive } = await resolveAnalysisSource({
|
|
159
160
|
site: args.site,
|
|
160
161
|
live: !!args.live,
|
|
161
|
-
json: !!args.json,
|
|
162
162
|
label: `report ${report.id}`,
|
|
163
163
|
types: provisional.map((step) => step.type),
|
|
164
164
|
sources: reportSources(provisional),
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { sitemapsCommandMeta } from "../command-meta.mjs";
|
|
2
|
-
import { OUTPUT_ARGS, applyOutputMode, logger, parseIntegerOption } from "../utils.mjs";
|
|
2
|
+
import { OUTPUT_ARGS, applyOutputMode, logger, parseIntegerOption, plural } from "../utils.mjs";
|
|
3
3
|
import { createCommandContext } from "../context.mjs";
|
|
4
|
-
import { discoverLiveSitemap, loadSitemapUrls } from "../sitemap.mjs";
|
|
5
4
|
import { HOSTED_ARGS, resolveHostedSite } from "../hosted-site.mjs";
|
|
5
|
+
import { discoverLiveSitemap, loadSitemapUrls } from "../sitemap.mjs";
|
|
6
6
|
import process from "node:process";
|
|
7
7
|
import { defineCommand } from "citty";
|
|
8
8
|
import { fetchSitemap } from "gscdump/sites";
|
|
@@ -59,12 +59,12 @@ const listCommand = defineCommand({
|
|
|
59
59
|
logger.warn("No sitemaps found");
|
|
60
60
|
return;
|
|
61
61
|
}
|
|
62
|
-
logger.success(`Found ${sitemaps.length
|
|
62
|
+
logger.success(`Found ${plural(sitemaps.length, "sitemap", "sitemaps")}:`);
|
|
63
63
|
console.log();
|
|
64
64
|
for (const sm of sitemaps) {
|
|
65
65
|
const pending = sm.isPending ? " \x1B[33m(pending)\x1B[0m" : "";
|
|
66
|
-
const errors = sm.errors ? ` \x1B[31m${sm.errors
|
|
67
|
-
const warnings = sm.warnings ? ` \x1B[33m${sm.warnings
|
|
66
|
+
const errors = sm.errors ? ` \x1B[31m${plural(sm.errors, "error", "errors")}\x1B[0m` : "";
|
|
67
|
+
const warnings = sm.warnings ? ` \x1B[33m${plural(sm.warnings, "warning", "warnings")}\x1B[0m` : "";
|
|
68
68
|
const submitted = sm.lastSubmitted ? ` \x1B[90msubmitted ${sm.lastSubmitted}\x1B[0m` : "";
|
|
69
69
|
console.log(` ${sm.path}${pending}${errors}${warnings}${submitted}`);
|
|
70
70
|
}
|
package/dist/commands/sites.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { connectSiteStep, formatHostedSync, getHostedAccount, localModeRequired, resolveAuthentication, siteManagerOf } from "../auth-state.mjs";
|
|
1
2
|
import { sitesCommandMeta } from "../command-meta.mjs";
|
|
2
|
-
import {
|
|
3
|
-
import { OUTPUT_ARGS, applyOutputMode, logger } from "../utils.mjs";
|
|
3
|
+
import { OUTPUT_ARGS, applyOutputMode, logger, plural } from "../utils.mjs";
|
|
4
4
|
import { createCommandContext, formatSiteResolution } from "../context.mjs";
|
|
5
5
|
import process from "node:process";
|
|
6
6
|
import { defineCommand } from "citty";
|
|
@@ -362,7 +362,7 @@ const verifyListCommand = defineCommand({
|
|
|
362
362
|
logger.warn("No verified WebResources found");
|
|
363
363
|
return;
|
|
364
364
|
}
|
|
365
|
-
logger.success(`${resources.length
|
|
365
|
+
logger.success(`${plural(resources.length, "verified WebResource", "verified WebResources")}:`);
|
|
366
366
|
console.log();
|
|
367
367
|
for (const r of resources) {
|
|
368
368
|
const id = r.id ?? "?";
|
|
@@ -422,7 +422,7 @@ const LIST_ARGS = {
|
|
|
422
422
|
}
|
|
423
423
|
};
|
|
424
424
|
async function runListHostedSites(args, authentication) {
|
|
425
|
-
if (args["with-sitemaps"] || args["owner-only"]) throw
|
|
425
|
+
if (args["with-sitemaps"] || args["owner-only"]) throw localModeRequired("--with-sitemaps and --owner-only read Search Console permissions.");
|
|
426
426
|
const { sites } = await getHostedAccount(authentication);
|
|
427
427
|
if (args.json) {
|
|
428
428
|
console.log(JSON.stringify(sites.map((site) => ({
|
|
@@ -438,10 +438,10 @@ async function runListHostedSites(args, authentication) {
|
|
|
438
438
|
return;
|
|
439
439
|
}
|
|
440
440
|
if (sites.length === 0) {
|
|
441
|
-
logger.warn(
|
|
441
|
+
logger.warn(`Your hosted record has no Sites. ${connectSiteStep(siteManagerOf(authentication))}`);
|
|
442
442
|
return;
|
|
443
443
|
}
|
|
444
|
-
logger.success(`Found ${sites.length
|
|
444
|
+
logger.success(`Found ${plural(sites.length, "hosted Site", "hosted Sites")}:`);
|
|
445
445
|
console.log();
|
|
446
446
|
for (const site of sites) console.log(` ${site.siteUrl} \x1B[90m(${site.siteId})\x1B[0m hosted sync ${formatHostedSync(site)}`);
|
|
447
447
|
}
|
|
@@ -470,10 +470,10 @@ async function runListSites(args) {
|
|
|
470
470
|
return;
|
|
471
471
|
}
|
|
472
472
|
if (sites.length === 0) {
|
|
473
|
-
logger.warn(ownerOnly ? "No owned
|
|
473
|
+
logger.warn(ownerOnly ? "No owned Sites found" : "No verified Sites found");
|
|
474
474
|
return;
|
|
475
475
|
}
|
|
476
|
-
logger.success(`Found ${sites.length
|
|
476
|
+
logger.success(`Found ${ownerOnly ? plural(sites.length, "owned Site", "owned Sites") : plural(sites.length, "verified Site", "verified Sites")}:`);
|
|
477
477
|
console.log();
|
|
478
478
|
for (const site of sites) {
|
|
479
479
|
const perm = site.permissionLevel === "siteOwner" ? "\x1B[32m" : "\x1B[90m";
|
|
@@ -492,10 +492,10 @@ async function runListSites(args) {
|
|
|
492
492
|
return;
|
|
493
493
|
}
|
|
494
494
|
if (sites.length === 0) {
|
|
495
|
-
logger.warn(ownerOnly ? "No owned
|
|
495
|
+
logger.warn(ownerOnly ? "No owned Sites found" : "No verified Sites found");
|
|
496
496
|
return;
|
|
497
497
|
}
|
|
498
|
-
logger.success(`Found ${sites.length
|
|
498
|
+
logger.success(`Found ${ownerOnly ? plural(sites.length, "owned Site", "owned Sites") : plural(sites.length, "Site", "Sites")}:`);
|
|
499
499
|
console.log();
|
|
500
500
|
for (const site of sites) {
|
|
501
501
|
const perm = site.permissionLevel === "siteOwner" ? "\x1B[32m" : "\x1B[90m";
|
package/dist/config.mjs
CHANGED
|
@@ -2,8 +2,8 @@ import { useCliRuntime } from "./runtime.mjs";
|
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { z } from "zod";
|
|
5
|
-
import { SearchTypes } from "gscdump/query";
|
|
6
5
|
import os from "node:os";
|
|
6
|
+
import { SearchTypes } from "gscdump/query";
|
|
7
7
|
function setConfigDir(dir) {
|
|
8
8
|
useCliRuntime().configDir = dir;
|
|
9
9
|
}
|
package/dist/context.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { LOCAL_MODE_REQUIRED, resolveAuthentication } from "./auth-state.mjs";
|
|
1
|
+
import { localModeRequired, resolveAuthentication } from "./auth-state.mjs";
|
|
3
2
|
import { loadResolvedConfig } from "./config.mjs";
|
|
3
|
+
import { openQuotaLedger, quotaFetchOptions } from "./quota-ledger.mjs";
|
|
4
4
|
import { resolveAuth } from "./auth.mjs";
|
|
5
5
|
import { createLocalStore } from "./local-store.mjs";
|
|
6
6
|
import { listStoreSites } from "./store-sites.mjs";
|
|
@@ -12,7 +12,7 @@ const FULL_SITE_URL_RE = /^(?:sc-domain:|https?:\/\/)/i;
|
|
|
12
12
|
async function createCommandContext(opts = {}) {
|
|
13
13
|
const { needsAuth = false, needsStore = false, interactive = false, byok, quota = true } = opts;
|
|
14
14
|
const { config, dataDir } = await loadResolvedConfig();
|
|
15
|
-
if (needsAuth && (await resolveAuthentication())._tag === "Hosted") throw
|
|
15
|
+
if (needsAuth && (await resolveAuthentication())._tag === "Hosted") throw localModeRequired();
|
|
16
16
|
const fetchOptions = needsAuth && quota ? quotaFetchOptions(await openQuotaLedger({ dataDir }), opts.fetchOptions) : opts.fetchOptions;
|
|
17
17
|
const auth = needsAuth ? await resolveAuth({
|
|
18
18
|
interactive,
|
package/dist/coverage.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { quotaLedgerPath, quotaStatus, readLedgerState } from "./quota-ledger.mjs";
|
|
2
|
+
import { plural } from "./utils.mjs";
|
|
2
3
|
import { INSPECTION_QPD_PER_PROPERTY } from "./inspection-record.mjs";
|
|
3
4
|
import { inspectionCandidates, loadInspectionState } from "./local-entities.mjs";
|
|
4
5
|
import { datesForJob, jobWindowStart } from "./sync-plan.mjs";
|
|
@@ -117,9 +118,6 @@ function hasSyncedDays(states, table, searchType) {
|
|
|
117
118
|
function n(value) {
|
|
118
119
|
return value.toLocaleString("en-US");
|
|
119
120
|
}
|
|
120
|
-
function plural(count, one, many) {
|
|
121
|
-
return `${n(count)} ${count === 1 ? one : many}`;
|
|
122
|
-
}
|
|
123
121
|
function clock(at) {
|
|
124
122
|
return new Date(at).toLocaleString("en-US", {
|
|
125
123
|
hour: "numeric",
|
package/dist/environment.mjs
CHANGED
|
@@ -23,8 +23,4 @@ function pickCliEnvironmentValue(names, values = useCliRuntime().environment) {
|
|
|
23
23
|
}
|
|
24
24
|
return null;
|
|
25
25
|
}
|
|
26
|
-
|
|
27
|
-
for (const [key, value] of Object.entries(updates)) if (value === void 0) delete values[key];
|
|
28
|
-
else values[key] = value;
|
|
29
|
-
}
|
|
30
|
-
export { applyCliEnvironment, pickCliEnvironmentValue, resolveCliEnvironment };
|
|
26
|
+
export { pickCliEnvironmentValue, resolveCliEnvironment };
|
package/dist/error-handler.mjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
+
import { stopDetailsOf } from "./stop.mjs";
|
|
1
2
|
import { isUsageError } from "./command-registry.mjs";
|
|
3
|
+
import { describeRecordReadRefusal } from "./hosted-query.mjs";
|
|
2
4
|
import { quotaStopOf } from "./quota-ledger.mjs";
|
|
5
|
+
import { isQueryError } from "gscdump/query";
|
|
3
6
|
import { isAnalysisError } from "@gscdump/analysis/errors";
|
|
7
|
+
import { parseRecordReadRefusal } from "@gscdump/contracts";
|
|
4
8
|
import { isEngineError } from "@gscdump/engine/errors";
|
|
5
9
|
import { classifyError } from "gscdump/errors";
|
|
6
|
-
import { isQueryError } from "gscdump/query";
|
|
7
|
-
const HOSTED_KEY_REJECTED_REASON = "gscdump.com rejected the API key";
|
|
8
|
-
const HOSTED_KEY_REJECTED = `${HOSTED_KEY_REJECTED_REASON}. Run \`gscdump auth login --mode hosted --api-key KEY\` with a valid key.`;
|
|
9
10
|
const QUOTA_MESSAGE_RE = /quota|rate\s*limit/i;
|
|
10
11
|
const ANSI_RE = /\x1B\[[0-9;]*m/g;
|
|
11
12
|
function suggestionForGscError(error) {
|
|
@@ -103,19 +104,39 @@ const DEFECT_ERRORS = /* @__PURE__ */ new Set([
|
|
|
103
104
|
"EvalError",
|
|
104
105
|
"URIError"
|
|
105
106
|
]);
|
|
107
|
+
function recordReadRefusalOf(error) {
|
|
108
|
+
if (!(error instanceof Error) || error.name !== "GscdumpV1Error") return null;
|
|
109
|
+
return parseRecordReadRefusal(error.details);
|
|
110
|
+
}
|
|
111
|
+
function recordReadStop(refusal, text) {
|
|
112
|
+
return {
|
|
113
|
+
code: refusal.reason === "record_not_ready" ? "RECORD_NOT_READY" : "RANGE_NOT_SYNCED",
|
|
114
|
+
message: text.hint ? `${text.message} ${text.hint}` : text.message,
|
|
115
|
+
nextCommand: null
|
|
116
|
+
};
|
|
117
|
+
}
|
|
106
118
|
function describeCliError(error) {
|
|
107
119
|
if (isUsageError(error)) return {
|
|
108
120
|
kind: "usage",
|
|
109
121
|
message: error.message
|
|
110
122
|
};
|
|
123
|
+
const stop = stopDetailsOf(error);
|
|
111
124
|
if (error instanceof Error && error.routeStop !== void 0) return {
|
|
112
125
|
kind: "stop",
|
|
113
|
-
message: error.message
|
|
126
|
+
message: error.message,
|
|
127
|
+
...stop ? { stop } : {}
|
|
114
128
|
};
|
|
115
129
|
if (error instanceof Error && quotaStopOf(error)) return {
|
|
116
130
|
kind: "stop",
|
|
117
131
|
message: error.message
|
|
118
132
|
};
|
|
133
|
+
if (error instanceof Error && stop) return {
|
|
134
|
+
kind: "expected",
|
|
135
|
+
message: error.message,
|
|
136
|
+
hint: "",
|
|
137
|
+
showAuthSources: false,
|
|
138
|
+
stop
|
|
139
|
+
};
|
|
119
140
|
if (error instanceof TypeError && error.message === "fetch failed") {
|
|
120
141
|
const cause = error.cause;
|
|
121
142
|
return {
|
|
@@ -136,13 +157,17 @@ function describeCliError(error) {
|
|
|
136
157
|
hint: error.mode === "local" ? "Pass --live to run against the GSC API." : "",
|
|
137
158
|
showAuthSources: false
|
|
138
159
|
};
|
|
160
|
+
const refusal = recordReadRefusalOf(error);
|
|
161
|
+
if (refusal) {
|
|
162
|
+
const text = describeRecordReadRefusal(refusal);
|
|
163
|
+
return {
|
|
164
|
+
kind: "expected",
|
|
165
|
+
...text,
|
|
166
|
+
showAuthSources: false,
|
|
167
|
+
stop: recordReadStop(refusal, text)
|
|
168
|
+
};
|
|
169
|
+
}
|
|
139
170
|
const classified = classifyError(error);
|
|
140
|
-
if (classified.message === HOSTED_KEY_REJECTED) return {
|
|
141
|
-
kind: "expected",
|
|
142
|
-
message: classified.message,
|
|
143
|
-
hint: "",
|
|
144
|
-
showAuthSources: false
|
|
145
|
-
};
|
|
146
171
|
return {
|
|
147
172
|
kind: "expected",
|
|
148
173
|
message: classified.message.replace(PARAM_NAME_RE, (name) => flagForParam(name)),
|
|
@@ -152,6 +177,7 @@ function describeCliError(error) {
|
|
|
152
177
|
}
|
|
153
178
|
async function reportCliError(error, options) {
|
|
154
179
|
const report = describeCliError(error);
|
|
180
|
+
if (options.json && (report.kind === "stop" || report.kind === "expected") && report.stop) (options.writeStdout ?? ((value) => console.log(value)))(JSON.stringify({ error: report.stop }, null, 2));
|
|
155
181
|
const red = (text) => options.color ? `\x1B[31m${text}\x1B[0m` : text;
|
|
156
182
|
const lines = [];
|
|
157
183
|
switch (report.kind) {
|
|
@@ -172,4 +198,4 @@ async function reportCliError(error, options) {
|
|
|
172
198
|
const text = lines.join("\n");
|
|
173
199
|
(options.write ?? ((value) => console.error(value)))(options.color ? text : text.replace(ANSI_RE, ""));
|
|
174
200
|
}
|
|
175
|
-
export {
|
|
201
|
+
export { LocalStoreUnsupportedError, describeCliError, flagForParam, isAuthError, reportCliError };
|
package/dist/hosted-query.mjs
CHANGED
|
@@ -28,4 +28,26 @@ async function queryHostedRows(client, siteId, state) {
|
|
|
28
28
|
}
|
|
29
29
|
return rows;
|
|
30
30
|
}
|
|
31
|
-
|
|
31
|
+
function describeRecordReadRefusal(refusal) {
|
|
32
|
+
const running = refusal.syncStatus === "syncing" || refusal.syncStatus === "pending";
|
|
33
|
+
if (refusal.reason === "record_not_ready") return {
|
|
34
|
+
message: "The Site's record is not readable yet.",
|
|
35
|
+
hint: refusal.syncStatus === "synced" && refusal.lastSyncAt !== void 0 ? `Sync finished at ${utcMinute(refusal.lastSyncAt)}. gscdump prepares the record for reads after Sync. Try again in a few minutes.` : running ? "Sync is still running. Run `gscdump sites` to see its progress, then try again when it finishes." : "Try again in a few minutes. Run `gscdump sites` to see the Sync state."
|
|
36
|
+
};
|
|
37
|
+
return {
|
|
38
|
+
message: `The Site's record does not hold ${refusal.missingStart === refusal.missingEnd ? refusal.missingStart : `${refusal.missingStart} to ${refusal.missingEnd}`}.`,
|
|
39
|
+
hint: rangeHint(refusal, running)
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
function rangeHint(refusal, running) {
|
|
43
|
+
const { oldestDateSynced: oldest, newestDateSynced: newest } = refusal;
|
|
44
|
+
const synced = oldest !== void 0 && newest !== void 0;
|
|
45
|
+
if (synced && refusal.missingStart >= oldest && refusal.missingEnd <= newest) return "Sync covers these days, but the record does not hold them yet. Pick an earlier end date with --end, or try again later.";
|
|
46
|
+
const covers = synced ? `Sync covers ${oldest} to ${newest}. ` : "";
|
|
47
|
+
if (running) return `${covers}Sync is still running. Try again when it finishes.`;
|
|
48
|
+
return synced ? `${covers}Pick dates in that range with --start and --end.` : "Run `gscdump sites` to see the Sync state.";
|
|
49
|
+
}
|
|
50
|
+
function utcMinute(unixSeconds) {
|
|
51
|
+
return `${(/* @__PURE__ */ new Date(unixSeconds * 1e3)).toISOString().slice(0, 16).replace("T", " ")} UTC`;
|
|
52
|
+
}
|
|
53
|
+
export { describeRecordReadRefusal, queryHostedRows };
|
package/dist/hosted-site.mjs
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { stopError } from "./stop.mjs";
|
|
2
|
+
import { connectSiteStep, getHostedAccount, hostedCredential, parseAuthentication, resolveAuthentication, siteManagerOf } from "./auth-state.mjs";
|
|
2
3
|
import { loadConfig } from "./config.mjs";
|
|
3
4
|
import { resolveCliEnvironment } from "./environment.mjs";
|
|
4
|
-
import { logger } from "./utils.mjs";
|
|
5
|
-
import process from "node:process";
|
|
6
5
|
import { createGscdumpV1Client } from "@gscdump/sdk/v1";
|
|
7
6
|
const HOSTED_ARGS = {
|
|
8
7
|
"site": {
|
|
@@ -57,35 +56,56 @@ function matchHostedSite(sites, target) {
|
|
|
57
56
|
sites: [...sites]
|
|
58
57
|
};
|
|
59
58
|
}
|
|
60
|
-
function
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
59
|
+
function noSitesStop(manager) {
|
|
60
|
+
return {
|
|
61
|
+
code: "NO_SITES",
|
|
62
|
+
message: `Your hosted record has no Sites. ${connectSiteStep(manager)} Then run the command again.`,
|
|
63
|
+
nextCommand: null
|
|
64
|
+
};
|
|
65
65
|
}
|
|
66
|
-
function
|
|
67
|
-
|
|
68
|
-
|
|
66
|
+
function siteMatchStop(match, manager) {
|
|
67
|
+
const nextCommand = "gscdump sites --json";
|
|
68
|
+
if (match.kind === "ambiguous") return {
|
|
69
|
+
code: "SITE_AMBIGUOUS",
|
|
70
|
+
message: `Multiple hosted Sites match "${match.target}": ${match.matches.map((site) => site.siteUrl).join(", ")}. Pass the exact Site URL with --site.`,
|
|
71
|
+
nextCommand
|
|
72
|
+
};
|
|
73
|
+
if (match.sites.length === 0) return noSitesStop(manager);
|
|
74
|
+
const available = `Hosted Sites: ${match.sites.map((site) => site.siteUrl).join(", ")}.`;
|
|
75
|
+
return match.target ? {
|
|
76
|
+
code: "SITE_NOT_FOUND",
|
|
77
|
+
message: `No hosted Site matches "${match.target}". ${available}`,
|
|
78
|
+
nextCommand
|
|
79
|
+
} : {
|
|
80
|
+
code: "SITE_REQUIRED",
|
|
81
|
+
message: `Pass --site. ${available}`,
|
|
82
|
+
nextCommand
|
|
83
|
+
};
|
|
69
84
|
}
|
|
70
85
|
async function resolveHostedSite(args, command) {
|
|
71
|
-
if (args._?.length)
|
|
86
|
+
if (args._?.length) throw new Error(`\`gscdump ${command.name}\` does not accept a positional Site ID. Pass --site instead.`);
|
|
72
87
|
const environment = resolveCliEnvironment().values;
|
|
73
88
|
const authentication = args["api-key"] ? parseAuthentication({
|
|
74
89
|
_tag: "Hosted",
|
|
75
90
|
apiKey: args["api-key"],
|
|
76
91
|
apiRoot: String(args["api-root"] || environment.GSCDUMP_API_ROOT || "https://gscdump.com/api")
|
|
77
92
|
}) : await resolveAuthentication();
|
|
78
|
-
if (authentication._tag !== "Hosted")
|
|
79
|
-
|
|
93
|
+
if (authentication._tag !== "Hosted") throw stopError({
|
|
94
|
+
code: "HOSTED_MODE_REQUIRED",
|
|
95
|
+
message: `\`gscdump ${command.name}\` reads the hosted record, so it needs Hosted mode. Run \`gscdump auth login --mode hosted\`, or pass --api-key. In Local mode, ${command.localAlternative}.`,
|
|
96
|
+
nextCommand: "gscdump auth login --mode hosted"
|
|
97
|
+
});
|
|
98
|
+
if (args["api-root"] && String(args["api-root"]).replace(/\/+$/, "") !== authentication.apiRoot) throw new Error("The API root changed. Pass --api-key for the new API root.");
|
|
80
99
|
const target = args.site ? String(args.site) : (await loadConfig()).defaultSite;
|
|
81
100
|
const match = matchHostedSite((await getHostedAccount(authentication)).sites, target);
|
|
82
|
-
if (match.kind !== "found")
|
|
101
|
+
if (match.kind !== "found") throw stopError(siteMatchStop(match, siteManagerOf(authentication)));
|
|
83
102
|
return {
|
|
84
103
|
client: createGscdumpV1Client({
|
|
85
104
|
apiRoot: authentication.apiRoot,
|
|
86
105
|
credential: hostedCredential(authentication)
|
|
87
106
|
}),
|
|
88
|
-
site: match.site
|
|
107
|
+
site: match.site,
|
|
108
|
+
authentication
|
|
89
109
|
};
|
|
90
110
|
}
|
|
91
|
-
export { HOSTED_ARGS, matchHostedSite, resolveHostedSite };
|
|
111
|
+
export { HOSTED_ARGS, matchHostedSite, noSitesStop, resolveHostedSite, siteMatchStop };
|
package/dist/mcp/errors.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { resolveBYOK, resolveServiceAccount } from "../auth.mjs";
|
|
2
2
|
import { COMPARISON_FLAGS, PERIOD_FLAGS } from "../window.mjs";
|
|
3
|
+
import { isQueryError } from "gscdump/query";
|
|
3
4
|
import { isAnalysisError } from "@gscdump/analysis/errors";
|
|
4
5
|
import { isEngineError } from "@gscdump/engine/errors";
|
|
5
6
|
import { classifyError } from "gscdump/errors";
|
|
6
|
-
import { isQueryError } from "gscdump/query";
|
|
7
7
|
const PERIODS = PERIOD_FLAGS;
|
|
8
8
|
const COMPARISONS = COMPARISON_FLAGS;
|
|
9
9
|
const mcpHandlerErrors = {
|
package/dist/mcp/types.mjs
CHANGED
package/dist/package.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
var version = "
|
|
1
|
+
var version = "5.1.0";
|
|
2
2
|
export { version };
|
package/dist/render/query.mjs
CHANGED
|
@@ -21,7 +21,7 @@ function renderQuery(input, options) {
|
|
|
21
21
|
if (clicks !== -1) columns[clicks] = barColumn(input.rows, "clicks", options);
|
|
22
22
|
}
|
|
23
23
|
return [
|
|
24
|
-
...textLines(`${parseGscSiteUrl(input.site).hostname} /
|
|
24
|
+
...textLines(`${parseGscSiteUrl(input.site).hostname} / ${input.dimensions.join(", ")}`, options, "accent"),
|
|
25
25
|
...textLines(`${input.start} to ${input.end}`, options, "muted"),
|
|
26
26
|
"",
|
|
27
27
|
...renderTable(input.rows, columns, options)
|
package/dist/route.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { stopError } from "./stop.mjs";
|
|
1
2
|
import { probeAuth } from "./auth.mjs";
|
|
2
3
|
import { listStoreSites } from "./store-sites.mjs";
|
|
3
4
|
import { formatSiteResolution, siteArg } from "./context.mjs";
|
|
@@ -199,11 +200,8 @@ function stopCode(route) {
|
|
|
199
200
|
case "partial": return "STORE_RANGE_NOT_COVERED";
|
|
200
201
|
}
|
|
201
202
|
}
|
|
202
|
-
function routeStopError(route,
|
|
203
|
-
return Object.assign(
|
|
204
|
-
name: "RouteStopError",
|
|
205
|
-
routeStop: route
|
|
206
|
-
});
|
|
203
|
+
function routeStopError(route, details) {
|
|
204
|
+
return Object.assign(stopError(details, "RouteStopError"), { routeStop: route });
|
|
207
205
|
}
|
|
208
206
|
async function needCoverage(needs, states) {
|
|
209
207
|
const reader = syncStateCoverageReader(states);
|
|
@@ -299,9 +297,7 @@ function describeStop(route, req, auth) {
|
|
|
299
297
|
} } : {}
|
|
300
298
|
};
|
|
301
299
|
}
|
|
302
|
-
function stopAtRoute(route, req, auth
|
|
303
|
-
|
|
304
|
-
if (opts.json) console.log(JSON.stringify({ error: details }, null, 2));
|
|
305
|
-
throw routeStopError(route, details.message);
|
|
300
|
+
function stopAtRoute(route, req, auth) {
|
|
301
|
+
throw routeStopError(route, describeStop(route, req, auth));
|
|
306
302
|
}
|
|
307
303
|
export { decideRoute, describeStop, liveCommand, liveNote, needCoverage, readRouteState, readSiteStates, resolveReadSite, routeMessage, routeStopError, stopAtRoute, stopCode, syncCommandFor };
|
package/dist/runtime.d.mts
CHANGED
|
@@ -7,6 +7,8 @@ export interface CliRuntime {
|
|
|
7
7
|
configDir: string;
|
|
8
8
|
configDirOverridden: boolean;
|
|
9
9
|
environment: CliEnvironmentSource;
|
|
10
|
+
/** The command writes JSON to stdout. A stop then prints its JSON form there too. */
|
|
11
|
+
jsonOutput: boolean;
|
|
10
12
|
logger: ConsolaInstance;
|
|
11
13
|
quiet: boolean;
|
|
12
14
|
rawArgs: string[];
|
package/dist/runtime.mjs
CHANGED
|
@@ -21,6 +21,7 @@ function createCliRuntime(opts = {}) {
|
|
|
21
21
|
configDir: opts.configDir ?? path.join(os.homedir(), ".config", "gscdump"),
|
|
22
22
|
configDirOverridden: false,
|
|
23
23
|
environment: opts.environment ?? process.env,
|
|
24
|
+
jsonOutput: false,
|
|
24
25
|
logger: baseLogger.withTag("gscdump"),
|
|
25
26
|
quiet: false,
|
|
26
27
|
rawArgs: [...opts.rawArgs ?? process.argv.slice(2)]
|
package/dist/stop.mjs
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
function stopError(details, name = "StopError") {
|
|
2
|
+
return Object.assign(new Error(details.message), {
|
|
3
|
+
name,
|
|
4
|
+
stopDetails: details
|
|
5
|
+
});
|
|
6
|
+
}
|
|
7
|
+
function stopDetailsOf(error) {
|
|
8
|
+
return error instanceof Error ? error.stopDetails : void 0;
|
|
9
|
+
}
|
|
10
|
+
export { stopDetailsOf, stopError };
|
package/dist/sync-plan.mjs
CHANGED
package/dist/utils.mjs
CHANGED
|
@@ -2,8 +2,8 @@ import { useCliRuntime } from "./runtime.mjs";
|
|
|
2
2
|
import { version } from "./package.mjs";
|
|
3
3
|
import process from "node:process";
|
|
4
4
|
import fs from "node:fs/promises";
|
|
5
|
-
import { SearchTypes } from "gscdump/query";
|
|
6
5
|
import os from "node:os";
|
|
6
|
+
import { SearchTypes } from "gscdump/query";
|
|
7
7
|
import { Buffer } from "node:buffer";
|
|
8
8
|
import { MAX_FETCH_BUDGET } from "@gscdump/engine/analysis-types";
|
|
9
9
|
const ALL_SEARCH_TYPES = Object.values(SearchTypes);
|
|
@@ -40,10 +40,14 @@ const OUTPUT_ARGS = {
|
|
|
40
40
|
description: "Suppress info/success output"
|
|
41
41
|
}
|
|
42
42
|
};
|
|
43
|
+
function setJsonOutput(json) {
|
|
44
|
+
useCliRuntime().jsonOutput = json;
|
|
45
|
+
}
|
|
43
46
|
function applyOutputMode(args) {
|
|
44
47
|
const json = Boolean(args.json);
|
|
45
48
|
const quiet = json || Boolean(args.quiet);
|
|
46
49
|
setQuiet(quiet);
|
|
50
|
+
setJsonOutput(json);
|
|
47
51
|
return {
|
|
48
52
|
json,
|
|
49
53
|
quiet
|
|
@@ -148,14 +152,14 @@ function clearLine() {
|
|
|
148
152
|
process.stdout.write("\r\x1B[K");
|
|
149
153
|
}
|
|
150
154
|
function displayPath(absPath) {
|
|
151
|
-
const cwd = process.cwd();
|
|
152
155
|
const home = os.homedir();
|
|
153
|
-
if (absPath === cwd) return ".";
|
|
154
|
-
if (absPath.startsWith(`${cwd}/`)) return absPath.slice(cwd.length + 1);
|
|
155
156
|
if (absPath === home) return "~";
|
|
156
157
|
if (absPath.startsWith(`${home}/`)) return `~/${absPath.slice(home.length + 1)}`;
|
|
157
158
|
return absPath;
|
|
158
159
|
}
|
|
160
|
+
function plural(count, one, many) {
|
|
161
|
+
return `${count.toLocaleString("en-US")} ${count === 1 ? one : many}`;
|
|
162
|
+
}
|
|
159
163
|
function formatAge(ms) {
|
|
160
164
|
const delta = Date.now() - ms;
|
|
161
165
|
if (delta < 6e4) return "just now";
|
|
@@ -193,4 +197,4 @@ async function readUrlList(input) {
|
|
|
193
197
|
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
194
198
|
return parseUrlLines(Buffer.concat(chunks).toString("utf-8"));
|
|
195
199
|
}
|
|
196
|
-
export { ALL_SEARCH_TYPES, OUTPUT_ARGS, VERSION, applyOutputMode, clearLine, color, cyan, dim, displayPath, formatAge, isColorEnabled, logger, parseFetchBudget, parseIntegerOption, parseNameList, parseSearchType, progressBar, readUrlList, red, runWithConcurrency, setNoColor, setQuiet, showSplash, toCSV, withConfiguredOutput };
|
|
200
|
+
export { ALL_SEARCH_TYPES, OUTPUT_ARGS, VERSION, applyOutputMode, clearLine, color, cyan, dim, displayPath, formatAge, isColorEnabled, logger, parseFetchBudget, parseIntegerOption, parseNameList, parseSearchType, plural, progressBar, readUrlList, red, runWithConcurrency, setJsonOutput, setNoColor, setQuiet, showSplash, toCSV, withConfiguredOutput };
|