@gscdump/cli 0.8.1 → 0.9.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/_chunks/config.mjs +2 -2
- package/dist/index.mjs +1600 -744
- package/package.json +7 -7
package/dist/index.mjs
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { t as __exportAll } from "./_chunks/rolldown-runtime.mjs";
|
|
3
|
-
import { t as ofetch } from "./_chunks/libs/ofetch.mjs";
|
|
4
3
|
import { a as loadConfig, c as setConfigDir, i as getConfigPath, n as defaultDataDir, o as resolveDataDir, r as getConfigDir, s as saveConfig } from "./_chunks/config.mjs";
|
|
5
|
-
import
|
|
6
|
-
import path from "node:path";
|
|
4
|
+
import { t as ofetch } from "./_chunks/libs/ofetch.mjs";
|
|
7
5
|
import process from "node:process";
|
|
8
6
|
import { defineCommand, runMain } from "citty";
|
|
9
7
|
import { defaultAnalyzerRegistry } from "@gscdump/analysis/registry";
|
|
8
|
+
import fs, { readFile, readdir, rm } from "node:fs/promises";
|
|
9
|
+
import path, { join } from "node:path";
|
|
10
10
|
import { AnalyzerCapabilityError, analyzeFromSource, createEngineQuerySource } from "@gscdump/analysis";
|
|
11
11
|
import { createGscApiQuerySource } from "@gscdump/engine-gsc-api";
|
|
12
|
+
import { decodeSiteId, normalizeSiteUrl } from "gscdump/tenant";
|
|
13
|
+
import os from "node:os";
|
|
12
14
|
import { cancel, confirm, isCancel, multiselect, select, text } from "@clack/prompts";
|
|
13
|
-
import { addSite, batchInspectUrls, batchRequestIndexing, createAuth, daysAgo, deleteSite, fetchSitemap, fetchSitesWithSitemaps, formatErrorForCli, getDateRange, getIndexingMetadata, getVerificationToken, googleSearchConsole, progressBar, requestIndexing, siteUrlToVerificationSite, verificationMethodsFor, verifySite } from "gscdump";
|
|
14
|
-
import fs, { readFile, rm } from "node:fs/promises";
|
|
15
|
+
import { addSite, batchInspectUrls, batchRequestIndexing, createAuth, daysAgo, deleteSite, discoverSitemap, fetchSitemap, fetchSitemapUrls, fetchSitesWithSitemaps, formatErrorForCli, getDateRange, getIndexingMetadata, getVerificationToken, getVerifiedSite, googleSearchConsole, listVerifiedSites, progressBar, requestIndexing, runSequentialBatch, siteUrlToVerificationSite, unverifySite, verificationMethodsFor, verifySite } from "gscdump";
|
|
15
16
|
import { createServer } from "node:http";
|
|
16
17
|
import { JWT, OAuth2Client } from "google-auth-library";
|
|
17
18
|
import { Buffer } from "node:buffer";
|
|
@@ -26,48 +27,11 @@ import { createEmptyTypesStore, createIndexingMetadataStore, createInspectionSto
|
|
|
26
27
|
import { createGscMcpServer } from "@gscdump/mcp/server";
|
|
27
28
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
28
29
|
import { SearchTypes, and, between, contains, country, date, device, eq, gsc, notRegex, page, query, regex, searchAppearance } from "gscdump/query";
|
|
30
|
+
import { defaultReportRegistry, dryRunReport, formatReport, runReport } from "@gscdump/analysis/report";
|
|
31
|
+
import { resolveWindow } from "@gscdump/engine/period";
|
|
29
32
|
import { inferLegacyTier } from "@gscdump/engine";
|
|
30
33
|
import { DEFAULT_ROLLUPS, rebuildRollups } from "@gscdump/analysis/rollups";
|
|
31
34
|
import { filesystemStats } from "@gscdump/engine/filesystem";
|
|
32
|
-
var LocalStoreUnsupportedError = class extends Error {
|
|
33
|
-
constructor(tool) {
|
|
34
|
-
super(`analysis "${tool}" is not yet implemented against the local Parquet store`);
|
|
35
|
-
this.name = "LocalStoreUnsupportedError";
|
|
36
|
-
}
|
|
37
|
-
};
|
|
38
|
-
var LocalStoreEmptyError = class extends Error {
|
|
39
|
-
constructor(siteUrl) {
|
|
40
|
-
super(`no local data synced for ${siteUrl} (run \`gscdump sync\` first)`);
|
|
41
|
-
this.name = "LocalStoreEmptyError";
|
|
42
|
-
}
|
|
43
|
-
};
|
|
44
|
-
async function hasLocalData(store, siteUrl) {
|
|
45
|
-
return (await store.engine.listLive({
|
|
46
|
-
userId: store.userId,
|
|
47
|
-
siteId: store.siteIdFor(siteUrl)
|
|
48
|
-
})).length > 0;
|
|
49
|
-
}
|
|
50
|
-
async function runLocalAnalysis(store, siteUrl, params) {
|
|
51
|
-
return analyzeFromSource(createEngineQuerySource({
|
|
52
|
-
engine: store.engine,
|
|
53
|
-
ctx: {
|
|
54
|
-
userId: store.userId,
|
|
55
|
-
siteId: store.siteIdFor(siteUrl)
|
|
56
|
-
}
|
|
57
|
-
}), params, defaultAnalyzerRegistry).catch((e) => {
|
|
58
|
-
if (e instanceof AnalyzerCapabilityError) throw new LocalStoreUnsupportedError(params.type);
|
|
59
|
-
throw e;
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
async function runLiveAnalysis(client, siteUrl, params) {
|
|
63
|
-
return analyzeFromSource(createGscApiQuerySource({
|
|
64
|
-
client,
|
|
65
|
-
siteUrl
|
|
66
|
-
}), params, defaultAnalyzerRegistry).catch((e) => {
|
|
67
|
-
if (e instanceof AnalyzerCapabilityError) throw new LocalStoreUnsupportedError(params.type);
|
|
68
|
-
throw e;
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
35
|
const ENV_LINE_RE$1 = /^([^=]+)=(.*)$/;
|
|
72
36
|
function parseEnvFile(envPath) {
|
|
73
37
|
let content;
|
|
@@ -110,7 +74,7 @@ function loadEnvFromCwd() {
|
|
|
110
74
|
}
|
|
111
75
|
return applied;
|
|
112
76
|
}
|
|
113
|
-
const VERSION = "0.
|
|
77
|
+
const VERSION = "0.9.0";
|
|
114
78
|
const baseLogger = createConsola({
|
|
115
79
|
stdout: process.stderr,
|
|
116
80
|
stderr: process.stderr
|
|
@@ -119,6 +83,28 @@ const logger = baseLogger.withTag("gscdump");
|
|
|
119
83
|
function setQuiet(quiet) {
|
|
120
84
|
if (quiet) baseLogger.level = 1;
|
|
121
85
|
}
|
|
86
|
+
const OUTPUT_ARGS = {
|
|
87
|
+
json: {
|
|
88
|
+
type: "boolean",
|
|
89
|
+
default: false,
|
|
90
|
+
description: "Output as JSON"
|
|
91
|
+
},
|
|
92
|
+
quiet: {
|
|
93
|
+
type: "boolean",
|
|
94
|
+
alias: "q",
|
|
95
|
+
default: false,
|
|
96
|
+
description: "Suppress info/success output"
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
function applyOutputMode(args) {
|
|
100
|
+
const json = Boolean(args.json);
|
|
101
|
+
const quiet = json || Boolean(args.quiet);
|
|
102
|
+
setQuiet(quiet);
|
|
103
|
+
return {
|
|
104
|
+
json,
|
|
105
|
+
quiet
|
|
106
|
+
};
|
|
107
|
+
}
|
|
122
108
|
let colorEnabled = (() => {
|
|
123
109
|
if (process.env.NO_COLOR) return false;
|
|
124
110
|
if (process.argv.includes("--no-color")) return false;
|
|
@@ -257,7 +243,8 @@ async function loadServiceAccount(jsonPath) {
|
|
|
257
243
|
});
|
|
258
244
|
}
|
|
259
245
|
async function resolveServiceAccount(opts = {}) {
|
|
260
|
-
|
|
246
|
+
let p = opts.path || process.env.GSC_SERVICE_ACCOUNT_JSON || process.env.GOOGLE_APPLICATION_CREDENTIALS;
|
|
247
|
+
if (!p) p = (await loadConfig().catch(() => null))?.serviceAccountPath;
|
|
261
248
|
if (!p) return null;
|
|
262
249
|
return loadServiceAccount(p);
|
|
263
250
|
}
|
|
@@ -538,15 +525,21 @@ function redactCred(v, keepTail = 6) {
|
|
|
538
525
|
async function describeAuthProvenance() {
|
|
539
526
|
const rows = [];
|
|
540
527
|
const warnings = [];
|
|
541
|
-
const
|
|
542
|
-
|
|
528
|
+
const saEnvPath = process.env.GSC_SERVICE_ACCOUNT_JSON || process.env.GOOGLE_APPLICATION_CREDENTIALS;
|
|
529
|
+
const saConfigPath = !saEnvPath ? (await loadConfig().catch(() => null))?.serviceAccountPath : void 0;
|
|
530
|
+
const saPath = saEnvPath || saConfigPath;
|
|
531
|
+
if (saEnvPath) {
|
|
543
532
|
const saEnv = process.env.GSC_SERVICE_ACCOUNT_JSON ? "GSC_SERVICE_ACCOUNT_JSON" : "GOOGLE_APPLICATION_CREDENTIALS";
|
|
544
533
|
rows.push({
|
|
545
534
|
field: "service_account",
|
|
546
535
|
source: envSourceLabel(saEnv),
|
|
547
|
-
value: displayPath(
|
|
536
|
+
value: displayPath(saEnvPath)
|
|
548
537
|
});
|
|
549
|
-
}
|
|
538
|
+
} else if (saConfigPath) rows.push({
|
|
539
|
+
field: "service_account",
|
|
540
|
+
source: `${displayPath(`${getConfigDir()}/config.json`)}`,
|
|
541
|
+
value: displayPath(saConfigPath)
|
|
542
|
+
});
|
|
550
543
|
const clientId = pickEnvSource("GSC_CLIENT_ID", "GOOGLE_CLIENT_ID");
|
|
551
544
|
const clientSecret = pickEnvSource("GSC_CLIENT_SECRET", "GOOGLE_CLIENT_SECRET");
|
|
552
545
|
const config = await loadConfig().catch(() => null);
|
|
@@ -706,6 +699,92 @@ async function gscErrorHandler(error) {
|
|
|
706
699
|
console.error();
|
|
707
700
|
process.exit(1);
|
|
708
701
|
}
|
|
702
|
+
var LocalStoreUnsupportedError = class extends Error {
|
|
703
|
+
constructor(tool) {
|
|
704
|
+
super(`analysis "${tool}" is not yet implemented against the local Parquet store`);
|
|
705
|
+
this.name = "LocalStoreUnsupportedError";
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
async function hasLocalData(store, siteUrl) {
|
|
709
|
+
return (await store.engine.listLive({
|
|
710
|
+
userId: store.userId,
|
|
711
|
+
siteId: store.siteIdFor(siteUrl)
|
|
712
|
+
})).length > 0;
|
|
713
|
+
}
|
|
714
|
+
async function listLocalSites(dataDir, userId = "local") {
|
|
715
|
+
return readdir(join(dataDir, `u_${userId}`), { withFileTypes: true }).then((entries) => entries.filter((e) => e.isDirectory() && (e.name.startsWith("d_") || e.name.startsWith("h_"))).map((e) => decodeSiteId(e.name))).catch(() => []);
|
|
716
|
+
}
|
|
717
|
+
function pickLocalSite(siteUrls, hint) {
|
|
718
|
+
if (siteUrls.length === 0) return null;
|
|
719
|
+
if (!hint) return siteUrls.length === 1 ? siteUrls[0] : null;
|
|
720
|
+
const normalized = normalizeSiteUrl(hint);
|
|
721
|
+
const exact = siteUrls.find((s) => s === normalized || s === hint);
|
|
722
|
+
if (exact) return exact;
|
|
723
|
+
return siteUrls.find((s) => s.includes(hint) || hint.includes(s)) ?? null;
|
|
724
|
+
}
|
|
725
|
+
async function resolveAnalysisSource(args) {
|
|
726
|
+
const isLive = !!args.live;
|
|
727
|
+
const format = args.json ? "json" : args.format ? String(args.format) : "table";
|
|
728
|
+
if (!isLive) {
|
|
729
|
+
const config = await loadConfig();
|
|
730
|
+
const dataDir = resolveDataDir(config);
|
|
731
|
+
const store = createLocalStore({ dataDir });
|
|
732
|
+
const siteHint = args.site ? String(args.site) : config.defaultSite;
|
|
733
|
+
const localSites = await listLocalSites(dataDir, store.userId);
|
|
734
|
+
const siteUrl = pickLocalSite(localSites, siteHint);
|
|
735
|
+
if (!siteUrl) {
|
|
736
|
+
if (localSites.length === 0) logger.error(`No local data found in ${dataDir}. Run \`gscdump sync\` first, or pass --live.`);
|
|
737
|
+
else logger.error(`Could not resolve site${siteHint ? ` from "${siteHint}"` : ""}. Local sites: ${localSites.join(", ")}`);
|
|
738
|
+
process.exit(1);
|
|
739
|
+
}
|
|
740
|
+
if (!await hasLocalData(store, siteUrl).catch(() => false)) {
|
|
741
|
+
logger.error(`No local data for ${siteUrl}. Run \`gscdump sync\` first, or pass --live.`);
|
|
742
|
+
process.exit(1);
|
|
743
|
+
}
|
|
744
|
+
const source = createEngineQuerySource({
|
|
745
|
+
engine: store.engine,
|
|
746
|
+
ctx: {
|
|
747
|
+
userId: store.userId,
|
|
748
|
+
siteId: store.siteIdFor(siteUrl)
|
|
749
|
+
}
|
|
750
|
+
});
|
|
751
|
+
const runAnalysis = (params) => analyzeFromSource(source, params, defaultAnalyzerRegistry).catch((e) => {
|
|
752
|
+
if (e instanceof AnalyzerCapabilityError) {
|
|
753
|
+
logger.error(`${new LocalStoreUnsupportedError(params.type).message}. Pass --live to run against the GSC API.`);
|
|
754
|
+
process.exit(1);
|
|
755
|
+
}
|
|
756
|
+
logger.error(`Local analysis failed: ${e.message}`);
|
|
757
|
+
process.exit(1);
|
|
758
|
+
});
|
|
759
|
+
return {
|
|
760
|
+
source,
|
|
761
|
+
siteUrl,
|
|
762
|
+
format,
|
|
763
|
+
isLive,
|
|
764
|
+
runAnalysis
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
const ctx = await createCommandContext({
|
|
768
|
+
needsAuth: true,
|
|
769
|
+
needsStore: false
|
|
770
|
+
});
|
|
771
|
+
const siteUrl = await ctx.resolveSite(args.site ? String(args.site) : void 0);
|
|
772
|
+
const source = createGscApiQuerySource({
|
|
773
|
+
client: ctx.client,
|
|
774
|
+
siteUrl
|
|
775
|
+
});
|
|
776
|
+
const runAnalysis = (params) => analyzeFromSource(source, params, defaultAnalyzerRegistry).catch((e) => {
|
|
777
|
+
if (e instanceof AnalyzerCapabilityError) throw new LocalStoreUnsupportedError(params.type);
|
|
778
|
+
return gscErrorHandler(e);
|
|
779
|
+
});
|
|
780
|
+
return {
|
|
781
|
+
source,
|
|
782
|
+
siteUrl,
|
|
783
|
+
format,
|
|
784
|
+
isLive,
|
|
785
|
+
runAnalysis
|
|
786
|
+
};
|
|
787
|
+
}
|
|
709
788
|
const ANALYSIS_TOOLS = defaultAnalyzerRegistry.listAnalyzerIds();
|
|
710
789
|
const TOOL_EXTRA_ARGS = {
|
|
711
790
|
brand: { "brand-terms": {
|
|
@@ -822,40 +901,14 @@ function makeToolCommand(tool) {
|
|
|
822
901
|
...extraArgs
|
|
823
902
|
},
|
|
824
903
|
async run({ args }) {
|
|
825
|
-
const
|
|
826
|
-
|
|
827
|
-
|
|
904
|
+
const { format, runAnalysis } = await resolveAnalysisSource({
|
|
905
|
+
site: args.site,
|
|
906
|
+
live: !!args.live,
|
|
907
|
+
json: !!args.json,
|
|
908
|
+
format: args.format
|
|
828
909
|
});
|
|
829
|
-
const siteUrl = await ctx.resolveSite(args.site);
|
|
830
910
|
logger.info(`Running ${tool} analysis...`);
|
|
831
|
-
const
|
|
832
|
-
const format = args.json ? "json" : String(args.format);
|
|
833
|
-
if (!args.live) {
|
|
834
|
-
const store = ctx.store;
|
|
835
|
-
if (!await hasLocalData(store, siteUrl).catch(() => false)) {
|
|
836
|
-
logger.error(`No local data for ${siteUrl}. Run \`gscdump sync\` first, or pass --live.`);
|
|
837
|
-
process.exit(1);
|
|
838
|
-
}
|
|
839
|
-
const localResult = await runLocalAnalysis(store, siteUrl, params).catch((e) => {
|
|
840
|
-
if (e instanceof LocalStoreUnsupportedError) {
|
|
841
|
-
logger.error(`${e.message}. Pass --live to run against the GSC API.`);
|
|
842
|
-
process.exit(1);
|
|
843
|
-
}
|
|
844
|
-
if (e instanceof LocalStoreEmptyError) {
|
|
845
|
-
logger.error(`${e.message}`);
|
|
846
|
-
process.exit(1);
|
|
847
|
-
}
|
|
848
|
-
logger.error(`Local analysis failed: ${e.message}`);
|
|
849
|
-
process.exit(1);
|
|
850
|
-
});
|
|
851
|
-
if (format === "json") {
|
|
852
|
-
console.log(JSON.stringify(localResult, null, 2));
|
|
853
|
-
return;
|
|
854
|
-
}
|
|
855
|
-
renderResults(localResult.results, localResult.results.length, format);
|
|
856
|
-
return;
|
|
857
|
-
}
|
|
858
|
-
const result = await runLiveAnalysis(ctx.client, siteUrl, params).catch(gscErrorHandler);
|
|
911
|
+
const result = await runAnalysis(buildParams(tool, args));
|
|
859
912
|
if (format === "json") {
|
|
860
913
|
console.log(JSON.stringify(result, null, 2));
|
|
861
914
|
return;
|
|
@@ -1036,41 +1089,319 @@ const analyzeCommand = defineCommand({
|
|
|
1036
1089
|
name: "analyze",
|
|
1037
1090
|
description: "SEO analysis tools"
|
|
1038
1091
|
},
|
|
1039
|
-
subCommands:
|
|
1092
|
+
subCommands: {
|
|
1093
|
+
list: defineCommand({
|
|
1094
|
+
meta: {
|
|
1095
|
+
name: "list",
|
|
1096
|
+
description: "List available analyzer ids"
|
|
1097
|
+
},
|
|
1098
|
+
args: { json: {
|
|
1099
|
+
type: "boolean",
|
|
1100
|
+
default: false,
|
|
1101
|
+
description: "Output as JSON"
|
|
1102
|
+
} },
|
|
1103
|
+
async run({ args }) {
|
|
1104
|
+
if (args.json) {
|
|
1105
|
+
console.log(JSON.stringify(ANALYSIS_TOOLS, null, 2));
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
for (const id of ANALYSIS_TOOLS) console.log(id);
|
|
1109
|
+
}
|
|
1110
|
+
}),
|
|
1111
|
+
...Object.fromEntries(ANALYSIS_TOOLS.map((tool) => [tool, makeToolCommand(tool)]))
|
|
1112
|
+
}
|
|
1113
|
+
});
|
|
1114
|
+
const ROOT_DIR = path.join(os.homedir(), ".config", "gscdump");
|
|
1115
|
+
const PROFILES_DIR = path.join(ROOT_DIR, "profiles");
|
|
1116
|
+
const ACTIVE_MARKER = path.join(ROOT_DIR, "active-profile");
|
|
1117
|
+
let activeOverride = null;
|
|
1118
|
+
let configDirOverridden = false;
|
|
1119
|
+
function getProfileDir(name) {
|
|
1120
|
+
return path.join(PROFILES_DIR, name);
|
|
1121
|
+
}
|
|
1122
|
+
function readActiveMarkerSync() {
|
|
1123
|
+
if (!fs$1.existsSync(ACTIVE_MARKER)) return null;
|
|
1124
|
+
return fs$1.readFileSync(ACTIVE_MARKER, "utf-8").trim() || null;
|
|
1125
|
+
}
|
|
1126
|
+
function resolveActiveProfile() {
|
|
1127
|
+
return activeOverride ?? process.env.GSCDUMP_PROFILE ?? readActiveMarkerSync();
|
|
1128
|
+
}
|
|
1129
|
+
function applyProfileFromCli(opts) {
|
|
1130
|
+
if (opts.configDir) {
|
|
1131
|
+
setConfigDir(opts.configDir);
|
|
1132
|
+
configDirOverridden = true;
|
|
1133
|
+
return;
|
|
1134
|
+
}
|
|
1135
|
+
if (opts.profile) {
|
|
1136
|
+
activeOverride = opts.profile;
|
|
1137
|
+
setConfigDir(getProfileDir(opts.profile));
|
|
1138
|
+
return;
|
|
1139
|
+
}
|
|
1140
|
+
const envProfile = process.env.GSCDUMP_PROFILE;
|
|
1141
|
+
if (envProfile) {
|
|
1142
|
+
setConfigDir(getProfileDir(envProfile));
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
const marker = readActiveMarkerSync();
|
|
1146
|
+
if (marker) setConfigDir(getProfileDir(marker));
|
|
1147
|
+
}
|
|
1148
|
+
async function setActiveProfile(name) {
|
|
1149
|
+
await fs.mkdir(ROOT_DIR, {
|
|
1150
|
+
recursive: true,
|
|
1151
|
+
mode: 448
|
|
1152
|
+
});
|
|
1153
|
+
if (name == null) {
|
|
1154
|
+
await fs.rm(ACTIVE_MARKER, { force: true }).catch(() => {});
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
await fs.writeFile(ACTIVE_MARKER, name, { mode: 384 });
|
|
1158
|
+
}
|
|
1159
|
+
async function listProfiles() {
|
|
1160
|
+
return fs.readdir(PROFILES_DIR).then((entries) => entries.filter((e) => !e.startsWith(".")).sort()).catch(() => []);
|
|
1161
|
+
}
|
|
1162
|
+
async function createProfile(name) {
|
|
1163
|
+
const dir = getProfileDir(name);
|
|
1164
|
+
await fs.mkdir(dir, {
|
|
1165
|
+
recursive: true,
|
|
1166
|
+
mode: 448
|
|
1167
|
+
});
|
|
1168
|
+
return dir;
|
|
1169
|
+
}
|
|
1170
|
+
function profileNameFromEmail(email) {
|
|
1171
|
+
return email.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1172
|
+
}
|
|
1173
|
+
async function adoptCurrentConfigAsProfile(name) {
|
|
1174
|
+
if (configDirOverridden) return null;
|
|
1175
|
+
if (resolveActiveProfile()) return null;
|
|
1176
|
+
const currentDir = getConfigDir();
|
|
1177
|
+
const targetDir = getProfileDir(name);
|
|
1178
|
+
if (currentDir === targetDir) return targetDir;
|
|
1179
|
+
await fs.mkdir(targetDir, {
|
|
1180
|
+
recursive: true,
|
|
1181
|
+
mode: 448
|
|
1182
|
+
});
|
|
1183
|
+
for (const f of ["tokens.json", "config.json"]) {
|
|
1184
|
+
const src = path.join(currentDir, f);
|
|
1185
|
+
const dst = path.join(targetDir, f);
|
|
1186
|
+
if (await fs.stat(src).then(() => true).catch(() => false)) await fs.rename(src, dst).catch(() => {});
|
|
1187
|
+
}
|
|
1188
|
+
await setActiveProfile(name);
|
|
1189
|
+
activeOverride = name;
|
|
1190
|
+
setConfigDir(targetDir);
|
|
1191
|
+
return targetDir;
|
|
1192
|
+
}
|
|
1193
|
+
const profileCommand = defineCommand({
|
|
1194
|
+
meta: {
|
|
1195
|
+
name: "profile",
|
|
1196
|
+
description: "Manage gscdump profiles (per-account token + config dirs)"
|
|
1197
|
+
},
|
|
1198
|
+
subCommands: {
|
|
1199
|
+
list: defineCommand({
|
|
1200
|
+
meta: {
|
|
1201
|
+
name: "list",
|
|
1202
|
+
description: "List configured profiles"
|
|
1203
|
+
},
|
|
1204
|
+
args: { ...OUTPUT_ARGS },
|
|
1205
|
+
async run({ args }) {
|
|
1206
|
+
const { json } = applyOutputMode(args);
|
|
1207
|
+
const names = await listProfiles();
|
|
1208
|
+
const active = resolveActiveProfile();
|
|
1209
|
+
if (json) {
|
|
1210
|
+
console.log(JSON.stringify({
|
|
1211
|
+
active,
|
|
1212
|
+
profiles: names
|
|
1213
|
+
}, null, 2));
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
if (names.length === 0) {
|
|
1217
|
+
logger.warn(`No profiles in ${PROFILES_DIR}`);
|
|
1218
|
+
logger.info("Run `gscdump auth login` to create one automatically, or `gscdump profile create <name>`");
|
|
1219
|
+
return;
|
|
1220
|
+
}
|
|
1221
|
+
for (const n of names) console.log(`${n === active ? "*" : " "} ${n}`);
|
|
1222
|
+
}
|
|
1223
|
+
}),
|
|
1224
|
+
path: defineCommand({
|
|
1225
|
+
meta: {
|
|
1226
|
+
name: "path",
|
|
1227
|
+
description: "Print the config directory for a profile"
|
|
1228
|
+
},
|
|
1229
|
+
args: { name: {
|
|
1230
|
+
type: "positional",
|
|
1231
|
+
required: false,
|
|
1232
|
+
description: "Profile name (default: active)"
|
|
1233
|
+
} },
|
|
1234
|
+
async run({ args }) {
|
|
1235
|
+
const name = args.name ? String(args.name) : resolveActiveProfile();
|
|
1236
|
+
if (!name) {
|
|
1237
|
+
logger.error("No profile specified and none active (set --profile, GSCDUMP_PROFILE, or run `gscdump profile use <name>`)");
|
|
1238
|
+
process.exit(1);
|
|
1239
|
+
}
|
|
1240
|
+
console.log(getProfileDir(name));
|
|
1241
|
+
}
|
|
1242
|
+
}),
|
|
1243
|
+
current: defineCommand({
|
|
1244
|
+
meta: {
|
|
1245
|
+
name: "current",
|
|
1246
|
+
description: "Print the active profile name"
|
|
1247
|
+
},
|
|
1248
|
+
async run() {
|
|
1249
|
+
const active = resolveActiveProfile();
|
|
1250
|
+
if (!active) process.exit(1);
|
|
1251
|
+
console.log(active);
|
|
1252
|
+
}
|
|
1253
|
+
}),
|
|
1254
|
+
use: defineCommand({
|
|
1255
|
+
meta: {
|
|
1256
|
+
name: "use",
|
|
1257
|
+
description: "Set the persisted active profile (subsequent commands no longer need --profile)"
|
|
1258
|
+
},
|
|
1259
|
+
args: {
|
|
1260
|
+
...OUTPUT_ARGS,
|
|
1261
|
+
name: {
|
|
1262
|
+
type: "positional",
|
|
1263
|
+
required: true,
|
|
1264
|
+
description: "Profile name"
|
|
1265
|
+
}
|
|
1266
|
+
},
|
|
1267
|
+
async run({ args }) {
|
|
1268
|
+
applyOutputMode(args);
|
|
1269
|
+
const name = String(args.name);
|
|
1270
|
+
const dir = getProfileDir(name);
|
|
1271
|
+
if (!await fs.stat(dir).then(() => true).catch(() => false)) {
|
|
1272
|
+
logger.error(`Profile not found: ${name}`);
|
|
1273
|
+
logger.info(`Create it with: gscdump profile create ${name}`);
|
|
1274
|
+
process.exit(1);
|
|
1275
|
+
}
|
|
1276
|
+
await setActiveProfile(name);
|
|
1277
|
+
logger.success(`Active profile: ${name}`);
|
|
1278
|
+
}
|
|
1279
|
+
}),
|
|
1280
|
+
create: defineCommand({
|
|
1281
|
+
meta: {
|
|
1282
|
+
name: "create",
|
|
1283
|
+
description: "Create an empty profile directory"
|
|
1284
|
+
},
|
|
1285
|
+
args: {
|
|
1286
|
+
...OUTPUT_ARGS,
|
|
1287
|
+
"name": {
|
|
1288
|
+
type: "positional",
|
|
1289
|
+
required: true,
|
|
1290
|
+
description: "Profile name"
|
|
1291
|
+
},
|
|
1292
|
+
"no-use": {
|
|
1293
|
+
type: "boolean",
|
|
1294
|
+
default: false,
|
|
1295
|
+
description: "Do not mark the new profile as active"
|
|
1296
|
+
}
|
|
1297
|
+
},
|
|
1298
|
+
async run({ args }) {
|
|
1299
|
+
applyOutputMode(args);
|
|
1300
|
+
const name = String(args.name);
|
|
1301
|
+
const dir = await createProfile(name);
|
|
1302
|
+
if (!args["no-use"]) await setActiveProfile(name);
|
|
1303
|
+
logger.success(`Created profile: ${name}${args["no-use"] ? "" : " (active)"}`);
|
|
1304
|
+
logger.info(displayPath(dir));
|
|
1305
|
+
}
|
|
1306
|
+
}),
|
|
1307
|
+
clear: defineCommand({
|
|
1308
|
+
meta: {
|
|
1309
|
+
name: "clear",
|
|
1310
|
+
description: "Clear the persisted active profile (commands fall back to root config dir)"
|
|
1311
|
+
},
|
|
1312
|
+
args: { ...OUTPUT_ARGS },
|
|
1313
|
+
async run({ args }) {
|
|
1314
|
+
applyOutputMode(args);
|
|
1315
|
+
await setActiveProfile(null);
|
|
1316
|
+
logger.success("Cleared active profile");
|
|
1317
|
+
}
|
|
1318
|
+
}),
|
|
1319
|
+
delete: defineCommand({
|
|
1320
|
+
meta: {
|
|
1321
|
+
name: "delete",
|
|
1322
|
+
description: "Remove a profile directory (tokens + config)"
|
|
1323
|
+
},
|
|
1324
|
+
args: {
|
|
1325
|
+
...OUTPUT_ARGS,
|
|
1326
|
+
name: {
|
|
1327
|
+
type: "positional",
|
|
1328
|
+
required: true,
|
|
1329
|
+
description: "Profile name"
|
|
1330
|
+
},
|
|
1331
|
+
yes: {
|
|
1332
|
+
type: "boolean",
|
|
1333
|
+
alias: "y",
|
|
1334
|
+
default: false,
|
|
1335
|
+
description: "Skip confirmation"
|
|
1336
|
+
}
|
|
1337
|
+
},
|
|
1338
|
+
async run({ args }) {
|
|
1339
|
+
applyOutputMode(args);
|
|
1340
|
+
const name = String(args.name);
|
|
1341
|
+
const dir = getProfileDir(name);
|
|
1342
|
+
if (!await fs.stat(dir).then(() => true).catch(() => false)) {
|
|
1343
|
+
logger.error(`Profile not found: ${name}`);
|
|
1344
|
+
process.exit(1);
|
|
1345
|
+
}
|
|
1346
|
+
if (!args.yes) {
|
|
1347
|
+
const ok = await confirm({
|
|
1348
|
+
message: `Delete profile "${name}" at ${dir}? Tokens and config will be lost.`,
|
|
1349
|
+
initialValue: false
|
|
1350
|
+
});
|
|
1351
|
+
if (isCancel(ok) || !ok) {
|
|
1352
|
+
logger.info("Cancelled");
|
|
1353
|
+
process.exit(0);
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
await fs.rm(dir, {
|
|
1357
|
+
recursive: true,
|
|
1358
|
+
force: true
|
|
1359
|
+
});
|
|
1360
|
+
if (readActiveMarkerSync() === name) await setActiveProfile(null);
|
|
1361
|
+
logger.success(`Removed profile: ${name}`);
|
|
1362
|
+
}
|
|
1363
|
+
})
|
|
1364
|
+
}
|
|
1040
1365
|
});
|
|
1366
|
+
const REQUIRED_SCOPES$1 = [
|
|
1367
|
+
"https://www.googleapis.com/auth/webmasters",
|
|
1368
|
+
"https://www.googleapis.com/auth/indexing",
|
|
1369
|
+
"https://www.googleapis.com/auth/siteverification"
|
|
1370
|
+
];
|
|
1041
1371
|
async function fetchTokenInfo(accessToken) {
|
|
1042
1372
|
return ofetch("https://oauth2.googleapis.com/tokeninfo", { query: { access_token: accessToken } }).catch(() => null);
|
|
1043
1373
|
}
|
|
1374
|
+
async function resolveLiveAuthState() {
|
|
1375
|
+
const tokens = await loadTokens();
|
|
1376
|
+
const byok = resolveBYOK();
|
|
1377
|
+
let liveToken = null;
|
|
1378
|
+
if (typeof byok === "string") liveToken = byok;
|
|
1379
|
+
else if (byok && "getAccessToken" in byok) liveToken = await byok.getAccessToken().then((r) => r.token ?? null).catch(() => null);
|
|
1380
|
+
else if (tokens?.access_token) liveToken = tokens.access_token;
|
|
1381
|
+
const tokenInfo = liveToken ? await fetchTokenInfo(liveToken) : null;
|
|
1382
|
+
const scopes = tokenInfo?.scope ? tokenInfo.scope.split(/\s+/).filter(Boolean) : [];
|
|
1383
|
+
const has = (s) => scopes.includes(s) || scopes.includes(s.replace(".readonly", ""));
|
|
1384
|
+
const missing = REQUIRED_SCOPES$1.filter((s) => !has(s));
|
|
1385
|
+
return {
|
|
1386
|
+
byok,
|
|
1387
|
+
tokens,
|
|
1388
|
+
liveToken,
|
|
1389
|
+
tokenInfo,
|
|
1390
|
+
scopes,
|
|
1391
|
+
missing
|
|
1392
|
+
};
|
|
1393
|
+
}
|
|
1044
1394
|
const statusCommand$1 = defineCommand({
|
|
1045
1395
|
meta: {
|
|
1046
1396
|
name: "status",
|
|
1047
1397
|
description: "Show current authentication status"
|
|
1048
1398
|
},
|
|
1049
|
-
args: {
|
|
1050
|
-
json: {
|
|
1051
|
-
type: "boolean",
|
|
1052
|
-
default: false,
|
|
1053
|
-
description: "Output as JSON"
|
|
1054
|
-
},
|
|
1055
|
-
quiet: {
|
|
1056
|
-
type: "boolean",
|
|
1057
|
-
alias: "q",
|
|
1058
|
-
default: false,
|
|
1059
|
-
description: "Suppress info/success output"
|
|
1060
|
-
}
|
|
1061
|
-
},
|
|
1399
|
+
args: { ...OUTPUT_ARGS },
|
|
1062
1400
|
async run({ args }) {
|
|
1063
|
-
|
|
1064
|
-
const tokens = await
|
|
1065
|
-
const byok = resolveBYOK();
|
|
1401
|
+
const { json } = applyOutputMode(args);
|
|
1402
|
+
const { byok, tokens, tokenInfo, scopes, missing } = await resolveLiveAuthState();
|
|
1066
1403
|
const byokKind = byok ? typeof byok === "string" ? "access-token" : "refresh-token" : null;
|
|
1067
|
-
|
|
1068
|
-
if (typeof byok === "string") liveToken = byok;
|
|
1069
|
-
else if (byok && "getAccessToken" in byok) liveToken = await byok.getAccessToken().then((r) => r.token ?? null).catch(() => null);
|
|
1070
|
-
else if (tokens?.access_token) liveToken = tokens.access_token;
|
|
1071
|
-
const tokenInfo = liveToken ? await fetchTokenInfo(liveToken) : null;
|
|
1072
|
-
const scopes = tokenInfo?.scope ? tokenInfo.scope.split(/\s+/).filter(Boolean) : [];
|
|
1073
|
-
if (args.json) {
|
|
1404
|
+
if (json) {
|
|
1074
1405
|
console.log(JSON.stringify({
|
|
1075
1406
|
authenticated: !!tokens || !!byok,
|
|
1076
1407
|
source: byok ? "byok" : tokens ? "saved-tokens" : null,
|
|
@@ -1089,14 +1420,7 @@ const statusCommand$1 = defineCommand({
|
|
|
1089
1420
|
const reportScopes = () => {
|
|
1090
1421
|
if (scopes.length === 0) return;
|
|
1091
1422
|
console.log(` Scopes:`);
|
|
1092
|
-
const required = [
|
|
1093
|
-
"https://www.googleapis.com/auth/webmasters",
|
|
1094
|
-
"https://www.googleapis.com/auth/indexing",
|
|
1095
|
-
"https://www.googleapis.com/auth/siteverification"
|
|
1096
|
-
];
|
|
1097
|
-
const has = (s) => scopes.includes(s) || scopes.includes(s.replace(".readonly", ""));
|
|
1098
1423
|
for (const s of scopes) console.log(` \x1B[90m└─\x1B[0m ${s}`);
|
|
1099
|
-
const missing = required.filter((s) => !has(s));
|
|
1100
1424
|
if (missing.length > 0) {
|
|
1101
1425
|
console.log(` \x1B[33mMissing scopes:\x1B[0m`);
|
|
1102
1426
|
for (const s of missing) console.log(` \x1B[90m└─\x1B[0m ${s}`);
|
|
@@ -1136,14 +1460,9 @@ const refreshCommand = defineCommand({
|
|
|
1136
1460
|
name: "refresh",
|
|
1137
1461
|
description: "Force-refresh saved OAuth tokens (no-op for BYOK)"
|
|
1138
1462
|
},
|
|
1139
|
-
args: {
|
|
1140
|
-
type: "boolean",
|
|
1141
|
-
alias: "q",
|
|
1142
|
-
default: false,
|
|
1143
|
-
description: "Suppress info/success output"
|
|
1144
|
-
} },
|
|
1463
|
+
args: { ...OUTPUT_ARGS },
|
|
1145
1464
|
async run({ args }) {
|
|
1146
|
-
|
|
1465
|
+
applyOutputMode(args);
|
|
1147
1466
|
if (resolveBYOK()) {
|
|
1148
1467
|
logger.info("BYOK detected; refresh handled per-call by the SDK");
|
|
1149
1468
|
return;
|
|
@@ -1181,6 +1500,7 @@ const authCommand = defineCommand({
|
|
|
1181
1500
|
description: "Run OAuth flow and persist tokens (skip if BYOK env vars set)"
|
|
1182
1501
|
},
|
|
1183
1502
|
args: {
|
|
1503
|
+
...OUTPUT_ARGS,
|
|
1184
1504
|
"force": {
|
|
1185
1505
|
type: "boolean",
|
|
1186
1506
|
alias: "f",
|
|
@@ -1195,22 +1515,17 @@ const authCommand = defineCommand({
|
|
|
1195
1515
|
"service-account": {
|
|
1196
1516
|
type: "string",
|
|
1197
1517
|
description: "Path to a service-account JSON key (skips OAuth)"
|
|
1198
|
-
},
|
|
1199
|
-
"quiet": {
|
|
1200
|
-
type: "boolean",
|
|
1201
|
-
alias: "q",
|
|
1202
|
-
default: false,
|
|
1203
|
-
description: "Suppress info/success output"
|
|
1204
1518
|
}
|
|
1205
1519
|
},
|
|
1206
1520
|
async run({ args }) {
|
|
1207
|
-
|
|
1521
|
+
applyOutputMode(args);
|
|
1208
1522
|
if (resolveBYOK() && !args.force) {
|
|
1209
1523
|
logger.info("BYOK env vars detected, no login needed (--force to override)");
|
|
1210
1524
|
return;
|
|
1211
1525
|
}
|
|
1212
1526
|
if (args["service-account"]) {
|
|
1213
|
-
const
|
|
1527
|
+
const saPath = path.resolve(String(args["service-account"]));
|
|
1528
|
+
const jwt = await loadServiceAccount(saPath).catch((e) => {
|
|
1214
1529
|
logger.error(`Service-account load failed: ${e.message}`);
|
|
1215
1530
|
process.exit(1);
|
|
1216
1531
|
});
|
|
@@ -1218,8 +1533,11 @@ const authCommand = defineCommand({
|
|
|
1218
1533
|
logger.error(`Service-account auth failed: ${e.message}`);
|
|
1219
1534
|
process.exit(1);
|
|
1220
1535
|
});
|
|
1536
|
+
const config = await loadConfig();
|
|
1537
|
+
config.serviceAccountPath = saPath;
|
|
1538
|
+
await saveConfig(config);
|
|
1221
1539
|
logger.success(`Service-account verified: ${jwt.email ?? "OK"}`);
|
|
1222
|
-
logger.info(`
|
|
1540
|
+
logger.info(`Saved path to config: ${saPath}`);
|
|
1223
1541
|
return;
|
|
1224
1542
|
}
|
|
1225
1543
|
if (args.force) await clearTokens();
|
|
@@ -1232,6 +1550,14 @@ const authCommand = defineCommand({
|
|
|
1232
1550
|
process.exit(1);
|
|
1233
1551
|
});
|
|
1234
1552
|
logger.success("Logged in");
|
|
1553
|
+
if (!resolveActiveProfile()) {
|
|
1554
|
+
const tokens = await loadTokens();
|
|
1555
|
+
const info = tokens?.access_token ? await fetchTokenInfo(tokens.access_token) : null;
|
|
1556
|
+
if (info?.email) {
|
|
1557
|
+
const name = profileNameFromEmail(info.email);
|
|
1558
|
+
if (await adoptCurrentConfigAsProfile(name).catch(() => null)) logger.success(`Saved as profile "${name}" (active)`);
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1235
1561
|
if (resolveBYOK()) {
|
|
1236
1562
|
console.log();
|
|
1237
1563
|
console.log(await formatAuthProvenance());
|
|
@@ -1243,18 +1569,44 @@ const authCommand = defineCommand({
|
|
|
1243
1569
|
name: "logout",
|
|
1244
1570
|
description: "Clear stored OAuth tokens"
|
|
1245
1571
|
},
|
|
1246
|
-
args: {
|
|
1247
|
-
type: "boolean",
|
|
1248
|
-
alias: "q",
|
|
1249
|
-
default: false,
|
|
1250
|
-
description: "Suppress info/success output"
|
|
1251
|
-
} },
|
|
1572
|
+
args: { ...OUTPUT_ARGS },
|
|
1252
1573
|
async run({ args }) {
|
|
1253
|
-
|
|
1574
|
+
applyOutputMode(args);
|
|
1254
1575
|
await clearTokens();
|
|
1576
|
+
const config = await loadConfig();
|
|
1577
|
+
if (config.serviceAccountPath) {
|
|
1578
|
+
delete config.serviceAccountPath;
|
|
1579
|
+
await saveConfig(config);
|
|
1580
|
+
logger.info("Cleared saved service-account path");
|
|
1581
|
+
}
|
|
1255
1582
|
}
|
|
1256
1583
|
}),
|
|
1257
|
-
refresh: refreshCommand
|
|
1584
|
+
refresh: refreshCommand,
|
|
1585
|
+
scopes: defineCommand({
|
|
1586
|
+
meta: {
|
|
1587
|
+
name: "scopes",
|
|
1588
|
+
description: "Print granted OAuth scopes (one per line); exits 1 if any required scope is missing"
|
|
1589
|
+
},
|
|
1590
|
+
args: { ...OUTPUT_ARGS },
|
|
1591
|
+
async run({ args }) {
|
|
1592
|
+
const { json } = applyOutputMode(args);
|
|
1593
|
+
const { liveToken, scopes, missing } = await resolveLiveAuthState();
|
|
1594
|
+
if (!liveToken) {
|
|
1595
|
+
if (json) console.log(JSON.stringify({
|
|
1596
|
+
scopes: [],
|
|
1597
|
+
missing: null
|
|
1598
|
+
}, null, 2));
|
|
1599
|
+
else logger.error("Not authenticated");
|
|
1600
|
+
process.exit(1);
|
|
1601
|
+
}
|
|
1602
|
+
if (json) console.log(JSON.stringify({
|
|
1603
|
+
scopes,
|
|
1604
|
+
missing
|
|
1605
|
+
}, null, 2));
|
|
1606
|
+
else for (const s of scopes) console.log(s);
|
|
1607
|
+
if (missing.length > 0) process.exit(1);
|
|
1608
|
+
}
|
|
1609
|
+
})
|
|
1258
1610
|
}
|
|
1259
1611
|
});
|
|
1260
1612
|
const showCommand = defineCommand({
|
|
@@ -1262,24 +1614,12 @@ const showCommand = defineCommand({
|
|
|
1262
1614
|
name: "show",
|
|
1263
1615
|
description: "Show current config"
|
|
1264
1616
|
},
|
|
1265
|
-
args: {
|
|
1266
|
-
json: {
|
|
1267
|
-
type: "boolean",
|
|
1268
|
-
default: false,
|
|
1269
|
-
description: "Output config as a single JSON object (suppresses path header)"
|
|
1270
|
-
},
|
|
1271
|
-
quiet: {
|
|
1272
|
-
type: "boolean",
|
|
1273
|
-
alias: "q",
|
|
1274
|
-
default: false,
|
|
1275
|
-
description: "Suppress info/success output"
|
|
1276
|
-
}
|
|
1277
|
-
},
|
|
1617
|
+
args: { ...OUTPUT_ARGS },
|
|
1278
1618
|
async run({ args }) {
|
|
1279
|
-
|
|
1619
|
+
const { json } = applyOutputMode(args);
|
|
1280
1620
|
const config = await loadConfig();
|
|
1281
1621
|
const configPath = getConfigPath();
|
|
1282
|
-
if (
|
|
1622
|
+
if (json) {
|
|
1283
1623
|
console.log(JSON.stringify({
|
|
1284
1624
|
path: configPath,
|
|
1285
1625
|
config
|
|
@@ -1303,7 +1643,8 @@ const VALID_KEYS = [
|
|
|
1303
1643
|
"dataDir",
|
|
1304
1644
|
"defaultLimit",
|
|
1305
1645
|
"defaultSearchType",
|
|
1306
|
-
"defaultDataState"
|
|
1646
|
+
"defaultDataState",
|
|
1647
|
+
"serviceAccountPath"
|
|
1307
1648
|
];
|
|
1308
1649
|
const NUMERIC_KEYS = new Set(["defaultLimit"]);
|
|
1309
1650
|
const configCommand = defineCommand({
|
|
@@ -1329,15 +1670,10 @@ const configCommand = defineCommand({
|
|
|
1329
1670
|
description: "Value to set",
|
|
1330
1671
|
required: true
|
|
1331
1672
|
},
|
|
1332
|
-
|
|
1333
|
-
type: "boolean",
|
|
1334
|
-
alias: "q",
|
|
1335
|
-
default: false,
|
|
1336
|
-
description: "Suppress info/success output"
|
|
1337
|
-
}
|
|
1673
|
+
...OUTPUT_ARGS
|
|
1338
1674
|
},
|
|
1339
1675
|
async run({ args }) {
|
|
1340
|
-
|
|
1676
|
+
applyOutputMode(args);
|
|
1341
1677
|
if (!VALID_KEYS.includes(args.key)) {
|
|
1342
1678
|
logger.error(`Invalid key: ${args.key}`);
|
|
1343
1679
|
logger.info(`Valid keys: ${VALID_KEYS.join(", ")}`);
|
|
@@ -1365,15 +1701,10 @@ const configCommand = defineCommand({
|
|
|
1365
1701
|
description: `Config key to remove (${VALID_KEYS.join(", ")})`,
|
|
1366
1702
|
required: true
|
|
1367
1703
|
},
|
|
1368
|
-
|
|
1369
|
-
type: "boolean",
|
|
1370
|
-
alias: "q",
|
|
1371
|
-
default: false,
|
|
1372
|
-
description: "Suppress info/success output"
|
|
1373
|
-
}
|
|
1704
|
+
...OUTPUT_ARGS
|
|
1374
1705
|
},
|
|
1375
1706
|
async run({ args }) {
|
|
1376
|
-
|
|
1707
|
+
applyOutputMode(args);
|
|
1377
1708
|
if (!VALID_KEYS.includes(args.key)) {
|
|
1378
1709
|
logger.error(`Invalid key: ${args.key}`);
|
|
1379
1710
|
logger.info(`Valid keys: ${VALID_KEYS.join(", ")}`);
|
|
@@ -1399,21 +1730,9 @@ const configCommand = defineCommand({
|
|
|
1399
1730
|
name: "validate",
|
|
1400
1731
|
description: "Validate the saved config (defaultSite is verified, dataDir exists/writable)"
|
|
1401
1732
|
},
|
|
1402
|
-
args: {
|
|
1403
|
-
json: {
|
|
1404
|
-
type: "boolean",
|
|
1405
|
-
default: false,
|
|
1406
|
-
description: "Output as JSON"
|
|
1407
|
-
},
|
|
1408
|
-
quiet: {
|
|
1409
|
-
type: "boolean",
|
|
1410
|
-
alias: "q",
|
|
1411
|
-
default: false,
|
|
1412
|
-
description: "Suppress info/success output"
|
|
1413
|
-
}
|
|
1414
|
-
},
|
|
1733
|
+
args: { ...OUTPUT_ARGS },
|
|
1415
1734
|
async run({ args }) {
|
|
1416
|
-
|
|
1735
|
+
const { json } = applyOutputMode(args);
|
|
1417
1736
|
const { resolveDataDir } = await import("./_chunks/config.mjs").then((n) => n.t);
|
|
1418
1737
|
const fs = await import("node:fs/promises");
|
|
1419
1738
|
const config = await loadConfig();
|
|
@@ -1459,7 +1778,31 @@ const configCommand = defineCommand({
|
|
|
1459
1778
|
level: "fail",
|
|
1460
1779
|
message: `unknown format: ${config.defaultFormat}`
|
|
1461
1780
|
});
|
|
1462
|
-
|
|
1781
|
+
const { SearchTypes } = await import("gscdump/query");
|
|
1782
|
+
const allowedSearchTypes = Object.values(SearchTypes);
|
|
1783
|
+
if (config.defaultSearchType && !allowedSearchTypes.includes(config.defaultSearchType)) issues.push({
|
|
1784
|
+
key: "defaultSearchType",
|
|
1785
|
+
level: "fail",
|
|
1786
|
+
message: `unknown search type: ${config.defaultSearchType} (allowed: ${allowedSearchTypes.join(", ")})`
|
|
1787
|
+
});
|
|
1788
|
+
const allowedDataStates = [
|
|
1789
|
+
"all",
|
|
1790
|
+
"final",
|
|
1791
|
+
"hourly_all"
|
|
1792
|
+
];
|
|
1793
|
+
if (config.defaultDataState && !allowedDataStates.includes(config.defaultDataState)) issues.push({
|
|
1794
|
+
key: "defaultDataState",
|
|
1795
|
+
level: "fail",
|
|
1796
|
+
message: `unknown data state: ${config.defaultDataState} (allowed: ${allowedDataStates.join(", ")})`
|
|
1797
|
+
});
|
|
1798
|
+
if (config.serviceAccountPath) {
|
|
1799
|
+
if (!await fs.stat(config.serviceAccountPath).catch(() => null)) issues.push({
|
|
1800
|
+
key: "serviceAccountPath",
|
|
1801
|
+
level: "fail",
|
|
1802
|
+
message: `${displayPath(config.serviceAccountPath)} does not exist`
|
|
1803
|
+
});
|
|
1804
|
+
}
|
|
1805
|
+
if (json) {
|
|
1463
1806
|
console.log(JSON.stringify({
|
|
1464
1807
|
ok: !issues.some((i) => i.level === "fail"),
|
|
1465
1808
|
issues
|
|
@@ -1798,12 +2141,9 @@ const doctorCommand = defineCommand({
|
|
|
1798
2141
|
name: "doctor",
|
|
1799
2142
|
description: "Run health checks (env, auth, scopes, time, dataDir, store, GSC reachability + ping, defaultSite)"
|
|
1800
2143
|
},
|
|
1801
|
-
args: {
|
|
1802
|
-
type: "boolean",
|
|
1803
|
-
default: false,
|
|
1804
|
-
description: "Output as JSON"
|
|
1805
|
-
} },
|
|
2144
|
+
args: { ...OUTPUT_ARGS },
|
|
1806
2145
|
async run({ args }) {
|
|
2146
|
+
const { json } = applyOutputMode(args);
|
|
1807
2147
|
const envResult = await checkEnv();
|
|
1808
2148
|
const [authResult, timeChecks, dataDirChecks, watermarkChecks, gscApi, indexingApi, siteVerificationApi] = await Promise.all([
|
|
1809
2149
|
checkAuth$1(envResult.envKeys),
|
|
@@ -1830,7 +2170,7 @@ const doctorCommand = defineCommand({
|
|
|
1830
2170
|
...siteVerificationApi,
|
|
1831
2171
|
...sitesChecks
|
|
1832
2172
|
];
|
|
1833
|
-
if (
|
|
2173
|
+
if (json) {
|
|
1834
2174
|
console.log(JSON.stringify({
|
|
1835
2175
|
checks: all,
|
|
1836
2176
|
ok: all.every((c) => c.status !== "fail")
|
|
@@ -1904,20 +2244,10 @@ const dumpCommand = defineCommand({
|
|
|
1904
2244
|
default: false,
|
|
1905
2245
|
description: "Compact every closed month into a single file before exporting"
|
|
1906
2246
|
},
|
|
1907
|
-
|
|
1908
|
-
type: "boolean",
|
|
1909
|
-
default: false,
|
|
1910
|
-
description: "Emit a JSON summary of what was exported"
|
|
1911
|
-
},
|
|
1912
|
-
"quiet": {
|
|
1913
|
-
type: "boolean",
|
|
1914
|
-
alias: "q",
|
|
1915
|
-
default: false,
|
|
1916
|
-
description: "Suppress progress output"
|
|
1917
|
-
}
|
|
2247
|
+
...OUTPUT_ARGS
|
|
1918
2248
|
},
|
|
1919
2249
|
async run({ args }) {
|
|
1920
|
-
|
|
2250
|
+
const { json, quiet } = applyOutputMode(args);
|
|
1921
2251
|
const format = String(args.format);
|
|
1922
2252
|
if (!FORMATS.includes(format)) {
|
|
1923
2253
|
logger.error(`Invalid --format: ${format}. Allowed: ${FORMATS.join(", ")}`);
|
|
@@ -1935,12 +2265,12 @@ const dumpCommand = defineCommand({
|
|
|
1935
2265
|
logger.warn("No sites with local data. Run `gscdump sync` first.");
|
|
1936
2266
|
process.exit(0);
|
|
1937
2267
|
}
|
|
1938
|
-
if (args.compact) for (const siteUrl of targets) await compactClosedMonths(store, siteUrl,
|
|
2268
|
+
if (args.compact) for (const siteUrl of targets) await compactClosedMonths(store, siteUrl, quiet);
|
|
1939
2269
|
const summary = [];
|
|
1940
2270
|
for (const siteUrl of targets) {
|
|
1941
2271
|
const entries = (await listLiveEntries(store, siteUrl)).filter((e) => !tablesFilter || tablesFilter.has(e.table));
|
|
1942
2272
|
if (entries.length === 0) {
|
|
1943
|
-
if (!
|
|
2273
|
+
if (!quiet) logger.warn(`No data for ${siteUrl}; skipping`);
|
|
1944
2274
|
continue;
|
|
1945
2275
|
}
|
|
1946
2276
|
if (format === "parquet") {
|
|
@@ -1963,7 +2293,7 @@ const dumpCommand = defineCommand({
|
|
|
1963
2293
|
});
|
|
1964
2294
|
}
|
|
1965
2295
|
}
|
|
1966
|
-
if (
|
|
2296
|
+
if (json) {
|
|
1967
2297
|
console.log(JSON.stringify({
|
|
1968
2298
|
outDir,
|
|
1969
2299
|
sites: summary
|
|
@@ -2092,20 +2422,10 @@ const inspectSubCommand = defineCommand({
|
|
|
2092
2422
|
default: "4",
|
|
2093
2423
|
description: "Concurrent in-flight inspect calls (default: 4)"
|
|
2094
2424
|
},
|
|
2095
|
-
|
|
2096
|
-
type: "boolean",
|
|
2097
|
-
default: false,
|
|
2098
|
-
description: "Emit a JSON summary of inspection results"
|
|
2099
|
-
},
|
|
2100
|
-
quiet: {
|
|
2101
|
-
type: "boolean",
|
|
2102
|
-
alias: "q",
|
|
2103
|
-
default: false,
|
|
2104
|
-
description: "Suppress progress output"
|
|
2105
|
-
}
|
|
2425
|
+
...OUTPUT_ARGS
|
|
2106
2426
|
},
|
|
2107
2427
|
async run({ args }) {
|
|
2108
|
-
|
|
2428
|
+
const { json, quiet } = applyOutputMode(args);
|
|
2109
2429
|
const ctx = await createCommandContext({
|
|
2110
2430
|
needsAuth: true,
|
|
2111
2431
|
needsStore: true
|
|
@@ -2115,7 +2435,6 @@ const inspectSubCommand = defineCommand({
|
|
|
2115
2435
|
const siteUrl = await ctx.resolveSite(args.site ? String(args.site) : void 0);
|
|
2116
2436
|
const limit = args.limit ? Number.parseInt(String(args.limit), 10) : INSPECTION_QPD_PER_PROPERTY;
|
|
2117
2437
|
const concurrency = Math.max(1, Number.parseInt(String(args.concurrency), 10) || 4);
|
|
2118
|
-
const quiet = Boolean(args.quiet) || Boolean(args.json);
|
|
2119
2438
|
const urls = (await readUrlList({ file: args.file ? String(args.file) : void 0 })).slice(0, limit);
|
|
2120
2439
|
if (urls.length === 0) {
|
|
2121
2440
|
logger.warn("No URLs to inspect.");
|
|
@@ -2163,7 +2482,7 @@ const inspectSubCommand = defineCommand({
|
|
|
2163
2482
|
userId: store.userId,
|
|
2164
2483
|
siteId: store.siteIdFor(siteUrl)
|
|
2165
2484
|
}, records);
|
|
2166
|
-
if (
|
|
2485
|
+
if (json) console.log(JSON.stringify({
|
|
2167
2486
|
site: siteUrl,
|
|
2168
2487
|
inspected: records.length,
|
|
2169
2488
|
failed,
|
|
@@ -2187,6 +2506,7 @@ const showSubCommand = defineCommand({
|
|
|
2187
2506
|
description: "Print the latest inspection record for a URL from the local entity store"
|
|
2188
2507
|
},
|
|
2189
2508
|
args: {
|
|
2509
|
+
...OUTPUT_ARGS,
|
|
2190
2510
|
site: {
|
|
2191
2511
|
type: "string",
|
|
2192
2512
|
alias: "s",
|
|
@@ -2196,14 +2516,10 @@ const showSubCommand = defineCommand({
|
|
|
2196
2516
|
type: "positional",
|
|
2197
2517
|
required: true,
|
|
2198
2518
|
description: "URL to look up"
|
|
2199
|
-
},
|
|
2200
|
-
json: {
|
|
2201
|
-
type: "boolean",
|
|
2202
|
-
default: false,
|
|
2203
|
-
description: "Output as JSON"
|
|
2204
2519
|
}
|
|
2205
2520
|
},
|
|
2206
2521
|
async run({ args }) {
|
|
2522
|
+
const { json } = applyOutputMode(args);
|
|
2207
2523
|
const ctx = await createCommandContext({
|
|
2208
2524
|
needsAuth: true,
|
|
2209
2525
|
needsStore: true
|
|
@@ -2218,7 +2534,7 @@ const showSubCommand = defineCommand({
|
|
|
2218
2534
|
logger.warn(`No inspection record for ${args.url}`);
|
|
2219
2535
|
process.exit(1);
|
|
2220
2536
|
}
|
|
2221
|
-
if (
|
|
2537
|
+
if (json) {
|
|
2222
2538
|
console.log(JSON.stringify(record, null, 2));
|
|
2223
2539
|
return;
|
|
2224
2540
|
}
|
|
@@ -2240,24 +2556,15 @@ const sitemapsSnapshotSubCommand = defineCommand({
|
|
|
2240
2556
|
description: "Fetch current sitemap state from GSC and persist to the local entity store"
|
|
2241
2557
|
},
|
|
2242
2558
|
args: {
|
|
2559
|
+
...OUTPUT_ARGS,
|
|
2243
2560
|
site: {
|
|
2244
2561
|
type: "string",
|
|
2245
2562
|
alias: "s",
|
|
2246
2563
|
description: "Site URL (e.g., sc-domain:example.com); defaults to config.defaultSite or prompt"
|
|
2247
|
-
},
|
|
2248
|
-
quiet: {
|
|
2249
|
-
type: "boolean",
|
|
2250
|
-
alias: "q",
|
|
2251
|
-
default: false,
|
|
2252
|
-
description: "Suppress progress output"
|
|
2253
|
-
},
|
|
2254
|
-
json: {
|
|
2255
|
-
type: "boolean",
|
|
2256
|
-
default: false,
|
|
2257
|
-
description: "Emit the snapshot JSON to stdout"
|
|
2258
2564
|
}
|
|
2259
2565
|
},
|
|
2260
2566
|
async run({ args }) {
|
|
2567
|
+
const { json, quiet } = applyOutputMode(args);
|
|
2261
2568
|
const ctx = await createCommandContext({
|
|
2262
2569
|
needsAuth: true,
|
|
2263
2570
|
needsStore: true
|
|
@@ -2265,7 +2572,6 @@ const sitemapsSnapshotSubCommand = defineCommand({
|
|
|
2265
2572
|
const client = ctx.client;
|
|
2266
2573
|
const store = ctx.store;
|
|
2267
2574
|
const siteUrl = await ctx.resolveSite(args.site ? String(args.site) : void 0);
|
|
2268
|
-
const quiet = Boolean(args.quiet);
|
|
2269
2575
|
const apiSitemaps = await client.sitemaps.list(siteUrl);
|
|
2270
2576
|
const capturedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2271
2577
|
const records = apiSitemaps.filter((s) => typeof s.path === "string").map((s) => ({
|
|
@@ -2289,7 +2595,7 @@ const sitemapsSnapshotSubCommand = defineCommand({
|
|
|
2289
2595
|
userId: store.userId,
|
|
2290
2596
|
siteId: store.siteIdFor(siteUrl)
|
|
2291
2597
|
}, records);
|
|
2292
|
-
if (
|
|
2598
|
+
if (json) {
|
|
2293
2599
|
console.log(JSON.stringify({
|
|
2294
2600
|
site: siteUrl,
|
|
2295
2601
|
capturedAt,
|
|
@@ -2314,6 +2620,7 @@ const sitemapsShowSubCommand = defineCommand({
|
|
|
2314
2620
|
description: "Print the latest captured sitemap state for a feedpath"
|
|
2315
2621
|
},
|
|
2316
2622
|
args: {
|
|
2623
|
+
...OUTPUT_ARGS,
|
|
2317
2624
|
site: {
|
|
2318
2625
|
type: "string",
|
|
2319
2626
|
alias: "s",
|
|
@@ -2323,14 +2630,10 @@ const sitemapsShowSubCommand = defineCommand({
|
|
|
2323
2630
|
type: "positional",
|
|
2324
2631
|
required: true,
|
|
2325
2632
|
description: "Sitemap path (feedpath)"
|
|
2326
|
-
},
|
|
2327
|
-
json: {
|
|
2328
|
-
type: "boolean",
|
|
2329
|
-
default: false,
|
|
2330
|
-
description: "Output as JSON"
|
|
2331
2633
|
}
|
|
2332
2634
|
},
|
|
2333
2635
|
async run({ args }) {
|
|
2636
|
+
const { json } = applyOutputMode(args);
|
|
2334
2637
|
const ctx = await createCommandContext({
|
|
2335
2638
|
needsAuth: true,
|
|
2336
2639
|
needsStore: true
|
|
@@ -2345,7 +2648,7 @@ const sitemapsShowSubCommand = defineCommand({
|
|
|
2345
2648
|
logger.warn(`No sitemap record for ${args.path}`);
|
|
2346
2649
|
process.exit(1);
|
|
2347
2650
|
}
|
|
2348
|
-
if (
|
|
2651
|
+
if (json) {
|
|
2349
2652
|
console.log(JSON.stringify(record, null, 2));
|
|
2350
2653
|
return;
|
|
2351
2654
|
}
|
|
@@ -2398,14 +2701,10 @@ const indexingSubCommand = defineCommand({
|
|
|
2398
2701
|
default: "4",
|
|
2399
2702
|
description: "Concurrent in-flight getMetadata calls (default: 4)"
|
|
2400
2703
|
},
|
|
2401
|
-
|
|
2402
|
-
type: "boolean",
|
|
2403
|
-
alias: "q",
|
|
2404
|
-
default: false,
|
|
2405
|
-
description: "Suppress progress output"
|
|
2406
|
-
}
|
|
2704
|
+
...OUTPUT_ARGS
|
|
2407
2705
|
},
|
|
2408
2706
|
async run({ args }) {
|
|
2707
|
+
const { quiet } = applyOutputMode(args);
|
|
2409
2708
|
const ctx = await createCommandContext({
|
|
2410
2709
|
needsAuth: true,
|
|
2411
2710
|
needsStore: true
|
|
@@ -2414,7 +2713,6 @@ const indexingSubCommand = defineCommand({
|
|
|
2414
2713
|
const store = ctx.store;
|
|
2415
2714
|
const siteUrl = await ctx.resolveSite(args.site ? String(args.site) : void 0);
|
|
2416
2715
|
const concurrency = Math.max(1, Number.parseInt(String(args.concurrency), 10) || 4);
|
|
2417
|
-
const quiet = Boolean(args.quiet);
|
|
2418
2716
|
const urls = await readUrlList({ file: args.file ? String(args.file) : void 0 });
|
|
2419
2717
|
if (urls.length === 0) {
|
|
2420
2718
|
logger.warn("No URLs to fetch metadata for.");
|
|
@@ -2490,6 +2788,14 @@ function parseRetries(v) {
|
|
|
2490
2788
|
const n = Number.parseInt(String(v), 10);
|
|
2491
2789
|
return Number.isFinite(n) && n >= 0 ? n : void 0;
|
|
2492
2790
|
}
|
|
2791
|
+
async function resolveUrlSource(args) {
|
|
2792
|
+
const fromSitemap = args["from-sitemap"];
|
|
2793
|
+
if (fromSitemap) return fetchSitemapUrls(String(fromSitemap)).catch((e) => {
|
|
2794
|
+
logger.error(`Sitemap fetch failed: ${e.message}`);
|
|
2795
|
+
process.exit(1);
|
|
2796
|
+
});
|
|
2797
|
+
return readUrlList$1(args);
|
|
2798
|
+
}
|
|
2493
2799
|
const submitCommand$1 = defineCommand({
|
|
2494
2800
|
meta: {
|
|
2495
2801
|
name: "submit",
|
|
@@ -2501,21 +2807,11 @@ const submitCommand$1 = defineCommand({
|
|
|
2501
2807
|
required: true,
|
|
2502
2808
|
description: "URL to submit"
|
|
2503
2809
|
},
|
|
2504
|
-
|
|
2505
|
-
type: "boolean",
|
|
2506
|
-
default: false,
|
|
2507
|
-
description: "Output as JSON"
|
|
2508
|
-
},
|
|
2509
|
-
quiet: {
|
|
2510
|
-
type: "boolean",
|
|
2511
|
-
alias: "q",
|
|
2512
|
-
default: false,
|
|
2513
|
-
description: "Suppress info/success output"
|
|
2514
|
-
},
|
|
2810
|
+
...OUTPUT_ARGS,
|
|
2515
2811
|
...RETRIES_ARG
|
|
2516
2812
|
},
|
|
2517
2813
|
async run({ args }) {
|
|
2518
|
-
|
|
2814
|
+
applyOutputMode(args);
|
|
2519
2815
|
const result = await requestIndexing((await createCommandContext({
|
|
2520
2816
|
needsAuth: true,
|
|
2521
2817
|
fetchOptions: { retry: parseRetries(args.retries) }
|
|
@@ -2539,21 +2835,11 @@ const removeCommand = defineCommand({
|
|
|
2539
2835
|
required: true,
|
|
2540
2836
|
description: "URL to mark removed"
|
|
2541
2837
|
},
|
|
2542
|
-
|
|
2543
|
-
type: "boolean",
|
|
2544
|
-
default: false,
|
|
2545
|
-
description: "Output as JSON"
|
|
2546
|
-
},
|
|
2547
|
-
quiet: {
|
|
2548
|
-
type: "boolean",
|
|
2549
|
-
alias: "q",
|
|
2550
|
-
default: false,
|
|
2551
|
-
description: "Suppress info/success output"
|
|
2552
|
-
},
|
|
2838
|
+
...OUTPUT_ARGS,
|
|
2553
2839
|
...RETRIES_ARG
|
|
2554
2840
|
},
|
|
2555
2841
|
async run({ args }) {
|
|
2556
|
-
|
|
2842
|
+
applyOutputMode(args);
|
|
2557
2843
|
const result = await requestIndexing((await createCommandContext({
|
|
2558
2844
|
needsAuth: true,
|
|
2559
2845
|
fetchOptions: { retry: parseRetries(args.retries) }
|
|
@@ -2590,7 +2876,7 @@ const statusCommand = defineCommand({
|
|
|
2590
2876
|
}
|
|
2591
2877
|
},
|
|
2592
2878
|
async run({ args }) {
|
|
2593
|
-
|
|
2879
|
+
applyOutputMode(args);
|
|
2594
2880
|
const meta = await getIndexingMetadata((await createCommandContext({ needsAuth: true })).client, args.url).catch(gscErrorHandler);
|
|
2595
2881
|
if (args.json) {
|
|
2596
2882
|
console.log(JSON.stringify(meta, null, 2));
|
|
@@ -2610,31 +2896,65 @@ const statusCommand = defineCommand({
|
|
|
2610
2896
|
}
|
|
2611
2897
|
});
|
|
2612
2898
|
const INDEXING_DAILY_QUOTA = 200;
|
|
2899
|
+
const INDEXING_PER_MINUTE_QUOTA = 600;
|
|
2900
|
+
const quotaCommand = defineCommand({
|
|
2901
|
+
meta: {
|
|
2902
|
+
name: "quota",
|
|
2903
|
+
description: "Show documented Indexing API quotas (no live counters; quota usage is not exposed by the API)"
|
|
2904
|
+
},
|
|
2905
|
+
args: { ...OUTPUT_ARGS },
|
|
2906
|
+
async run({ args }) {
|
|
2907
|
+
const { json } = applyOutputMode(args);
|
|
2908
|
+
const payload = {
|
|
2909
|
+
perDay: INDEXING_DAILY_QUOTA,
|
|
2910
|
+
perMinute: INDEXING_PER_MINUTE_QUOTA,
|
|
2911
|
+
note: "Documented defaults. Google does not expose live counters; track yours by counting submit calls.",
|
|
2912
|
+
docs: "https://developers.google.com/search/apis/indexing-api/v3/quota-pricing"
|
|
2913
|
+
};
|
|
2914
|
+
if (json) {
|
|
2915
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
2916
|
+
return;
|
|
2917
|
+
}
|
|
2918
|
+
console.log();
|
|
2919
|
+
console.log(` \x1B[1mIndexing API quota\x1B[0m`);
|
|
2920
|
+
console.log(` Per day: ${payload.perDay}`);
|
|
2921
|
+
console.log(` Per minute: ${payload.perMinute}`);
|
|
2922
|
+
console.log();
|
|
2923
|
+
console.log(` \x1B[90m${payload.note}\x1B[0m`);
|
|
2924
|
+
console.log(` \x1B[90mDocs: ${payload.docs}\x1B[0m`);
|
|
2925
|
+
console.log();
|
|
2926
|
+
}
|
|
2927
|
+
});
|
|
2613
2928
|
const indexingCommand = defineCommand({
|
|
2614
2929
|
meta: {
|
|
2615
2930
|
name: "indexing",
|
|
2616
2931
|
description: "Notify Google about URL updates/removals (Indexing API)"
|
|
2617
2932
|
},
|
|
2618
2933
|
subCommands: {
|
|
2619
|
-
submit: submitCommand$1,
|
|
2620
|
-
remove: removeCommand,
|
|
2621
|
-
status: statusCommand,
|
|
2622
|
-
batch: defineCommand({
|
|
2934
|
+
"submit": submitCommand$1,
|
|
2935
|
+
"remove": removeCommand,
|
|
2936
|
+
"status": statusCommand,
|
|
2937
|
+
"batch": defineCommand({
|
|
2623
2938
|
meta: {
|
|
2624
2939
|
name: "batch",
|
|
2625
2940
|
description: "Submit many URLs from a file or stdin (one URL per line)"
|
|
2626
2941
|
},
|
|
2627
2942
|
args: {
|
|
2943
|
+
...OUTPUT_ARGS,
|
|
2628
2944
|
"urls": {
|
|
2629
2945
|
type: "positional",
|
|
2630
2946
|
required: false,
|
|
2631
|
-
description: "URLs (or use --file/stdin)"
|
|
2947
|
+
description: "URLs (or use --file/--from-sitemap/stdin)"
|
|
2632
2948
|
},
|
|
2633
2949
|
"file": {
|
|
2634
2950
|
type: "string",
|
|
2635
2951
|
alias: "f",
|
|
2636
2952
|
description: "File with URLs (one per line)"
|
|
2637
2953
|
},
|
|
2954
|
+
"from-sitemap": {
|
|
2955
|
+
type: "string",
|
|
2956
|
+
description: "Sitemap URL (or sitemap index) to pull URLs from"
|
|
2957
|
+
},
|
|
2638
2958
|
"type": {
|
|
2639
2959
|
type: "string",
|
|
2640
2960
|
default: "URL_UPDATED",
|
|
@@ -2651,17 +2971,6 @@ const indexingCommand = defineCommand({
|
|
|
2651
2971
|
default: "1",
|
|
2652
2972
|
description: "Concurrent in-flight requests"
|
|
2653
2973
|
},
|
|
2654
|
-
"quiet": {
|
|
2655
|
-
type: "boolean",
|
|
2656
|
-
alias: "q",
|
|
2657
|
-
default: false,
|
|
2658
|
-
description: "Suppress progress output"
|
|
2659
|
-
},
|
|
2660
|
-
"json": {
|
|
2661
|
-
type: "boolean",
|
|
2662
|
-
default: false,
|
|
2663
|
-
description: "Output as JSON"
|
|
2664
|
-
},
|
|
2665
2974
|
"yes": {
|
|
2666
2975
|
type: "boolean",
|
|
2667
2976
|
alias: "y",
|
|
@@ -2674,10 +2983,10 @@ const indexingCommand = defineCommand({
|
|
|
2674
2983
|
}
|
|
2675
2984
|
},
|
|
2676
2985
|
async run({ args }) {
|
|
2677
|
-
|
|
2678
|
-
const urls = await
|
|
2986
|
+
applyOutputMode(args);
|
|
2987
|
+
const urls = await resolveUrlSource(args);
|
|
2679
2988
|
if (urls.length === 0) {
|
|
2680
|
-
logger.error("No URLs provided. Pass URLs as args, --file, or stdin.");
|
|
2989
|
+
logger.error("No URLs provided. Pass URLs as args, --file, --from-sitemap, or stdin.");
|
|
2681
2990
|
process.exit(1);
|
|
2682
2991
|
}
|
|
2683
2992
|
const type = String(args.type);
|
|
@@ -2710,7 +3019,71 @@ const indexingCommand = defineCommand({
|
|
|
2710
3019
|
}
|
|
2711
3020
|
if (!args.quiet) logger.success(`Submitted ${results.length}/${urls.length} URLs`);
|
|
2712
3021
|
}
|
|
2713
|
-
})
|
|
3022
|
+
}),
|
|
3023
|
+
"batch-status": defineCommand({
|
|
3024
|
+
meta: {
|
|
3025
|
+
name: "batch-status",
|
|
3026
|
+
description: "Get indexing notification metadata for many URLs"
|
|
3027
|
+
},
|
|
3028
|
+
args: {
|
|
3029
|
+
...OUTPUT_ARGS,
|
|
3030
|
+
"urls": {
|
|
3031
|
+
type: "positional",
|
|
3032
|
+
required: false,
|
|
3033
|
+
description: "URLs (or use --file/--from-sitemap/stdin)"
|
|
3034
|
+
},
|
|
3035
|
+
"file": {
|
|
3036
|
+
type: "string",
|
|
3037
|
+
alias: "f",
|
|
3038
|
+
description: "File with URLs (one per line)"
|
|
3039
|
+
},
|
|
3040
|
+
"from-sitemap": {
|
|
3041
|
+
type: "string",
|
|
3042
|
+
description: "Sitemap URL (or sitemap index) to pull URLs from"
|
|
3043
|
+
},
|
|
3044
|
+
"delay-ms": {
|
|
3045
|
+
type: "string",
|
|
3046
|
+
default: "100",
|
|
3047
|
+
description: "Delay between requests"
|
|
3048
|
+
},
|
|
3049
|
+
"concurrency": {
|
|
3050
|
+
type: "string",
|
|
3051
|
+
alias: "c",
|
|
3052
|
+
default: "1",
|
|
3053
|
+
description: "Concurrent in-flight requests"
|
|
3054
|
+
},
|
|
3055
|
+
"retries": {
|
|
3056
|
+
type: "string",
|
|
3057
|
+
description: "Override per-call retry count (default: 3)"
|
|
3058
|
+
}
|
|
3059
|
+
},
|
|
3060
|
+
async run({ args }) {
|
|
3061
|
+
applyOutputMode(args);
|
|
3062
|
+
const urls = await resolveUrlSource(args);
|
|
3063
|
+
if (urls.length === 0) {
|
|
3064
|
+
logger.error("No URLs provided. Pass URLs as args, --file, --from-sitemap, or stdin.");
|
|
3065
|
+
process.exit(1);
|
|
3066
|
+
}
|
|
3067
|
+
const ctx = await createCommandContext({
|
|
3068
|
+
needsAuth: true,
|
|
3069
|
+
fetchOptions: { retry: parseRetries(args.retries) }
|
|
3070
|
+
});
|
|
3071
|
+
const delayMs = Number.parseInt(String(args["delay-ms"]), 10);
|
|
3072
|
+
const concurrency = Math.max(1, Number.parseInt(String(args.concurrency), 10) || 1);
|
|
3073
|
+
if (!args.json && !args.quiet) logger.info(`Fetching status for ${urls.length} URLs ...`);
|
|
3074
|
+
const results = await runSequentialBatch(urls, (url) => getIndexingMetadata(ctx.client, url), {
|
|
3075
|
+
delayMs,
|
|
3076
|
+
concurrency,
|
|
3077
|
+
onProgress: args.json || args.quiet ? void 0 : (r, i, total) => logger.info(`[${i + 1}/${total}] ${r.url}`)
|
|
3078
|
+
}).catch(gscErrorHandler);
|
|
3079
|
+
if (args.json) {
|
|
3080
|
+
console.log(JSON.stringify(results, null, 2));
|
|
3081
|
+
return;
|
|
3082
|
+
}
|
|
3083
|
+
if (!args.quiet) logger.success(`Fetched ${results.length}/${urls.length} URLs`);
|
|
3084
|
+
}
|
|
3085
|
+
}),
|
|
3086
|
+
"quota": quotaCommand
|
|
2714
3087
|
}
|
|
2715
3088
|
});
|
|
2716
3089
|
const ENV_LINE_RE = /^([^=]+)=(.*)$/;
|
|
@@ -2758,15 +3131,10 @@ const initCommand = defineCommand({
|
|
|
2758
3131
|
default: false,
|
|
2759
3132
|
description: "Skip dataDir prompt (auth-only setup)"
|
|
2760
3133
|
},
|
|
2761
|
-
|
|
2762
|
-
type: "boolean",
|
|
2763
|
-
alias: "q",
|
|
2764
|
-
default: false,
|
|
2765
|
-
description: "Suppress info/success output"
|
|
2766
|
-
}
|
|
3134
|
+
...OUTPUT_ARGS
|
|
2767
3135
|
},
|
|
2768
3136
|
async run({ args }) {
|
|
2769
|
-
|
|
3137
|
+
applyOutputMode(args);
|
|
2770
3138
|
const config = await loadConfig();
|
|
2771
3139
|
if (config.clientId && config.clientSecret && !args.force) {
|
|
2772
3140
|
logger.info("Already configured");
|
|
@@ -2946,111 +3314,111 @@ function printInspection(url, inspection) {
|
|
|
2946
3314
|
}
|
|
2947
3315
|
console.log();
|
|
2948
3316
|
}
|
|
2949
|
-
const
|
|
3317
|
+
const batchCommand = defineCommand({
|
|
2950
3318
|
meta: {
|
|
2951
|
-
name: "
|
|
2952
|
-
description: "Inspect
|
|
3319
|
+
name: "batch",
|
|
3320
|
+
description: "Inspect many URLs from a file or stdin (one URL per line)"
|
|
2953
3321
|
},
|
|
2954
3322
|
args: {
|
|
2955
|
-
|
|
3323
|
+
...OUTPUT_ARGS,
|
|
3324
|
+
"site": {
|
|
2956
3325
|
type: "string",
|
|
2957
3326
|
alias: "s",
|
|
2958
3327
|
description: "Site URL (defaults to config.defaultSite or prompt)"
|
|
2959
3328
|
},
|
|
2960
|
-
|
|
3329
|
+
"urls": {
|
|
2961
3330
|
type: "positional",
|
|
2962
|
-
required:
|
|
2963
|
-
description: "
|
|
3331
|
+
required: false,
|
|
3332
|
+
description: "URLs (or use --file/--from-sitemap/stdin)"
|
|
2964
3333
|
},
|
|
2965
|
-
|
|
2966
|
-
type: "
|
|
2967
|
-
|
|
2968
|
-
description: "
|
|
3334
|
+
"file": {
|
|
3335
|
+
type: "string",
|
|
3336
|
+
alias: "f",
|
|
3337
|
+
description: "File with URLs (one per line)"
|
|
2969
3338
|
},
|
|
2970
|
-
|
|
2971
|
-
type: "
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
3339
|
+
"from-sitemap": {
|
|
3340
|
+
type: "string",
|
|
3341
|
+
description: "Sitemap URL (or sitemap index) to pull URLs from"
|
|
3342
|
+
},
|
|
3343
|
+
"delay-ms": {
|
|
3344
|
+
type: "string",
|
|
3345
|
+
default: "200",
|
|
3346
|
+
description: "Delay between requests"
|
|
3347
|
+
},
|
|
3348
|
+
"concurrency": {
|
|
3349
|
+
type: "string",
|
|
3350
|
+
alias: "c",
|
|
3351
|
+
default: "1",
|
|
3352
|
+
description: "Concurrent in-flight requests"
|
|
2975
3353
|
}
|
|
2976
3354
|
},
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3002
|
-
|
|
3003
|
-
|
|
3004
|
-
|
|
3005
|
-
|
|
3006
|
-
|
|
3007
|
-
|
|
3008
|
-
|
|
3009
|
-
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3355
|
+
async run({ args }) {
|
|
3356
|
+
const { json, quiet } = applyOutputMode(args);
|
|
3357
|
+
const urls = args["from-sitemap"] ? await fetchSitemapUrls(String(args["from-sitemap"])).catch((e) => {
|
|
3358
|
+
logger.error(`Sitemap fetch failed: ${e.message}`);
|
|
3359
|
+
process.exit(1);
|
|
3360
|
+
}) : await readUrlList$1(args);
|
|
3361
|
+
if (urls.length === 0) {
|
|
3362
|
+
logger.error("No URLs provided. Pass URLs as args, --file, --from-sitemap, or stdin.");
|
|
3363
|
+
process.exit(1);
|
|
3364
|
+
}
|
|
3365
|
+
const ctx = await createCommandContext({ needsAuth: true });
|
|
3366
|
+
const siteUrl = await ctx.resolveSite(args.site ? String(args.site) : void 0);
|
|
3367
|
+
const delayMs = Number.parseInt(String(args["delay-ms"]), 10);
|
|
3368
|
+
const concurrency = Math.max(1, Number.parseInt(String(args.concurrency), 10) || 1);
|
|
3369
|
+
if (!quiet) logger.info(`Inspecting ${urls.length} URLs ...`);
|
|
3370
|
+
const results = await batchInspectUrls(ctx.client, siteUrl, urls, {
|
|
3371
|
+
delayMs,
|
|
3372
|
+
concurrency,
|
|
3373
|
+
onProgress: quiet ? void 0 : (r, i, total) => logger.info(`[${i + 1}/${total}] ${r.url} ${r.isIndexed ? "PASS" : "FAIL"}`)
|
|
3374
|
+
}).catch(gscErrorHandler);
|
|
3375
|
+
if (json) {
|
|
3376
|
+
const flattened = results.map((r) => {
|
|
3377
|
+
const indexStatus = r.inspection?.indexStatusResult;
|
|
3378
|
+
return {
|
|
3379
|
+
url: r.url,
|
|
3380
|
+
verdict: indexStatus?.verdict || null,
|
|
3381
|
+
coverageState: indexStatus?.coverageState || null,
|
|
3382
|
+
indexingState: indexStatus?.indexingState || null,
|
|
3383
|
+
lastCrawlTime: indexStatus?.lastCrawlTime || null,
|
|
3384
|
+
isIndexed: r.isIndexed,
|
|
3385
|
+
raw: r.inspection
|
|
3386
|
+
};
|
|
3387
|
+
});
|
|
3388
|
+
console.log(JSON.stringify(flattened, null, 2));
|
|
3389
|
+
return;
|
|
3390
|
+
}
|
|
3391
|
+
const indexed = results.filter((r) => r.isIndexed).length;
|
|
3392
|
+
if (!quiet) logger.success(`Inspected ${results.length} URLs (${indexed} indexed, ${results.length - indexed} not)`);
|
|
3393
|
+
}
|
|
3394
|
+
});
|
|
3395
|
+
const inspectCommand = defineCommand({
|
|
3396
|
+
meta: {
|
|
3397
|
+
name: "inspect",
|
|
3398
|
+
description: "Inspect URL indexing status (single URL; use `inspect batch` for many)"
|
|
3399
|
+
},
|
|
3400
|
+
args: {
|
|
3401
|
+
...OUTPUT_ARGS,
|
|
3402
|
+
site: {
|
|
3403
|
+
type: "string",
|
|
3404
|
+
alias: "s",
|
|
3405
|
+
description: "Site URL (defaults to config.defaultSite or prompt)"
|
|
3020
3406
|
},
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
logger.error("No URLs provided. Pass URLs as args, --file, or stdin.");
|
|
3026
|
-
process.exit(1);
|
|
3027
|
-
}
|
|
3028
|
-
const ctx = await createCommandContext({ needsAuth: true });
|
|
3029
|
-
const siteUrl = await ctx.resolveSite(args.site ? String(args.site) : void 0);
|
|
3030
|
-
const delayMs = Number.parseInt(String(args["delay-ms"]), 10);
|
|
3031
|
-
const concurrency = Math.max(1, Number.parseInt(String(args.concurrency), 10) || 1);
|
|
3032
|
-
if (!args.json && !args.quiet) logger.info(`Inspecting ${urls.length} URLs ...`);
|
|
3033
|
-
const results = await batchInspectUrls(ctx.client, siteUrl, urls, {
|
|
3034
|
-
delayMs,
|
|
3035
|
-
concurrency,
|
|
3036
|
-
onProgress: args.json || args.quiet ? void 0 : (r, i, total) => logger.info(`[${i + 1}/${total}] ${r.url} ${r.isIndexed ? "PASS" : "FAIL"}`)
|
|
3037
|
-
}).catch(gscErrorHandler);
|
|
3038
|
-
if (args.json) {
|
|
3039
|
-
console.log(JSON.stringify(results, null, 2));
|
|
3040
|
-
return;
|
|
3041
|
-
}
|
|
3042
|
-
const indexed = results.filter((r) => r.isIndexed).length;
|
|
3043
|
-
if (!args.quiet) logger.success(`Inspected ${results.length} URLs (${indexed} indexed, ${results.length - indexed} not)`);
|
|
3407
|
+
url: {
|
|
3408
|
+
type: "positional",
|
|
3409
|
+
required: true,
|
|
3410
|
+
description: "URL to inspect"
|
|
3044
3411
|
}
|
|
3045
|
-
}
|
|
3412
|
+
},
|
|
3413
|
+
subCommands: { batch: batchCommand },
|
|
3046
3414
|
async run({ args }) {
|
|
3047
|
-
|
|
3415
|
+
const { json } = applyOutputMode(args);
|
|
3048
3416
|
const ctx = await createCommandContext({ needsAuth: true });
|
|
3049
3417
|
const siteUrl = await ctx.resolveSite(args.site ? String(args.site) : void 0);
|
|
3050
3418
|
const result = await ctx.client.inspect(siteUrl, args.url).catch(gscErrorHandler);
|
|
3051
3419
|
const inspection = result?.inspectionResult;
|
|
3052
3420
|
const indexStatus = inspection?.indexStatusResult;
|
|
3053
|
-
if (
|
|
3421
|
+
if (json) {
|
|
3054
3422
|
console.log(JSON.stringify({
|
|
3055
3423
|
url: args.url,
|
|
3056
3424
|
verdict: indexStatus?.verdict || null,
|
|
@@ -3622,6 +3990,222 @@ async function writeOutput(opts) {
|
|
|
3622
3990
|
function isKnownTable$1(name) {
|
|
3623
3991
|
return allTables().includes(name);
|
|
3624
3992
|
}
|
|
3993
|
+
const REPORT_IDS = defaultReportRegistry.listReportIds();
|
|
3994
|
+
const PERIOD_ALIASES = {
|
|
3995
|
+
"7d": "last-7d",
|
|
3996
|
+
"28d": "last-28d",
|
|
3997
|
+
"30d": "last-30d",
|
|
3998
|
+
"90d": "last-90d",
|
|
3999
|
+
"180d": "last-180d",
|
|
4000
|
+
"365d": "last-365d",
|
|
4001
|
+
"last-7d": "last-7d",
|
|
4002
|
+
"last-28d": "last-28d",
|
|
4003
|
+
"last-30d": "last-30d",
|
|
4004
|
+
"last-90d": "last-90d",
|
|
4005
|
+
"last-180d": "last-180d",
|
|
4006
|
+
"last-365d": "last-365d",
|
|
4007
|
+
"mtd": "mtd",
|
|
4008
|
+
"ytd": "ytd",
|
|
4009
|
+
"custom": "custom"
|
|
4010
|
+
};
|
|
4011
|
+
const COMPARISON_ALIASES = {
|
|
4012
|
+
"none": "none",
|
|
4013
|
+
"prev": "prev-period",
|
|
4014
|
+
"prev-period": "prev-period",
|
|
4015
|
+
"prior": "prev-period",
|
|
4016
|
+
"prior-period": "prev-period",
|
|
4017
|
+
"yoy": "yoy"
|
|
4018
|
+
};
|
|
4019
|
+
function resolvePeriod(input, fallback) {
|
|
4020
|
+
if (!input) return fallback;
|
|
4021
|
+
const preset = PERIOD_ALIASES[input.toLowerCase()];
|
|
4022
|
+
if (!preset) throw new Error(`Unknown --period "${input}". Supported: 7d, 28d, 30d, 90d, 180d, 365d, mtd, ytd, custom.`);
|
|
4023
|
+
return preset;
|
|
4024
|
+
}
|
|
4025
|
+
function resolveComparison(input, fallback) {
|
|
4026
|
+
if (!input) return fallback;
|
|
4027
|
+
const mode = COMPARISON_ALIASES[input.toLowerCase()];
|
|
4028
|
+
if (!mode) throw new Error(`Unknown --vs "${input}". Supported: none, prev-period, yoy.`);
|
|
4029
|
+
return mode;
|
|
4030
|
+
}
|
|
4031
|
+
function reportArgsToCitty(spec) {
|
|
4032
|
+
const out = {};
|
|
4033
|
+
for (const [key, def] of Object.entries(spec)) out[key] = {
|
|
4034
|
+
type: def.type === "boolean" ? "boolean" : "string",
|
|
4035
|
+
description: def.description,
|
|
4036
|
+
default: def.default == null ? void 0 : String(def.default),
|
|
4037
|
+
alias: def.alias,
|
|
4038
|
+
required: def.required
|
|
4039
|
+
};
|
|
4040
|
+
return out;
|
|
4041
|
+
}
|
|
4042
|
+
function buildReportParams(report, args) {
|
|
4043
|
+
const params = {};
|
|
4044
|
+
for (const [key, def] of Object.entries(report.argsSpec)) {
|
|
4045
|
+
const raw = args[key];
|
|
4046
|
+
if (raw == null || raw === "") continue;
|
|
4047
|
+
if (def.type === "number") {
|
|
4048
|
+
const n = Number(raw);
|
|
4049
|
+
if (Number.isFinite(n)) params[toCamel(key)] = n;
|
|
4050
|
+
} else if (def.type === "boolean") params[toCamel(key)] = !!raw;
|
|
4051
|
+
else params[toCamel(key)] = raw;
|
|
4052
|
+
}
|
|
4053
|
+
return params;
|
|
4054
|
+
}
|
|
4055
|
+
function toCamel(kebab) {
|
|
4056
|
+
return kebab.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
4057
|
+
}
|
|
4058
|
+
function makeReportCommand(report) {
|
|
4059
|
+
const reportArgs = reportArgsToCitty(report.argsSpec);
|
|
4060
|
+
return defineCommand({
|
|
4061
|
+
meta: {
|
|
4062
|
+
name: report.id,
|
|
4063
|
+
description: report.description
|
|
4064
|
+
},
|
|
4065
|
+
args: {
|
|
4066
|
+
"site": {
|
|
4067
|
+
type: "string",
|
|
4068
|
+
alias: "s",
|
|
4069
|
+
description: "Site URL"
|
|
4070
|
+
},
|
|
4071
|
+
"period": {
|
|
4072
|
+
type: "string",
|
|
4073
|
+
description: "Window: 7d|28d|90d|mtd|ytd|custom",
|
|
4074
|
+
default: presetToFlag(report.defaultPeriod)
|
|
4075
|
+
},
|
|
4076
|
+
"vs": {
|
|
4077
|
+
type: "string",
|
|
4078
|
+
description: "Comparison: none|prev-period|yoy",
|
|
4079
|
+
default: report.defaultComparison
|
|
4080
|
+
},
|
|
4081
|
+
"start": {
|
|
4082
|
+
type: "string",
|
|
4083
|
+
description: "Custom start date (YYYY-MM-DD)"
|
|
4084
|
+
},
|
|
4085
|
+
"end": {
|
|
4086
|
+
type: "string",
|
|
4087
|
+
description: "Custom end date (YYYY-MM-DD)"
|
|
4088
|
+
},
|
|
4089
|
+
"prev-start": {
|
|
4090
|
+
type: "string",
|
|
4091
|
+
description: "Override comparison start"
|
|
4092
|
+
},
|
|
4093
|
+
"prev-end": {
|
|
4094
|
+
type: "string",
|
|
4095
|
+
description: "Override comparison end"
|
|
4096
|
+
},
|
|
4097
|
+
"live": {
|
|
4098
|
+
type: "boolean",
|
|
4099
|
+
default: false,
|
|
4100
|
+
description: "Force live GSC API; bypass local store"
|
|
4101
|
+
},
|
|
4102
|
+
"json": {
|
|
4103
|
+
type: "boolean",
|
|
4104
|
+
default: false,
|
|
4105
|
+
description: "Emit full ReportResult JSON"
|
|
4106
|
+
},
|
|
4107
|
+
"explain": {
|
|
4108
|
+
type: "boolean",
|
|
4109
|
+
default: false,
|
|
4110
|
+
description: "Print plan steps + window without executing"
|
|
4111
|
+
},
|
|
4112
|
+
"dry-run": {
|
|
4113
|
+
type: "boolean",
|
|
4114
|
+
default: false,
|
|
4115
|
+
description: "Alias for --explain"
|
|
4116
|
+
},
|
|
4117
|
+
...reportArgs
|
|
4118
|
+
},
|
|
4119
|
+
async run({ args }) {
|
|
4120
|
+
const preset = resolvePeriod(args.period, report.defaultPeriod);
|
|
4121
|
+
const comparison = resolveComparison(args.vs, report.defaultComparison);
|
|
4122
|
+
const window = resolveWindow({
|
|
4123
|
+
preset,
|
|
4124
|
+
comparison,
|
|
4125
|
+
start: args.start,
|
|
4126
|
+
end: args.end
|
|
4127
|
+
});
|
|
4128
|
+
if (args["prev-start"] && args["prev-end"]) window.comparison = {
|
|
4129
|
+
start: String(args["prev-start"]),
|
|
4130
|
+
end: String(args["prev-end"])
|
|
4131
|
+
};
|
|
4132
|
+
const params = buildReportParams(report, args);
|
|
4133
|
+
if (args.explain || args["dry-run"]) {
|
|
4134
|
+
const dry = await dryRunReport(report, {
|
|
4135
|
+
site: args.site ? String(args.site) : "(unresolved)",
|
|
4136
|
+
window,
|
|
4137
|
+
params,
|
|
4138
|
+
registryVersion: defaultReportRegistry.version
|
|
4139
|
+
});
|
|
4140
|
+
console.log(JSON.stringify({
|
|
4141
|
+
id: report.id,
|
|
4142
|
+
window,
|
|
4143
|
+
comparison,
|
|
4144
|
+
plan: dry.steps
|
|
4145
|
+
}, null, 2));
|
|
4146
|
+
return;
|
|
4147
|
+
}
|
|
4148
|
+
const { source, siteUrl } = await resolveAnalysisSource({
|
|
4149
|
+
site: args.site,
|
|
4150
|
+
live: !!args.live,
|
|
4151
|
+
json: !!args.json
|
|
4152
|
+
});
|
|
4153
|
+
const result = await runReport(report, {
|
|
4154
|
+
source,
|
|
4155
|
+
analyzers: defaultAnalyzerRegistry,
|
|
4156
|
+
ctx: {
|
|
4157
|
+
site: siteUrl,
|
|
4158
|
+
window,
|
|
4159
|
+
params,
|
|
4160
|
+
registryVersion: defaultReportRegistry.version
|
|
4161
|
+
}
|
|
4162
|
+
});
|
|
4163
|
+
if (args.json) {
|
|
4164
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4165
|
+
return;
|
|
4166
|
+
}
|
|
4167
|
+
console.log(formatReport(result));
|
|
4168
|
+
if (result.meta.degraded) logger.warn(`degraded: ${result.meta.steps.filter((s) => s.status === "error").map((s) => `${s.key}(${s.error})`).join(", ")}`);
|
|
4169
|
+
}
|
|
4170
|
+
});
|
|
4171
|
+
}
|
|
4172
|
+
function presetToFlag(preset) {
|
|
4173
|
+
if (preset === "mtd" || preset === "ytd" || preset === "custom") return preset;
|
|
4174
|
+
return preset.replace(/^last-/, "");
|
|
4175
|
+
}
|
|
4176
|
+
const reportCommand = defineCommand({
|
|
4177
|
+
meta: {
|
|
4178
|
+
name: "report",
|
|
4179
|
+
description: "Run an intent-keyed report (composes analyzers into bounded sections)"
|
|
4180
|
+
},
|
|
4181
|
+
subCommands: {
|
|
4182
|
+
list: defineCommand({
|
|
4183
|
+
meta: {
|
|
4184
|
+
name: "list",
|
|
4185
|
+
description: "List available report ids"
|
|
4186
|
+
},
|
|
4187
|
+
args: { json: {
|
|
4188
|
+
type: "boolean",
|
|
4189
|
+
default: false,
|
|
4190
|
+
description: "Output as JSON"
|
|
4191
|
+
} },
|
|
4192
|
+
async run({ args }) {
|
|
4193
|
+
const reports = defaultReportRegistry.listReports().map((r) => ({
|
|
4194
|
+
id: r.id,
|
|
4195
|
+
description: r.description,
|
|
4196
|
+
defaultPeriod: r.defaultPeriod,
|
|
4197
|
+
defaultComparison: r.defaultComparison
|
|
4198
|
+
}));
|
|
4199
|
+
if (args.json) {
|
|
4200
|
+
console.log(JSON.stringify(reports, null, 2));
|
|
4201
|
+
return;
|
|
4202
|
+
}
|
|
4203
|
+
for (const r of reports) console.log(`${r.id.padEnd(16)} ${r.description}`);
|
|
4204
|
+
}
|
|
4205
|
+
}),
|
|
4206
|
+
...Object.fromEntries(REPORT_IDS.map((id) => [id, makeReportCommand(defaultReportRegistry.getReport(id))]))
|
|
4207
|
+
}
|
|
4208
|
+
});
|
|
3625
4209
|
const sitemapsCommand = defineCommand({
|
|
3626
4210
|
meta: {
|
|
3627
4211
|
name: "sitemaps",
|
|
@@ -3634,16 +4218,12 @@ const sitemapsCommand = defineCommand({
|
|
|
3634
4218
|
description: "List sitemaps for a site"
|
|
3635
4219
|
},
|
|
3636
4220
|
args: {
|
|
4221
|
+
...OUTPUT_ARGS,
|
|
3637
4222
|
site: {
|
|
3638
4223
|
type: "string",
|
|
3639
4224
|
alias: "s",
|
|
3640
4225
|
description: "Site URL (e.g., sc-domain:example.com or https://example.com/)"
|
|
3641
4226
|
},
|
|
3642
|
-
json: {
|
|
3643
|
-
type: "boolean",
|
|
3644
|
-
default: false,
|
|
3645
|
-
description: "Output as JSON"
|
|
3646
|
-
},
|
|
3647
4227
|
pending: {
|
|
3648
4228
|
type: "boolean",
|
|
3649
4229
|
default: false,
|
|
@@ -3656,12 +4236,10 @@ const sitemapsCommand = defineCommand({
|
|
|
3656
4236
|
}
|
|
3657
4237
|
},
|
|
3658
4238
|
async run({ args }) {
|
|
4239
|
+
const { json } = applyOutputMode(args);
|
|
3659
4240
|
const ctx = await createCommandContext({ needsAuth: true });
|
|
3660
4241
|
const siteUrl = await ctx.resolveSite(args.site ? String(args.site) : void 0);
|
|
3661
|
-
let sitemaps = (await ctx.client.sitemaps.list(siteUrl).catch((
|
|
3662
|
-
logger.error(`Failed to fetch sitemaps: ${e.message}`);
|
|
3663
|
-
process.exit(1);
|
|
3664
|
-
})).map((sm) => ({
|
|
4242
|
+
let sitemaps = (await ctx.client.sitemaps.list(siteUrl).catch(gscErrorHandler)).map((sm) => ({
|
|
3665
4243
|
path: sm.path,
|
|
3666
4244
|
type: sm.type || void 0,
|
|
3667
4245
|
isPending: sm.isPending || false,
|
|
@@ -3672,7 +4250,7 @@ const sitemapsCommand = defineCommand({
|
|
|
3672
4250
|
}));
|
|
3673
4251
|
if (args.pending) sitemaps = sitemaps.filter((sm) => sm.isPending);
|
|
3674
4252
|
if (args.errored) sitemaps = sitemaps.filter((sm) => sm.errors > 0);
|
|
3675
|
-
if (
|
|
4253
|
+
if (json) {
|
|
3676
4254
|
console.log(JSON.stringify(sitemaps, null, 2));
|
|
3677
4255
|
return;
|
|
3678
4256
|
}
|
|
@@ -3697,6 +4275,7 @@ const sitemapsCommand = defineCommand({
|
|
|
3697
4275
|
description: "Get details for a specific sitemap"
|
|
3698
4276
|
},
|
|
3699
4277
|
args: {
|
|
4278
|
+
...OUTPUT_ARGS,
|
|
3700
4279
|
site: {
|
|
3701
4280
|
type: "string",
|
|
3702
4281
|
alias: "s",
|
|
@@ -3706,19 +4285,15 @@ const sitemapsCommand = defineCommand({
|
|
|
3706
4285
|
type: "positional",
|
|
3707
4286
|
required: true,
|
|
3708
4287
|
description: "Sitemap URL"
|
|
3709
|
-
},
|
|
3710
|
-
json: {
|
|
3711
|
-
type: "boolean",
|
|
3712
|
-
default: false,
|
|
3713
|
-
description: "Output as JSON"
|
|
3714
4288
|
}
|
|
3715
4289
|
},
|
|
3716
4290
|
async run({ args }) {
|
|
4291
|
+
const { json } = applyOutputMode(args);
|
|
3717
4292
|
const ctx = await createCommandContext({ needsAuth: true });
|
|
3718
4293
|
const siteUrl = await ctx.resolveSite(args.site ? String(args.site) : void 0);
|
|
3719
4294
|
const client = ctx.client;
|
|
3720
4295
|
const sitemap = await fetchSitemap(client, siteUrl, args.url).catch(gscErrorHandler);
|
|
3721
|
-
if (
|
|
4296
|
+
if (json) {
|
|
3722
4297
|
console.log(JSON.stringify(sitemap, null, 2));
|
|
3723
4298
|
return;
|
|
3724
4299
|
}
|
|
@@ -3743,6 +4318,7 @@ const sitemapsCommand = defineCommand({
|
|
|
3743
4318
|
description: "Submit a sitemap to GSC"
|
|
3744
4319
|
},
|
|
3745
4320
|
args: {
|
|
4321
|
+
...OUTPUT_ARGS,
|
|
3746
4322
|
site: {
|
|
3747
4323
|
type: "string",
|
|
3748
4324
|
alias: "s",
|
|
@@ -3755,12 +4331,18 @@ const sitemapsCommand = defineCommand({
|
|
|
3755
4331
|
}
|
|
3756
4332
|
},
|
|
3757
4333
|
async run({ args }) {
|
|
4334
|
+
const { json } = applyOutputMode(args);
|
|
3758
4335
|
const ctx = await createCommandContext({ needsAuth: true });
|
|
3759
4336
|
const siteUrl = await ctx.resolveSite(args.site ? String(args.site) : void 0);
|
|
3760
|
-
await ctx.client.sitemaps.submit(siteUrl, args.url).catch(
|
|
3761
|
-
|
|
3762
|
-
|
|
3763
|
-
|
|
4337
|
+
await ctx.client.sitemaps.submit(siteUrl, args.url).catch(gscErrorHandler);
|
|
4338
|
+
if (json) {
|
|
4339
|
+
console.log(JSON.stringify({
|
|
4340
|
+
siteUrl,
|
|
4341
|
+
feedpath: args.url,
|
|
4342
|
+
status: "submitted"
|
|
4343
|
+
}, null, 2));
|
|
4344
|
+
return;
|
|
4345
|
+
}
|
|
3764
4346
|
logger.success(`Submitted sitemap: ${args.url}`);
|
|
3765
4347
|
}
|
|
3766
4348
|
}),
|
|
@@ -3770,6 +4352,7 @@ const sitemapsCommand = defineCommand({
|
|
|
3770
4352
|
description: "Delete a sitemap from GSC"
|
|
3771
4353
|
},
|
|
3772
4354
|
args: {
|
|
4355
|
+
...OUTPUT_ARGS,
|
|
3773
4356
|
site: {
|
|
3774
4357
|
type: "string",
|
|
3775
4358
|
alias: "s",
|
|
@@ -3782,13 +4365,94 @@ const sitemapsCommand = defineCommand({
|
|
|
3782
4365
|
}
|
|
3783
4366
|
},
|
|
3784
4367
|
async run({ args }) {
|
|
4368
|
+
const { json } = applyOutputMode(args);
|
|
3785
4369
|
const ctx = await createCommandContext({ needsAuth: true });
|
|
3786
4370
|
const siteUrl = await ctx.resolveSite(args.site ? String(args.site) : void 0);
|
|
3787
|
-
await ctx.client.sitemaps.delete(siteUrl, args.url).catch(
|
|
3788
|
-
|
|
4371
|
+
await ctx.client.sitemaps.delete(siteUrl, args.url).catch(gscErrorHandler);
|
|
4372
|
+
if (json) {
|
|
4373
|
+
console.log(JSON.stringify({
|
|
4374
|
+
siteUrl,
|
|
4375
|
+
feedpath: args.url,
|
|
4376
|
+
status: "deleted"
|
|
4377
|
+
}, null, 2));
|
|
4378
|
+
return;
|
|
4379
|
+
}
|
|
4380
|
+
logger.success(`Deleted sitemap: ${args.url}`);
|
|
4381
|
+
}
|
|
4382
|
+
}),
|
|
4383
|
+
discover: defineCommand({
|
|
4384
|
+
meta: {
|
|
4385
|
+
name: "discover",
|
|
4386
|
+
description: "Probe a domain's robots.txt + common paths for an advertised sitemap (no auth needed)"
|
|
4387
|
+
},
|
|
4388
|
+
args: {
|
|
4389
|
+
...OUTPUT_ARGS,
|
|
4390
|
+
domain: {
|
|
4391
|
+
type: "positional",
|
|
4392
|
+
required: true,
|
|
4393
|
+
description: "Domain (e.g., example.com)"
|
|
4394
|
+
}
|
|
4395
|
+
},
|
|
4396
|
+
async run({ args }) {
|
|
4397
|
+
const { json } = applyOutputMode(args);
|
|
4398
|
+
const domain = String(args.domain).replace(/^https?:\/\//, "").replace(/\/.*$/, "");
|
|
4399
|
+
const url = await discoverSitemap(domain).catch(() => null);
|
|
4400
|
+
if (json) {
|
|
4401
|
+
console.log(JSON.stringify({
|
|
4402
|
+
domain,
|
|
4403
|
+
sitemap: url
|
|
4404
|
+
}, null, 2));
|
|
4405
|
+
return;
|
|
4406
|
+
}
|
|
4407
|
+
if (!url) {
|
|
4408
|
+
logger.warn(`No sitemap discovered for ${domain}`);
|
|
4409
|
+
process.exit(1);
|
|
4410
|
+
}
|
|
4411
|
+
logger.success(`Discovered sitemap: ${url}`);
|
|
4412
|
+
}
|
|
4413
|
+
}),
|
|
4414
|
+
urls: defineCommand({
|
|
4415
|
+
meta: {
|
|
4416
|
+
name: "urls",
|
|
4417
|
+
description: "Fetch a sitemap (or sitemap index) and dump its <loc> URLs (no auth needed)"
|
|
4418
|
+
},
|
|
4419
|
+
args: {
|
|
4420
|
+
...OUTPUT_ARGS,
|
|
4421
|
+
"url": {
|
|
4422
|
+
type: "positional",
|
|
4423
|
+
required: true,
|
|
4424
|
+
description: "Sitemap URL (index files are followed)"
|
|
4425
|
+
},
|
|
4426
|
+
"limit": {
|
|
4427
|
+
type: "string",
|
|
4428
|
+
alias: "l",
|
|
4429
|
+
description: "Stop after N URLs across all nested sitemaps"
|
|
4430
|
+
},
|
|
4431
|
+
"max-depth": {
|
|
4432
|
+
type: "string",
|
|
4433
|
+
description: "Max sitemap-index nesting depth (default: 3)"
|
|
4434
|
+
}
|
|
4435
|
+
},
|
|
4436
|
+
async run({ args }) {
|
|
4437
|
+
const { json } = applyOutputMode(args);
|
|
4438
|
+
const limit = args.limit ? Number.parseInt(String(args.limit), 10) : void 0;
|
|
4439
|
+
const maxDepth = args["max-depth"] ? Number.parseInt(String(args["max-depth"]), 10) : void 0;
|
|
4440
|
+
const urls = await fetchSitemapUrls(String(args.url), {
|
|
4441
|
+
limit,
|
|
4442
|
+
maxDepth
|
|
4443
|
+
}).catch((e) => {
|
|
4444
|
+
logger.error(`Sitemap fetch failed: ${e.message}`);
|
|
3789
4445
|
process.exit(1);
|
|
3790
4446
|
});
|
|
3791
|
-
|
|
4447
|
+
if (json) {
|
|
4448
|
+
console.log(JSON.stringify({
|
|
4449
|
+
sitemap: args.url,
|
|
4450
|
+
count: urls.length,
|
|
4451
|
+
urls
|
|
4452
|
+
}, null, 2));
|
|
4453
|
+
return;
|
|
4454
|
+
}
|
|
4455
|
+
for (const u of urls) console.log(u);
|
|
3792
4456
|
}
|
|
3793
4457
|
})
|
|
3794
4458
|
}
|
|
@@ -3847,281 +4511,442 @@ function printPlacementInstructions(method, siteUrl, token) {
|
|
|
3847
4511
|
break;
|
|
3848
4512
|
}
|
|
3849
4513
|
case "ANALYTICS":
|
|
3850
|
-
console.log(` Make sure
|
|
4514
|
+
console.log(` Make sure the Google Analytics tracking tag is installed on the site.`);
|
|
4515
|
+
console.log(` Expected tracking ID:`);
|
|
4516
|
+
console.log();
|
|
4517
|
+
console.log(` \x1B[2m${token}\x1B[0m`);
|
|
3851
4518
|
break;
|
|
3852
4519
|
case "TAG_MANAGER":
|
|
3853
|
-
console.log(` Make sure
|
|
4520
|
+
console.log(` Make sure the Google Tag Manager container snippet is installed on the site.`);
|
|
4521
|
+
console.log(` Expected container ID:`);
|
|
4522
|
+
console.log();
|
|
4523
|
+
console.log(` \x1B[2m${token}\x1B[0m`);
|
|
3854
4524
|
break;
|
|
3855
4525
|
}
|
|
3856
4526
|
console.log();
|
|
3857
4527
|
console.log(` \x1B[90mThen run:\x1B[0m gscdump sites verify ${siteUrl} --method ${method}`);
|
|
3858
4528
|
console.log();
|
|
3859
4529
|
}
|
|
3860
|
-
const
|
|
4530
|
+
const addCommand = defineCommand({
|
|
3861
4531
|
meta: {
|
|
3862
|
-
name: "
|
|
3863
|
-
description: "
|
|
4532
|
+
name: "add",
|
|
4533
|
+
description: "Register a property in Search Console (pass --verify to chain token + verify in one call)"
|
|
3864
4534
|
},
|
|
3865
4535
|
args: {
|
|
3866
|
-
|
|
3867
|
-
type: "
|
|
3868
|
-
|
|
3869
|
-
description: "
|
|
4536
|
+
url: {
|
|
4537
|
+
type: "positional",
|
|
4538
|
+
required: true,
|
|
4539
|
+
description: "Property URL (https://example.com/ or sc-domain:example.com)"
|
|
3870
4540
|
},
|
|
3871
|
-
|
|
4541
|
+
verify: {
|
|
3872
4542
|
type: "boolean",
|
|
3873
4543
|
default: false,
|
|
3874
|
-
description: "
|
|
4544
|
+
description: "After adding, fetch a verification token and trigger Google's validation"
|
|
3875
4545
|
},
|
|
3876
|
-
|
|
3877
|
-
type: "
|
|
3878
|
-
|
|
3879
|
-
description: "
|
|
4546
|
+
method: {
|
|
4547
|
+
type: "string",
|
|
4548
|
+
alias: "m",
|
|
4549
|
+
description: "Verification method (used with --verify; default: META for URL-prefix, DNS_TXT for sc-domain:)"
|
|
3880
4550
|
},
|
|
3881
|
-
|
|
4551
|
+
...OUTPUT_ARGS
|
|
4552
|
+
},
|
|
4553
|
+
async run({ args }) {
|
|
4554
|
+
applyOutputMode(args);
|
|
4555
|
+
const ctx = await createCommandContext({ needsAuth: true });
|
|
4556
|
+
await addSite(ctx.client, args.url).catch(gscErrorHandler);
|
|
4557
|
+
if (!args.verify) {
|
|
4558
|
+
if (args.json) {
|
|
4559
|
+
console.log(JSON.stringify({
|
|
4560
|
+
siteUrl: args.url,
|
|
4561
|
+
status: "added",
|
|
4562
|
+
verified: false
|
|
4563
|
+
}, null, 2));
|
|
4564
|
+
return;
|
|
4565
|
+
}
|
|
4566
|
+
logger.success(`Added: ${args.url}`);
|
|
4567
|
+
logger.info(`Property is in unverified state. Verify ownership next:`);
|
|
4568
|
+
const method = pickDefaultMethod(args.url);
|
|
4569
|
+
console.log(` \x1B[2mgscdump sites verify-token ${args.url} --method ${method}\x1B[0m`);
|
|
4570
|
+
return;
|
|
4571
|
+
}
|
|
4572
|
+
const method = validateMethod(args.url, args.method ?? pickDefaultMethod(args.url));
|
|
4573
|
+
const tokenResult = await getVerificationToken(ctx.client, args.url, method).catch(gscErrorHandler);
|
|
4574
|
+
if (args.json) {
|
|
4575
|
+
console.log(JSON.stringify({
|
|
4576
|
+
siteUrl: args.url,
|
|
4577
|
+
status: "added",
|
|
4578
|
+
method,
|
|
4579
|
+
token: tokenResult.token,
|
|
4580
|
+
site: tokenResult.site,
|
|
4581
|
+
verified: false,
|
|
4582
|
+
next: "Place the token, then run `gscdump sites verify <url> --method <m>`"
|
|
4583
|
+
}, null, 2));
|
|
4584
|
+
return;
|
|
4585
|
+
}
|
|
4586
|
+
logger.success(`Added: ${args.url}`);
|
|
4587
|
+
printPlacementInstructions(method, args.url, tokenResult.token);
|
|
4588
|
+
const ok = await confirm({
|
|
4589
|
+
message: "Token placed? Trigger Google verification now?",
|
|
4590
|
+
initialValue: true
|
|
4591
|
+
});
|
|
4592
|
+
if (isCancel(ok) || !ok) {
|
|
4593
|
+
logger.info("Skipped verification. Run `gscdump sites verify` once the token is live.");
|
|
4594
|
+
return;
|
|
4595
|
+
}
|
|
4596
|
+
const resource = await verifySite(ctx.client, args.url, method).catch(gscErrorHandler);
|
|
4597
|
+
logger.success(`Verified: ${args.url}`);
|
|
4598
|
+
if (resource.owners?.length) {
|
|
4599
|
+
console.log();
|
|
4600
|
+
console.log(` Owners:`);
|
|
4601
|
+
for (const o of resource.owners) console.log(` \x1B[90m└─\x1B[0m ${o}`);
|
|
4602
|
+
}
|
|
4603
|
+
}
|
|
4604
|
+
});
|
|
4605
|
+
const deleteCommand = defineCommand({
|
|
4606
|
+
meta: {
|
|
4607
|
+
name: "delete",
|
|
4608
|
+
description: "Remove a property from Search Console"
|
|
4609
|
+
},
|
|
4610
|
+
args: {
|
|
4611
|
+
url: {
|
|
4612
|
+
type: "positional",
|
|
4613
|
+
required: true,
|
|
4614
|
+
description: "Property URL to remove"
|
|
4615
|
+
},
|
|
4616
|
+
yes: {
|
|
3882
4617
|
type: "boolean",
|
|
3883
|
-
alias: "
|
|
4618
|
+
alias: "y",
|
|
3884
4619
|
default: false,
|
|
3885
|
-
description: "
|
|
3886
|
-
}
|
|
4620
|
+
description: "Skip confirmation prompt"
|
|
4621
|
+
},
|
|
4622
|
+
...OUTPUT_ARGS
|
|
3887
4623
|
},
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
description: "Property URL (https://example.com/ or sc-domain:example.com)"
|
|
3899
|
-
},
|
|
3900
|
-
json: {
|
|
3901
|
-
type: "boolean",
|
|
3902
|
-
default: false,
|
|
3903
|
-
description: "Output as JSON"
|
|
3904
|
-
},
|
|
3905
|
-
quiet: {
|
|
3906
|
-
type: "boolean",
|
|
3907
|
-
alias: "q",
|
|
3908
|
-
default: false,
|
|
3909
|
-
description: "Suppress info/success output"
|
|
3910
|
-
}
|
|
3911
|
-
},
|
|
3912
|
-
async run({ args }) {
|
|
3913
|
-
setQuiet(Boolean(args.quiet) || Boolean(args.json));
|
|
3914
|
-
await addSite((await createCommandContext({ needsAuth: true })).client, args.url).catch(gscErrorHandler);
|
|
3915
|
-
if (args.json) {
|
|
3916
|
-
console.log(JSON.stringify({
|
|
3917
|
-
siteUrl: args.url,
|
|
3918
|
-
status: "added",
|
|
3919
|
-
verified: false
|
|
3920
|
-
}, null, 2));
|
|
3921
|
-
return;
|
|
3922
|
-
}
|
|
3923
|
-
logger.success(`Added: ${args.url}`);
|
|
3924
|
-
logger.info(`Property is in unverified state. Verify ownership next:`);
|
|
3925
|
-
const method = pickDefaultMethod(args.url);
|
|
3926
|
-
console.log(` \x1B[2mgscdump sites verify-token ${args.url} --method ${method}\x1B[0m`);
|
|
3927
|
-
}
|
|
3928
|
-
}),
|
|
3929
|
-
"delete": defineCommand({
|
|
3930
|
-
meta: {
|
|
3931
|
-
name: "delete",
|
|
3932
|
-
description: "Remove a property from Search Console"
|
|
3933
|
-
},
|
|
3934
|
-
args: {
|
|
3935
|
-
url: {
|
|
3936
|
-
type: "positional",
|
|
3937
|
-
required: true,
|
|
3938
|
-
description: "Property URL to remove"
|
|
3939
|
-
},
|
|
3940
|
-
yes: {
|
|
3941
|
-
type: "boolean",
|
|
3942
|
-
alias: "y",
|
|
3943
|
-
default: false,
|
|
3944
|
-
description: "Skip confirmation prompt"
|
|
3945
|
-
},
|
|
3946
|
-
json: {
|
|
3947
|
-
type: "boolean",
|
|
3948
|
-
default: false,
|
|
3949
|
-
description: "Output as JSON"
|
|
3950
|
-
},
|
|
3951
|
-
quiet: {
|
|
3952
|
-
type: "boolean",
|
|
3953
|
-
alias: "q",
|
|
3954
|
-
default: false,
|
|
3955
|
-
description: "Suppress info/success output"
|
|
3956
|
-
}
|
|
3957
|
-
},
|
|
3958
|
-
async run({ args }) {
|
|
3959
|
-
setQuiet(Boolean(args.quiet) || Boolean(args.json));
|
|
3960
|
-
if (!args.yes && !args.json) {
|
|
3961
|
-
const ok = await confirm({
|
|
3962
|
-
message: `Remove ${args.url} from Search Console? Local synced data is unaffected.`,
|
|
3963
|
-
initialValue: false
|
|
3964
|
-
});
|
|
3965
|
-
if (isCancel(ok) || !ok) {
|
|
3966
|
-
logger.info("Cancelled");
|
|
3967
|
-
process.exit(0);
|
|
3968
|
-
}
|
|
3969
|
-
}
|
|
3970
|
-
await deleteSite((await createCommandContext({ needsAuth: true })).client, args.url).catch(gscErrorHandler);
|
|
3971
|
-
if (args.json) {
|
|
3972
|
-
console.log(JSON.stringify({
|
|
3973
|
-
siteUrl: args.url,
|
|
3974
|
-
status: "deleted"
|
|
3975
|
-
}, null, 2));
|
|
3976
|
-
return;
|
|
3977
|
-
}
|
|
3978
|
-
logger.success(`Removed: ${args.url}`);
|
|
3979
|
-
}
|
|
3980
|
-
}),
|
|
3981
|
-
"verify-token": defineCommand({
|
|
3982
|
-
meta: {
|
|
3983
|
-
name: "verify-token",
|
|
3984
|
-
description: "Get a verification token to place on the site or in DNS"
|
|
3985
|
-
},
|
|
3986
|
-
args: {
|
|
3987
|
-
url: {
|
|
3988
|
-
type: "positional",
|
|
3989
|
-
required: true,
|
|
3990
|
-
description: "Property URL"
|
|
3991
|
-
},
|
|
3992
|
-
method: {
|
|
3993
|
-
type: "string",
|
|
3994
|
-
alias: "m",
|
|
3995
|
-
description: "META, FILE, DNS_TXT, DNS_CNAME, ANALYTICS, TAG_MANAGER (default: META for URL-prefix, DNS_TXT for sc-domain:)"
|
|
3996
|
-
},
|
|
3997
|
-
json: {
|
|
3998
|
-
type: "boolean",
|
|
3999
|
-
default: false,
|
|
4000
|
-
description: "Output as JSON"
|
|
4001
|
-
},
|
|
4002
|
-
quiet: {
|
|
4003
|
-
type: "boolean",
|
|
4004
|
-
alias: "q",
|
|
4005
|
-
default: false,
|
|
4006
|
-
description: "Suppress info/success output"
|
|
4007
|
-
}
|
|
4008
|
-
},
|
|
4009
|
-
async run({ args }) {
|
|
4010
|
-
setQuiet(Boolean(args.quiet) || Boolean(args.json));
|
|
4011
|
-
const method = validateMethod(args.url, args.method ?? pickDefaultMethod(args.url));
|
|
4012
|
-
const result = await getVerificationToken((await createCommandContext({ needsAuth: true })).client, args.url, method).catch(gscErrorHandler);
|
|
4013
|
-
if (args.json) {
|
|
4014
|
-
console.log(JSON.stringify({
|
|
4015
|
-
siteUrl: args.url,
|
|
4016
|
-
method,
|
|
4017
|
-
token: result.token,
|
|
4018
|
-
site: result.site
|
|
4019
|
-
}, null, 2));
|
|
4020
|
-
return;
|
|
4021
|
-
}
|
|
4022
|
-
printPlacementInstructions(method, args.url, result.token);
|
|
4624
|
+
async run({ args }) {
|
|
4625
|
+
applyOutputMode(args);
|
|
4626
|
+
if (!args.yes && !args.json) {
|
|
4627
|
+
const ok = await confirm({
|
|
4628
|
+
message: `Remove ${args.url} from Search Console? Local synced data is unaffected.`,
|
|
4629
|
+
initialValue: false
|
|
4630
|
+
});
|
|
4631
|
+
if (isCancel(ok) || !ok) {
|
|
4632
|
+
logger.info("Cancelled");
|
|
4633
|
+
process.exit(0);
|
|
4023
4634
|
}
|
|
4024
|
-
}
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4635
|
+
}
|
|
4636
|
+
await deleteSite((await createCommandContext({ needsAuth: true })).client, args.url).catch(gscErrorHandler);
|
|
4637
|
+
if (args.json) {
|
|
4638
|
+
console.log(JSON.stringify({
|
|
4639
|
+
siteUrl: args.url,
|
|
4640
|
+
status: "deleted"
|
|
4641
|
+
}, null, 2));
|
|
4642
|
+
return;
|
|
4643
|
+
}
|
|
4644
|
+
logger.success(`Removed: ${args.url}`);
|
|
4645
|
+
}
|
|
4646
|
+
});
|
|
4647
|
+
const verifyTokenCommand = defineCommand({
|
|
4648
|
+
meta: {
|
|
4649
|
+
name: "verify-token",
|
|
4650
|
+
description: "Get a verification token to place on the site or in DNS"
|
|
4651
|
+
},
|
|
4652
|
+
args: {
|
|
4653
|
+
url: {
|
|
4654
|
+
type: "positional",
|
|
4655
|
+
required: true,
|
|
4656
|
+
description: "Property URL"
|
|
4657
|
+
},
|
|
4658
|
+
method: {
|
|
4659
|
+
type: "string",
|
|
4660
|
+
alias: "m",
|
|
4661
|
+
description: "META, FILE, DNS_TXT, DNS_CNAME, ANALYTICS, TAG_MANAGER (default: META for URL-prefix, DNS_TXT for sc-domain:)"
|
|
4662
|
+
},
|
|
4663
|
+
...OUTPUT_ARGS
|
|
4664
|
+
},
|
|
4665
|
+
async run({ args }) {
|
|
4666
|
+
applyOutputMode(args);
|
|
4667
|
+
const method = validateMethod(args.url, args.method ?? pickDefaultMethod(args.url));
|
|
4668
|
+
const result = await getVerificationToken((await createCommandContext({ needsAuth: true })).client, args.url, method).catch(gscErrorHandler);
|
|
4669
|
+
if (args.json) {
|
|
4670
|
+
console.log(JSON.stringify({
|
|
4671
|
+
siteUrl: args.url,
|
|
4672
|
+
method,
|
|
4673
|
+
token: result.token,
|
|
4674
|
+
site: result.site
|
|
4675
|
+
}, null, 2));
|
|
4676
|
+
return;
|
|
4677
|
+
}
|
|
4678
|
+
printPlacementInstructions(method, args.url, result.token);
|
|
4679
|
+
}
|
|
4680
|
+
});
|
|
4681
|
+
const verifyCommand = defineCommand({
|
|
4682
|
+
meta: {
|
|
4683
|
+
name: "verify",
|
|
4684
|
+
description: "Trigger verification — Google fetches/validates the token you placed"
|
|
4685
|
+
},
|
|
4686
|
+
args: {
|
|
4687
|
+
url: {
|
|
4688
|
+
type: "positional",
|
|
4689
|
+
required: true,
|
|
4690
|
+
description: "Property URL"
|
|
4691
|
+
},
|
|
4692
|
+
method: {
|
|
4693
|
+
type: "string",
|
|
4694
|
+
alias: "m",
|
|
4695
|
+
description: "Verification method to validate (must match the one used for verify-token)"
|
|
4696
|
+
},
|
|
4697
|
+
...OUTPUT_ARGS
|
|
4698
|
+
},
|
|
4699
|
+
async run({ args }) {
|
|
4700
|
+
applyOutputMode(args);
|
|
4701
|
+
const method = validateMethod(args.url, args.method ?? pickDefaultMethod(args.url));
|
|
4702
|
+
const resource = await verifySite((await createCommandContext({ needsAuth: true })).client, args.url, method).catch(gscErrorHandler);
|
|
4703
|
+
if (args.json) {
|
|
4704
|
+
console.log(JSON.stringify({
|
|
4705
|
+
siteUrl: args.url,
|
|
4706
|
+
method,
|
|
4707
|
+
resource
|
|
4708
|
+
}, null, 2));
|
|
4709
|
+
return;
|
|
4710
|
+
}
|
|
4711
|
+
logger.success(`Verified: ${args.url}`);
|
|
4712
|
+
if (resource.owners?.length) {
|
|
4713
|
+
console.log();
|
|
4714
|
+
console.log(` Owners:`);
|
|
4715
|
+
for (const o of resource.owners) console.log(` \x1B[90m└─\x1B[0m ${o}`);
|
|
4716
|
+
}
|
|
4717
|
+
}
|
|
4718
|
+
});
|
|
4719
|
+
const verifyGetCommand = defineCommand({
|
|
4720
|
+
meta: {
|
|
4721
|
+
name: "verify-get",
|
|
4722
|
+
description: "Get a single verified WebResource by id"
|
|
4723
|
+
},
|
|
4724
|
+
args: {
|
|
4725
|
+
id: {
|
|
4726
|
+
type: "positional",
|
|
4727
|
+
required: true,
|
|
4728
|
+
description: "WebResource id (from `sites verify-list`)"
|
|
4729
|
+
},
|
|
4730
|
+
...OUTPUT_ARGS
|
|
4731
|
+
},
|
|
4732
|
+
async run({ args }) {
|
|
4733
|
+
applyOutputMode(args);
|
|
4734
|
+
const resource = await getVerifiedSite((await createCommandContext({ needsAuth: true })).client, args.id).catch(gscErrorHandler);
|
|
4735
|
+
if (args.json) {
|
|
4736
|
+
console.log(JSON.stringify(resource, null, 2));
|
|
4737
|
+
return;
|
|
4738
|
+
}
|
|
4739
|
+
const ident = resource.site?.identifier ?? resource.id ?? "?";
|
|
4740
|
+
const type = resource.site?.type === "INET_DOMAIN" ? "domain" : "site";
|
|
4741
|
+
console.log();
|
|
4742
|
+
console.log(` \x1B[1m${ident}\x1B[0m \x1B[90m(${type})\x1B[0m`);
|
|
4743
|
+
console.log(` \x1B[90mid:\x1B[0m ${resource.id ?? "?"}`);
|
|
4744
|
+
if (resource.owners?.length) {
|
|
4745
|
+
console.log(` Owners:`);
|
|
4746
|
+
for (const o of resource.owners) console.log(` \x1B[90m└─\x1B[0m ${o}`);
|
|
4747
|
+
}
|
|
4748
|
+
console.log();
|
|
4749
|
+
}
|
|
4750
|
+
});
|
|
4751
|
+
const unverifyCommand = defineCommand({
|
|
4752
|
+
meta: {
|
|
4753
|
+
name: "unverify",
|
|
4754
|
+
description: "Drop your verified ownership of a WebResource (remove the placed token first!)"
|
|
4755
|
+
},
|
|
4756
|
+
args: {
|
|
4757
|
+
id: {
|
|
4758
|
+
type: "positional",
|
|
4759
|
+
required: true,
|
|
4760
|
+
description: "WebResource id (from `sites verify-list`)"
|
|
4761
|
+
},
|
|
4762
|
+
yes: {
|
|
4763
|
+
type: "boolean",
|
|
4764
|
+
alias: "y",
|
|
4765
|
+
default: false,
|
|
4766
|
+
description: "Skip confirmation prompt"
|
|
4767
|
+
},
|
|
4768
|
+
...OUTPUT_ARGS
|
|
4769
|
+
},
|
|
4770
|
+
async run({ args }) {
|
|
4771
|
+
applyOutputMode(args);
|
|
4772
|
+
if (!args.yes && !args.json) {
|
|
4773
|
+
const ok = await confirm({
|
|
4774
|
+
message: `Unverify WebResource ${args.id}? Remove any placed verification token first or Google may re-verify.`,
|
|
4775
|
+
initialValue: false
|
|
4776
|
+
});
|
|
4777
|
+
if (isCancel(ok) || !ok) {
|
|
4778
|
+
logger.info("Cancelled");
|
|
4779
|
+
process.exit(0);
|
|
4071
4780
|
}
|
|
4072
|
-
}
|
|
4781
|
+
}
|
|
4782
|
+
await unverifySite((await createCommandContext({ needsAuth: true })).client, args.id).catch(gscErrorHandler);
|
|
4783
|
+
if (args.json) {
|
|
4784
|
+
console.log(JSON.stringify({
|
|
4785
|
+
id: args.id,
|
|
4786
|
+
status: "unverified"
|
|
4787
|
+
}, null, 2));
|
|
4788
|
+
return;
|
|
4789
|
+
}
|
|
4790
|
+
logger.success(`Unverified: ${args.id}`);
|
|
4791
|
+
}
|
|
4792
|
+
});
|
|
4793
|
+
const verifyListCommand = defineCommand({
|
|
4794
|
+
meta: {
|
|
4795
|
+
name: "verify-list",
|
|
4796
|
+
description: "List verified WebResources from the Site Verification API (distinct from Search Console properties)"
|
|
4073
4797
|
},
|
|
4798
|
+
args: { ...OUTPUT_ARGS },
|
|
4074
4799
|
async run({ args }) {
|
|
4075
|
-
|
|
4076
|
-
const
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4800
|
+
applyOutputMode(args);
|
|
4801
|
+
const resources = await listVerifiedSites((await createCommandContext({ needsAuth: true })).client).catch(gscErrorHandler);
|
|
4802
|
+
if (args.json) {
|
|
4803
|
+
console.log(JSON.stringify(resources, null, 2));
|
|
4804
|
+
return;
|
|
4805
|
+
}
|
|
4806
|
+
if (resources.length === 0) {
|
|
4807
|
+
logger.warn("No verified WebResources found");
|
|
4808
|
+
return;
|
|
4809
|
+
}
|
|
4810
|
+
logger.success(`${resources.length} verified WebResources:`);
|
|
4811
|
+
console.log();
|
|
4812
|
+
for (const r of resources) {
|
|
4813
|
+
const id = r.id ?? "?";
|
|
4814
|
+
const site = r.site;
|
|
4815
|
+
const ident = site?.identifier ?? id;
|
|
4816
|
+
const type = site?.type === "INET_DOMAIN" ? "domain" : "site";
|
|
4817
|
+
console.log(` \x1B[1m${ident}\x1B[0m \x1B[90m(${type})\x1B[0m`);
|
|
4818
|
+
if (r.owners?.length) for (const o of r.owners) console.log(` \x1B[90m└─\x1B[0m ${o}`);
|
|
4819
|
+
}
|
|
4820
|
+
}
|
|
4821
|
+
});
|
|
4822
|
+
const getCommand = defineCommand({
|
|
4823
|
+
meta: {
|
|
4824
|
+
name: "get",
|
|
4825
|
+
description: "Show a single property's permissionLevel from the sites list"
|
|
4826
|
+
},
|
|
4827
|
+
args: {
|
|
4828
|
+
url: {
|
|
4829
|
+
type: "positional",
|
|
4830
|
+
required: true,
|
|
4831
|
+
description: "Property URL"
|
|
4832
|
+
},
|
|
4833
|
+
...OUTPUT_ARGS
|
|
4834
|
+
},
|
|
4835
|
+
async run({ args }) {
|
|
4836
|
+
applyOutputMode(args);
|
|
4837
|
+
const site = (await (await createCommandContext({ needsAuth: true })).loadSites()).find((s) => s.siteUrl === args.url);
|
|
4838
|
+
if (!site) {
|
|
4081
4839
|
if (args.json) {
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
sitemapCounts: {
|
|
4085
|
-
total: s.sitemaps.length,
|
|
4086
|
-
pending: s.sitemaps.filter((sm) => sm.isPending).length,
|
|
4087
|
-
errored: s.sitemaps.filter((sm) => Number(sm.errors) > 0).length
|
|
4088
|
-
}
|
|
4089
|
-
}));
|
|
4090
|
-
console.log(JSON.stringify(enriched, null, 2));
|
|
4091
|
-
return;
|
|
4092
|
-
}
|
|
4093
|
-
if (sites.length === 0) {
|
|
4094
|
-
logger.warn(ownerOnly ? "No owned sites found" : "No verified sites found");
|
|
4095
|
-
return;
|
|
4096
|
-
}
|
|
4097
|
-
logger.success(`Found ${sites.length} ${ownerOnly ? "owned" : "verified"} sites:`);
|
|
4098
|
-
console.log();
|
|
4099
|
-
for (const site of sites) {
|
|
4100
|
-
const perm = site.permissionLevel === "siteOwner" ? "\x1B[32m" : "\x1B[90m";
|
|
4101
|
-
console.log(` ${site.siteUrl} ${perm}(${site.permissionLevel})\x1B[0m`);
|
|
4102
|
-
for (const sm of site.sitemaps) {
|
|
4103
|
-
const pending = sm.isPending ? " \x1B[33m(pending)\x1B[0m" : "";
|
|
4104
|
-
console.log(` \x1B[90m└─\x1B[0m ${sm.path}${pending}`);
|
|
4105
|
-
}
|
|
4840
|
+
console.log(JSON.stringify(null));
|
|
4841
|
+
process.exit(1);
|
|
4106
4842
|
}
|
|
4843
|
+
logger.error(`Not found: ${args.url}`);
|
|
4844
|
+
process.exit(1);
|
|
4845
|
+
}
|
|
4846
|
+
if (args.json) {
|
|
4847
|
+
console.log(JSON.stringify(site, null, 2));
|
|
4107
4848
|
return;
|
|
4108
4849
|
}
|
|
4109
|
-
const
|
|
4850
|
+
const perm = site.permissionLevel === "siteOwner" ? "\x1B[32m" : "\x1B[90m";
|
|
4851
|
+
console.log();
|
|
4852
|
+
console.log(` \x1B[1m${site.siteUrl}\x1B[0m`);
|
|
4853
|
+
console.log(` Permission: ${perm}${site.permissionLevel}\x1B[0m`);
|
|
4854
|
+
console.log();
|
|
4855
|
+
}
|
|
4856
|
+
});
|
|
4857
|
+
const LIST_ARGS = {
|
|
4858
|
+
...OUTPUT_ARGS,
|
|
4859
|
+
"with-sitemaps": {
|
|
4860
|
+
type: "boolean",
|
|
4861
|
+
default: false,
|
|
4862
|
+
description: "Include sitemaps for each owned site"
|
|
4863
|
+
},
|
|
4864
|
+
"owner-only": {
|
|
4865
|
+
type: "boolean",
|
|
4866
|
+
default: false,
|
|
4867
|
+
description: "Filter to permissionLevel=siteOwner"
|
|
4868
|
+
}
|
|
4869
|
+
};
|
|
4870
|
+
async function runListSites(args) {
|
|
4871
|
+
applyOutputMode(args);
|
|
4872
|
+
const ctx = await createCommandContext({ needsAuth: true });
|
|
4873
|
+
const ownerOnly = Boolean(args["owner-only"]);
|
|
4874
|
+
if (args["with-sitemaps"]) {
|
|
4875
|
+
const all = await fetchSitesWithSitemaps(ctx.client).catch(gscErrorHandler);
|
|
4110
4876
|
const sites = ownerOnly ? all.filter((s) => s.permissionLevel === "siteOwner") : all;
|
|
4111
4877
|
if (args.json) {
|
|
4112
|
-
|
|
4878
|
+
const enriched = sites.map((s) => ({
|
|
4879
|
+
...s,
|
|
4880
|
+
sitemapCounts: {
|
|
4881
|
+
total: s.sitemaps.length,
|
|
4882
|
+
pending: s.sitemaps.filter((sm) => sm.isPending).length,
|
|
4883
|
+
errored: s.sitemaps.filter((sm) => Number(sm.errors) > 0).length
|
|
4884
|
+
}
|
|
4885
|
+
}));
|
|
4886
|
+
console.log(JSON.stringify(enriched, null, 2));
|
|
4113
4887
|
return;
|
|
4114
4888
|
}
|
|
4115
4889
|
if (sites.length === 0) {
|
|
4116
4890
|
logger.warn(ownerOnly ? "No owned sites found" : "No verified sites found");
|
|
4117
4891
|
return;
|
|
4118
4892
|
}
|
|
4119
|
-
logger.success(`Found ${sites.length} ${ownerOnly ? "owned
|
|
4893
|
+
logger.success(`Found ${sites.length} ${ownerOnly ? "owned" : "verified"} sites:`);
|
|
4120
4894
|
console.log();
|
|
4121
4895
|
for (const site of sites) {
|
|
4122
4896
|
const perm = site.permissionLevel === "siteOwner" ? "\x1B[32m" : "\x1B[90m";
|
|
4123
4897
|
console.log(` ${site.siteUrl} ${perm}(${site.permissionLevel})\x1B[0m`);
|
|
4898
|
+
for (const sm of site.sitemaps) {
|
|
4899
|
+
const pending = sm.isPending ? " \x1B[33m(pending)\x1B[0m" : "";
|
|
4900
|
+
console.log(` \x1B[90m└─\x1B[0m ${sm.path}${pending}`);
|
|
4901
|
+
}
|
|
4124
4902
|
}
|
|
4903
|
+
return;
|
|
4904
|
+
}
|
|
4905
|
+
const all = await ctx.loadSites();
|
|
4906
|
+
const sites = ownerOnly ? all.filter((s) => s.permissionLevel === "siteOwner") : all;
|
|
4907
|
+
if (args.json) {
|
|
4908
|
+
console.log(JSON.stringify(sites, null, 2));
|
|
4909
|
+
return;
|
|
4910
|
+
}
|
|
4911
|
+
if (sites.length === 0) {
|
|
4912
|
+
logger.warn(ownerOnly ? "No owned sites found" : "No verified sites found");
|
|
4913
|
+
return;
|
|
4914
|
+
}
|
|
4915
|
+
logger.success(`Found ${sites.length} ${ownerOnly ? "owned " : ""}sites:`);
|
|
4916
|
+
console.log();
|
|
4917
|
+
for (const site of sites) {
|
|
4918
|
+
const perm = site.permissionLevel === "siteOwner" ? "\x1B[32m" : "\x1B[90m";
|
|
4919
|
+
console.log(` ${site.siteUrl} ${perm}(${site.permissionLevel})\x1B[0m`);
|
|
4920
|
+
}
|
|
4921
|
+
}
|
|
4922
|
+
const sitesCommand = defineCommand({
|
|
4923
|
+
meta: {
|
|
4924
|
+
name: "sites",
|
|
4925
|
+
description: "List GSC sites; manage properties (add/delete) and verify ownership"
|
|
4926
|
+
},
|
|
4927
|
+
args: LIST_ARGS,
|
|
4928
|
+
subCommands: {
|
|
4929
|
+
"list": defineCommand({
|
|
4930
|
+
meta: {
|
|
4931
|
+
name: "list",
|
|
4932
|
+
description: "List GSC sites (alias of bare `sites`)"
|
|
4933
|
+
},
|
|
4934
|
+
args: LIST_ARGS,
|
|
4935
|
+
async run({ args }) {
|
|
4936
|
+
await runListSites(args);
|
|
4937
|
+
}
|
|
4938
|
+
}),
|
|
4939
|
+
"add": addCommand,
|
|
4940
|
+
"delete": deleteCommand,
|
|
4941
|
+
"get": getCommand,
|
|
4942
|
+
"verify-token": verifyTokenCommand,
|
|
4943
|
+
"verify": verifyCommand,
|
|
4944
|
+
"verify-list": verifyListCommand,
|
|
4945
|
+
"verify-get": verifyGetCommand,
|
|
4946
|
+
"unverify": unverifyCommand
|
|
4947
|
+
},
|
|
4948
|
+
async run({ args }) {
|
|
4949
|
+
await runListSites(args);
|
|
4125
4950
|
}
|
|
4126
4951
|
});
|
|
4127
4952
|
const compactCommand = defineCommand({
|
|
@@ -4152,20 +4977,10 @@ const compactCommand = defineCommand({
|
|
|
4152
4977
|
default: false,
|
|
4153
4978
|
description: "Report tier counts per (table, site) without compacting"
|
|
4154
4979
|
},
|
|
4155
|
-
|
|
4156
|
-
type: "boolean",
|
|
4157
|
-
default: false,
|
|
4158
|
-
description: "Output a JSON summary"
|
|
4159
|
-
},
|
|
4160
|
-
"quiet": {
|
|
4161
|
-
type: "boolean",
|
|
4162
|
-
alias: "q",
|
|
4163
|
-
default: false,
|
|
4164
|
-
description: "Suppress progress output"
|
|
4165
|
-
}
|
|
4980
|
+
...OUTPUT_ARGS
|
|
4166
4981
|
},
|
|
4167
4982
|
async run({ args }) {
|
|
4168
|
-
|
|
4983
|
+
const { json } = applyOutputMode(args);
|
|
4169
4984
|
const store = (await createCommandContext({ needsStore: true })).store;
|
|
4170
4985
|
const siteId = args.site ? store.siteIdFor(String(args.site)) : void 0;
|
|
4171
4986
|
const dryRun = Boolean(args["dry-run"]);
|
|
@@ -4187,7 +5002,7 @@ const compactCommand = defineCommand({
|
|
|
4187
5002
|
...countByTier(group)
|
|
4188
5003
|
});
|
|
4189
5004
|
}
|
|
4190
|
-
if (
|
|
5005
|
+
if (json) {
|
|
4191
5006
|
console.log(JSON.stringify({
|
|
4192
5007
|
thresholds,
|
|
4193
5008
|
plan: report
|
|
@@ -4222,7 +5037,7 @@ const compactCommand = defineCommand({
|
|
|
4222
5037
|
});
|
|
4223
5038
|
}
|
|
4224
5039
|
}
|
|
4225
|
-
if (
|
|
5040
|
+
if (json) {
|
|
4226
5041
|
console.log(JSON.stringify({
|
|
4227
5042
|
thresholds,
|
|
4228
5043
|
compacted: summary
|
|
@@ -4314,20 +5129,10 @@ const exportCommand = defineCommand({
|
|
|
4314
5129
|
default: false,
|
|
4315
5130
|
description: "Overwrite the output file if it already exists"
|
|
4316
5131
|
},
|
|
4317
|
-
|
|
4318
|
-
type: "boolean",
|
|
4319
|
-
default: false,
|
|
4320
|
-
description: "Output a JSON summary instead of formatted text"
|
|
4321
|
-
},
|
|
4322
|
-
quiet: {
|
|
4323
|
-
type: "boolean",
|
|
4324
|
-
alias: "q",
|
|
4325
|
-
default: false,
|
|
4326
|
-
description: "Suppress info/success output"
|
|
4327
|
-
}
|
|
5132
|
+
...OUTPUT_ARGS
|
|
4328
5133
|
},
|
|
4329
5134
|
async run({ args }) {
|
|
4330
|
-
|
|
5135
|
+
const { json } = applyOutputMode(args);
|
|
4331
5136
|
const store = (await createCommandContext({ needsStore: true })).store;
|
|
4332
5137
|
const siteId = args.site ? store.siteIdFor(args.site) : void 0;
|
|
4333
5138
|
const result = await exportToDuckDB({
|
|
@@ -4338,7 +5143,7 @@ const exportCommand = defineCommand({
|
|
|
4338
5143
|
outPath: args.out,
|
|
4339
5144
|
force: args.force
|
|
4340
5145
|
});
|
|
4341
|
-
if (
|
|
5146
|
+
if (json) {
|
|
4342
5147
|
console.log(JSON.stringify(result, null, 2));
|
|
4343
5148
|
return;
|
|
4344
5149
|
}
|
|
@@ -4374,20 +5179,10 @@ const gcCommand = defineCommand({
|
|
|
4374
5179
|
default: false,
|
|
4375
5180
|
description: "List retired manifest entries past the grace window without deleting"
|
|
4376
5181
|
},
|
|
4377
|
-
|
|
4378
|
-
type: "boolean",
|
|
4379
|
-
default: false,
|
|
4380
|
-
description: "Output a JSON summary"
|
|
4381
|
-
},
|
|
4382
|
-
"quiet": {
|
|
4383
|
-
type: "boolean",
|
|
4384
|
-
alias: "q",
|
|
4385
|
-
default: false,
|
|
4386
|
-
description: "Suppress progress output"
|
|
4387
|
-
}
|
|
5182
|
+
...OUTPUT_ARGS
|
|
4388
5183
|
},
|
|
4389
5184
|
async run({ args }) {
|
|
4390
|
-
|
|
5185
|
+
const { json } = applyOutputMode(args);
|
|
4391
5186
|
const store = (await createCommandContext({ needsStore: true })).store;
|
|
4392
5187
|
const siteId = args.site ? store.siteIdFor(String(args.site)) : void 0;
|
|
4393
5188
|
const graceMs = Number(args["grace-hours"]) * 36e5;
|
|
@@ -4408,7 +5203,7 @@ const gcCommand = defineCommand({
|
|
|
4408
5203
|
objectKey: e.objectKey
|
|
4409
5204
|
});
|
|
4410
5205
|
}
|
|
4411
|
-
if (
|
|
5206
|
+
if (json) {
|
|
4412
5207
|
console.log(JSON.stringify({
|
|
4413
5208
|
graceHours: Number(args["grace-hours"]),
|
|
4414
5209
|
candidates
|
|
@@ -4425,7 +5220,7 @@ const gcCommand = defineCommand({
|
|
|
4425
5220
|
userId: store.userId,
|
|
4426
5221
|
siteId
|
|
4427
5222
|
}, graceMs);
|
|
4428
|
-
if (
|
|
5223
|
+
if (json) {
|
|
4429
5224
|
console.log(JSON.stringify({
|
|
4430
5225
|
graceHours: Number(args["grace-hours"]),
|
|
4431
5226
|
deleted: result.deleted
|
|
@@ -4446,25 +5241,15 @@ const rollupsCommand = defineCommand({
|
|
|
4446
5241
|
description: "Rebuild post-sync rollups (daily totals, weekly totals, top-N tables) for a site"
|
|
4447
5242
|
},
|
|
4448
5243
|
args: {
|
|
5244
|
+
...OUTPUT_ARGS,
|
|
4449
5245
|
site: {
|
|
4450
5246
|
type: "string",
|
|
4451
5247
|
alias: "s",
|
|
4452
5248
|
description: "Restrict to a single site (default: all sites with local data)"
|
|
4453
|
-
},
|
|
4454
|
-
json: {
|
|
4455
|
-
type: "boolean",
|
|
4456
|
-
default: false,
|
|
4457
|
-
description: "Output a JSON summary"
|
|
4458
|
-
},
|
|
4459
|
-
quiet: {
|
|
4460
|
-
type: "boolean",
|
|
4461
|
-
alias: "q",
|
|
4462
|
-
default: false,
|
|
4463
|
-
description: "Suppress progress output"
|
|
4464
5249
|
}
|
|
4465
5250
|
},
|
|
4466
5251
|
async run({ args }) {
|
|
4467
|
-
|
|
5252
|
+
const { json } = applyOutputMode(args);
|
|
4468
5253
|
const store = (await createCommandContext({ needsStore: true })).store;
|
|
4469
5254
|
const explicitSiteId = args.site ? store.siteIdFor(String(args.site)) : void 0;
|
|
4470
5255
|
const allSiteIds = /* @__PURE__ */ new Set();
|
|
@@ -4477,7 +5262,7 @@ const rollupsCommand = defineCommand({
|
|
|
4477
5262
|
for (const e of entries) if (e.siteId) allSiteIds.add(e.siteId);
|
|
4478
5263
|
}
|
|
4479
5264
|
if (allSiteIds.size === 0) {
|
|
4480
|
-
if (
|
|
5265
|
+
if (json) console.log(JSON.stringify({
|
|
4481
5266
|
sites: [],
|
|
4482
5267
|
totalBytes: 0
|
|
4483
5268
|
}, null, 2));
|
|
@@ -4508,11 +5293,11 @@ const rollupsCommand = defineCommand({
|
|
|
4508
5293
|
bytes: r.bytes,
|
|
4509
5294
|
objectKey: r.objectKey
|
|
4510
5295
|
});
|
|
4511
|
-
if (!
|
|
5296
|
+
if (!json) console.log(` ${r.id.padEnd(20)} ${(r.bytes / 1024).toFixed(1).padStart(8)} KB ${r.objectKey}`);
|
|
4512
5297
|
}
|
|
4513
5298
|
summary.push(site);
|
|
4514
5299
|
}
|
|
4515
|
-
if (
|
|
5300
|
+
if (json) {
|
|
4516
5301
|
console.log(JSON.stringify({
|
|
4517
5302
|
sites: summary,
|
|
4518
5303
|
totalBytes
|
|
@@ -4529,24 +5314,14 @@ const statsCommand = defineCommand({
|
|
|
4529
5314
|
description: "Show row/byte counts per table and on-disk footprint"
|
|
4530
5315
|
},
|
|
4531
5316
|
args: {
|
|
4532
|
-
|
|
4533
|
-
type: "boolean",
|
|
4534
|
-
default: false,
|
|
4535
|
-
description: "Output as JSON"
|
|
4536
|
-
},
|
|
5317
|
+
...OUTPUT_ARGS,
|
|
4537
5318
|
site: {
|
|
4538
5319
|
type: "string",
|
|
4539
5320
|
description: "Limit to one site URL (sc-domain:example.com, https://example.com/, ...)"
|
|
4540
|
-
},
|
|
4541
|
-
quiet: {
|
|
4542
|
-
type: "boolean",
|
|
4543
|
-
alias: "q",
|
|
4544
|
-
default: false,
|
|
4545
|
-
description: "Suppress info/success output"
|
|
4546
5321
|
}
|
|
4547
5322
|
},
|
|
4548
5323
|
async run({ args }) {
|
|
4549
|
-
|
|
5324
|
+
const { json } = applyOutputMode(args);
|
|
4550
5325
|
const store = (await createCommandContext({ needsStore: true })).store;
|
|
4551
5326
|
let siteId;
|
|
4552
5327
|
if (args.site) {
|
|
@@ -4578,7 +5353,7 @@ const statsCommand = defineCommand({
|
|
|
4578
5353
|
files: 0,
|
|
4579
5354
|
bytes: 0
|
|
4580
5355
|
}));
|
|
4581
|
-
if (
|
|
5356
|
+
if (json) {
|
|
4582
5357
|
const payload = {
|
|
4583
5358
|
dataDir: store.dataDir,
|
|
4584
5359
|
disk,
|
|
@@ -4664,11 +5439,98 @@ const storeCommand = defineCommand({
|
|
|
4664
5439
|
description: "Manage the local DuckDB/Parquet store"
|
|
4665
5440
|
},
|
|
4666
5441
|
subCommands: {
|
|
4667
|
-
stats: statsCommand,
|
|
4668
|
-
compact: compactCommand,
|
|
4669
|
-
gc: gcCommand,
|
|
4670
|
-
export: exportCommand,
|
|
4671
|
-
rollups: rollupsCommand
|
|
5442
|
+
"stats": statsCommand,
|
|
5443
|
+
"compact": compactCommand,
|
|
5444
|
+
"gc": gcCommand,
|
|
5445
|
+
"export": exportCommand,
|
|
5446
|
+
"rollups": rollupsCommand,
|
|
5447
|
+
"rm-site": defineCommand({
|
|
5448
|
+
meta: {
|
|
5449
|
+
name: "rm-site",
|
|
5450
|
+
description: "Delete every parquet, manifest, watermark, and sync-state record for a single site"
|
|
5451
|
+
},
|
|
5452
|
+
args: {
|
|
5453
|
+
site: {
|
|
5454
|
+
type: "positional",
|
|
5455
|
+
required: true,
|
|
5456
|
+
description: "Site URL (e.g. sc-domain:example.com)"
|
|
5457
|
+
},
|
|
5458
|
+
yes: {
|
|
5459
|
+
type: "boolean",
|
|
5460
|
+
alias: "y",
|
|
5461
|
+
default: false,
|
|
5462
|
+
description: "Skip confirmation prompt"
|
|
5463
|
+
},
|
|
5464
|
+
...OUTPUT_ARGS
|
|
5465
|
+
},
|
|
5466
|
+
async run({ args }) {
|
|
5467
|
+
const { json } = applyOutputMode(args);
|
|
5468
|
+
const store = (await createCommandContext({ needsStore: true })).store;
|
|
5469
|
+
const siteId = store.siteIdFor(String(args.site));
|
|
5470
|
+
if (!args.yes && !json) {
|
|
5471
|
+
const ok = await confirm({
|
|
5472
|
+
message: `Delete ALL local data for ${args.site}? This is irreversible.`,
|
|
5473
|
+
initialValue: false
|
|
5474
|
+
});
|
|
5475
|
+
if (isCancel(ok) || !ok) {
|
|
5476
|
+
logger.info("Cancelled");
|
|
5477
|
+
process.exit(0);
|
|
5478
|
+
}
|
|
5479
|
+
}
|
|
5480
|
+
const result = await store.engine.purgeTenant({
|
|
5481
|
+
userId: store.userId,
|
|
5482
|
+
siteId
|
|
5483
|
+
});
|
|
5484
|
+
if (json) {
|
|
5485
|
+
console.log(JSON.stringify(result, null, 2));
|
|
5486
|
+
return;
|
|
5487
|
+
}
|
|
5488
|
+
logger.success(`Removed local data for ${args.site}`);
|
|
5489
|
+
console.log(` Objects deleted: ${result.objectsDeleted}`);
|
|
5490
|
+
console.log(` Manifest entries: ${result.entriesRemoved}`);
|
|
5491
|
+
console.log(` Watermarks: ${result.watermarksRemoved}`);
|
|
5492
|
+
console.log(` Sync states: ${result.syncStatesRemoved}`);
|
|
5493
|
+
}
|
|
5494
|
+
}),
|
|
5495
|
+
"reset": defineCommand({
|
|
5496
|
+
meta: {
|
|
5497
|
+
name: "reset",
|
|
5498
|
+
description: "Wipe the entire local store (every site, every table). Irreversible."
|
|
5499
|
+
},
|
|
5500
|
+
args: {
|
|
5501
|
+
yes: {
|
|
5502
|
+
type: "boolean",
|
|
5503
|
+
alias: "y",
|
|
5504
|
+
default: false,
|
|
5505
|
+
description: "Skip confirmation prompt"
|
|
5506
|
+
},
|
|
5507
|
+
...OUTPUT_ARGS
|
|
5508
|
+
},
|
|
5509
|
+
async run({ args }) {
|
|
5510
|
+
const { json } = applyOutputMode(args);
|
|
5511
|
+
const store = (await createCommandContext({ needsStore: true })).store;
|
|
5512
|
+
if (!args.yes && !json) {
|
|
5513
|
+
const ok = await confirm({
|
|
5514
|
+
message: `Wipe the entire local store under ${store.dataDir}? This deletes data for ALL sites.`,
|
|
5515
|
+
initialValue: false
|
|
5516
|
+
});
|
|
5517
|
+
if (isCancel(ok) || !ok) {
|
|
5518
|
+
logger.info("Cancelled");
|
|
5519
|
+
process.exit(0);
|
|
5520
|
+
}
|
|
5521
|
+
}
|
|
5522
|
+
const result = await store.engine.purgeTenant({ userId: store.userId });
|
|
5523
|
+
if (json) {
|
|
5524
|
+
console.log(JSON.stringify(result, null, 2));
|
|
5525
|
+
return;
|
|
5526
|
+
}
|
|
5527
|
+
logger.success("Local store reset");
|
|
5528
|
+
console.log(` Objects deleted: ${result.objectsDeleted}`);
|
|
5529
|
+
console.log(` Manifest entries: ${result.entriesRemoved}`);
|
|
5530
|
+
console.log(` Watermarks: ${result.watermarksRemoved}`);
|
|
5531
|
+
console.log(` Sync states: ${result.syncStatesRemoved}`);
|
|
5532
|
+
}
|
|
5533
|
+
})
|
|
4672
5534
|
}
|
|
4673
5535
|
});
|
|
4674
5536
|
const DEFAULT_TABLES = [
|
|
@@ -4844,12 +5706,7 @@ const syncCommand = defineCommand({
|
|
|
4844
5706
|
type: "boolean",
|
|
4845
5707
|
description: "Sync the last 450 days (full GSC history)"
|
|
4846
5708
|
},
|
|
4847
|
-
|
|
4848
|
-
type: "boolean",
|
|
4849
|
-
alias: "q",
|
|
4850
|
-
default: false,
|
|
4851
|
-
description: "Suppress progress output"
|
|
4852
|
-
},
|
|
5709
|
+
...OUTPUT_ARGS,
|
|
4853
5710
|
"force": {
|
|
4854
5711
|
type: "boolean",
|
|
4855
5712
|
default: false,
|
|
@@ -4860,11 +5717,6 @@ const syncCommand = defineCommand({
|
|
|
4860
5717
|
default: false,
|
|
4861
5718
|
description: "Print watermarks + sync-state summary instead of syncing"
|
|
4862
5719
|
},
|
|
4863
|
-
"json": {
|
|
4864
|
-
type: "boolean",
|
|
4865
|
-
default: false,
|
|
4866
|
-
description: "With --status: emit JSON"
|
|
4867
|
-
},
|
|
4868
5720
|
"concurrency": {
|
|
4869
5721
|
type: "string",
|
|
4870
5722
|
alias: "c",
|
|
@@ -4887,8 +5739,9 @@ const syncCommand = defineCommand({
|
|
|
4887
5739
|
}
|
|
4888
5740
|
},
|
|
4889
5741
|
async run({ args }) {
|
|
5742
|
+
const { json, quiet } = applyOutputMode(args);
|
|
4890
5743
|
if (args.status) {
|
|
4891
|
-
await printSyncStatus(await loadConfig(), args.site ? String(args.site) : void 0,
|
|
5744
|
+
await printSyncStatus(await loadConfig(), args.site ? String(args.site) : void 0, json);
|
|
4892
5745
|
return;
|
|
4893
5746
|
}
|
|
4894
5747
|
const ctx = await createCommandContext({
|
|
@@ -4923,7 +5776,7 @@ const syncCommand = defineCommand({
|
|
|
4923
5776
|
logger.warn(`All requested types (${requestedTypes.join(", ")}) are marked empty for this site. Pass --force-types to re-probe.`);
|
|
4924
5777
|
return;
|
|
4925
5778
|
}
|
|
4926
|
-
if (skippedTypes.length > 0 && !
|
|
5779
|
+
if (skippedTypes.length > 0 && !quiet) logger.info(`Skipping ${skippedTypes.join(", ")} (marked empty for this site; pass --force-types to re-probe).`);
|
|
4927
5780
|
const endDate = args.end ? String(args.end) : daysAgo(DEFAULT_PENDING_DAYS);
|
|
4928
5781
|
let startDate;
|
|
4929
5782
|
if (args.start) startDate = String(args.start);
|
|
@@ -4953,7 +5806,7 @@ const syncCommand = defineCommand({
|
|
|
4953
5806
|
return;
|
|
4954
5807
|
}
|
|
4955
5808
|
args.force = true;
|
|
4956
|
-
if (!
|
|
5809
|
+
if (!quiet) logger.info(`--retry-failed: ${dates.length} date(s) to retry`);
|
|
4957
5810
|
}
|
|
4958
5811
|
if (args["dry-run"]) {
|
|
4959
5812
|
const plan = [];
|
|
@@ -4962,7 +5815,7 @@ const syncCommand = defineCommand({
|
|
|
4962
5815
|
searchType: type,
|
|
4963
5816
|
date
|
|
4964
5817
|
});
|
|
4965
|
-
if (
|
|
5818
|
+
if (json) {
|
|
4966
5819
|
console.log(JSON.stringify({
|
|
4967
5820
|
siteUrl,
|
|
4968
5821
|
range: {
|
|
@@ -4985,7 +5838,7 @@ const syncCommand = defineCommand({
|
|
|
4985
5838
|
logger.info("Pass without --dry-run to execute.");
|
|
4986
5839
|
return;
|
|
4987
5840
|
}
|
|
4988
|
-
if (!
|
|
5841
|
+
if (!quiet) {
|
|
4989
5842
|
logger.info(`Syncing ${siteUrl} (${tables.join(", ")}) [${types.join(", ")}] → ${displayPath(store.dataDir)}`);
|
|
4990
5843
|
logger.info(`Range: ${startDate} → ${endDate} (${dates.length} days)`);
|
|
4991
5844
|
}
|
|
@@ -5002,7 +5855,7 @@ const syncCommand = defineCommand({
|
|
|
5002
5855
|
label
|
|
5003
5856
|
});
|
|
5004
5857
|
}
|
|
5005
|
-
const progress = createProgressTracker(dates.length * jobs.length,
|
|
5858
|
+
const progress = createProgressTracker(dates.length * jobs.length, quiet);
|
|
5006
5859
|
if (serialTables) for (const job of jobs) totals[job.label] = await syncTable(store, siteUrl, job.table, job.type, dates, client, concurrency, args.force, progress);
|
|
5007
5860
|
else {
|
|
5008
5861
|
const results = await Promise.all(jobs.map((job) => syncTable(store, siteUrl, job.table, job.type, dates, client, concurrency, args.force, progress)));
|
|
@@ -5012,7 +5865,7 @@ const syncCommand = defineCommand({
|
|
|
5012
5865
|
}
|
|
5013
5866
|
progress.done();
|
|
5014
5867
|
const seconds = ((Date.now() - start) / 1e3).toFixed(1);
|
|
5015
|
-
if (!
|
|
5868
|
+
if (!quiet) {
|
|
5016
5869
|
logger.success(`Synced ${siteUrl} in ${seconds}s`);
|
|
5017
5870
|
for (const [t, n] of Object.entries(totals)) {
|
|
5018
5871
|
const suffix = [n.skipped > 0 ? `${n.skipped} skipped` : null, n.failed > 0 ? `\x1B[31m${n.failed} failed\x1B[0m` : null].filter(Boolean).join(", ");
|
|
@@ -5041,7 +5894,7 @@ const syncCommand = defineCommand({
|
|
|
5041
5894
|
userId: store.userId,
|
|
5042
5895
|
siteId
|
|
5043
5896
|
}, toMark);
|
|
5044
|
-
if (!
|
|
5897
|
+
if (!quiet) logger.info(`Marked empty for future syncs: ${toMark.join(", ")} (0 rows across ${dates.length} days; pass --force-types to re-probe).`);
|
|
5045
5898
|
}
|
|
5046
5899
|
}
|
|
5047
5900
|
if (forceTypes && emptyTypesDoc.emptyTypes.length > 0) {
|
|
@@ -5052,13 +5905,13 @@ const syncCommand = defineCommand({
|
|
|
5052
5905
|
userId: store.userId,
|
|
5053
5906
|
siteId
|
|
5054
5907
|
}, toClear);
|
|
5055
|
-
if (!
|
|
5908
|
+
if (!quiet) logger.info(`Cleared empty markers for: ${toClear.join(", ")} (re-probe found data).`);
|
|
5056
5909
|
}
|
|
5057
5910
|
}
|
|
5058
5911
|
const noRollups = Boolean(args["no-rollups"]);
|
|
5059
5912
|
const anyRowsSynced = Object.values(totals).some((t) => t.rows > 0);
|
|
5060
5913
|
if (!noRollups && anyRowsSynced) {
|
|
5061
|
-
if (!
|
|
5914
|
+
if (!quiet) logger.info(`Rebuilding rollups for [${siteId}] (${DEFAULT_ROLLUPS.length} rollups)…`);
|
|
5062
5915
|
const rollupStart = Date.now();
|
|
5063
5916
|
const results = await rebuildRollups({
|
|
5064
5917
|
engine: store.engine,
|
|
@@ -5072,7 +5925,7 @@ const syncCommand = defineCommand({
|
|
|
5072
5925
|
logger.warn(`Rollup rebuild failed: ${err.message}`);
|
|
5073
5926
|
return [];
|
|
5074
5927
|
});
|
|
5075
|
-
if (!
|
|
5928
|
+
if (!quiet && results.length > 0) {
|
|
5076
5929
|
const kb = results.reduce((a, r) => a + r.bytes, 0) / 1024;
|
|
5077
5930
|
const ms = Date.now() - rollupStart;
|
|
5078
5931
|
logger.success(`Rebuilt ${results.length} rollup(s) in ${ms}ms — ${kb.toFixed(1)} KB`);
|
|
@@ -5160,10 +6013,11 @@ function shouldShowSplash() {
|
|
|
5160
6013
|
function applyGlobalArgs() {
|
|
5161
6014
|
const argv = process.argv;
|
|
5162
6015
|
if (argv.includes("--no-color") || process.env.NO_COLOR) setNoColor(true);
|
|
5163
|
-
const profile = pluckArgValue(argv, "--profile")
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
6016
|
+
const profile = pluckArgValue(argv, "--profile");
|
|
6017
|
+
applyProfileFromCli({
|
|
6018
|
+
configDir: pluckArgValue(argv, "--config-dir") ?? process.env.GSCDUMP_CONFIG_DIR ?? null,
|
|
6019
|
+
profile
|
|
6020
|
+
});
|
|
5167
6021
|
if (argv.includes("-v") && !argv.includes("--version")) {
|
|
5168
6022
|
const i = argv.indexOf("-v");
|
|
5169
6023
|
argv[i] = "--version";
|
|
@@ -5205,8 +6059,10 @@ runMain(defineCommand({
|
|
|
5205
6059
|
indexing: indexingCommand,
|
|
5206
6060
|
entities: entitiesCommand,
|
|
5207
6061
|
analyze: analyzeCommand,
|
|
6062
|
+
report: reportCommand,
|
|
5208
6063
|
auth: authCommand,
|
|
5209
6064
|
config: configCommand,
|
|
6065
|
+
profile: profileCommand,
|
|
5210
6066
|
doctor: doctorCommand,
|
|
5211
6067
|
mcp: mcpCommand
|
|
5212
6068
|
},
|