@gscdump/cli 0.8.1 → 0.8.2
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/_chunks/libs/node-fetch-native.mjs +1 -1
- package/dist/index.mjs +1295 -680
- package/package.json +7 -7
package/dist/index.mjs
CHANGED
|
@@ -2,20 +2,20 @@
|
|
|
2
2
|
import { t as __exportAll } from "./_chunks/rolldown-runtime.mjs";
|
|
3
3
|
import { t as ofetch } from "./_chunks/libs/ofetch.mjs";
|
|
4
4
|
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 os from "node:os";
|
|
6
|
-
import path from "node:path";
|
|
7
5
|
import process from "node:process";
|
|
8
6
|
import { defineCommand, runMain } from "citty";
|
|
9
7
|
import { defaultAnalyzerRegistry } from "@gscdump/analysis/registry";
|
|
10
8
|
import { AnalyzerCapabilityError, analyzeFromSource, createEngineQuerySource } from "@gscdump/analysis";
|
|
11
9
|
import { createGscApiQuerySource } from "@gscdump/engine-gsc-api";
|
|
12
10
|
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";
|
|
11
|
+
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";
|
|
14
12
|
import fs, { readFile, rm } from "node:fs/promises";
|
|
15
13
|
import { createServer } from "node:http";
|
|
14
|
+
import path from "node:path";
|
|
16
15
|
import { JWT, OAuth2Client } from "google-auth-library";
|
|
17
16
|
import { Buffer } from "node:buffer";
|
|
18
17
|
import fs$1 from "node:fs";
|
|
18
|
+
import os from "node:os";
|
|
19
19
|
import { createConsola } from "consola";
|
|
20
20
|
import { createNodeHarness } from "@gscdump/engine-duckdb-node";
|
|
21
21
|
import { TABLE_DIMS, transformGscRow } from "@gscdump/engine/ingest";
|
|
@@ -110,7 +110,7 @@ function loadEnvFromCwd() {
|
|
|
110
110
|
}
|
|
111
111
|
return applied;
|
|
112
112
|
}
|
|
113
|
-
const VERSION = "0.8.
|
|
113
|
+
const VERSION = "0.8.2";
|
|
114
114
|
const baseLogger = createConsola({
|
|
115
115
|
stdout: process.stderr,
|
|
116
116
|
stderr: process.stderr
|
|
@@ -119,6 +119,28 @@ const logger = baseLogger.withTag("gscdump");
|
|
|
119
119
|
function setQuiet(quiet) {
|
|
120
120
|
if (quiet) baseLogger.level = 1;
|
|
121
121
|
}
|
|
122
|
+
const OUTPUT_ARGS = {
|
|
123
|
+
json: {
|
|
124
|
+
type: "boolean",
|
|
125
|
+
default: false,
|
|
126
|
+
description: "Output as JSON"
|
|
127
|
+
},
|
|
128
|
+
quiet: {
|
|
129
|
+
type: "boolean",
|
|
130
|
+
alias: "q",
|
|
131
|
+
default: false,
|
|
132
|
+
description: "Suppress info/success output"
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
function applyOutputMode(args) {
|
|
136
|
+
const json = Boolean(args.json);
|
|
137
|
+
const quiet = json || Boolean(args.quiet);
|
|
138
|
+
setQuiet(quiet);
|
|
139
|
+
return {
|
|
140
|
+
json,
|
|
141
|
+
quiet
|
|
142
|
+
};
|
|
143
|
+
}
|
|
122
144
|
let colorEnabled = (() => {
|
|
123
145
|
if (process.env.NO_COLOR) return false;
|
|
124
146
|
if (process.argv.includes("--no-color")) return false;
|
|
@@ -257,7 +279,8 @@ async function loadServiceAccount(jsonPath) {
|
|
|
257
279
|
});
|
|
258
280
|
}
|
|
259
281
|
async function resolveServiceAccount(opts = {}) {
|
|
260
|
-
|
|
282
|
+
let p = opts.path || process.env.GSC_SERVICE_ACCOUNT_JSON || process.env.GOOGLE_APPLICATION_CREDENTIALS;
|
|
283
|
+
if (!p) p = (await loadConfig().catch(() => null))?.serviceAccountPath;
|
|
261
284
|
if (!p) return null;
|
|
262
285
|
return loadServiceAccount(p);
|
|
263
286
|
}
|
|
@@ -538,15 +561,21 @@ function redactCred(v, keepTail = 6) {
|
|
|
538
561
|
async function describeAuthProvenance() {
|
|
539
562
|
const rows = [];
|
|
540
563
|
const warnings = [];
|
|
541
|
-
const
|
|
542
|
-
|
|
564
|
+
const saEnvPath = process.env.GSC_SERVICE_ACCOUNT_JSON || process.env.GOOGLE_APPLICATION_CREDENTIALS;
|
|
565
|
+
const saConfigPath = !saEnvPath ? (await loadConfig().catch(() => null))?.serviceAccountPath : void 0;
|
|
566
|
+
const saPath = saEnvPath || saConfigPath;
|
|
567
|
+
if (saEnvPath) {
|
|
543
568
|
const saEnv = process.env.GSC_SERVICE_ACCOUNT_JSON ? "GSC_SERVICE_ACCOUNT_JSON" : "GOOGLE_APPLICATION_CREDENTIALS";
|
|
544
569
|
rows.push({
|
|
545
570
|
field: "service_account",
|
|
546
571
|
source: envSourceLabel(saEnv),
|
|
547
|
-
value: displayPath(
|
|
572
|
+
value: displayPath(saEnvPath)
|
|
548
573
|
});
|
|
549
|
-
}
|
|
574
|
+
} else if (saConfigPath) rows.push({
|
|
575
|
+
field: "service_account",
|
|
576
|
+
source: `${displayPath(`${getConfigDir()}/config.json`)}`,
|
|
577
|
+
value: displayPath(saConfigPath)
|
|
578
|
+
});
|
|
550
579
|
const clientId = pickEnvSource("GSC_CLIENT_ID", "GOOGLE_CLIENT_ID");
|
|
551
580
|
const clientSecret = pickEnvSource("GSC_CLIENT_SECRET", "GOOGLE_CLIENT_SECRET");
|
|
552
581
|
const config = await loadConfig().catch(() => null);
|
|
@@ -1036,41 +1065,319 @@ const analyzeCommand = defineCommand({
|
|
|
1036
1065
|
name: "analyze",
|
|
1037
1066
|
description: "SEO analysis tools"
|
|
1038
1067
|
},
|
|
1039
|
-
subCommands:
|
|
1068
|
+
subCommands: {
|
|
1069
|
+
list: defineCommand({
|
|
1070
|
+
meta: {
|
|
1071
|
+
name: "list",
|
|
1072
|
+
description: "List available analyzer ids"
|
|
1073
|
+
},
|
|
1074
|
+
args: { json: {
|
|
1075
|
+
type: "boolean",
|
|
1076
|
+
default: false,
|
|
1077
|
+
description: "Output as JSON"
|
|
1078
|
+
} },
|
|
1079
|
+
async run({ args }) {
|
|
1080
|
+
if (args.json) {
|
|
1081
|
+
console.log(JSON.stringify(ANALYSIS_TOOLS, null, 2));
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
for (const id of ANALYSIS_TOOLS) console.log(id);
|
|
1085
|
+
}
|
|
1086
|
+
}),
|
|
1087
|
+
...Object.fromEntries(ANALYSIS_TOOLS.map((tool) => [tool, makeToolCommand(tool)]))
|
|
1088
|
+
}
|
|
1089
|
+
});
|
|
1090
|
+
const ROOT_DIR = path.join(os.homedir(), ".config", "gscdump");
|
|
1091
|
+
const PROFILES_DIR = path.join(ROOT_DIR, "profiles");
|
|
1092
|
+
const ACTIVE_MARKER = path.join(ROOT_DIR, "active-profile");
|
|
1093
|
+
let activeOverride = null;
|
|
1094
|
+
let configDirOverridden = false;
|
|
1095
|
+
function getProfileDir(name) {
|
|
1096
|
+
return path.join(PROFILES_DIR, name);
|
|
1097
|
+
}
|
|
1098
|
+
function readActiveMarkerSync() {
|
|
1099
|
+
if (!fs$1.existsSync(ACTIVE_MARKER)) return null;
|
|
1100
|
+
return fs$1.readFileSync(ACTIVE_MARKER, "utf-8").trim() || null;
|
|
1101
|
+
}
|
|
1102
|
+
function resolveActiveProfile() {
|
|
1103
|
+
return activeOverride ?? process.env.GSCDUMP_PROFILE ?? readActiveMarkerSync();
|
|
1104
|
+
}
|
|
1105
|
+
function applyProfileFromCli(opts) {
|
|
1106
|
+
if (opts.configDir) {
|
|
1107
|
+
setConfigDir(opts.configDir);
|
|
1108
|
+
configDirOverridden = true;
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
if (opts.profile) {
|
|
1112
|
+
activeOverride = opts.profile;
|
|
1113
|
+
setConfigDir(getProfileDir(opts.profile));
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
1116
|
+
const envProfile = process.env.GSCDUMP_PROFILE;
|
|
1117
|
+
if (envProfile) {
|
|
1118
|
+
setConfigDir(getProfileDir(envProfile));
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
const marker = readActiveMarkerSync();
|
|
1122
|
+
if (marker) setConfigDir(getProfileDir(marker));
|
|
1123
|
+
}
|
|
1124
|
+
async function setActiveProfile(name) {
|
|
1125
|
+
await fs.mkdir(ROOT_DIR, {
|
|
1126
|
+
recursive: true,
|
|
1127
|
+
mode: 448
|
|
1128
|
+
});
|
|
1129
|
+
if (name == null) {
|
|
1130
|
+
await fs.rm(ACTIVE_MARKER, { force: true }).catch(() => {});
|
|
1131
|
+
return;
|
|
1132
|
+
}
|
|
1133
|
+
await fs.writeFile(ACTIVE_MARKER, name, { mode: 384 });
|
|
1134
|
+
}
|
|
1135
|
+
async function listProfiles() {
|
|
1136
|
+
return fs.readdir(PROFILES_DIR).then((entries) => entries.filter((e) => !e.startsWith(".")).sort()).catch(() => []);
|
|
1137
|
+
}
|
|
1138
|
+
async function createProfile(name) {
|
|
1139
|
+
const dir = getProfileDir(name);
|
|
1140
|
+
await fs.mkdir(dir, {
|
|
1141
|
+
recursive: true,
|
|
1142
|
+
mode: 448
|
|
1143
|
+
});
|
|
1144
|
+
return dir;
|
|
1145
|
+
}
|
|
1146
|
+
function profileNameFromEmail(email) {
|
|
1147
|
+
return email.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
1148
|
+
}
|
|
1149
|
+
async function adoptCurrentConfigAsProfile(name) {
|
|
1150
|
+
if (configDirOverridden) return null;
|
|
1151
|
+
if (resolveActiveProfile()) return null;
|
|
1152
|
+
const currentDir = getConfigDir();
|
|
1153
|
+
const targetDir = getProfileDir(name);
|
|
1154
|
+
if (currentDir === targetDir) return targetDir;
|
|
1155
|
+
await fs.mkdir(targetDir, {
|
|
1156
|
+
recursive: true,
|
|
1157
|
+
mode: 448
|
|
1158
|
+
});
|
|
1159
|
+
for (const f of ["tokens.json", "config.json"]) {
|
|
1160
|
+
const src = path.join(currentDir, f);
|
|
1161
|
+
const dst = path.join(targetDir, f);
|
|
1162
|
+
if (await fs.stat(src).then(() => true).catch(() => false)) await fs.rename(src, dst).catch(() => {});
|
|
1163
|
+
}
|
|
1164
|
+
await setActiveProfile(name);
|
|
1165
|
+
activeOverride = name;
|
|
1166
|
+
setConfigDir(targetDir);
|
|
1167
|
+
return targetDir;
|
|
1168
|
+
}
|
|
1169
|
+
const profileCommand = defineCommand({
|
|
1170
|
+
meta: {
|
|
1171
|
+
name: "profile",
|
|
1172
|
+
description: "Manage gscdump profiles (per-account token + config dirs)"
|
|
1173
|
+
},
|
|
1174
|
+
subCommands: {
|
|
1175
|
+
list: defineCommand({
|
|
1176
|
+
meta: {
|
|
1177
|
+
name: "list",
|
|
1178
|
+
description: "List configured profiles"
|
|
1179
|
+
},
|
|
1180
|
+
args: { ...OUTPUT_ARGS },
|
|
1181
|
+
async run({ args }) {
|
|
1182
|
+
const { json } = applyOutputMode(args);
|
|
1183
|
+
const names = await listProfiles();
|
|
1184
|
+
const active = resolveActiveProfile();
|
|
1185
|
+
if (json) {
|
|
1186
|
+
console.log(JSON.stringify({
|
|
1187
|
+
active,
|
|
1188
|
+
profiles: names
|
|
1189
|
+
}, null, 2));
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
if (names.length === 0) {
|
|
1193
|
+
logger.warn(`No profiles in ${PROFILES_DIR}`);
|
|
1194
|
+
logger.info("Run `gscdump auth login` to create one automatically, or `gscdump profile create <name>`");
|
|
1195
|
+
return;
|
|
1196
|
+
}
|
|
1197
|
+
for (const n of names) console.log(`${n === active ? "*" : " "} ${n}`);
|
|
1198
|
+
}
|
|
1199
|
+
}),
|
|
1200
|
+
path: defineCommand({
|
|
1201
|
+
meta: {
|
|
1202
|
+
name: "path",
|
|
1203
|
+
description: "Print the config directory for a profile"
|
|
1204
|
+
},
|
|
1205
|
+
args: { name: {
|
|
1206
|
+
type: "positional",
|
|
1207
|
+
required: false,
|
|
1208
|
+
description: "Profile name (default: active)"
|
|
1209
|
+
} },
|
|
1210
|
+
async run({ args }) {
|
|
1211
|
+
const name = args.name ? String(args.name) : resolveActiveProfile();
|
|
1212
|
+
if (!name) {
|
|
1213
|
+
logger.error("No profile specified and none active (set --profile, GSCDUMP_PROFILE, or run `gscdump profile use <name>`)");
|
|
1214
|
+
process.exit(1);
|
|
1215
|
+
}
|
|
1216
|
+
console.log(getProfileDir(name));
|
|
1217
|
+
}
|
|
1218
|
+
}),
|
|
1219
|
+
current: defineCommand({
|
|
1220
|
+
meta: {
|
|
1221
|
+
name: "current",
|
|
1222
|
+
description: "Print the active profile name"
|
|
1223
|
+
},
|
|
1224
|
+
async run() {
|
|
1225
|
+
const active = resolveActiveProfile();
|
|
1226
|
+
if (!active) process.exit(1);
|
|
1227
|
+
console.log(active);
|
|
1228
|
+
}
|
|
1229
|
+
}),
|
|
1230
|
+
use: defineCommand({
|
|
1231
|
+
meta: {
|
|
1232
|
+
name: "use",
|
|
1233
|
+
description: "Set the persisted active profile (subsequent commands no longer need --profile)"
|
|
1234
|
+
},
|
|
1235
|
+
args: {
|
|
1236
|
+
...OUTPUT_ARGS,
|
|
1237
|
+
name: {
|
|
1238
|
+
type: "positional",
|
|
1239
|
+
required: true,
|
|
1240
|
+
description: "Profile name"
|
|
1241
|
+
}
|
|
1242
|
+
},
|
|
1243
|
+
async run({ args }) {
|
|
1244
|
+
applyOutputMode(args);
|
|
1245
|
+
const name = String(args.name);
|
|
1246
|
+
const dir = getProfileDir(name);
|
|
1247
|
+
if (!await fs.stat(dir).then(() => true).catch(() => false)) {
|
|
1248
|
+
logger.error(`Profile not found: ${name}`);
|
|
1249
|
+
logger.info(`Create it with: gscdump profile create ${name}`);
|
|
1250
|
+
process.exit(1);
|
|
1251
|
+
}
|
|
1252
|
+
await setActiveProfile(name);
|
|
1253
|
+
logger.success(`Active profile: ${name}`);
|
|
1254
|
+
}
|
|
1255
|
+
}),
|
|
1256
|
+
create: defineCommand({
|
|
1257
|
+
meta: {
|
|
1258
|
+
name: "create",
|
|
1259
|
+
description: "Create an empty profile directory"
|
|
1260
|
+
},
|
|
1261
|
+
args: {
|
|
1262
|
+
...OUTPUT_ARGS,
|
|
1263
|
+
"name": {
|
|
1264
|
+
type: "positional",
|
|
1265
|
+
required: true,
|
|
1266
|
+
description: "Profile name"
|
|
1267
|
+
},
|
|
1268
|
+
"no-use": {
|
|
1269
|
+
type: "boolean",
|
|
1270
|
+
default: false,
|
|
1271
|
+
description: "Do not mark the new profile as active"
|
|
1272
|
+
}
|
|
1273
|
+
},
|
|
1274
|
+
async run({ args }) {
|
|
1275
|
+
applyOutputMode(args);
|
|
1276
|
+
const name = String(args.name);
|
|
1277
|
+
const dir = await createProfile(name);
|
|
1278
|
+
if (!args["no-use"]) await setActiveProfile(name);
|
|
1279
|
+
logger.success(`Created profile: ${name}${args["no-use"] ? "" : " (active)"}`);
|
|
1280
|
+
logger.info(displayPath(dir));
|
|
1281
|
+
}
|
|
1282
|
+
}),
|
|
1283
|
+
clear: defineCommand({
|
|
1284
|
+
meta: {
|
|
1285
|
+
name: "clear",
|
|
1286
|
+
description: "Clear the persisted active profile (commands fall back to root config dir)"
|
|
1287
|
+
},
|
|
1288
|
+
args: { ...OUTPUT_ARGS },
|
|
1289
|
+
async run({ args }) {
|
|
1290
|
+
applyOutputMode(args);
|
|
1291
|
+
await setActiveProfile(null);
|
|
1292
|
+
logger.success("Cleared active profile");
|
|
1293
|
+
}
|
|
1294
|
+
}),
|
|
1295
|
+
delete: defineCommand({
|
|
1296
|
+
meta: {
|
|
1297
|
+
name: "delete",
|
|
1298
|
+
description: "Remove a profile directory (tokens + config)"
|
|
1299
|
+
},
|
|
1300
|
+
args: {
|
|
1301
|
+
...OUTPUT_ARGS,
|
|
1302
|
+
name: {
|
|
1303
|
+
type: "positional",
|
|
1304
|
+
required: true,
|
|
1305
|
+
description: "Profile name"
|
|
1306
|
+
},
|
|
1307
|
+
yes: {
|
|
1308
|
+
type: "boolean",
|
|
1309
|
+
alias: "y",
|
|
1310
|
+
default: false,
|
|
1311
|
+
description: "Skip confirmation"
|
|
1312
|
+
}
|
|
1313
|
+
},
|
|
1314
|
+
async run({ args }) {
|
|
1315
|
+
applyOutputMode(args);
|
|
1316
|
+
const name = String(args.name);
|
|
1317
|
+
const dir = getProfileDir(name);
|
|
1318
|
+
if (!await fs.stat(dir).then(() => true).catch(() => false)) {
|
|
1319
|
+
logger.error(`Profile not found: ${name}`);
|
|
1320
|
+
process.exit(1);
|
|
1321
|
+
}
|
|
1322
|
+
if (!args.yes) {
|
|
1323
|
+
const ok = await confirm({
|
|
1324
|
+
message: `Delete profile "${name}" at ${dir}? Tokens and config will be lost.`,
|
|
1325
|
+
initialValue: false
|
|
1326
|
+
});
|
|
1327
|
+
if (isCancel(ok) || !ok) {
|
|
1328
|
+
logger.info("Cancelled");
|
|
1329
|
+
process.exit(0);
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
await fs.rm(dir, {
|
|
1333
|
+
recursive: true,
|
|
1334
|
+
force: true
|
|
1335
|
+
});
|
|
1336
|
+
if (readActiveMarkerSync() === name) await setActiveProfile(null);
|
|
1337
|
+
logger.success(`Removed profile: ${name}`);
|
|
1338
|
+
}
|
|
1339
|
+
})
|
|
1340
|
+
}
|
|
1040
1341
|
});
|
|
1342
|
+
const REQUIRED_SCOPES$1 = [
|
|
1343
|
+
"https://www.googleapis.com/auth/webmasters",
|
|
1344
|
+
"https://www.googleapis.com/auth/indexing",
|
|
1345
|
+
"https://www.googleapis.com/auth/siteverification"
|
|
1346
|
+
];
|
|
1041
1347
|
async function fetchTokenInfo(accessToken) {
|
|
1042
1348
|
return ofetch("https://oauth2.googleapis.com/tokeninfo", { query: { access_token: accessToken } }).catch(() => null);
|
|
1043
1349
|
}
|
|
1350
|
+
async function resolveLiveAuthState() {
|
|
1351
|
+
const tokens = await loadTokens();
|
|
1352
|
+
const byok = resolveBYOK();
|
|
1353
|
+
let liveToken = null;
|
|
1354
|
+
if (typeof byok === "string") liveToken = byok;
|
|
1355
|
+
else if (byok && "getAccessToken" in byok) liveToken = await byok.getAccessToken().then((r) => r.token ?? null).catch(() => null);
|
|
1356
|
+
else if (tokens?.access_token) liveToken = tokens.access_token;
|
|
1357
|
+
const tokenInfo = liveToken ? await fetchTokenInfo(liveToken) : null;
|
|
1358
|
+
const scopes = tokenInfo?.scope ? tokenInfo.scope.split(/\s+/).filter(Boolean) : [];
|
|
1359
|
+
const has = (s) => scopes.includes(s) || scopes.includes(s.replace(".readonly", ""));
|
|
1360
|
+
const missing = REQUIRED_SCOPES$1.filter((s) => !has(s));
|
|
1361
|
+
return {
|
|
1362
|
+
byok,
|
|
1363
|
+
tokens,
|
|
1364
|
+
liveToken,
|
|
1365
|
+
tokenInfo,
|
|
1366
|
+
scopes,
|
|
1367
|
+
missing
|
|
1368
|
+
};
|
|
1369
|
+
}
|
|
1044
1370
|
const statusCommand$1 = defineCommand({
|
|
1045
1371
|
meta: {
|
|
1046
1372
|
name: "status",
|
|
1047
1373
|
description: "Show current authentication status"
|
|
1048
1374
|
},
|
|
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
|
-
},
|
|
1375
|
+
args: { ...OUTPUT_ARGS },
|
|
1062
1376
|
async run({ args }) {
|
|
1063
|
-
|
|
1064
|
-
const tokens = await
|
|
1065
|
-
const byok = resolveBYOK();
|
|
1377
|
+
const { json } = applyOutputMode(args);
|
|
1378
|
+
const { byok, tokens, tokenInfo, scopes, missing } = await resolveLiveAuthState();
|
|
1066
1379
|
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) {
|
|
1380
|
+
if (json) {
|
|
1074
1381
|
console.log(JSON.stringify({
|
|
1075
1382
|
authenticated: !!tokens || !!byok,
|
|
1076
1383
|
source: byok ? "byok" : tokens ? "saved-tokens" : null,
|
|
@@ -1089,14 +1396,7 @@ const statusCommand$1 = defineCommand({
|
|
|
1089
1396
|
const reportScopes = () => {
|
|
1090
1397
|
if (scopes.length === 0) return;
|
|
1091
1398
|
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
1399
|
for (const s of scopes) console.log(` \x1B[90m└─\x1B[0m ${s}`);
|
|
1099
|
-
const missing = required.filter((s) => !has(s));
|
|
1100
1400
|
if (missing.length > 0) {
|
|
1101
1401
|
console.log(` \x1B[33mMissing scopes:\x1B[0m`);
|
|
1102
1402
|
for (const s of missing) console.log(` \x1B[90m└─\x1B[0m ${s}`);
|
|
@@ -1136,14 +1436,9 @@ const refreshCommand = defineCommand({
|
|
|
1136
1436
|
name: "refresh",
|
|
1137
1437
|
description: "Force-refresh saved OAuth tokens (no-op for BYOK)"
|
|
1138
1438
|
},
|
|
1139
|
-
args: {
|
|
1140
|
-
type: "boolean",
|
|
1141
|
-
alias: "q",
|
|
1142
|
-
default: false,
|
|
1143
|
-
description: "Suppress info/success output"
|
|
1144
|
-
} },
|
|
1439
|
+
args: { ...OUTPUT_ARGS },
|
|
1145
1440
|
async run({ args }) {
|
|
1146
|
-
|
|
1441
|
+
applyOutputMode(args);
|
|
1147
1442
|
if (resolveBYOK()) {
|
|
1148
1443
|
logger.info("BYOK detected; refresh handled per-call by the SDK");
|
|
1149
1444
|
return;
|
|
@@ -1181,6 +1476,7 @@ const authCommand = defineCommand({
|
|
|
1181
1476
|
description: "Run OAuth flow and persist tokens (skip if BYOK env vars set)"
|
|
1182
1477
|
},
|
|
1183
1478
|
args: {
|
|
1479
|
+
...OUTPUT_ARGS,
|
|
1184
1480
|
"force": {
|
|
1185
1481
|
type: "boolean",
|
|
1186
1482
|
alias: "f",
|
|
@@ -1195,22 +1491,17 @@ const authCommand = defineCommand({
|
|
|
1195
1491
|
"service-account": {
|
|
1196
1492
|
type: "string",
|
|
1197
1493
|
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
1494
|
}
|
|
1205
1495
|
},
|
|
1206
1496
|
async run({ args }) {
|
|
1207
|
-
|
|
1497
|
+
applyOutputMode(args);
|
|
1208
1498
|
if (resolveBYOK() && !args.force) {
|
|
1209
1499
|
logger.info("BYOK env vars detected, no login needed (--force to override)");
|
|
1210
1500
|
return;
|
|
1211
1501
|
}
|
|
1212
1502
|
if (args["service-account"]) {
|
|
1213
|
-
const
|
|
1503
|
+
const saPath = path.resolve(String(args["service-account"]));
|
|
1504
|
+
const jwt = await loadServiceAccount(saPath).catch((e) => {
|
|
1214
1505
|
logger.error(`Service-account load failed: ${e.message}`);
|
|
1215
1506
|
process.exit(1);
|
|
1216
1507
|
});
|
|
@@ -1218,8 +1509,11 @@ const authCommand = defineCommand({
|
|
|
1218
1509
|
logger.error(`Service-account auth failed: ${e.message}`);
|
|
1219
1510
|
process.exit(1);
|
|
1220
1511
|
});
|
|
1512
|
+
const config = await loadConfig();
|
|
1513
|
+
config.serviceAccountPath = saPath;
|
|
1514
|
+
await saveConfig(config);
|
|
1221
1515
|
logger.success(`Service-account verified: ${jwt.email ?? "OK"}`);
|
|
1222
|
-
logger.info(`
|
|
1516
|
+
logger.info(`Saved path to config: ${saPath}`);
|
|
1223
1517
|
return;
|
|
1224
1518
|
}
|
|
1225
1519
|
if (args.force) await clearTokens();
|
|
@@ -1232,6 +1526,14 @@ const authCommand = defineCommand({
|
|
|
1232
1526
|
process.exit(1);
|
|
1233
1527
|
});
|
|
1234
1528
|
logger.success("Logged in");
|
|
1529
|
+
if (!resolveActiveProfile()) {
|
|
1530
|
+
const tokens = await loadTokens();
|
|
1531
|
+
const info = tokens?.access_token ? await fetchTokenInfo(tokens.access_token) : null;
|
|
1532
|
+
if (info?.email) {
|
|
1533
|
+
const name = profileNameFromEmail(info.email);
|
|
1534
|
+
if (await adoptCurrentConfigAsProfile(name).catch(() => null)) logger.success(`Saved as profile "${name}" (active)`);
|
|
1535
|
+
}
|
|
1536
|
+
}
|
|
1235
1537
|
if (resolveBYOK()) {
|
|
1236
1538
|
console.log();
|
|
1237
1539
|
console.log(await formatAuthProvenance());
|
|
@@ -1243,18 +1545,44 @@ const authCommand = defineCommand({
|
|
|
1243
1545
|
name: "logout",
|
|
1244
1546
|
description: "Clear stored OAuth tokens"
|
|
1245
1547
|
},
|
|
1246
|
-
args: {
|
|
1247
|
-
type: "boolean",
|
|
1248
|
-
alias: "q",
|
|
1249
|
-
default: false,
|
|
1250
|
-
description: "Suppress info/success output"
|
|
1251
|
-
} },
|
|
1548
|
+
args: { ...OUTPUT_ARGS },
|
|
1252
1549
|
async run({ args }) {
|
|
1253
|
-
|
|
1550
|
+
applyOutputMode(args);
|
|
1254
1551
|
await clearTokens();
|
|
1552
|
+
const config = await loadConfig();
|
|
1553
|
+
if (config.serviceAccountPath) {
|
|
1554
|
+
delete config.serviceAccountPath;
|
|
1555
|
+
await saveConfig(config);
|
|
1556
|
+
logger.info("Cleared saved service-account path");
|
|
1557
|
+
}
|
|
1255
1558
|
}
|
|
1256
1559
|
}),
|
|
1257
|
-
refresh: refreshCommand
|
|
1560
|
+
refresh: refreshCommand,
|
|
1561
|
+
scopes: defineCommand({
|
|
1562
|
+
meta: {
|
|
1563
|
+
name: "scopes",
|
|
1564
|
+
description: "Print granted OAuth scopes (one per line); exits 1 if any required scope is missing"
|
|
1565
|
+
},
|
|
1566
|
+
args: { ...OUTPUT_ARGS },
|
|
1567
|
+
async run({ args }) {
|
|
1568
|
+
const { json } = applyOutputMode(args);
|
|
1569
|
+
const { liveToken, scopes, missing } = await resolveLiveAuthState();
|
|
1570
|
+
if (!liveToken) {
|
|
1571
|
+
if (json) console.log(JSON.stringify({
|
|
1572
|
+
scopes: [],
|
|
1573
|
+
missing: null
|
|
1574
|
+
}, null, 2));
|
|
1575
|
+
else logger.error("Not authenticated");
|
|
1576
|
+
process.exit(1);
|
|
1577
|
+
}
|
|
1578
|
+
if (json) console.log(JSON.stringify({
|
|
1579
|
+
scopes,
|
|
1580
|
+
missing
|
|
1581
|
+
}, null, 2));
|
|
1582
|
+
else for (const s of scopes) console.log(s);
|
|
1583
|
+
if (missing.length > 0) process.exit(1);
|
|
1584
|
+
}
|
|
1585
|
+
})
|
|
1258
1586
|
}
|
|
1259
1587
|
});
|
|
1260
1588
|
const showCommand = defineCommand({
|
|
@@ -1262,24 +1590,12 @@ const showCommand = defineCommand({
|
|
|
1262
1590
|
name: "show",
|
|
1263
1591
|
description: "Show current config"
|
|
1264
1592
|
},
|
|
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
|
-
},
|
|
1593
|
+
args: { ...OUTPUT_ARGS },
|
|
1278
1594
|
async run({ args }) {
|
|
1279
|
-
|
|
1595
|
+
const { json } = applyOutputMode(args);
|
|
1280
1596
|
const config = await loadConfig();
|
|
1281
1597
|
const configPath = getConfigPath();
|
|
1282
|
-
if (
|
|
1598
|
+
if (json) {
|
|
1283
1599
|
console.log(JSON.stringify({
|
|
1284
1600
|
path: configPath,
|
|
1285
1601
|
config
|
|
@@ -1303,7 +1619,8 @@ const VALID_KEYS = [
|
|
|
1303
1619
|
"dataDir",
|
|
1304
1620
|
"defaultLimit",
|
|
1305
1621
|
"defaultSearchType",
|
|
1306
|
-
"defaultDataState"
|
|
1622
|
+
"defaultDataState",
|
|
1623
|
+
"serviceAccountPath"
|
|
1307
1624
|
];
|
|
1308
1625
|
const NUMERIC_KEYS = new Set(["defaultLimit"]);
|
|
1309
1626
|
const configCommand = defineCommand({
|
|
@@ -1329,15 +1646,10 @@ const configCommand = defineCommand({
|
|
|
1329
1646
|
description: "Value to set",
|
|
1330
1647
|
required: true
|
|
1331
1648
|
},
|
|
1332
|
-
|
|
1333
|
-
type: "boolean",
|
|
1334
|
-
alias: "q",
|
|
1335
|
-
default: false,
|
|
1336
|
-
description: "Suppress info/success output"
|
|
1337
|
-
}
|
|
1649
|
+
...OUTPUT_ARGS
|
|
1338
1650
|
},
|
|
1339
1651
|
async run({ args }) {
|
|
1340
|
-
|
|
1652
|
+
applyOutputMode(args);
|
|
1341
1653
|
if (!VALID_KEYS.includes(args.key)) {
|
|
1342
1654
|
logger.error(`Invalid key: ${args.key}`);
|
|
1343
1655
|
logger.info(`Valid keys: ${VALID_KEYS.join(", ")}`);
|
|
@@ -1365,15 +1677,10 @@ const configCommand = defineCommand({
|
|
|
1365
1677
|
description: `Config key to remove (${VALID_KEYS.join(", ")})`,
|
|
1366
1678
|
required: true
|
|
1367
1679
|
},
|
|
1368
|
-
|
|
1369
|
-
type: "boolean",
|
|
1370
|
-
alias: "q",
|
|
1371
|
-
default: false,
|
|
1372
|
-
description: "Suppress info/success output"
|
|
1373
|
-
}
|
|
1680
|
+
...OUTPUT_ARGS
|
|
1374
1681
|
},
|
|
1375
1682
|
async run({ args }) {
|
|
1376
|
-
|
|
1683
|
+
applyOutputMode(args);
|
|
1377
1684
|
if (!VALID_KEYS.includes(args.key)) {
|
|
1378
1685
|
logger.error(`Invalid key: ${args.key}`);
|
|
1379
1686
|
logger.info(`Valid keys: ${VALID_KEYS.join(", ")}`);
|
|
@@ -1399,21 +1706,9 @@ const configCommand = defineCommand({
|
|
|
1399
1706
|
name: "validate",
|
|
1400
1707
|
description: "Validate the saved config (defaultSite is verified, dataDir exists/writable)"
|
|
1401
1708
|
},
|
|
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
|
-
},
|
|
1709
|
+
args: { ...OUTPUT_ARGS },
|
|
1415
1710
|
async run({ args }) {
|
|
1416
|
-
|
|
1711
|
+
const { json } = applyOutputMode(args);
|
|
1417
1712
|
const { resolveDataDir } = await import("./_chunks/config.mjs").then((n) => n.t);
|
|
1418
1713
|
const fs = await import("node:fs/promises");
|
|
1419
1714
|
const config = await loadConfig();
|
|
@@ -1459,7 +1754,31 @@ const configCommand = defineCommand({
|
|
|
1459
1754
|
level: "fail",
|
|
1460
1755
|
message: `unknown format: ${config.defaultFormat}`
|
|
1461
1756
|
});
|
|
1462
|
-
|
|
1757
|
+
const { SearchTypes } = await import("gscdump/query");
|
|
1758
|
+
const allowedSearchTypes = Object.values(SearchTypes);
|
|
1759
|
+
if (config.defaultSearchType && !allowedSearchTypes.includes(config.defaultSearchType)) issues.push({
|
|
1760
|
+
key: "defaultSearchType",
|
|
1761
|
+
level: "fail",
|
|
1762
|
+
message: `unknown search type: ${config.defaultSearchType} (allowed: ${allowedSearchTypes.join(", ")})`
|
|
1763
|
+
});
|
|
1764
|
+
const allowedDataStates = [
|
|
1765
|
+
"all",
|
|
1766
|
+
"final",
|
|
1767
|
+
"hourly_all"
|
|
1768
|
+
];
|
|
1769
|
+
if (config.defaultDataState && !allowedDataStates.includes(config.defaultDataState)) issues.push({
|
|
1770
|
+
key: "defaultDataState",
|
|
1771
|
+
level: "fail",
|
|
1772
|
+
message: `unknown data state: ${config.defaultDataState} (allowed: ${allowedDataStates.join(", ")})`
|
|
1773
|
+
});
|
|
1774
|
+
if (config.serviceAccountPath) {
|
|
1775
|
+
if (!await fs.stat(config.serviceAccountPath).catch(() => null)) issues.push({
|
|
1776
|
+
key: "serviceAccountPath",
|
|
1777
|
+
level: "fail",
|
|
1778
|
+
message: `${displayPath(config.serviceAccountPath)} does not exist`
|
|
1779
|
+
});
|
|
1780
|
+
}
|
|
1781
|
+
if (json) {
|
|
1463
1782
|
console.log(JSON.stringify({
|
|
1464
1783
|
ok: !issues.some((i) => i.level === "fail"),
|
|
1465
1784
|
issues
|
|
@@ -1798,12 +2117,9 @@ const doctorCommand = defineCommand({
|
|
|
1798
2117
|
name: "doctor",
|
|
1799
2118
|
description: "Run health checks (env, auth, scopes, time, dataDir, store, GSC reachability + ping, defaultSite)"
|
|
1800
2119
|
},
|
|
1801
|
-
args: {
|
|
1802
|
-
type: "boolean",
|
|
1803
|
-
default: false,
|
|
1804
|
-
description: "Output as JSON"
|
|
1805
|
-
} },
|
|
2120
|
+
args: { ...OUTPUT_ARGS },
|
|
1806
2121
|
async run({ args }) {
|
|
2122
|
+
const { json } = applyOutputMode(args);
|
|
1807
2123
|
const envResult = await checkEnv();
|
|
1808
2124
|
const [authResult, timeChecks, dataDirChecks, watermarkChecks, gscApi, indexingApi, siteVerificationApi] = await Promise.all([
|
|
1809
2125
|
checkAuth$1(envResult.envKeys),
|
|
@@ -1830,7 +2146,7 @@ const doctorCommand = defineCommand({
|
|
|
1830
2146
|
...siteVerificationApi,
|
|
1831
2147
|
...sitesChecks
|
|
1832
2148
|
];
|
|
1833
|
-
if (
|
|
2149
|
+
if (json) {
|
|
1834
2150
|
console.log(JSON.stringify({
|
|
1835
2151
|
checks: all,
|
|
1836
2152
|
ok: all.every((c) => c.status !== "fail")
|
|
@@ -1904,20 +2220,10 @@ const dumpCommand = defineCommand({
|
|
|
1904
2220
|
default: false,
|
|
1905
2221
|
description: "Compact every closed month into a single file before exporting"
|
|
1906
2222
|
},
|
|
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
|
-
}
|
|
2223
|
+
...OUTPUT_ARGS
|
|
1918
2224
|
},
|
|
1919
2225
|
async run({ args }) {
|
|
1920
|
-
|
|
2226
|
+
const { json, quiet } = applyOutputMode(args);
|
|
1921
2227
|
const format = String(args.format);
|
|
1922
2228
|
if (!FORMATS.includes(format)) {
|
|
1923
2229
|
logger.error(`Invalid --format: ${format}. Allowed: ${FORMATS.join(", ")}`);
|
|
@@ -1935,12 +2241,12 @@ const dumpCommand = defineCommand({
|
|
|
1935
2241
|
logger.warn("No sites with local data. Run `gscdump sync` first.");
|
|
1936
2242
|
process.exit(0);
|
|
1937
2243
|
}
|
|
1938
|
-
if (args.compact) for (const siteUrl of targets) await compactClosedMonths(store, siteUrl,
|
|
2244
|
+
if (args.compact) for (const siteUrl of targets) await compactClosedMonths(store, siteUrl, quiet);
|
|
1939
2245
|
const summary = [];
|
|
1940
2246
|
for (const siteUrl of targets) {
|
|
1941
2247
|
const entries = (await listLiveEntries(store, siteUrl)).filter((e) => !tablesFilter || tablesFilter.has(e.table));
|
|
1942
2248
|
if (entries.length === 0) {
|
|
1943
|
-
if (!
|
|
2249
|
+
if (!quiet) logger.warn(`No data for ${siteUrl}; skipping`);
|
|
1944
2250
|
continue;
|
|
1945
2251
|
}
|
|
1946
2252
|
if (format === "parquet") {
|
|
@@ -1963,7 +2269,7 @@ const dumpCommand = defineCommand({
|
|
|
1963
2269
|
});
|
|
1964
2270
|
}
|
|
1965
2271
|
}
|
|
1966
|
-
if (
|
|
2272
|
+
if (json) {
|
|
1967
2273
|
console.log(JSON.stringify({
|
|
1968
2274
|
outDir,
|
|
1969
2275
|
sites: summary
|
|
@@ -2092,20 +2398,10 @@ const inspectSubCommand = defineCommand({
|
|
|
2092
2398
|
default: "4",
|
|
2093
2399
|
description: "Concurrent in-flight inspect calls (default: 4)"
|
|
2094
2400
|
},
|
|
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
|
-
}
|
|
2401
|
+
...OUTPUT_ARGS
|
|
2106
2402
|
},
|
|
2107
2403
|
async run({ args }) {
|
|
2108
|
-
|
|
2404
|
+
const { json, quiet } = applyOutputMode(args);
|
|
2109
2405
|
const ctx = await createCommandContext({
|
|
2110
2406
|
needsAuth: true,
|
|
2111
2407
|
needsStore: true
|
|
@@ -2115,7 +2411,6 @@ const inspectSubCommand = defineCommand({
|
|
|
2115
2411
|
const siteUrl = await ctx.resolveSite(args.site ? String(args.site) : void 0);
|
|
2116
2412
|
const limit = args.limit ? Number.parseInt(String(args.limit), 10) : INSPECTION_QPD_PER_PROPERTY;
|
|
2117
2413
|
const concurrency = Math.max(1, Number.parseInt(String(args.concurrency), 10) || 4);
|
|
2118
|
-
const quiet = Boolean(args.quiet) || Boolean(args.json);
|
|
2119
2414
|
const urls = (await readUrlList({ file: args.file ? String(args.file) : void 0 })).slice(0, limit);
|
|
2120
2415
|
if (urls.length === 0) {
|
|
2121
2416
|
logger.warn("No URLs to inspect.");
|
|
@@ -2163,7 +2458,7 @@ const inspectSubCommand = defineCommand({
|
|
|
2163
2458
|
userId: store.userId,
|
|
2164
2459
|
siteId: store.siteIdFor(siteUrl)
|
|
2165
2460
|
}, records);
|
|
2166
|
-
if (
|
|
2461
|
+
if (json) console.log(JSON.stringify({
|
|
2167
2462
|
site: siteUrl,
|
|
2168
2463
|
inspected: records.length,
|
|
2169
2464
|
failed,
|
|
@@ -2187,6 +2482,7 @@ const showSubCommand = defineCommand({
|
|
|
2187
2482
|
description: "Print the latest inspection record for a URL from the local entity store"
|
|
2188
2483
|
},
|
|
2189
2484
|
args: {
|
|
2485
|
+
...OUTPUT_ARGS,
|
|
2190
2486
|
site: {
|
|
2191
2487
|
type: "string",
|
|
2192
2488
|
alias: "s",
|
|
@@ -2196,14 +2492,10 @@ const showSubCommand = defineCommand({
|
|
|
2196
2492
|
type: "positional",
|
|
2197
2493
|
required: true,
|
|
2198
2494
|
description: "URL to look up"
|
|
2199
|
-
},
|
|
2200
|
-
json: {
|
|
2201
|
-
type: "boolean",
|
|
2202
|
-
default: false,
|
|
2203
|
-
description: "Output as JSON"
|
|
2204
2495
|
}
|
|
2205
2496
|
},
|
|
2206
2497
|
async run({ args }) {
|
|
2498
|
+
const { json } = applyOutputMode(args);
|
|
2207
2499
|
const ctx = await createCommandContext({
|
|
2208
2500
|
needsAuth: true,
|
|
2209
2501
|
needsStore: true
|
|
@@ -2218,7 +2510,7 @@ const showSubCommand = defineCommand({
|
|
|
2218
2510
|
logger.warn(`No inspection record for ${args.url}`);
|
|
2219
2511
|
process.exit(1);
|
|
2220
2512
|
}
|
|
2221
|
-
if (
|
|
2513
|
+
if (json) {
|
|
2222
2514
|
console.log(JSON.stringify(record, null, 2));
|
|
2223
2515
|
return;
|
|
2224
2516
|
}
|
|
@@ -2240,24 +2532,15 @@ const sitemapsSnapshotSubCommand = defineCommand({
|
|
|
2240
2532
|
description: "Fetch current sitemap state from GSC and persist to the local entity store"
|
|
2241
2533
|
},
|
|
2242
2534
|
args: {
|
|
2535
|
+
...OUTPUT_ARGS,
|
|
2243
2536
|
site: {
|
|
2244
2537
|
type: "string",
|
|
2245
2538
|
alias: "s",
|
|
2246
2539
|
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
2540
|
}
|
|
2259
2541
|
},
|
|
2260
2542
|
async run({ args }) {
|
|
2543
|
+
const { json, quiet } = applyOutputMode(args);
|
|
2261
2544
|
const ctx = await createCommandContext({
|
|
2262
2545
|
needsAuth: true,
|
|
2263
2546
|
needsStore: true
|
|
@@ -2265,7 +2548,6 @@ const sitemapsSnapshotSubCommand = defineCommand({
|
|
|
2265
2548
|
const client = ctx.client;
|
|
2266
2549
|
const store = ctx.store;
|
|
2267
2550
|
const siteUrl = await ctx.resolveSite(args.site ? String(args.site) : void 0);
|
|
2268
|
-
const quiet = Boolean(args.quiet);
|
|
2269
2551
|
const apiSitemaps = await client.sitemaps.list(siteUrl);
|
|
2270
2552
|
const capturedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2271
2553
|
const records = apiSitemaps.filter((s) => typeof s.path === "string").map((s) => ({
|
|
@@ -2289,7 +2571,7 @@ const sitemapsSnapshotSubCommand = defineCommand({
|
|
|
2289
2571
|
userId: store.userId,
|
|
2290
2572
|
siteId: store.siteIdFor(siteUrl)
|
|
2291
2573
|
}, records);
|
|
2292
|
-
if (
|
|
2574
|
+
if (json) {
|
|
2293
2575
|
console.log(JSON.stringify({
|
|
2294
2576
|
site: siteUrl,
|
|
2295
2577
|
capturedAt,
|
|
@@ -2314,6 +2596,7 @@ const sitemapsShowSubCommand = defineCommand({
|
|
|
2314
2596
|
description: "Print the latest captured sitemap state for a feedpath"
|
|
2315
2597
|
},
|
|
2316
2598
|
args: {
|
|
2599
|
+
...OUTPUT_ARGS,
|
|
2317
2600
|
site: {
|
|
2318
2601
|
type: "string",
|
|
2319
2602
|
alias: "s",
|
|
@@ -2323,14 +2606,10 @@ const sitemapsShowSubCommand = defineCommand({
|
|
|
2323
2606
|
type: "positional",
|
|
2324
2607
|
required: true,
|
|
2325
2608
|
description: "Sitemap path (feedpath)"
|
|
2326
|
-
},
|
|
2327
|
-
json: {
|
|
2328
|
-
type: "boolean",
|
|
2329
|
-
default: false,
|
|
2330
|
-
description: "Output as JSON"
|
|
2331
2609
|
}
|
|
2332
2610
|
},
|
|
2333
2611
|
async run({ args }) {
|
|
2612
|
+
const { json } = applyOutputMode(args);
|
|
2334
2613
|
const ctx = await createCommandContext({
|
|
2335
2614
|
needsAuth: true,
|
|
2336
2615
|
needsStore: true
|
|
@@ -2345,7 +2624,7 @@ const sitemapsShowSubCommand = defineCommand({
|
|
|
2345
2624
|
logger.warn(`No sitemap record for ${args.path}`);
|
|
2346
2625
|
process.exit(1);
|
|
2347
2626
|
}
|
|
2348
|
-
if (
|
|
2627
|
+
if (json) {
|
|
2349
2628
|
console.log(JSON.stringify(record, null, 2));
|
|
2350
2629
|
return;
|
|
2351
2630
|
}
|
|
@@ -2398,14 +2677,10 @@ const indexingSubCommand = defineCommand({
|
|
|
2398
2677
|
default: "4",
|
|
2399
2678
|
description: "Concurrent in-flight getMetadata calls (default: 4)"
|
|
2400
2679
|
},
|
|
2401
|
-
|
|
2402
|
-
type: "boolean",
|
|
2403
|
-
alias: "q",
|
|
2404
|
-
default: false,
|
|
2405
|
-
description: "Suppress progress output"
|
|
2406
|
-
}
|
|
2680
|
+
...OUTPUT_ARGS
|
|
2407
2681
|
},
|
|
2408
2682
|
async run({ args }) {
|
|
2683
|
+
const { quiet } = applyOutputMode(args);
|
|
2409
2684
|
const ctx = await createCommandContext({
|
|
2410
2685
|
needsAuth: true,
|
|
2411
2686
|
needsStore: true
|
|
@@ -2414,7 +2689,6 @@ const indexingSubCommand = defineCommand({
|
|
|
2414
2689
|
const store = ctx.store;
|
|
2415
2690
|
const siteUrl = await ctx.resolveSite(args.site ? String(args.site) : void 0);
|
|
2416
2691
|
const concurrency = Math.max(1, Number.parseInt(String(args.concurrency), 10) || 4);
|
|
2417
|
-
const quiet = Boolean(args.quiet);
|
|
2418
2692
|
const urls = await readUrlList({ file: args.file ? String(args.file) : void 0 });
|
|
2419
2693
|
if (urls.length === 0) {
|
|
2420
2694
|
logger.warn("No URLs to fetch metadata for.");
|
|
@@ -2490,6 +2764,14 @@ function parseRetries(v) {
|
|
|
2490
2764
|
const n = Number.parseInt(String(v), 10);
|
|
2491
2765
|
return Number.isFinite(n) && n >= 0 ? n : void 0;
|
|
2492
2766
|
}
|
|
2767
|
+
async function resolveUrlSource(args) {
|
|
2768
|
+
const fromSitemap = args["from-sitemap"];
|
|
2769
|
+
if (fromSitemap) return fetchSitemapUrls(String(fromSitemap)).catch((e) => {
|
|
2770
|
+
logger.error(`Sitemap fetch failed: ${e.message}`);
|
|
2771
|
+
process.exit(1);
|
|
2772
|
+
});
|
|
2773
|
+
return readUrlList$1(args);
|
|
2774
|
+
}
|
|
2493
2775
|
const submitCommand$1 = defineCommand({
|
|
2494
2776
|
meta: {
|
|
2495
2777
|
name: "submit",
|
|
@@ -2501,21 +2783,11 @@ const submitCommand$1 = defineCommand({
|
|
|
2501
2783
|
required: true,
|
|
2502
2784
|
description: "URL to submit"
|
|
2503
2785
|
},
|
|
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
|
-
},
|
|
2786
|
+
...OUTPUT_ARGS,
|
|
2515
2787
|
...RETRIES_ARG
|
|
2516
2788
|
},
|
|
2517
2789
|
async run({ args }) {
|
|
2518
|
-
|
|
2790
|
+
applyOutputMode(args);
|
|
2519
2791
|
const result = await requestIndexing((await createCommandContext({
|
|
2520
2792
|
needsAuth: true,
|
|
2521
2793
|
fetchOptions: { retry: parseRetries(args.retries) }
|
|
@@ -2539,21 +2811,11 @@ const removeCommand = defineCommand({
|
|
|
2539
2811
|
required: true,
|
|
2540
2812
|
description: "URL to mark removed"
|
|
2541
2813
|
},
|
|
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
|
-
},
|
|
2814
|
+
...OUTPUT_ARGS,
|
|
2553
2815
|
...RETRIES_ARG
|
|
2554
2816
|
},
|
|
2555
2817
|
async run({ args }) {
|
|
2556
|
-
|
|
2818
|
+
applyOutputMode(args);
|
|
2557
2819
|
const result = await requestIndexing((await createCommandContext({
|
|
2558
2820
|
needsAuth: true,
|
|
2559
2821
|
fetchOptions: { retry: parseRetries(args.retries) }
|
|
@@ -2590,7 +2852,7 @@ const statusCommand = defineCommand({
|
|
|
2590
2852
|
}
|
|
2591
2853
|
},
|
|
2592
2854
|
async run({ args }) {
|
|
2593
|
-
|
|
2855
|
+
applyOutputMode(args);
|
|
2594
2856
|
const meta = await getIndexingMetadata((await createCommandContext({ needsAuth: true })).client, args.url).catch(gscErrorHandler);
|
|
2595
2857
|
if (args.json) {
|
|
2596
2858
|
console.log(JSON.stringify(meta, null, 2));
|
|
@@ -2610,31 +2872,65 @@ const statusCommand = defineCommand({
|
|
|
2610
2872
|
}
|
|
2611
2873
|
});
|
|
2612
2874
|
const INDEXING_DAILY_QUOTA = 200;
|
|
2875
|
+
const INDEXING_PER_MINUTE_QUOTA = 600;
|
|
2876
|
+
const quotaCommand = defineCommand({
|
|
2877
|
+
meta: {
|
|
2878
|
+
name: "quota",
|
|
2879
|
+
description: "Show documented Indexing API quotas (no live counters; quota usage is not exposed by the API)"
|
|
2880
|
+
},
|
|
2881
|
+
args: { ...OUTPUT_ARGS },
|
|
2882
|
+
async run({ args }) {
|
|
2883
|
+
const { json } = applyOutputMode(args);
|
|
2884
|
+
const payload = {
|
|
2885
|
+
perDay: INDEXING_DAILY_QUOTA,
|
|
2886
|
+
perMinute: INDEXING_PER_MINUTE_QUOTA,
|
|
2887
|
+
note: "Documented defaults. Google does not expose live counters; track yours by counting submit calls.",
|
|
2888
|
+
docs: "https://developers.google.com/search/apis/indexing-api/v3/quota-pricing"
|
|
2889
|
+
};
|
|
2890
|
+
if (json) {
|
|
2891
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
2892
|
+
return;
|
|
2893
|
+
}
|
|
2894
|
+
console.log();
|
|
2895
|
+
console.log(` \x1B[1mIndexing API quota\x1B[0m`);
|
|
2896
|
+
console.log(` Per day: ${payload.perDay}`);
|
|
2897
|
+
console.log(` Per minute: ${payload.perMinute}`);
|
|
2898
|
+
console.log();
|
|
2899
|
+
console.log(` \x1B[90m${payload.note}\x1B[0m`);
|
|
2900
|
+
console.log(` \x1B[90mDocs: ${payload.docs}\x1B[0m`);
|
|
2901
|
+
console.log();
|
|
2902
|
+
}
|
|
2903
|
+
});
|
|
2613
2904
|
const indexingCommand = defineCommand({
|
|
2614
2905
|
meta: {
|
|
2615
2906
|
name: "indexing",
|
|
2616
2907
|
description: "Notify Google about URL updates/removals (Indexing API)"
|
|
2617
2908
|
},
|
|
2618
2909
|
subCommands: {
|
|
2619
|
-
submit: submitCommand$1,
|
|
2620
|
-
remove: removeCommand,
|
|
2621
|
-
status: statusCommand,
|
|
2622
|
-
batch: defineCommand({
|
|
2910
|
+
"submit": submitCommand$1,
|
|
2911
|
+
"remove": removeCommand,
|
|
2912
|
+
"status": statusCommand,
|
|
2913
|
+
"batch": defineCommand({
|
|
2623
2914
|
meta: {
|
|
2624
2915
|
name: "batch",
|
|
2625
2916
|
description: "Submit many URLs from a file or stdin (one URL per line)"
|
|
2626
2917
|
},
|
|
2627
2918
|
args: {
|
|
2919
|
+
...OUTPUT_ARGS,
|
|
2628
2920
|
"urls": {
|
|
2629
2921
|
type: "positional",
|
|
2630
2922
|
required: false,
|
|
2631
|
-
description: "URLs (or use --file/stdin)"
|
|
2923
|
+
description: "URLs (or use --file/--from-sitemap/stdin)"
|
|
2632
2924
|
},
|
|
2633
2925
|
"file": {
|
|
2634
2926
|
type: "string",
|
|
2635
2927
|
alias: "f",
|
|
2636
2928
|
description: "File with URLs (one per line)"
|
|
2637
2929
|
},
|
|
2930
|
+
"from-sitemap": {
|
|
2931
|
+
type: "string",
|
|
2932
|
+
description: "Sitemap URL (or sitemap index) to pull URLs from"
|
|
2933
|
+
},
|
|
2638
2934
|
"type": {
|
|
2639
2935
|
type: "string",
|
|
2640
2936
|
default: "URL_UPDATED",
|
|
@@ -2651,17 +2947,6 @@ const indexingCommand = defineCommand({
|
|
|
2651
2947
|
default: "1",
|
|
2652
2948
|
description: "Concurrent in-flight requests"
|
|
2653
2949
|
},
|
|
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
2950
|
"yes": {
|
|
2666
2951
|
type: "boolean",
|
|
2667
2952
|
alias: "y",
|
|
@@ -2674,10 +2959,10 @@ const indexingCommand = defineCommand({
|
|
|
2674
2959
|
}
|
|
2675
2960
|
},
|
|
2676
2961
|
async run({ args }) {
|
|
2677
|
-
|
|
2678
|
-
const urls = await
|
|
2962
|
+
applyOutputMode(args);
|
|
2963
|
+
const urls = await resolveUrlSource(args);
|
|
2679
2964
|
if (urls.length === 0) {
|
|
2680
|
-
logger.error("No URLs provided. Pass URLs as args, --file, or stdin.");
|
|
2965
|
+
logger.error("No URLs provided. Pass URLs as args, --file, --from-sitemap, or stdin.");
|
|
2681
2966
|
process.exit(1);
|
|
2682
2967
|
}
|
|
2683
2968
|
const type = String(args.type);
|
|
@@ -2710,7 +2995,71 @@ const indexingCommand = defineCommand({
|
|
|
2710
2995
|
}
|
|
2711
2996
|
if (!args.quiet) logger.success(`Submitted ${results.length}/${urls.length} URLs`);
|
|
2712
2997
|
}
|
|
2713
|
-
})
|
|
2998
|
+
}),
|
|
2999
|
+
"batch-status": defineCommand({
|
|
3000
|
+
meta: {
|
|
3001
|
+
name: "batch-status",
|
|
3002
|
+
description: "Get indexing notification metadata for many URLs"
|
|
3003
|
+
},
|
|
3004
|
+
args: {
|
|
3005
|
+
...OUTPUT_ARGS,
|
|
3006
|
+
"urls": {
|
|
3007
|
+
type: "positional",
|
|
3008
|
+
required: false,
|
|
3009
|
+
description: "URLs (or use --file/--from-sitemap/stdin)"
|
|
3010
|
+
},
|
|
3011
|
+
"file": {
|
|
3012
|
+
type: "string",
|
|
3013
|
+
alias: "f",
|
|
3014
|
+
description: "File with URLs (one per line)"
|
|
3015
|
+
},
|
|
3016
|
+
"from-sitemap": {
|
|
3017
|
+
type: "string",
|
|
3018
|
+
description: "Sitemap URL (or sitemap index) to pull URLs from"
|
|
3019
|
+
},
|
|
3020
|
+
"delay-ms": {
|
|
3021
|
+
type: "string",
|
|
3022
|
+
default: "100",
|
|
3023
|
+
description: "Delay between requests"
|
|
3024
|
+
},
|
|
3025
|
+
"concurrency": {
|
|
3026
|
+
type: "string",
|
|
3027
|
+
alias: "c",
|
|
3028
|
+
default: "1",
|
|
3029
|
+
description: "Concurrent in-flight requests"
|
|
3030
|
+
},
|
|
3031
|
+
"retries": {
|
|
3032
|
+
type: "string",
|
|
3033
|
+
description: "Override per-call retry count (default: 3)"
|
|
3034
|
+
}
|
|
3035
|
+
},
|
|
3036
|
+
async run({ args }) {
|
|
3037
|
+
applyOutputMode(args);
|
|
3038
|
+
const urls = await resolveUrlSource(args);
|
|
3039
|
+
if (urls.length === 0) {
|
|
3040
|
+
logger.error("No URLs provided. Pass URLs as args, --file, --from-sitemap, or stdin.");
|
|
3041
|
+
process.exit(1);
|
|
3042
|
+
}
|
|
3043
|
+
const ctx = await createCommandContext({
|
|
3044
|
+
needsAuth: true,
|
|
3045
|
+
fetchOptions: { retry: parseRetries(args.retries) }
|
|
3046
|
+
});
|
|
3047
|
+
const delayMs = Number.parseInt(String(args["delay-ms"]), 10);
|
|
3048
|
+
const concurrency = Math.max(1, Number.parseInt(String(args.concurrency), 10) || 1);
|
|
3049
|
+
if (!args.json && !args.quiet) logger.info(`Fetching status for ${urls.length} URLs ...`);
|
|
3050
|
+
const results = await runSequentialBatch(urls, (url) => getIndexingMetadata(ctx.client, url), {
|
|
3051
|
+
delayMs,
|
|
3052
|
+
concurrency,
|
|
3053
|
+
onProgress: args.json || args.quiet ? void 0 : (r, i, total) => logger.info(`[${i + 1}/${total}] ${r.url}`)
|
|
3054
|
+
}).catch(gscErrorHandler);
|
|
3055
|
+
if (args.json) {
|
|
3056
|
+
console.log(JSON.stringify(results, null, 2));
|
|
3057
|
+
return;
|
|
3058
|
+
}
|
|
3059
|
+
if (!args.quiet) logger.success(`Fetched ${results.length}/${urls.length} URLs`);
|
|
3060
|
+
}
|
|
3061
|
+
}),
|
|
3062
|
+
"quota": quotaCommand
|
|
2714
3063
|
}
|
|
2715
3064
|
});
|
|
2716
3065
|
const ENV_LINE_RE = /^([^=]+)=(.*)$/;
|
|
@@ -2758,15 +3107,10 @@ const initCommand = defineCommand({
|
|
|
2758
3107
|
default: false,
|
|
2759
3108
|
description: "Skip dataDir prompt (auth-only setup)"
|
|
2760
3109
|
},
|
|
2761
|
-
|
|
2762
|
-
type: "boolean",
|
|
2763
|
-
alias: "q",
|
|
2764
|
-
default: false,
|
|
2765
|
-
description: "Suppress info/success output"
|
|
2766
|
-
}
|
|
3110
|
+
...OUTPUT_ARGS
|
|
2767
3111
|
},
|
|
2768
3112
|
async run({ args }) {
|
|
2769
|
-
|
|
3113
|
+
applyOutputMode(args);
|
|
2770
3114
|
const config = await loadConfig();
|
|
2771
3115
|
if (config.clientId && config.clientSecret && !args.force) {
|
|
2772
3116
|
logger.info("Already configured");
|
|
@@ -2946,12 +3290,91 @@ function printInspection(url, inspection) {
|
|
|
2946
3290
|
}
|
|
2947
3291
|
console.log();
|
|
2948
3292
|
}
|
|
3293
|
+
const batchCommand = defineCommand({
|
|
3294
|
+
meta: {
|
|
3295
|
+
name: "batch",
|
|
3296
|
+
description: "Inspect many URLs from a file or stdin (one URL per line)"
|
|
3297
|
+
},
|
|
3298
|
+
args: {
|
|
3299
|
+
...OUTPUT_ARGS,
|
|
3300
|
+
"site": {
|
|
3301
|
+
type: "string",
|
|
3302
|
+
alias: "s",
|
|
3303
|
+
description: "Site URL (defaults to config.defaultSite or prompt)"
|
|
3304
|
+
},
|
|
3305
|
+
"urls": {
|
|
3306
|
+
type: "positional",
|
|
3307
|
+
required: false,
|
|
3308
|
+
description: "URLs (or use --file/--from-sitemap/stdin)"
|
|
3309
|
+
},
|
|
3310
|
+
"file": {
|
|
3311
|
+
type: "string",
|
|
3312
|
+
alias: "f",
|
|
3313
|
+
description: "File with URLs (one per line)"
|
|
3314
|
+
},
|
|
3315
|
+
"from-sitemap": {
|
|
3316
|
+
type: "string",
|
|
3317
|
+
description: "Sitemap URL (or sitemap index) to pull URLs from"
|
|
3318
|
+
},
|
|
3319
|
+
"delay-ms": {
|
|
3320
|
+
type: "string",
|
|
3321
|
+
default: "200",
|
|
3322
|
+
description: "Delay between requests"
|
|
3323
|
+
},
|
|
3324
|
+
"concurrency": {
|
|
3325
|
+
type: "string",
|
|
3326
|
+
alias: "c",
|
|
3327
|
+
default: "1",
|
|
3328
|
+
description: "Concurrent in-flight requests"
|
|
3329
|
+
}
|
|
3330
|
+
},
|
|
3331
|
+
async run({ args }) {
|
|
3332
|
+
const { json, quiet } = applyOutputMode(args);
|
|
3333
|
+
const urls = args["from-sitemap"] ? await fetchSitemapUrls(String(args["from-sitemap"])).catch((e) => {
|
|
3334
|
+
logger.error(`Sitemap fetch failed: ${e.message}`);
|
|
3335
|
+
process.exit(1);
|
|
3336
|
+
}) : await readUrlList$1(args);
|
|
3337
|
+
if (urls.length === 0) {
|
|
3338
|
+
logger.error("No URLs provided. Pass URLs as args, --file, --from-sitemap, or stdin.");
|
|
3339
|
+
process.exit(1);
|
|
3340
|
+
}
|
|
3341
|
+
const ctx = await createCommandContext({ needsAuth: true });
|
|
3342
|
+
const siteUrl = await ctx.resolveSite(args.site ? String(args.site) : void 0);
|
|
3343
|
+
const delayMs = Number.parseInt(String(args["delay-ms"]), 10);
|
|
3344
|
+
const concurrency = Math.max(1, Number.parseInt(String(args.concurrency), 10) || 1);
|
|
3345
|
+
if (!quiet) logger.info(`Inspecting ${urls.length} URLs ...`);
|
|
3346
|
+
const results = await batchInspectUrls(ctx.client, siteUrl, urls, {
|
|
3347
|
+
delayMs,
|
|
3348
|
+
concurrency,
|
|
3349
|
+
onProgress: quiet ? void 0 : (r, i, total) => logger.info(`[${i + 1}/${total}] ${r.url} ${r.isIndexed ? "PASS" : "FAIL"}`)
|
|
3350
|
+
}).catch(gscErrorHandler);
|
|
3351
|
+
if (json) {
|
|
3352
|
+
const flattened = results.map((r) => {
|
|
3353
|
+
const indexStatus = r.inspection?.indexStatusResult;
|
|
3354
|
+
return {
|
|
3355
|
+
url: r.url,
|
|
3356
|
+
verdict: indexStatus?.verdict || null,
|
|
3357
|
+
coverageState: indexStatus?.coverageState || null,
|
|
3358
|
+
indexingState: indexStatus?.indexingState || null,
|
|
3359
|
+
lastCrawlTime: indexStatus?.lastCrawlTime || null,
|
|
3360
|
+
isIndexed: r.isIndexed,
|
|
3361
|
+
raw: r.inspection
|
|
3362
|
+
};
|
|
3363
|
+
});
|
|
3364
|
+
console.log(JSON.stringify(flattened, null, 2));
|
|
3365
|
+
return;
|
|
3366
|
+
}
|
|
3367
|
+
const indexed = results.filter((r) => r.isIndexed).length;
|
|
3368
|
+
if (!quiet) logger.success(`Inspected ${results.length} URLs (${indexed} indexed, ${results.length - indexed} not)`);
|
|
3369
|
+
}
|
|
3370
|
+
});
|
|
2949
3371
|
const inspectCommand = defineCommand({
|
|
2950
3372
|
meta: {
|
|
2951
3373
|
name: "inspect",
|
|
2952
3374
|
description: "Inspect URL indexing status (single URL; use `inspect batch` for many)"
|
|
2953
3375
|
},
|
|
2954
3376
|
args: {
|
|
3377
|
+
...OUTPUT_ARGS,
|
|
2955
3378
|
site: {
|
|
2956
3379
|
type: "string",
|
|
2957
3380
|
alias: "s",
|
|
@@ -2961,96 +3384,17 @@ const inspectCommand = defineCommand({
|
|
|
2961
3384
|
type: "positional",
|
|
2962
3385
|
required: true,
|
|
2963
3386
|
description: "URL to inspect"
|
|
2964
|
-
},
|
|
2965
|
-
json: {
|
|
2966
|
-
type: "boolean",
|
|
2967
|
-
default: false,
|
|
2968
|
-
description: "Output as JSON"
|
|
2969
|
-
},
|
|
2970
|
-
quiet: {
|
|
2971
|
-
type: "boolean",
|
|
2972
|
-
alias: "q",
|
|
2973
|
-
default: false,
|
|
2974
|
-
description: "Suppress info/success output"
|
|
2975
3387
|
}
|
|
2976
3388
|
},
|
|
2977
|
-
subCommands: { batch:
|
|
2978
|
-
meta: {
|
|
2979
|
-
name: "batch",
|
|
2980
|
-
description: "Inspect many URLs from a file or stdin (one URL per line)"
|
|
2981
|
-
},
|
|
2982
|
-
args: {
|
|
2983
|
-
"site": {
|
|
2984
|
-
type: "string",
|
|
2985
|
-
alias: "s",
|
|
2986
|
-
description: "Site URL (defaults to config.defaultSite or prompt)"
|
|
2987
|
-
},
|
|
2988
|
-
"urls": {
|
|
2989
|
-
type: "positional",
|
|
2990
|
-
required: false,
|
|
2991
|
-
description: "URLs (or use --file/stdin)"
|
|
2992
|
-
},
|
|
2993
|
-
"file": {
|
|
2994
|
-
type: "string",
|
|
2995
|
-
alias: "f",
|
|
2996
|
-
description: "File with URLs (one per line)"
|
|
2997
|
-
},
|
|
2998
|
-
"delay-ms": {
|
|
2999
|
-
type: "string",
|
|
3000
|
-
default: "200",
|
|
3001
|
-
description: "Delay between requests"
|
|
3002
|
-
},
|
|
3003
|
-
"concurrency": {
|
|
3004
|
-
type: "string",
|
|
3005
|
-
alias: "c",
|
|
3006
|
-
default: "1",
|
|
3007
|
-
description: "Concurrent in-flight requests"
|
|
3008
|
-
},
|
|
3009
|
-
"quiet": {
|
|
3010
|
-
type: "boolean",
|
|
3011
|
-
alias: "q",
|
|
3012
|
-
default: false,
|
|
3013
|
-
description: "Suppress progress output"
|
|
3014
|
-
},
|
|
3015
|
-
"json": {
|
|
3016
|
-
type: "boolean",
|
|
3017
|
-
default: false,
|
|
3018
|
-
description: "Output as JSON"
|
|
3019
|
-
}
|
|
3020
|
-
},
|
|
3021
|
-
async run({ args }) {
|
|
3022
|
-
setQuiet(Boolean(args.quiet) || Boolean(args.json));
|
|
3023
|
-
const urls = await readUrlList$1(args);
|
|
3024
|
-
if (urls.length === 0) {
|
|
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)`);
|
|
3044
|
-
}
|
|
3045
|
-
}) },
|
|
3389
|
+
subCommands: { batch: batchCommand },
|
|
3046
3390
|
async run({ args }) {
|
|
3047
|
-
|
|
3391
|
+
const { json } = applyOutputMode(args);
|
|
3048
3392
|
const ctx = await createCommandContext({ needsAuth: true });
|
|
3049
3393
|
const siteUrl = await ctx.resolveSite(args.site ? String(args.site) : void 0);
|
|
3050
3394
|
const result = await ctx.client.inspect(siteUrl, args.url).catch(gscErrorHandler);
|
|
3051
3395
|
const inspection = result?.inspectionResult;
|
|
3052
3396
|
const indexStatus = inspection?.indexStatusResult;
|
|
3053
|
-
if (
|
|
3397
|
+
if (json) {
|
|
3054
3398
|
console.log(JSON.stringify({
|
|
3055
3399
|
url: args.url,
|
|
3056
3400
|
verdict: indexStatus?.verdict || null,
|
|
@@ -3634,16 +3978,12 @@ const sitemapsCommand = defineCommand({
|
|
|
3634
3978
|
description: "List sitemaps for a site"
|
|
3635
3979
|
},
|
|
3636
3980
|
args: {
|
|
3981
|
+
...OUTPUT_ARGS,
|
|
3637
3982
|
site: {
|
|
3638
3983
|
type: "string",
|
|
3639
3984
|
alias: "s",
|
|
3640
3985
|
description: "Site URL (e.g., sc-domain:example.com or https://example.com/)"
|
|
3641
3986
|
},
|
|
3642
|
-
json: {
|
|
3643
|
-
type: "boolean",
|
|
3644
|
-
default: false,
|
|
3645
|
-
description: "Output as JSON"
|
|
3646
|
-
},
|
|
3647
3987
|
pending: {
|
|
3648
3988
|
type: "boolean",
|
|
3649
3989
|
default: false,
|
|
@@ -3656,12 +3996,10 @@ const sitemapsCommand = defineCommand({
|
|
|
3656
3996
|
}
|
|
3657
3997
|
},
|
|
3658
3998
|
async run({ args }) {
|
|
3999
|
+
const { json } = applyOutputMode(args);
|
|
3659
4000
|
const ctx = await createCommandContext({ needsAuth: true });
|
|
3660
4001
|
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) => ({
|
|
4002
|
+
let sitemaps = (await ctx.client.sitemaps.list(siteUrl).catch(gscErrorHandler)).map((sm) => ({
|
|
3665
4003
|
path: sm.path,
|
|
3666
4004
|
type: sm.type || void 0,
|
|
3667
4005
|
isPending: sm.isPending || false,
|
|
@@ -3672,7 +4010,7 @@ const sitemapsCommand = defineCommand({
|
|
|
3672
4010
|
}));
|
|
3673
4011
|
if (args.pending) sitemaps = sitemaps.filter((sm) => sm.isPending);
|
|
3674
4012
|
if (args.errored) sitemaps = sitemaps.filter((sm) => sm.errors > 0);
|
|
3675
|
-
if (
|
|
4013
|
+
if (json) {
|
|
3676
4014
|
console.log(JSON.stringify(sitemaps, null, 2));
|
|
3677
4015
|
return;
|
|
3678
4016
|
}
|
|
@@ -3697,6 +4035,7 @@ const sitemapsCommand = defineCommand({
|
|
|
3697
4035
|
description: "Get details for a specific sitemap"
|
|
3698
4036
|
},
|
|
3699
4037
|
args: {
|
|
4038
|
+
...OUTPUT_ARGS,
|
|
3700
4039
|
site: {
|
|
3701
4040
|
type: "string",
|
|
3702
4041
|
alias: "s",
|
|
@@ -3706,19 +4045,15 @@ const sitemapsCommand = defineCommand({
|
|
|
3706
4045
|
type: "positional",
|
|
3707
4046
|
required: true,
|
|
3708
4047
|
description: "Sitemap URL"
|
|
3709
|
-
},
|
|
3710
|
-
json: {
|
|
3711
|
-
type: "boolean",
|
|
3712
|
-
default: false,
|
|
3713
|
-
description: "Output as JSON"
|
|
3714
4048
|
}
|
|
3715
4049
|
},
|
|
3716
4050
|
async run({ args }) {
|
|
4051
|
+
const { json } = applyOutputMode(args);
|
|
3717
4052
|
const ctx = await createCommandContext({ needsAuth: true });
|
|
3718
4053
|
const siteUrl = await ctx.resolveSite(args.site ? String(args.site) : void 0);
|
|
3719
4054
|
const client = ctx.client;
|
|
3720
4055
|
const sitemap = await fetchSitemap(client, siteUrl, args.url).catch(gscErrorHandler);
|
|
3721
|
-
if (
|
|
4056
|
+
if (json) {
|
|
3722
4057
|
console.log(JSON.stringify(sitemap, null, 2));
|
|
3723
4058
|
return;
|
|
3724
4059
|
}
|
|
@@ -3743,6 +4078,7 @@ const sitemapsCommand = defineCommand({
|
|
|
3743
4078
|
description: "Submit a sitemap to GSC"
|
|
3744
4079
|
},
|
|
3745
4080
|
args: {
|
|
4081
|
+
...OUTPUT_ARGS,
|
|
3746
4082
|
site: {
|
|
3747
4083
|
type: "string",
|
|
3748
4084
|
alias: "s",
|
|
@@ -3755,12 +4091,18 @@ const sitemapsCommand = defineCommand({
|
|
|
3755
4091
|
}
|
|
3756
4092
|
},
|
|
3757
4093
|
async run({ args }) {
|
|
4094
|
+
const { json } = applyOutputMode(args);
|
|
3758
4095
|
const ctx = await createCommandContext({ needsAuth: true });
|
|
3759
4096
|
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
|
-
|
|
4097
|
+
await ctx.client.sitemaps.submit(siteUrl, args.url).catch(gscErrorHandler);
|
|
4098
|
+
if (json) {
|
|
4099
|
+
console.log(JSON.stringify({
|
|
4100
|
+
siteUrl,
|
|
4101
|
+
feedpath: args.url,
|
|
4102
|
+
status: "submitted"
|
|
4103
|
+
}, null, 2));
|
|
4104
|
+
return;
|
|
4105
|
+
}
|
|
3764
4106
|
logger.success(`Submitted sitemap: ${args.url}`);
|
|
3765
4107
|
}
|
|
3766
4108
|
}),
|
|
@@ -3770,6 +4112,7 @@ const sitemapsCommand = defineCommand({
|
|
|
3770
4112
|
description: "Delete a sitemap from GSC"
|
|
3771
4113
|
},
|
|
3772
4114
|
args: {
|
|
4115
|
+
...OUTPUT_ARGS,
|
|
3773
4116
|
site: {
|
|
3774
4117
|
type: "string",
|
|
3775
4118
|
alias: "s",
|
|
@@ -3782,26 +4125,107 @@ const sitemapsCommand = defineCommand({
|
|
|
3782
4125
|
}
|
|
3783
4126
|
},
|
|
3784
4127
|
async run({ args }) {
|
|
4128
|
+
const { json } = applyOutputMode(args);
|
|
3785
4129
|
const ctx = await createCommandContext({ needsAuth: true });
|
|
3786
4130
|
const siteUrl = await ctx.resolveSite(args.site ? String(args.site) : void 0);
|
|
3787
|
-
await ctx.client.sitemaps.delete(siteUrl, args.url).catch(
|
|
3788
|
-
|
|
3789
|
-
|
|
3790
|
-
|
|
4131
|
+
await ctx.client.sitemaps.delete(siteUrl, args.url).catch(gscErrorHandler);
|
|
4132
|
+
if (json) {
|
|
4133
|
+
console.log(JSON.stringify({
|
|
4134
|
+
siteUrl,
|
|
4135
|
+
feedpath: args.url,
|
|
4136
|
+
status: "deleted"
|
|
4137
|
+
}, null, 2));
|
|
4138
|
+
return;
|
|
4139
|
+
}
|
|
3791
4140
|
logger.success(`Deleted sitemap: ${args.url}`);
|
|
3792
4141
|
}
|
|
3793
|
-
})
|
|
3794
|
-
|
|
3795
|
-
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
4142
|
+
}),
|
|
4143
|
+
discover: defineCommand({
|
|
4144
|
+
meta: {
|
|
4145
|
+
name: "discover",
|
|
4146
|
+
description: "Probe a domain's robots.txt + common paths for an advertised sitemap (no auth needed)"
|
|
4147
|
+
},
|
|
4148
|
+
args: {
|
|
4149
|
+
...OUTPUT_ARGS,
|
|
4150
|
+
domain: {
|
|
4151
|
+
type: "positional",
|
|
4152
|
+
required: true,
|
|
4153
|
+
description: "Domain (e.g., example.com)"
|
|
4154
|
+
}
|
|
4155
|
+
},
|
|
4156
|
+
async run({ args }) {
|
|
4157
|
+
const { json } = applyOutputMode(args);
|
|
4158
|
+
const domain = String(args.domain).replace(/^https?:\/\//, "").replace(/\/.*$/, "");
|
|
4159
|
+
const url = await discoverSitemap(domain).catch(() => null);
|
|
4160
|
+
if (json) {
|
|
4161
|
+
console.log(JSON.stringify({
|
|
4162
|
+
domain,
|
|
4163
|
+
sitemap: url
|
|
4164
|
+
}, null, 2));
|
|
4165
|
+
return;
|
|
4166
|
+
}
|
|
4167
|
+
if (!url) {
|
|
4168
|
+
logger.warn(`No sitemap discovered for ${domain}`);
|
|
4169
|
+
process.exit(1);
|
|
4170
|
+
}
|
|
4171
|
+
logger.success(`Discovered sitemap: ${url}`);
|
|
4172
|
+
}
|
|
4173
|
+
}),
|
|
4174
|
+
urls: defineCommand({
|
|
4175
|
+
meta: {
|
|
4176
|
+
name: "urls",
|
|
4177
|
+
description: "Fetch a sitemap (or sitemap index) and dump its <loc> URLs (no auth needed)"
|
|
4178
|
+
},
|
|
4179
|
+
args: {
|
|
4180
|
+
...OUTPUT_ARGS,
|
|
4181
|
+
"url": {
|
|
4182
|
+
type: "positional",
|
|
4183
|
+
required: true,
|
|
4184
|
+
description: "Sitemap URL (index files are followed)"
|
|
4185
|
+
},
|
|
4186
|
+
"limit": {
|
|
4187
|
+
type: "string",
|
|
4188
|
+
alias: "l",
|
|
4189
|
+
description: "Stop after N URLs across all nested sitemaps"
|
|
4190
|
+
},
|
|
4191
|
+
"max-depth": {
|
|
4192
|
+
type: "string",
|
|
4193
|
+
description: "Max sitemap-index nesting depth (default: 3)"
|
|
4194
|
+
}
|
|
4195
|
+
},
|
|
4196
|
+
async run({ args }) {
|
|
4197
|
+
const { json } = applyOutputMode(args);
|
|
4198
|
+
const limit = args.limit ? Number.parseInt(String(args.limit), 10) : void 0;
|
|
4199
|
+
const maxDepth = args["max-depth"] ? Number.parseInt(String(args["max-depth"]), 10) : void 0;
|
|
4200
|
+
const urls = await fetchSitemapUrls(String(args.url), {
|
|
4201
|
+
limit,
|
|
4202
|
+
maxDepth
|
|
4203
|
+
}).catch((e) => {
|
|
4204
|
+
logger.error(`Sitemap fetch failed: ${e.message}`);
|
|
4205
|
+
process.exit(1);
|
|
4206
|
+
});
|
|
4207
|
+
if (json) {
|
|
4208
|
+
console.log(JSON.stringify({
|
|
4209
|
+
sitemap: args.url,
|
|
4210
|
+
count: urls.length,
|
|
4211
|
+
urls
|
|
4212
|
+
}, null, 2));
|
|
4213
|
+
return;
|
|
4214
|
+
}
|
|
4215
|
+
for (const u of urls) console.log(u);
|
|
4216
|
+
}
|
|
4217
|
+
})
|
|
4218
|
+
}
|
|
4219
|
+
});
|
|
4220
|
+
const ALL_METHODS = [
|
|
4221
|
+
"META",
|
|
4222
|
+
"FILE",
|
|
4223
|
+
"DNS_TXT",
|
|
4224
|
+
"DNS_CNAME",
|
|
4225
|
+
"ANALYTICS",
|
|
4226
|
+
"TAG_MANAGER"
|
|
4227
|
+
];
|
|
4228
|
+
function pickDefaultMethod(siteUrl) {
|
|
3805
4229
|
return siteUrl.startsWith("sc-domain:") ? "DNS_TXT" : "META";
|
|
3806
4230
|
}
|
|
3807
4231
|
function validateMethod(siteUrl, method) {
|
|
@@ -3847,281 +4271,442 @@ function printPlacementInstructions(method, siteUrl, token) {
|
|
|
3847
4271
|
break;
|
|
3848
4272
|
}
|
|
3849
4273
|
case "ANALYTICS":
|
|
3850
|
-
console.log(` Make sure
|
|
4274
|
+
console.log(` Make sure the Google Analytics tracking tag is installed on the site.`);
|
|
4275
|
+
console.log(` Expected tracking ID:`);
|
|
4276
|
+
console.log();
|
|
4277
|
+
console.log(` \x1B[2m${token}\x1B[0m`);
|
|
3851
4278
|
break;
|
|
3852
4279
|
case "TAG_MANAGER":
|
|
3853
|
-
console.log(` Make sure
|
|
4280
|
+
console.log(` Make sure the Google Tag Manager container snippet is installed on the site.`);
|
|
4281
|
+
console.log(` Expected container ID:`);
|
|
4282
|
+
console.log();
|
|
4283
|
+
console.log(` \x1B[2m${token}\x1B[0m`);
|
|
3854
4284
|
break;
|
|
3855
4285
|
}
|
|
3856
4286
|
console.log();
|
|
3857
4287
|
console.log(` \x1B[90mThen run:\x1B[0m gscdump sites verify ${siteUrl} --method ${method}`);
|
|
3858
4288
|
console.log();
|
|
3859
4289
|
}
|
|
3860
|
-
const
|
|
4290
|
+
const addCommand = defineCommand({
|
|
3861
4291
|
meta: {
|
|
3862
|
-
name: "
|
|
3863
|
-
description: "
|
|
4292
|
+
name: "add",
|
|
4293
|
+
description: "Register a property in Search Console (pass --verify to chain token + verify in one call)"
|
|
3864
4294
|
},
|
|
3865
4295
|
args: {
|
|
3866
|
-
|
|
3867
|
-
type: "
|
|
3868
|
-
|
|
3869
|
-
description: "
|
|
4296
|
+
url: {
|
|
4297
|
+
type: "positional",
|
|
4298
|
+
required: true,
|
|
4299
|
+
description: "Property URL (https://example.com/ or sc-domain:example.com)"
|
|
3870
4300
|
},
|
|
3871
|
-
|
|
4301
|
+
verify: {
|
|
3872
4302
|
type: "boolean",
|
|
3873
4303
|
default: false,
|
|
3874
|
-
description: "
|
|
4304
|
+
description: "After adding, fetch a verification token and trigger Google's validation"
|
|
4305
|
+
},
|
|
4306
|
+
method: {
|
|
4307
|
+
type: "string",
|
|
4308
|
+
alias: "m",
|
|
4309
|
+
description: "Verification method (used with --verify; default: META for URL-prefix, DNS_TXT for sc-domain:)"
|
|
4310
|
+
},
|
|
4311
|
+
...OUTPUT_ARGS
|
|
4312
|
+
},
|
|
4313
|
+
async run({ args }) {
|
|
4314
|
+
applyOutputMode(args);
|
|
4315
|
+
const ctx = await createCommandContext({ needsAuth: true });
|
|
4316
|
+
await addSite(ctx.client, args.url).catch(gscErrorHandler);
|
|
4317
|
+
if (!args.verify) {
|
|
4318
|
+
if (args.json) {
|
|
4319
|
+
console.log(JSON.stringify({
|
|
4320
|
+
siteUrl: args.url,
|
|
4321
|
+
status: "added",
|
|
4322
|
+
verified: false
|
|
4323
|
+
}, null, 2));
|
|
4324
|
+
return;
|
|
4325
|
+
}
|
|
4326
|
+
logger.success(`Added: ${args.url}`);
|
|
4327
|
+
logger.info(`Property is in unverified state. Verify ownership next:`);
|
|
4328
|
+
const method = pickDefaultMethod(args.url);
|
|
4329
|
+
console.log(` \x1B[2mgscdump sites verify-token ${args.url} --method ${method}\x1B[0m`);
|
|
4330
|
+
return;
|
|
4331
|
+
}
|
|
4332
|
+
const method = validateMethod(args.url, args.method ?? pickDefaultMethod(args.url));
|
|
4333
|
+
const tokenResult = await getVerificationToken(ctx.client, args.url, method).catch(gscErrorHandler);
|
|
4334
|
+
if (args.json) {
|
|
4335
|
+
console.log(JSON.stringify({
|
|
4336
|
+
siteUrl: args.url,
|
|
4337
|
+
status: "added",
|
|
4338
|
+
method,
|
|
4339
|
+
token: tokenResult.token,
|
|
4340
|
+
site: tokenResult.site,
|
|
4341
|
+
verified: false,
|
|
4342
|
+
next: "Place the token, then run `gscdump sites verify <url> --method <m>`"
|
|
4343
|
+
}, null, 2));
|
|
4344
|
+
return;
|
|
4345
|
+
}
|
|
4346
|
+
logger.success(`Added: ${args.url}`);
|
|
4347
|
+
printPlacementInstructions(method, args.url, tokenResult.token);
|
|
4348
|
+
const ok = await confirm({
|
|
4349
|
+
message: "Token placed? Trigger Google verification now?",
|
|
4350
|
+
initialValue: true
|
|
4351
|
+
});
|
|
4352
|
+
if (isCancel(ok) || !ok) {
|
|
4353
|
+
logger.info("Skipped verification. Run `gscdump sites verify` once the token is live.");
|
|
4354
|
+
return;
|
|
4355
|
+
}
|
|
4356
|
+
const resource = await verifySite(ctx.client, args.url, method).catch(gscErrorHandler);
|
|
4357
|
+
logger.success(`Verified: ${args.url}`);
|
|
4358
|
+
if (resource.owners?.length) {
|
|
4359
|
+
console.log();
|
|
4360
|
+
console.log(` Owners:`);
|
|
4361
|
+
for (const o of resource.owners) console.log(` \x1B[90m└─\x1B[0m ${o}`);
|
|
4362
|
+
}
|
|
4363
|
+
}
|
|
4364
|
+
});
|
|
4365
|
+
const deleteCommand = defineCommand({
|
|
4366
|
+
meta: {
|
|
4367
|
+
name: "delete",
|
|
4368
|
+
description: "Remove a property from Search Console"
|
|
4369
|
+
},
|
|
4370
|
+
args: {
|
|
4371
|
+
url: {
|
|
4372
|
+
type: "positional",
|
|
4373
|
+
required: true,
|
|
4374
|
+
description: "Property URL to remove"
|
|
3875
4375
|
},
|
|
3876
|
-
|
|
4376
|
+
yes: {
|
|
3877
4377
|
type: "boolean",
|
|
4378
|
+
alias: "y",
|
|
3878
4379
|
default: false,
|
|
3879
|
-
description: "
|
|
4380
|
+
description: "Skip confirmation prompt"
|
|
3880
4381
|
},
|
|
3881
|
-
|
|
4382
|
+
...OUTPUT_ARGS
|
|
4383
|
+
},
|
|
4384
|
+
async run({ args }) {
|
|
4385
|
+
applyOutputMode(args);
|
|
4386
|
+
if (!args.yes && !args.json) {
|
|
4387
|
+
const ok = await confirm({
|
|
4388
|
+
message: `Remove ${args.url} from Search Console? Local synced data is unaffected.`,
|
|
4389
|
+
initialValue: false
|
|
4390
|
+
});
|
|
4391
|
+
if (isCancel(ok) || !ok) {
|
|
4392
|
+
logger.info("Cancelled");
|
|
4393
|
+
process.exit(0);
|
|
4394
|
+
}
|
|
4395
|
+
}
|
|
4396
|
+
await deleteSite((await createCommandContext({ needsAuth: true })).client, args.url).catch(gscErrorHandler);
|
|
4397
|
+
if (args.json) {
|
|
4398
|
+
console.log(JSON.stringify({
|
|
4399
|
+
siteUrl: args.url,
|
|
4400
|
+
status: "deleted"
|
|
4401
|
+
}, null, 2));
|
|
4402
|
+
return;
|
|
4403
|
+
}
|
|
4404
|
+
logger.success(`Removed: ${args.url}`);
|
|
4405
|
+
}
|
|
4406
|
+
});
|
|
4407
|
+
const verifyTokenCommand = defineCommand({
|
|
4408
|
+
meta: {
|
|
4409
|
+
name: "verify-token",
|
|
4410
|
+
description: "Get a verification token to place on the site or in DNS"
|
|
4411
|
+
},
|
|
4412
|
+
args: {
|
|
4413
|
+
url: {
|
|
4414
|
+
type: "positional",
|
|
4415
|
+
required: true,
|
|
4416
|
+
description: "Property URL"
|
|
4417
|
+
},
|
|
4418
|
+
method: {
|
|
4419
|
+
type: "string",
|
|
4420
|
+
alias: "m",
|
|
4421
|
+
description: "META, FILE, DNS_TXT, DNS_CNAME, ANALYTICS, TAG_MANAGER (default: META for URL-prefix, DNS_TXT for sc-domain:)"
|
|
4422
|
+
},
|
|
4423
|
+
...OUTPUT_ARGS
|
|
4424
|
+
},
|
|
4425
|
+
async run({ args }) {
|
|
4426
|
+
applyOutputMode(args);
|
|
4427
|
+
const method = validateMethod(args.url, args.method ?? pickDefaultMethod(args.url));
|
|
4428
|
+
const result = await getVerificationToken((await createCommandContext({ needsAuth: true })).client, args.url, method).catch(gscErrorHandler);
|
|
4429
|
+
if (args.json) {
|
|
4430
|
+
console.log(JSON.stringify({
|
|
4431
|
+
siteUrl: args.url,
|
|
4432
|
+
method,
|
|
4433
|
+
token: result.token,
|
|
4434
|
+
site: result.site
|
|
4435
|
+
}, null, 2));
|
|
4436
|
+
return;
|
|
4437
|
+
}
|
|
4438
|
+
printPlacementInstructions(method, args.url, result.token);
|
|
4439
|
+
}
|
|
4440
|
+
});
|
|
4441
|
+
const verifyCommand = defineCommand({
|
|
4442
|
+
meta: {
|
|
4443
|
+
name: "verify",
|
|
4444
|
+
description: "Trigger verification — Google fetches/validates the token you placed"
|
|
4445
|
+
},
|
|
4446
|
+
args: {
|
|
4447
|
+
url: {
|
|
4448
|
+
type: "positional",
|
|
4449
|
+
required: true,
|
|
4450
|
+
description: "Property URL"
|
|
4451
|
+
},
|
|
4452
|
+
method: {
|
|
4453
|
+
type: "string",
|
|
4454
|
+
alias: "m",
|
|
4455
|
+
description: "Verification method to validate (must match the one used for verify-token)"
|
|
4456
|
+
},
|
|
4457
|
+
...OUTPUT_ARGS
|
|
4458
|
+
},
|
|
4459
|
+
async run({ args }) {
|
|
4460
|
+
applyOutputMode(args);
|
|
4461
|
+
const method = validateMethod(args.url, args.method ?? pickDefaultMethod(args.url));
|
|
4462
|
+
const resource = await verifySite((await createCommandContext({ needsAuth: true })).client, args.url, method).catch(gscErrorHandler);
|
|
4463
|
+
if (args.json) {
|
|
4464
|
+
console.log(JSON.stringify({
|
|
4465
|
+
siteUrl: args.url,
|
|
4466
|
+
method,
|
|
4467
|
+
resource
|
|
4468
|
+
}, null, 2));
|
|
4469
|
+
return;
|
|
4470
|
+
}
|
|
4471
|
+
logger.success(`Verified: ${args.url}`);
|
|
4472
|
+
if (resource.owners?.length) {
|
|
4473
|
+
console.log();
|
|
4474
|
+
console.log(` Owners:`);
|
|
4475
|
+
for (const o of resource.owners) console.log(` \x1B[90m└─\x1B[0m ${o}`);
|
|
4476
|
+
}
|
|
4477
|
+
}
|
|
4478
|
+
});
|
|
4479
|
+
const verifyGetCommand = defineCommand({
|
|
4480
|
+
meta: {
|
|
4481
|
+
name: "verify-get",
|
|
4482
|
+
description: "Get a single verified WebResource by id"
|
|
4483
|
+
},
|
|
4484
|
+
args: {
|
|
4485
|
+
id: {
|
|
4486
|
+
type: "positional",
|
|
4487
|
+
required: true,
|
|
4488
|
+
description: "WebResource id (from `sites verify-list`)"
|
|
4489
|
+
},
|
|
4490
|
+
...OUTPUT_ARGS
|
|
4491
|
+
},
|
|
4492
|
+
async run({ args }) {
|
|
4493
|
+
applyOutputMode(args);
|
|
4494
|
+
const resource = await getVerifiedSite((await createCommandContext({ needsAuth: true })).client, args.id).catch(gscErrorHandler);
|
|
4495
|
+
if (args.json) {
|
|
4496
|
+
console.log(JSON.stringify(resource, null, 2));
|
|
4497
|
+
return;
|
|
4498
|
+
}
|
|
4499
|
+
const ident = resource.site?.identifier ?? resource.id ?? "?";
|
|
4500
|
+
const type = resource.site?.type === "INET_DOMAIN" ? "domain" : "site";
|
|
4501
|
+
console.log();
|
|
4502
|
+
console.log(` \x1B[1m${ident}\x1B[0m \x1B[90m(${type})\x1B[0m`);
|
|
4503
|
+
console.log(` \x1B[90mid:\x1B[0m ${resource.id ?? "?"}`);
|
|
4504
|
+
if (resource.owners?.length) {
|
|
4505
|
+
console.log(` Owners:`);
|
|
4506
|
+
for (const o of resource.owners) console.log(` \x1B[90m└─\x1B[0m ${o}`);
|
|
4507
|
+
}
|
|
4508
|
+
console.log();
|
|
4509
|
+
}
|
|
4510
|
+
});
|
|
4511
|
+
const unverifyCommand = defineCommand({
|
|
4512
|
+
meta: {
|
|
4513
|
+
name: "unverify",
|
|
4514
|
+
description: "Drop your verified ownership of a WebResource (remove the placed token first!)"
|
|
4515
|
+
},
|
|
4516
|
+
args: {
|
|
4517
|
+
id: {
|
|
4518
|
+
type: "positional",
|
|
4519
|
+
required: true,
|
|
4520
|
+
description: "WebResource id (from `sites verify-list`)"
|
|
4521
|
+
},
|
|
4522
|
+
yes: {
|
|
3882
4523
|
type: "boolean",
|
|
3883
|
-
alias: "
|
|
4524
|
+
alias: "y",
|
|
3884
4525
|
default: false,
|
|
3885
|
-
description: "
|
|
3886
|
-
}
|
|
4526
|
+
description: "Skip confirmation prompt"
|
|
4527
|
+
},
|
|
4528
|
+
...OUTPUT_ARGS
|
|
3887
4529
|
},
|
|
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);
|
|
4023
|
-
}
|
|
4024
|
-
}),
|
|
4025
|
-
"verify": defineCommand({
|
|
4026
|
-
meta: {
|
|
4027
|
-
name: "verify",
|
|
4028
|
-
description: "Trigger verification — Google fetches/validates the token you placed"
|
|
4029
|
-
},
|
|
4030
|
-
args: {
|
|
4031
|
-
url: {
|
|
4032
|
-
type: "positional",
|
|
4033
|
-
required: true,
|
|
4034
|
-
description: "Property URL"
|
|
4035
|
-
},
|
|
4036
|
-
method: {
|
|
4037
|
-
type: "string",
|
|
4038
|
-
alias: "m",
|
|
4039
|
-
description: "Verification method to validate (must match the one used for verify-token)"
|
|
4040
|
-
},
|
|
4041
|
-
json: {
|
|
4042
|
-
type: "boolean",
|
|
4043
|
-
default: false,
|
|
4044
|
-
description: "Output as JSON"
|
|
4045
|
-
},
|
|
4046
|
-
quiet: {
|
|
4047
|
-
type: "boolean",
|
|
4048
|
-
alias: "q",
|
|
4049
|
-
default: false,
|
|
4050
|
-
description: "Suppress info/success output"
|
|
4051
|
-
}
|
|
4052
|
-
},
|
|
4053
|
-
async run({ args }) {
|
|
4054
|
-
setQuiet(Boolean(args.quiet) || Boolean(args.json));
|
|
4055
|
-
const method = validateMethod(args.url, args.method ?? pickDefaultMethod(args.url));
|
|
4056
|
-
const resource = await verifySite((await createCommandContext({ needsAuth: true })).client, args.url, method).catch(gscErrorHandler);
|
|
4057
|
-
if (args.json) {
|
|
4058
|
-
console.log(JSON.stringify({
|
|
4059
|
-
siteUrl: args.url,
|
|
4060
|
-
method,
|
|
4061
|
-
resource
|
|
4062
|
-
}, null, 2));
|
|
4063
|
-
return;
|
|
4064
|
-
}
|
|
4065
|
-
logger.success(`Verified: ${args.url}`);
|
|
4066
|
-
if (resource.owners?.length) {
|
|
4067
|
-
console.log();
|
|
4068
|
-
console.log(` Owners:`);
|
|
4069
|
-
for (const o of resource.owners) console.log(` \x1B[90m└─\x1B[0m ${o}`);
|
|
4070
|
-
}
|
|
4530
|
+
async run({ args }) {
|
|
4531
|
+
applyOutputMode(args);
|
|
4532
|
+
if (!args.yes && !args.json) {
|
|
4533
|
+
const ok = await confirm({
|
|
4534
|
+
message: `Unverify WebResource ${args.id}? Remove any placed verification token first or Google may re-verify.`,
|
|
4535
|
+
initialValue: false
|
|
4536
|
+
});
|
|
4537
|
+
if (isCancel(ok) || !ok) {
|
|
4538
|
+
logger.info("Cancelled");
|
|
4539
|
+
process.exit(0);
|
|
4071
4540
|
}
|
|
4072
|
-
}
|
|
4541
|
+
}
|
|
4542
|
+
await unverifySite((await createCommandContext({ needsAuth: true })).client, args.id).catch(gscErrorHandler);
|
|
4543
|
+
if (args.json) {
|
|
4544
|
+
console.log(JSON.stringify({
|
|
4545
|
+
id: args.id,
|
|
4546
|
+
status: "unverified"
|
|
4547
|
+
}, null, 2));
|
|
4548
|
+
return;
|
|
4549
|
+
}
|
|
4550
|
+
logger.success(`Unverified: ${args.id}`);
|
|
4551
|
+
}
|
|
4552
|
+
});
|
|
4553
|
+
const verifyListCommand = defineCommand({
|
|
4554
|
+
meta: {
|
|
4555
|
+
name: "verify-list",
|
|
4556
|
+
description: "List verified WebResources from the Site Verification API (distinct from Search Console properties)"
|
|
4073
4557
|
},
|
|
4558
|
+
args: { ...OUTPUT_ARGS },
|
|
4074
4559
|
async run({ args }) {
|
|
4075
|
-
|
|
4076
|
-
const
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4560
|
+
applyOutputMode(args);
|
|
4561
|
+
const resources = await listVerifiedSites((await createCommandContext({ needsAuth: true })).client).catch(gscErrorHandler);
|
|
4562
|
+
if (args.json) {
|
|
4563
|
+
console.log(JSON.stringify(resources, null, 2));
|
|
4564
|
+
return;
|
|
4565
|
+
}
|
|
4566
|
+
if (resources.length === 0) {
|
|
4567
|
+
logger.warn("No verified WebResources found");
|
|
4568
|
+
return;
|
|
4569
|
+
}
|
|
4570
|
+
logger.success(`${resources.length} verified WebResources:`);
|
|
4571
|
+
console.log();
|
|
4572
|
+
for (const r of resources) {
|
|
4573
|
+
const id = r.id ?? "?";
|
|
4574
|
+
const site = r.site;
|
|
4575
|
+
const ident = site?.identifier ?? id;
|
|
4576
|
+
const type = site?.type === "INET_DOMAIN" ? "domain" : "site";
|
|
4577
|
+
console.log(` \x1B[1m${ident}\x1B[0m \x1B[90m(${type})\x1B[0m`);
|
|
4578
|
+
if (r.owners?.length) for (const o of r.owners) console.log(` \x1B[90m└─\x1B[0m ${o}`);
|
|
4579
|
+
}
|
|
4580
|
+
}
|
|
4581
|
+
});
|
|
4582
|
+
const getCommand = defineCommand({
|
|
4583
|
+
meta: {
|
|
4584
|
+
name: "get",
|
|
4585
|
+
description: "Show a single property's permissionLevel from the sites list"
|
|
4586
|
+
},
|
|
4587
|
+
args: {
|
|
4588
|
+
url: {
|
|
4589
|
+
type: "positional",
|
|
4590
|
+
required: true,
|
|
4591
|
+
description: "Property URL"
|
|
4592
|
+
},
|
|
4593
|
+
...OUTPUT_ARGS
|
|
4594
|
+
},
|
|
4595
|
+
async run({ args }) {
|
|
4596
|
+
applyOutputMode(args);
|
|
4597
|
+
const site = (await (await createCommandContext({ needsAuth: true })).loadSites()).find((s) => s.siteUrl === args.url);
|
|
4598
|
+
if (!site) {
|
|
4081
4599
|
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
|
-
}
|
|
4600
|
+
console.log(JSON.stringify(null));
|
|
4601
|
+
process.exit(1);
|
|
4106
4602
|
}
|
|
4603
|
+
logger.error(`Not found: ${args.url}`);
|
|
4604
|
+
process.exit(1);
|
|
4605
|
+
}
|
|
4606
|
+
if (args.json) {
|
|
4607
|
+
console.log(JSON.stringify(site, null, 2));
|
|
4107
4608
|
return;
|
|
4108
4609
|
}
|
|
4109
|
-
const
|
|
4610
|
+
const perm = site.permissionLevel === "siteOwner" ? "\x1B[32m" : "\x1B[90m";
|
|
4611
|
+
console.log();
|
|
4612
|
+
console.log(` \x1B[1m${site.siteUrl}\x1B[0m`);
|
|
4613
|
+
console.log(` Permission: ${perm}${site.permissionLevel}\x1B[0m`);
|
|
4614
|
+
console.log();
|
|
4615
|
+
}
|
|
4616
|
+
});
|
|
4617
|
+
const LIST_ARGS = {
|
|
4618
|
+
...OUTPUT_ARGS,
|
|
4619
|
+
"with-sitemaps": {
|
|
4620
|
+
type: "boolean",
|
|
4621
|
+
default: false,
|
|
4622
|
+
description: "Include sitemaps for each owned site"
|
|
4623
|
+
},
|
|
4624
|
+
"owner-only": {
|
|
4625
|
+
type: "boolean",
|
|
4626
|
+
default: false,
|
|
4627
|
+
description: "Filter to permissionLevel=siteOwner"
|
|
4628
|
+
}
|
|
4629
|
+
};
|
|
4630
|
+
async function runListSites(args) {
|
|
4631
|
+
applyOutputMode(args);
|
|
4632
|
+
const ctx = await createCommandContext({ needsAuth: true });
|
|
4633
|
+
const ownerOnly = Boolean(args["owner-only"]);
|
|
4634
|
+
if (args["with-sitemaps"]) {
|
|
4635
|
+
const all = await fetchSitesWithSitemaps(ctx.client).catch(gscErrorHandler);
|
|
4110
4636
|
const sites = ownerOnly ? all.filter((s) => s.permissionLevel === "siteOwner") : all;
|
|
4111
4637
|
if (args.json) {
|
|
4112
|
-
|
|
4638
|
+
const enriched = sites.map((s) => ({
|
|
4639
|
+
...s,
|
|
4640
|
+
sitemapCounts: {
|
|
4641
|
+
total: s.sitemaps.length,
|
|
4642
|
+
pending: s.sitemaps.filter((sm) => sm.isPending).length,
|
|
4643
|
+
errored: s.sitemaps.filter((sm) => Number(sm.errors) > 0).length
|
|
4644
|
+
}
|
|
4645
|
+
}));
|
|
4646
|
+
console.log(JSON.stringify(enriched, null, 2));
|
|
4113
4647
|
return;
|
|
4114
4648
|
}
|
|
4115
4649
|
if (sites.length === 0) {
|
|
4116
4650
|
logger.warn(ownerOnly ? "No owned sites found" : "No verified sites found");
|
|
4117
4651
|
return;
|
|
4118
4652
|
}
|
|
4119
|
-
logger.success(`Found ${sites.length} ${ownerOnly ? "owned
|
|
4653
|
+
logger.success(`Found ${sites.length} ${ownerOnly ? "owned" : "verified"} sites:`);
|
|
4120
4654
|
console.log();
|
|
4121
4655
|
for (const site of sites) {
|
|
4122
4656
|
const perm = site.permissionLevel === "siteOwner" ? "\x1B[32m" : "\x1B[90m";
|
|
4123
4657
|
console.log(` ${site.siteUrl} ${perm}(${site.permissionLevel})\x1B[0m`);
|
|
4658
|
+
for (const sm of site.sitemaps) {
|
|
4659
|
+
const pending = sm.isPending ? " \x1B[33m(pending)\x1B[0m" : "";
|
|
4660
|
+
console.log(` \x1B[90m└─\x1B[0m ${sm.path}${pending}`);
|
|
4661
|
+
}
|
|
4124
4662
|
}
|
|
4663
|
+
return;
|
|
4664
|
+
}
|
|
4665
|
+
const all = await ctx.loadSites();
|
|
4666
|
+
const sites = ownerOnly ? all.filter((s) => s.permissionLevel === "siteOwner") : all;
|
|
4667
|
+
if (args.json) {
|
|
4668
|
+
console.log(JSON.stringify(sites, null, 2));
|
|
4669
|
+
return;
|
|
4670
|
+
}
|
|
4671
|
+
if (sites.length === 0) {
|
|
4672
|
+
logger.warn(ownerOnly ? "No owned sites found" : "No verified sites found");
|
|
4673
|
+
return;
|
|
4674
|
+
}
|
|
4675
|
+
logger.success(`Found ${sites.length} ${ownerOnly ? "owned " : ""}sites:`);
|
|
4676
|
+
console.log();
|
|
4677
|
+
for (const site of sites) {
|
|
4678
|
+
const perm = site.permissionLevel === "siteOwner" ? "\x1B[32m" : "\x1B[90m";
|
|
4679
|
+
console.log(` ${site.siteUrl} ${perm}(${site.permissionLevel})\x1B[0m`);
|
|
4680
|
+
}
|
|
4681
|
+
}
|
|
4682
|
+
const sitesCommand = defineCommand({
|
|
4683
|
+
meta: {
|
|
4684
|
+
name: "sites",
|
|
4685
|
+
description: "List GSC sites; manage properties (add/delete) and verify ownership"
|
|
4686
|
+
},
|
|
4687
|
+
args: LIST_ARGS,
|
|
4688
|
+
subCommands: {
|
|
4689
|
+
"list": defineCommand({
|
|
4690
|
+
meta: {
|
|
4691
|
+
name: "list",
|
|
4692
|
+
description: "List GSC sites (alias of bare `sites`)"
|
|
4693
|
+
},
|
|
4694
|
+
args: LIST_ARGS,
|
|
4695
|
+
async run({ args }) {
|
|
4696
|
+
await runListSites(args);
|
|
4697
|
+
}
|
|
4698
|
+
}),
|
|
4699
|
+
"add": addCommand,
|
|
4700
|
+
"delete": deleteCommand,
|
|
4701
|
+
"get": getCommand,
|
|
4702
|
+
"verify-token": verifyTokenCommand,
|
|
4703
|
+
"verify": verifyCommand,
|
|
4704
|
+
"verify-list": verifyListCommand,
|
|
4705
|
+
"verify-get": verifyGetCommand,
|
|
4706
|
+
"unverify": unverifyCommand
|
|
4707
|
+
},
|
|
4708
|
+
async run({ args }) {
|
|
4709
|
+
await runListSites(args);
|
|
4125
4710
|
}
|
|
4126
4711
|
});
|
|
4127
4712
|
const compactCommand = defineCommand({
|
|
@@ -4152,20 +4737,10 @@ const compactCommand = defineCommand({
|
|
|
4152
4737
|
default: false,
|
|
4153
4738
|
description: "Report tier counts per (table, site) without compacting"
|
|
4154
4739
|
},
|
|
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
|
-
}
|
|
4740
|
+
...OUTPUT_ARGS
|
|
4166
4741
|
},
|
|
4167
4742
|
async run({ args }) {
|
|
4168
|
-
|
|
4743
|
+
const { json } = applyOutputMode(args);
|
|
4169
4744
|
const store = (await createCommandContext({ needsStore: true })).store;
|
|
4170
4745
|
const siteId = args.site ? store.siteIdFor(String(args.site)) : void 0;
|
|
4171
4746
|
const dryRun = Boolean(args["dry-run"]);
|
|
@@ -4187,7 +4762,7 @@ const compactCommand = defineCommand({
|
|
|
4187
4762
|
...countByTier(group)
|
|
4188
4763
|
});
|
|
4189
4764
|
}
|
|
4190
|
-
if (
|
|
4765
|
+
if (json) {
|
|
4191
4766
|
console.log(JSON.stringify({
|
|
4192
4767
|
thresholds,
|
|
4193
4768
|
plan: report
|
|
@@ -4222,7 +4797,7 @@ const compactCommand = defineCommand({
|
|
|
4222
4797
|
});
|
|
4223
4798
|
}
|
|
4224
4799
|
}
|
|
4225
|
-
if (
|
|
4800
|
+
if (json) {
|
|
4226
4801
|
console.log(JSON.stringify({
|
|
4227
4802
|
thresholds,
|
|
4228
4803
|
compacted: summary
|
|
@@ -4314,20 +4889,10 @@ const exportCommand = defineCommand({
|
|
|
4314
4889
|
default: false,
|
|
4315
4890
|
description: "Overwrite the output file if it already exists"
|
|
4316
4891
|
},
|
|
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
|
-
}
|
|
4892
|
+
...OUTPUT_ARGS
|
|
4328
4893
|
},
|
|
4329
4894
|
async run({ args }) {
|
|
4330
|
-
|
|
4895
|
+
const { json } = applyOutputMode(args);
|
|
4331
4896
|
const store = (await createCommandContext({ needsStore: true })).store;
|
|
4332
4897
|
const siteId = args.site ? store.siteIdFor(args.site) : void 0;
|
|
4333
4898
|
const result = await exportToDuckDB({
|
|
@@ -4338,7 +4903,7 @@ const exportCommand = defineCommand({
|
|
|
4338
4903
|
outPath: args.out,
|
|
4339
4904
|
force: args.force
|
|
4340
4905
|
});
|
|
4341
|
-
if (
|
|
4906
|
+
if (json) {
|
|
4342
4907
|
console.log(JSON.stringify(result, null, 2));
|
|
4343
4908
|
return;
|
|
4344
4909
|
}
|
|
@@ -4374,20 +4939,10 @@ const gcCommand = defineCommand({
|
|
|
4374
4939
|
default: false,
|
|
4375
4940
|
description: "List retired manifest entries past the grace window without deleting"
|
|
4376
4941
|
},
|
|
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
|
-
}
|
|
4942
|
+
...OUTPUT_ARGS
|
|
4388
4943
|
},
|
|
4389
4944
|
async run({ args }) {
|
|
4390
|
-
|
|
4945
|
+
const { json } = applyOutputMode(args);
|
|
4391
4946
|
const store = (await createCommandContext({ needsStore: true })).store;
|
|
4392
4947
|
const siteId = args.site ? store.siteIdFor(String(args.site)) : void 0;
|
|
4393
4948
|
const graceMs = Number(args["grace-hours"]) * 36e5;
|
|
@@ -4408,7 +4963,7 @@ const gcCommand = defineCommand({
|
|
|
4408
4963
|
objectKey: e.objectKey
|
|
4409
4964
|
});
|
|
4410
4965
|
}
|
|
4411
|
-
if (
|
|
4966
|
+
if (json) {
|
|
4412
4967
|
console.log(JSON.stringify({
|
|
4413
4968
|
graceHours: Number(args["grace-hours"]),
|
|
4414
4969
|
candidates
|
|
@@ -4425,7 +4980,7 @@ const gcCommand = defineCommand({
|
|
|
4425
4980
|
userId: store.userId,
|
|
4426
4981
|
siteId
|
|
4427
4982
|
}, graceMs);
|
|
4428
|
-
if (
|
|
4983
|
+
if (json) {
|
|
4429
4984
|
console.log(JSON.stringify({
|
|
4430
4985
|
graceHours: Number(args["grace-hours"]),
|
|
4431
4986
|
deleted: result.deleted
|
|
@@ -4446,25 +5001,15 @@ const rollupsCommand = defineCommand({
|
|
|
4446
5001
|
description: "Rebuild post-sync rollups (daily totals, weekly totals, top-N tables) for a site"
|
|
4447
5002
|
},
|
|
4448
5003
|
args: {
|
|
5004
|
+
...OUTPUT_ARGS,
|
|
4449
5005
|
site: {
|
|
4450
5006
|
type: "string",
|
|
4451
5007
|
alias: "s",
|
|
4452
5008
|
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
5009
|
}
|
|
4465
5010
|
},
|
|
4466
5011
|
async run({ args }) {
|
|
4467
|
-
|
|
5012
|
+
const { json } = applyOutputMode(args);
|
|
4468
5013
|
const store = (await createCommandContext({ needsStore: true })).store;
|
|
4469
5014
|
const explicitSiteId = args.site ? store.siteIdFor(String(args.site)) : void 0;
|
|
4470
5015
|
const allSiteIds = /* @__PURE__ */ new Set();
|
|
@@ -4477,7 +5022,7 @@ const rollupsCommand = defineCommand({
|
|
|
4477
5022
|
for (const e of entries) if (e.siteId) allSiteIds.add(e.siteId);
|
|
4478
5023
|
}
|
|
4479
5024
|
if (allSiteIds.size === 0) {
|
|
4480
|
-
if (
|
|
5025
|
+
if (json) console.log(JSON.stringify({
|
|
4481
5026
|
sites: [],
|
|
4482
5027
|
totalBytes: 0
|
|
4483
5028
|
}, null, 2));
|
|
@@ -4508,11 +5053,11 @@ const rollupsCommand = defineCommand({
|
|
|
4508
5053
|
bytes: r.bytes,
|
|
4509
5054
|
objectKey: r.objectKey
|
|
4510
5055
|
});
|
|
4511
|
-
if (!
|
|
5056
|
+
if (!json) console.log(` ${r.id.padEnd(20)} ${(r.bytes / 1024).toFixed(1).padStart(8)} KB ${r.objectKey}`);
|
|
4512
5057
|
}
|
|
4513
5058
|
summary.push(site);
|
|
4514
5059
|
}
|
|
4515
|
-
if (
|
|
5060
|
+
if (json) {
|
|
4516
5061
|
console.log(JSON.stringify({
|
|
4517
5062
|
sites: summary,
|
|
4518
5063
|
totalBytes
|
|
@@ -4529,24 +5074,14 @@ const statsCommand = defineCommand({
|
|
|
4529
5074
|
description: "Show row/byte counts per table and on-disk footprint"
|
|
4530
5075
|
},
|
|
4531
5076
|
args: {
|
|
4532
|
-
|
|
4533
|
-
type: "boolean",
|
|
4534
|
-
default: false,
|
|
4535
|
-
description: "Output as JSON"
|
|
4536
|
-
},
|
|
5077
|
+
...OUTPUT_ARGS,
|
|
4537
5078
|
site: {
|
|
4538
5079
|
type: "string",
|
|
4539
5080
|
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
5081
|
}
|
|
4547
5082
|
},
|
|
4548
5083
|
async run({ args }) {
|
|
4549
|
-
|
|
5084
|
+
const { json } = applyOutputMode(args);
|
|
4550
5085
|
const store = (await createCommandContext({ needsStore: true })).store;
|
|
4551
5086
|
let siteId;
|
|
4552
5087
|
if (args.site) {
|
|
@@ -4578,7 +5113,7 @@ const statsCommand = defineCommand({
|
|
|
4578
5113
|
files: 0,
|
|
4579
5114
|
bytes: 0
|
|
4580
5115
|
}));
|
|
4581
|
-
if (
|
|
5116
|
+
if (json) {
|
|
4582
5117
|
const payload = {
|
|
4583
5118
|
dataDir: store.dataDir,
|
|
4584
5119
|
disk,
|
|
@@ -4664,11 +5199,98 @@ const storeCommand = defineCommand({
|
|
|
4664
5199
|
description: "Manage the local DuckDB/Parquet store"
|
|
4665
5200
|
},
|
|
4666
5201
|
subCommands: {
|
|
4667
|
-
stats: statsCommand,
|
|
4668
|
-
compact: compactCommand,
|
|
4669
|
-
gc: gcCommand,
|
|
4670
|
-
export: exportCommand,
|
|
4671
|
-
rollups: rollupsCommand
|
|
5202
|
+
"stats": statsCommand,
|
|
5203
|
+
"compact": compactCommand,
|
|
5204
|
+
"gc": gcCommand,
|
|
5205
|
+
"export": exportCommand,
|
|
5206
|
+
"rollups": rollupsCommand,
|
|
5207
|
+
"rm-site": defineCommand({
|
|
5208
|
+
meta: {
|
|
5209
|
+
name: "rm-site",
|
|
5210
|
+
description: "Delete every parquet, manifest, watermark, and sync-state record for a single site"
|
|
5211
|
+
},
|
|
5212
|
+
args: {
|
|
5213
|
+
site: {
|
|
5214
|
+
type: "positional",
|
|
5215
|
+
required: true,
|
|
5216
|
+
description: "Site URL (e.g. sc-domain:example.com)"
|
|
5217
|
+
},
|
|
5218
|
+
yes: {
|
|
5219
|
+
type: "boolean",
|
|
5220
|
+
alias: "y",
|
|
5221
|
+
default: false,
|
|
5222
|
+
description: "Skip confirmation prompt"
|
|
5223
|
+
},
|
|
5224
|
+
...OUTPUT_ARGS
|
|
5225
|
+
},
|
|
5226
|
+
async run({ args }) {
|
|
5227
|
+
const { json } = applyOutputMode(args);
|
|
5228
|
+
const store = (await createCommandContext({ needsStore: true })).store;
|
|
5229
|
+
const siteId = store.siteIdFor(String(args.site));
|
|
5230
|
+
if (!args.yes && !json) {
|
|
5231
|
+
const ok = await confirm({
|
|
5232
|
+
message: `Delete ALL local data for ${args.site}? This is irreversible.`,
|
|
5233
|
+
initialValue: false
|
|
5234
|
+
});
|
|
5235
|
+
if (isCancel(ok) || !ok) {
|
|
5236
|
+
logger.info("Cancelled");
|
|
5237
|
+
process.exit(0);
|
|
5238
|
+
}
|
|
5239
|
+
}
|
|
5240
|
+
const result = await store.engine.purgeTenant({
|
|
5241
|
+
userId: store.userId,
|
|
5242
|
+
siteId
|
|
5243
|
+
});
|
|
5244
|
+
if (json) {
|
|
5245
|
+
console.log(JSON.stringify(result, null, 2));
|
|
5246
|
+
return;
|
|
5247
|
+
}
|
|
5248
|
+
logger.success(`Removed local data for ${args.site}`);
|
|
5249
|
+
console.log(` Objects deleted: ${result.objectsDeleted}`);
|
|
5250
|
+
console.log(` Manifest entries: ${result.entriesRemoved}`);
|
|
5251
|
+
console.log(` Watermarks: ${result.watermarksRemoved}`);
|
|
5252
|
+
console.log(` Sync states: ${result.syncStatesRemoved}`);
|
|
5253
|
+
}
|
|
5254
|
+
}),
|
|
5255
|
+
"reset": defineCommand({
|
|
5256
|
+
meta: {
|
|
5257
|
+
name: "reset",
|
|
5258
|
+
description: "Wipe the entire local store (every site, every table). Irreversible."
|
|
5259
|
+
},
|
|
5260
|
+
args: {
|
|
5261
|
+
yes: {
|
|
5262
|
+
type: "boolean",
|
|
5263
|
+
alias: "y",
|
|
5264
|
+
default: false,
|
|
5265
|
+
description: "Skip confirmation prompt"
|
|
5266
|
+
},
|
|
5267
|
+
...OUTPUT_ARGS
|
|
5268
|
+
},
|
|
5269
|
+
async run({ args }) {
|
|
5270
|
+
const { json } = applyOutputMode(args);
|
|
5271
|
+
const store = (await createCommandContext({ needsStore: true })).store;
|
|
5272
|
+
if (!args.yes && !json) {
|
|
5273
|
+
const ok = await confirm({
|
|
5274
|
+
message: `Wipe the entire local store under ${store.dataDir}? This deletes data for ALL sites.`,
|
|
5275
|
+
initialValue: false
|
|
5276
|
+
});
|
|
5277
|
+
if (isCancel(ok) || !ok) {
|
|
5278
|
+
logger.info("Cancelled");
|
|
5279
|
+
process.exit(0);
|
|
5280
|
+
}
|
|
5281
|
+
}
|
|
5282
|
+
const result = await store.engine.purgeTenant({ userId: store.userId });
|
|
5283
|
+
if (json) {
|
|
5284
|
+
console.log(JSON.stringify(result, null, 2));
|
|
5285
|
+
return;
|
|
5286
|
+
}
|
|
5287
|
+
logger.success("Local store reset");
|
|
5288
|
+
console.log(` Objects deleted: ${result.objectsDeleted}`);
|
|
5289
|
+
console.log(` Manifest entries: ${result.entriesRemoved}`);
|
|
5290
|
+
console.log(` Watermarks: ${result.watermarksRemoved}`);
|
|
5291
|
+
console.log(` Sync states: ${result.syncStatesRemoved}`);
|
|
5292
|
+
}
|
|
5293
|
+
})
|
|
4672
5294
|
}
|
|
4673
5295
|
});
|
|
4674
5296
|
const DEFAULT_TABLES = [
|
|
@@ -4844,12 +5466,7 @@ const syncCommand = defineCommand({
|
|
|
4844
5466
|
type: "boolean",
|
|
4845
5467
|
description: "Sync the last 450 days (full GSC history)"
|
|
4846
5468
|
},
|
|
4847
|
-
|
|
4848
|
-
type: "boolean",
|
|
4849
|
-
alias: "q",
|
|
4850
|
-
default: false,
|
|
4851
|
-
description: "Suppress progress output"
|
|
4852
|
-
},
|
|
5469
|
+
...OUTPUT_ARGS,
|
|
4853
5470
|
"force": {
|
|
4854
5471
|
type: "boolean",
|
|
4855
5472
|
default: false,
|
|
@@ -4860,11 +5477,6 @@ const syncCommand = defineCommand({
|
|
|
4860
5477
|
default: false,
|
|
4861
5478
|
description: "Print watermarks + sync-state summary instead of syncing"
|
|
4862
5479
|
},
|
|
4863
|
-
"json": {
|
|
4864
|
-
type: "boolean",
|
|
4865
|
-
default: false,
|
|
4866
|
-
description: "With --status: emit JSON"
|
|
4867
|
-
},
|
|
4868
5480
|
"concurrency": {
|
|
4869
5481
|
type: "string",
|
|
4870
5482
|
alias: "c",
|
|
@@ -4887,8 +5499,9 @@ const syncCommand = defineCommand({
|
|
|
4887
5499
|
}
|
|
4888
5500
|
},
|
|
4889
5501
|
async run({ args }) {
|
|
5502
|
+
const { json, quiet } = applyOutputMode(args);
|
|
4890
5503
|
if (args.status) {
|
|
4891
|
-
await printSyncStatus(await loadConfig(), args.site ? String(args.site) : void 0,
|
|
5504
|
+
await printSyncStatus(await loadConfig(), args.site ? String(args.site) : void 0, json);
|
|
4892
5505
|
return;
|
|
4893
5506
|
}
|
|
4894
5507
|
const ctx = await createCommandContext({
|
|
@@ -4923,7 +5536,7 @@ const syncCommand = defineCommand({
|
|
|
4923
5536
|
logger.warn(`All requested types (${requestedTypes.join(", ")}) are marked empty for this site. Pass --force-types to re-probe.`);
|
|
4924
5537
|
return;
|
|
4925
5538
|
}
|
|
4926
|
-
if (skippedTypes.length > 0 && !
|
|
5539
|
+
if (skippedTypes.length > 0 && !quiet) logger.info(`Skipping ${skippedTypes.join(", ")} (marked empty for this site; pass --force-types to re-probe).`);
|
|
4927
5540
|
const endDate = args.end ? String(args.end) : daysAgo(DEFAULT_PENDING_DAYS);
|
|
4928
5541
|
let startDate;
|
|
4929
5542
|
if (args.start) startDate = String(args.start);
|
|
@@ -4953,7 +5566,7 @@ const syncCommand = defineCommand({
|
|
|
4953
5566
|
return;
|
|
4954
5567
|
}
|
|
4955
5568
|
args.force = true;
|
|
4956
|
-
if (!
|
|
5569
|
+
if (!quiet) logger.info(`--retry-failed: ${dates.length} date(s) to retry`);
|
|
4957
5570
|
}
|
|
4958
5571
|
if (args["dry-run"]) {
|
|
4959
5572
|
const plan = [];
|
|
@@ -4962,7 +5575,7 @@ const syncCommand = defineCommand({
|
|
|
4962
5575
|
searchType: type,
|
|
4963
5576
|
date
|
|
4964
5577
|
});
|
|
4965
|
-
if (
|
|
5578
|
+
if (json) {
|
|
4966
5579
|
console.log(JSON.stringify({
|
|
4967
5580
|
siteUrl,
|
|
4968
5581
|
range: {
|
|
@@ -4985,7 +5598,7 @@ const syncCommand = defineCommand({
|
|
|
4985
5598
|
logger.info("Pass without --dry-run to execute.");
|
|
4986
5599
|
return;
|
|
4987
5600
|
}
|
|
4988
|
-
if (!
|
|
5601
|
+
if (!quiet) {
|
|
4989
5602
|
logger.info(`Syncing ${siteUrl} (${tables.join(", ")}) [${types.join(", ")}] → ${displayPath(store.dataDir)}`);
|
|
4990
5603
|
logger.info(`Range: ${startDate} → ${endDate} (${dates.length} days)`);
|
|
4991
5604
|
}
|
|
@@ -5002,7 +5615,7 @@ const syncCommand = defineCommand({
|
|
|
5002
5615
|
label
|
|
5003
5616
|
});
|
|
5004
5617
|
}
|
|
5005
|
-
const progress = createProgressTracker(dates.length * jobs.length,
|
|
5618
|
+
const progress = createProgressTracker(dates.length * jobs.length, quiet);
|
|
5006
5619
|
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
5620
|
else {
|
|
5008
5621
|
const results = await Promise.all(jobs.map((job) => syncTable(store, siteUrl, job.table, job.type, dates, client, concurrency, args.force, progress)));
|
|
@@ -5012,7 +5625,7 @@ const syncCommand = defineCommand({
|
|
|
5012
5625
|
}
|
|
5013
5626
|
progress.done();
|
|
5014
5627
|
const seconds = ((Date.now() - start) / 1e3).toFixed(1);
|
|
5015
|
-
if (!
|
|
5628
|
+
if (!quiet) {
|
|
5016
5629
|
logger.success(`Synced ${siteUrl} in ${seconds}s`);
|
|
5017
5630
|
for (const [t, n] of Object.entries(totals)) {
|
|
5018
5631
|
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 +5654,7 @@ const syncCommand = defineCommand({
|
|
|
5041
5654
|
userId: store.userId,
|
|
5042
5655
|
siteId
|
|
5043
5656
|
}, toMark);
|
|
5044
|
-
if (!
|
|
5657
|
+
if (!quiet) logger.info(`Marked empty for future syncs: ${toMark.join(", ")} (0 rows across ${dates.length} days; pass --force-types to re-probe).`);
|
|
5045
5658
|
}
|
|
5046
5659
|
}
|
|
5047
5660
|
if (forceTypes && emptyTypesDoc.emptyTypes.length > 0) {
|
|
@@ -5052,13 +5665,13 @@ const syncCommand = defineCommand({
|
|
|
5052
5665
|
userId: store.userId,
|
|
5053
5666
|
siteId
|
|
5054
5667
|
}, toClear);
|
|
5055
|
-
if (!
|
|
5668
|
+
if (!quiet) logger.info(`Cleared empty markers for: ${toClear.join(", ")} (re-probe found data).`);
|
|
5056
5669
|
}
|
|
5057
5670
|
}
|
|
5058
5671
|
const noRollups = Boolean(args["no-rollups"]);
|
|
5059
5672
|
const anyRowsSynced = Object.values(totals).some((t) => t.rows > 0);
|
|
5060
5673
|
if (!noRollups && anyRowsSynced) {
|
|
5061
|
-
if (!
|
|
5674
|
+
if (!quiet) logger.info(`Rebuilding rollups for [${siteId}] (${DEFAULT_ROLLUPS.length} rollups)…`);
|
|
5062
5675
|
const rollupStart = Date.now();
|
|
5063
5676
|
const results = await rebuildRollups({
|
|
5064
5677
|
engine: store.engine,
|
|
@@ -5072,7 +5685,7 @@ const syncCommand = defineCommand({
|
|
|
5072
5685
|
logger.warn(`Rollup rebuild failed: ${err.message}`);
|
|
5073
5686
|
return [];
|
|
5074
5687
|
});
|
|
5075
|
-
if (!
|
|
5688
|
+
if (!quiet && results.length > 0) {
|
|
5076
5689
|
const kb = results.reduce((a, r) => a + r.bytes, 0) / 1024;
|
|
5077
5690
|
const ms = Date.now() - rollupStart;
|
|
5078
5691
|
logger.success(`Rebuilt ${results.length} rollup(s) in ${ms}ms — ${kb.toFixed(1)} KB`);
|
|
@@ -5160,10 +5773,11 @@ function shouldShowSplash() {
|
|
|
5160
5773
|
function applyGlobalArgs() {
|
|
5161
5774
|
const argv = process.argv;
|
|
5162
5775
|
if (argv.includes("--no-color") || process.env.NO_COLOR) setNoColor(true);
|
|
5163
|
-
const profile = pluckArgValue(argv, "--profile")
|
|
5164
|
-
|
|
5165
|
-
|
|
5166
|
-
|
|
5776
|
+
const profile = pluckArgValue(argv, "--profile");
|
|
5777
|
+
applyProfileFromCli({
|
|
5778
|
+
configDir: pluckArgValue(argv, "--config-dir") ?? process.env.GSCDUMP_CONFIG_DIR ?? null,
|
|
5779
|
+
profile
|
|
5780
|
+
});
|
|
5167
5781
|
if (argv.includes("-v") && !argv.includes("--version")) {
|
|
5168
5782
|
const i = argv.indexOf("-v");
|
|
5169
5783
|
argv[i] = "--version";
|
|
@@ -5207,6 +5821,7 @@ runMain(defineCommand({
|
|
|
5207
5821
|
analyze: analyzeCommand,
|
|
5208
5822
|
auth: authCommand,
|
|
5209
5823
|
config: configCommand,
|
|
5824
|
+
profile: profileCommand,
|
|
5210
5825
|
doctor: doctorCommand,
|
|
5211
5826
|
mcp: mcpCommand
|
|
5212
5827
|
},
|