@gscdump/cli 0.17.5 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.mjs +624 -427
- package/package.json +6 -6
- package/dist/_chunks/config.mjs +0 -54
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as loadConfig, c as setConfigDir, i as getConfigPath, l as __exportAll, n as defaultDataDir, o as resolveDataDir, r as getConfigDir, s as saveConfig } from "./_chunks/config.mjs";
|
|
3
2
|
import process from "node:process";
|
|
4
3
|
import { defineCommand, runMain } from "citty";
|
|
5
4
|
import { defaultAnalyzerRegistry } from "@gscdump/analysis/registry";
|
|
@@ -15,9 +14,9 @@ import { createServer } from "node:http";
|
|
|
15
14
|
import { JWT, OAuth2Client } from "google-auth-library";
|
|
16
15
|
import { ofetch } from "ofetch";
|
|
17
16
|
import fs$1 from "node:fs";
|
|
18
|
-
import { Buffer } from "node:buffer";
|
|
17
|
+
import { Buffer as Buffer$1 } from "node:buffer";
|
|
19
18
|
import { createConsola } from "consola";
|
|
20
|
-
import { SearchTypes, and, between, contains, country, date, device, eq, gsc, notRegex, page, query, regex, searchAppearance } from "gscdump/query";
|
|
19
|
+
import { SearchTypes, and, between, contains, country, date, device, eq, gsc, hour, notRegex, page, query, regex, searchAppearance } from "gscdump/query";
|
|
21
20
|
import { createNodeHarness } from "@gscdump/engine/node";
|
|
22
21
|
import { TABLE_DIMS, transformGscRow } from "@gscdump/engine/ingest";
|
|
23
22
|
import { allTables, inferTable } from "@gscdump/engine/schema";
|
|
@@ -31,6 +30,56 @@ import { resolveWindow } from "@gscdump/engine/period";
|
|
|
31
30
|
import { inferLegacyTier } from "@gscdump/engine";
|
|
32
31
|
import { DEFAULT_ROLLUPS, rebuildRollups } from "@gscdump/engine/rollups";
|
|
33
32
|
import { filesystemStats } from "@gscdump/engine/filesystem";
|
|
33
|
+
var __defProp = Object.defineProperty;
|
|
34
|
+
var __exportAll = (all, no_symbols) => {
|
|
35
|
+
let target = {};
|
|
36
|
+
for (var name in all) __defProp(target, name, {
|
|
37
|
+
get: all[name],
|
|
38
|
+
enumerable: true
|
|
39
|
+
});
|
|
40
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
41
|
+
return target;
|
|
42
|
+
};
|
|
43
|
+
var config_exports = /* @__PURE__ */ __exportAll({
|
|
44
|
+
defaultDataDir: () => defaultDataDir,
|
|
45
|
+
getConfigDir: () => getConfigDir,
|
|
46
|
+
getConfigPath: () => getConfigPath,
|
|
47
|
+
loadConfig: () => loadConfig,
|
|
48
|
+
resolveDataDir: () => resolveDataDir,
|
|
49
|
+
saveConfig: () => saveConfig,
|
|
50
|
+
setConfigDir: () => setConfigDir
|
|
51
|
+
});
|
|
52
|
+
let configDir = path.join(os.homedir(), ".config", "gscdump");
|
|
53
|
+
function setConfigDir(dir) {
|
|
54
|
+
configDir = dir;
|
|
55
|
+
}
|
|
56
|
+
function getConfigDir() {
|
|
57
|
+
return configDir;
|
|
58
|
+
}
|
|
59
|
+
function defaultDataDir() {
|
|
60
|
+
return path.join(os.homedir(), ".gscdump", "data");
|
|
61
|
+
}
|
|
62
|
+
function resolveDataDir(config) {
|
|
63
|
+
return expandTilde(config.dataDir ?? defaultDataDir());
|
|
64
|
+
}
|
|
65
|
+
function expandTilde(p) {
|
|
66
|
+
if (p === "~") return os.homedir();
|
|
67
|
+
if (p.startsWith("~/")) return path.join(os.homedir(), p.slice(2));
|
|
68
|
+
return p;
|
|
69
|
+
}
|
|
70
|
+
async function loadConfig() {
|
|
71
|
+
return fs.readFile(path.join(configDir, "config.json"), "utf-8").then((data) => JSON.parse(data)).catch(() => ({}));
|
|
72
|
+
}
|
|
73
|
+
async function saveConfig(config) {
|
|
74
|
+
await fs.mkdir(configDir, {
|
|
75
|
+
recursive: true,
|
|
76
|
+
mode: 448
|
|
77
|
+
});
|
|
78
|
+
await fs.writeFile(path.join(configDir, "config.json"), JSON.stringify(config, null, 2), { mode: 384 });
|
|
79
|
+
}
|
|
80
|
+
function getConfigPath() {
|
|
81
|
+
return path.join(configDir, "config.json");
|
|
82
|
+
}
|
|
34
83
|
const ENV_LINE_RE$1 = /^([^=]+)=(.*)$/;
|
|
35
84
|
function parseEnvFile(envPath) {
|
|
36
85
|
let content;
|
|
@@ -73,9 +122,16 @@ function loadEnvFromCwd() {
|
|
|
73
122
|
}
|
|
74
123
|
return applied;
|
|
75
124
|
}
|
|
76
|
-
var version = "0.
|
|
125
|
+
var version = "0.18.0";
|
|
77
126
|
const ALL_SEARCH_TYPES$1 = Object.values(SearchTypes);
|
|
78
127
|
const VERSION = version;
|
|
128
|
+
function noSubcommandSelected(parent, subNames) {
|
|
129
|
+
const idx = process.argv.indexOf(parent);
|
|
130
|
+
if (idx < 0) return true;
|
|
131
|
+
const next = process.argv[idx + 1];
|
|
132
|
+
if (!next) return true;
|
|
133
|
+
return !subNames.includes(next);
|
|
134
|
+
}
|
|
79
135
|
const baseLogger = createConsola({
|
|
80
136
|
stdout: process.stderr,
|
|
81
137
|
stderr: process.stderr
|
|
@@ -128,7 +184,7 @@ function wrapStdoutForNoColor() {
|
|
|
128
184
|
const original = process.stdout.write.bind(process.stdout);
|
|
129
185
|
process.stdout.write = ((chunk, ...rest) => {
|
|
130
186
|
if (typeof chunk === "string") chunk = chunk.replace(ANSI_RE, "");
|
|
131
|
-
else if (chunk instanceof Uint8Array) chunk = Buffer.from(chunk).toString("utf8").replace(ANSI_RE, "");
|
|
187
|
+
else if (chunk instanceof Uint8Array) chunk = Buffer$1.from(chunk).toString("utf8").replace(ANSI_RE, "");
|
|
132
188
|
return original(chunk, ...rest);
|
|
133
189
|
});
|
|
134
190
|
}
|
|
@@ -201,7 +257,7 @@ async function readUrlList$1(args) {
|
|
|
201
257
|
if (!process.stdin.isTTY) {
|
|
202
258
|
const chunks = [];
|
|
203
259
|
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
204
|
-
return Buffer.concat(chunks).toString("utf-8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
|
|
260
|
+
return Buffer$1.concat(chunks).toString("utf-8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
|
|
205
261
|
}
|
|
206
262
|
return [];
|
|
207
263
|
}
|
|
@@ -335,8 +391,10 @@ async function getAuthCredentials(interactive) {
|
|
|
335
391
|
const envClientId = process.env.GOOGLE_CLIENT_ID;
|
|
336
392
|
const envClientSecret = process.env.GOOGLE_CLIENT_SECRET;
|
|
337
393
|
if (envClientId && envClientSecret) {
|
|
338
|
-
|
|
339
|
-
|
|
394
|
+
if (interactive) {
|
|
395
|
+
logger.info("Using OAuth client from env");
|
|
396
|
+
console.log(` \x1B[90m${envClientId}\x1B[0m`);
|
|
397
|
+
}
|
|
340
398
|
return {
|
|
341
399
|
clientId: envClientId,
|
|
342
400
|
clientSecret: envClientSecret
|
|
@@ -344,8 +402,10 @@ async function getAuthCredentials(interactive) {
|
|
|
344
402
|
}
|
|
345
403
|
const config = await loadConfig();
|
|
346
404
|
if (config.clientId && config.clientSecret) {
|
|
347
|
-
|
|
348
|
-
|
|
405
|
+
if (interactive) {
|
|
406
|
+
logger.info(`Using OAuth client from ${displayPath(`${getConfigDir()}/config.json`)}`);
|
|
407
|
+
console.log(` \x1B[90m${config.clientId}\x1B[0m`);
|
|
408
|
+
}
|
|
349
409
|
return {
|
|
350
410
|
clientId: config.clientId,
|
|
351
411
|
clientSecret: config.clientSecret
|
|
@@ -429,13 +489,18 @@ async function getAuthCodeViaLoopback(authUrl) {
|
|
|
429
489
|
console.log(` \x1B[90mIf browser doesn't open, visit:\x1B[0m`);
|
|
430
490
|
console.log(` \x1B[36m${fullAuthUrl}\x1B[0m`);
|
|
431
491
|
console.log();
|
|
492
|
+
console.log(` \x1B[90mIf Google says "redirect_uri_mismatch", your OAuth client is`);
|
|
493
|
+
console.log(` not a "Desktop application" type. Create a Desktop client at`);
|
|
494
|
+
console.log(` https://console.cloud.google.com/apis/credentials, then run`);
|
|
495
|
+
console.log(` \`gscdump init --force\` with the new ID/secret.\x1B[0m`);
|
|
496
|
+
console.log();
|
|
432
497
|
import("open").then(({ default: open }) => open(fullAuthUrl)).catch(() => {
|
|
433
498
|
logger.warn("Could not open browser automatically");
|
|
434
499
|
});
|
|
435
500
|
});
|
|
436
501
|
server.on("error", (err) => settle(() => reject(err)));
|
|
437
502
|
timeoutId = setTimeout(() => {
|
|
438
|
-
settle(() => reject(/* @__PURE__ */ new Error("Authorization timed out")));
|
|
503
|
+
settle(() => reject(/* @__PURE__ */ new Error("Authorization timed out. If Google showed \"redirect_uri_mismatch\", your OAuth client must be type \"Desktop application\" (create one at https://console.cloud.google.com/apis/credentials and run `gscdump init --force`).")));
|
|
439
504
|
}, 300 * 1e3);
|
|
440
505
|
});
|
|
441
506
|
}
|
|
@@ -455,23 +520,39 @@ async function authenticate(credentials, interactive, opts = {}) {
|
|
|
455
520
|
return oauth2Client;
|
|
456
521
|
}
|
|
457
522
|
const existingTokens = !opts.force ? await loadTokens() : null;
|
|
523
|
+
let refreshFailed = false;
|
|
524
|
+
let refreshError = null;
|
|
458
525
|
if (existingTokens) {
|
|
459
526
|
oauth2Client.setCredentials(existingTokens);
|
|
460
527
|
if (existingTokens.expiry_date && existingTokens.expiry_date < Date.now()) {
|
|
461
|
-
const
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
528
|
+
const result = await oauth2Client.refreshAccessToken().then((r) => ({
|
|
529
|
+
credentials: r.credentials,
|
|
530
|
+
error: null
|
|
531
|
+
})).catch((e) => ({
|
|
532
|
+
credentials: null,
|
|
533
|
+
error: e
|
|
534
|
+
}));
|
|
535
|
+
if (result.credentials) {
|
|
536
|
+
await saveTokens(result.credentials);
|
|
537
|
+
oauth2Client.setCredentials(result.credentials);
|
|
538
|
+
if (interactive) logger.success("Token refreshed");
|
|
466
539
|
return oauth2Client;
|
|
467
540
|
}
|
|
541
|
+
refreshFailed = true;
|
|
542
|
+
refreshError = result.error;
|
|
468
543
|
} else {
|
|
469
|
-
logger.success("Using saved credentials");
|
|
544
|
+
if (interactive) logger.success("Using saved credentials");
|
|
470
545
|
return oauth2Client;
|
|
471
546
|
}
|
|
472
547
|
}
|
|
473
548
|
if (!interactive) {
|
|
474
|
-
|
|
549
|
+
if (refreshFailed) {
|
|
550
|
+
logger.error(`Token refresh failed${refreshError ? `: ${refreshError.message}` : ""}`);
|
|
551
|
+
logger.info("Refresh token may be revoked or expired. Run `gscdump auth login` to re-authenticate.");
|
|
552
|
+
} else {
|
|
553
|
+
logger.error("Not authenticated");
|
|
554
|
+
logger.info("Run `gscdump auth login` (or `gscdump init` for full setup).");
|
|
555
|
+
}
|
|
475
556
|
process.exit(1);
|
|
476
557
|
}
|
|
477
558
|
if (opts.noBrowser) {
|
|
@@ -1125,6 +1206,183 @@ const analyzeCommand = defineCommand({
|
|
|
1125
1206
|
...Object.fromEntries(ANALYSIS_TOOLS.map((tool) => [tool, makeToolCommand(tool)]))
|
|
1126
1207
|
}
|
|
1127
1208
|
});
|
|
1209
|
+
const ENV_LINE_RE = /^([^=]+)=(.*)$/;
|
|
1210
|
+
async function promptDataDir(existing) {
|
|
1211
|
+
const fallback = existing ?? defaultDataDir();
|
|
1212
|
+
const answer = await text({
|
|
1213
|
+
message: "Where should Parquet data be stored?",
|
|
1214
|
+
placeholder: fallback,
|
|
1215
|
+
defaultValue: fallback
|
|
1216
|
+
});
|
|
1217
|
+
if (isCancel(answer)) process.exit(1);
|
|
1218
|
+
return String(answer) || fallback;
|
|
1219
|
+
}
|
|
1220
|
+
async function loadEnvFile() {
|
|
1221
|
+
const envPath = path.join(process.cwd(), ".env");
|
|
1222
|
+
const content = await fs.readFile(envPath, "utf-8").catch(() => null);
|
|
1223
|
+
if (!content) return null;
|
|
1224
|
+
const env = {};
|
|
1225
|
+
for (const line of content.split("\n")) {
|
|
1226
|
+
const trimmed = line.trim();
|
|
1227
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
1228
|
+
const match = trimmed.match(ENV_LINE_RE);
|
|
1229
|
+
if (match) {
|
|
1230
|
+
const key = match[1].trim();
|
|
1231
|
+
let value = match[2].trim();
|
|
1232
|
+
if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
|
|
1233
|
+
env[key] = value;
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
return env;
|
|
1237
|
+
}
|
|
1238
|
+
const initCommand = defineCommand({
|
|
1239
|
+
meta: {
|
|
1240
|
+
name: "init",
|
|
1241
|
+
description: "Set up GSCDump authentication"
|
|
1242
|
+
},
|
|
1243
|
+
args: {
|
|
1244
|
+
"force": {
|
|
1245
|
+
type: "boolean",
|
|
1246
|
+
alias: "f",
|
|
1247
|
+
description: "Force re-initialization"
|
|
1248
|
+
},
|
|
1249
|
+
"no-store": {
|
|
1250
|
+
type: "boolean",
|
|
1251
|
+
default: false,
|
|
1252
|
+
description: "Skip dataDir prompt (auth-only setup)"
|
|
1253
|
+
},
|
|
1254
|
+
...OUTPUT_ARGS
|
|
1255
|
+
},
|
|
1256
|
+
async run({ args }) {
|
|
1257
|
+
applyOutputMode(args);
|
|
1258
|
+
const config = await loadConfig();
|
|
1259
|
+
if (config.clientId && config.clientSecret && !args.force) {
|
|
1260
|
+
logger.info("Already configured");
|
|
1261
|
+
const tokens = await loadTokens();
|
|
1262
|
+
if (!tokens) logger.info("No saved tokens. Run `gscdump auth login` to authenticate.");
|
|
1263
|
+
else {
|
|
1264
|
+
const isExpired = tokens.expiry_date && tokens.expiry_date < Date.now();
|
|
1265
|
+
if (isExpired && tokens.refresh_token) logger.info("Tokens expired but refresh available. Any live command will auto-refresh, or run `gscdump auth refresh`.");
|
|
1266
|
+
else if (isExpired) logger.warn("Tokens expired without a refresh token. Run `gscdump auth login` to re-authenticate.");
|
|
1267
|
+
else logger.info("Next: `gscdump sites` to list properties, or `gscdump query --help`.");
|
|
1268
|
+
}
|
|
1269
|
+
logger.info("Run `gscdump init --force` to reconfigure.");
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
const byok = resolveBYOK();
|
|
1273
|
+
if (byok) {
|
|
1274
|
+
const dataDir = args["no-store"] ? void 0 : await promptDataDir(config.dataDir);
|
|
1275
|
+
await saveConfig({
|
|
1276
|
+
...config,
|
|
1277
|
+
dataDir: dataDir ?? config.dataDir
|
|
1278
|
+
});
|
|
1279
|
+
logger.success(`BYOK detected (${typeof byok === "string" ? "access-token" : "refresh-token"}) — auth setup skipped`);
|
|
1280
|
+
logger.success("Setup complete! Run gscdump to get started.");
|
|
1281
|
+
return;
|
|
1282
|
+
}
|
|
1283
|
+
const envFile = await loadEnvFile();
|
|
1284
|
+
const envCid = envFile?.GSC_CLIENT_ID ?? envFile?.GOOGLE_CLIENT_ID;
|
|
1285
|
+
const envSec = envFile?.GSC_CLIENT_SECRET ?? envFile?.GOOGLE_CLIENT_SECRET;
|
|
1286
|
+
const envRef = envFile?.GSC_REFRESH_TOKEN ?? envFile?.GOOGLE_REFRESH_TOKEN;
|
|
1287
|
+
const envAcc = envFile?.GSC_ACCESS_TOKEN ?? envFile?.GOOGLE_ACCESS_TOKEN;
|
|
1288
|
+
if (envCid && envSec && envRef) {
|
|
1289
|
+
logger.info("Found .env file with credentials");
|
|
1290
|
+
process.env.GOOGLE_CLIENT_ID = envCid;
|
|
1291
|
+
process.env.GOOGLE_CLIENT_SECRET = envSec;
|
|
1292
|
+
process.env.GOOGLE_REFRESH_TOKEN = envRef;
|
|
1293
|
+
if (envAcc) process.env.GOOGLE_ACCESS_TOKEN = envAcc;
|
|
1294
|
+
await saveConfig({
|
|
1295
|
+
...config,
|
|
1296
|
+
clientId: envCid,
|
|
1297
|
+
clientSecret: envSec,
|
|
1298
|
+
dataDir: config.dataDir ?? defaultDataDir()
|
|
1299
|
+
});
|
|
1300
|
+
const creds = (await authenticate({
|
|
1301
|
+
clientId: envCid,
|
|
1302
|
+
clientSecret: envSec
|
|
1303
|
+
}, false)).credentials;
|
|
1304
|
+
if (creds.access_token) await saveTokens({
|
|
1305
|
+
access_token: creds.access_token,
|
|
1306
|
+
refresh_token: creds.refresh_token || envRef,
|
|
1307
|
+
expiry_date: creds.expiry_date
|
|
1308
|
+
});
|
|
1309
|
+
console.log();
|
|
1310
|
+
logger.success("Setup complete using .env credentials! Run gscdump to get started.");
|
|
1311
|
+
return;
|
|
1312
|
+
}
|
|
1313
|
+
console.log();
|
|
1314
|
+
console.log(" \x1B[1mWelcome to GSCDump!\x1B[0m");
|
|
1315
|
+
console.log(" \x1B[90mGoogle Search Console data extraction CLI\x1B[0m");
|
|
1316
|
+
console.log();
|
|
1317
|
+
const dataDir = args["no-store"] ? void 0 : await promptDataDir(config.dataDir);
|
|
1318
|
+
const credentials = await getAuthCredentials(true);
|
|
1319
|
+
await saveConfig({
|
|
1320
|
+
...config,
|
|
1321
|
+
...dataDir ? { dataDir } : {},
|
|
1322
|
+
clientId: credentials.clientId,
|
|
1323
|
+
clientSecret: credentials.clientSecret
|
|
1324
|
+
});
|
|
1325
|
+
await smokeTest(await authenticate(credentials, true));
|
|
1326
|
+
await maybeWriteEnvFile(credentials.clientId, credentials.clientSecret);
|
|
1327
|
+
console.log();
|
|
1328
|
+
logger.success("Setup complete! Run gscdump to get started.");
|
|
1329
|
+
}
|
|
1330
|
+
});
|
|
1331
|
+
async function smokeTest(oauth) {
|
|
1332
|
+
await runSmokeTest(oauth);
|
|
1333
|
+
}
|
|
1334
|
+
async function runSmokeTest(oauth) {
|
|
1335
|
+
const sites = await googleSearchConsole(oauth).sites().catch((e) => e);
|
|
1336
|
+
if (sites instanceof Error) {
|
|
1337
|
+
const msg = sites.message;
|
|
1338
|
+
const apiDisabledMatch = msg.match(/projects?\/(\d+)/) ?? msg.match(/project (\d+)/);
|
|
1339
|
+
if (/has not been used in project|API has not been used|SERVICE_DISABLED/i.test(msg)) {
|
|
1340
|
+
logger.error("Search Console API is not enabled for this Google Cloud project.");
|
|
1341
|
+
const project = apiDisabledMatch?.[1];
|
|
1342
|
+
const url = project ? `https://console.developers.google.com/apis/api/searchconsole.googleapis.com/overview?project=${project}` : "https://console.developers.google.com/apis/api/searchconsole.googleapis.com/overview";
|
|
1343
|
+
logger.info(`Enable it here, then retry: ${url}`);
|
|
1344
|
+
logger.info("Note: it can take a few minutes to propagate after enabling.");
|
|
1345
|
+
return;
|
|
1346
|
+
}
|
|
1347
|
+
if (/insufficient|scope|forbidden|403/i.test(msg)) {
|
|
1348
|
+
logger.warn(`Smoke test failed (likely missing scopes): ${msg}`);
|
|
1349
|
+
logger.info("Run `gscdump auth login --force` to re-consent with the required scopes.");
|
|
1350
|
+
return;
|
|
1351
|
+
}
|
|
1352
|
+
logger.warn(`Smoke test failed: ${msg}`);
|
|
1353
|
+
logger.info("Auth saved, but verify via `gscdump auth status` / `gscdump doctor`.");
|
|
1354
|
+
return;
|
|
1355
|
+
}
|
|
1356
|
+
logger.success(`Verified: ${sites.length} GSC site(s) accessible`);
|
|
1357
|
+
}
|
|
1358
|
+
async function maybeWriteEnvFile(clientId, clientSecret) {
|
|
1359
|
+
const tokens = await loadTokens();
|
|
1360
|
+
if (!tokens?.refresh_token) return;
|
|
1361
|
+
const wants = await confirm({
|
|
1362
|
+
message: "Write a `.env` file with these credentials? (handy for CI / other machines)",
|
|
1363
|
+
initialValue: false
|
|
1364
|
+
});
|
|
1365
|
+
if (isCancel(wants) || !wants) return;
|
|
1366
|
+
const envPath = path.join(process.cwd(), ".env");
|
|
1367
|
+
if (await fs.stat(envPath).then(() => true).catch(() => false)) {
|
|
1368
|
+
const overwrite = await confirm({
|
|
1369
|
+
message: `${displayPath(envPath)} exists. Overwrite?`,
|
|
1370
|
+
initialValue: false
|
|
1371
|
+
});
|
|
1372
|
+
if (isCancel(overwrite) || !overwrite) {
|
|
1373
|
+
logger.info(`Skipped — keep credentials at ${displayPath(envPath)} manually if needed`);
|
|
1374
|
+
return;
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
const content = [
|
|
1378
|
+
`GSC_CLIENT_ID=${clientId}`,
|
|
1379
|
+
`GSC_CLIENT_SECRET=${clientSecret}`,
|
|
1380
|
+
`GSC_REFRESH_TOKEN=${tokens.refresh_token}`,
|
|
1381
|
+
""
|
|
1382
|
+
].join("\n");
|
|
1383
|
+
await fs.writeFile(envPath, content, { mode: 384 });
|
|
1384
|
+
logger.success(`Wrote ${displayPath(envPath)}`);
|
|
1385
|
+
}
|
|
1128
1386
|
const ROOT_DIR = path.join(os.homedir(), ".config", "gscdump");
|
|
1129
1387
|
const PROFILES_DIR = path.join(ROOT_DIR, "profiles");
|
|
1130
1388
|
const ACTIVE_MARKER = path.join(ROOT_DIR, "active-profile");
|
|
@@ -1204,37 +1462,38 @@ async function adoptCurrentConfigAsProfile(name) {
|
|
|
1204
1462
|
setConfigDir(targetDir);
|
|
1205
1463
|
return targetDir;
|
|
1206
1464
|
}
|
|
1465
|
+
const listCmd = defineCommand({
|
|
1466
|
+
meta: {
|
|
1467
|
+
name: "list",
|
|
1468
|
+
description: "List configured profiles"
|
|
1469
|
+
},
|
|
1470
|
+
args: { ...OUTPUT_ARGS },
|
|
1471
|
+
async run({ args }) {
|
|
1472
|
+
const { json } = applyOutputMode(args);
|
|
1473
|
+
const names = await listProfiles();
|
|
1474
|
+
const active = resolveActiveProfile();
|
|
1475
|
+
if (json) {
|
|
1476
|
+
console.log(JSON.stringify({
|
|
1477
|
+
active,
|
|
1478
|
+
profiles: names
|
|
1479
|
+
}, null, 2));
|
|
1480
|
+
return;
|
|
1481
|
+
}
|
|
1482
|
+
if (names.length === 0) {
|
|
1483
|
+
logger.warn(`No profiles in ${PROFILES_DIR}`);
|
|
1484
|
+
logger.info("Run `gscdump auth login` to create one automatically, or `gscdump profile create <name>`");
|
|
1485
|
+
return;
|
|
1486
|
+
}
|
|
1487
|
+
for (const n of names) console.log(`${n === active ? "*" : " "} ${n}`);
|
|
1488
|
+
}
|
|
1489
|
+
});
|
|
1207
1490
|
const profileCommand = defineCommand({
|
|
1208
1491
|
meta: {
|
|
1209
1492
|
name: "profile",
|
|
1210
1493
|
description: "Manage gscdump profiles (per-account token + config dirs)"
|
|
1211
1494
|
},
|
|
1212
1495
|
subCommands: {
|
|
1213
|
-
list:
|
|
1214
|
-
meta: {
|
|
1215
|
-
name: "list",
|
|
1216
|
-
description: "List configured profiles"
|
|
1217
|
-
},
|
|
1218
|
-
args: { ...OUTPUT_ARGS },
|
|
1219
|
-
async run({ args }) {
|
|
1220
|
-
const { json } = applyOutputMode(args);
|
|
1221
|
-
const names = await listProfiles();
|
|
1222
|
-
const active = resolveActiveProfile();
|
|
1223
|
-
if (json) {
|
|
1224
|
-
console.log(JSON.stringify({
|
|
1225
|
-
active,
|
|
1226
|
-
profiles: names
|
|
1227
|
-
}, null, 2));
|
|
1228
|
-
return;
|
|
1229
|
-
}
|
|
1230
|
-
if (names.length === 0) {
|
|
1231
|
-
logger.warn(`No profiles in ${PROFILES_DIR}`);
|
|
1232
|
-
logger.info("Run `gscdump auth login` to create one automatically, or `gscdump profile create <name>`");
|
|
1233
|
-
return;
|
|
1234
|
-
}
|
|
1235
|
-
for (const n of names) console.log(`${n === active ? "*" : " "} ${n}`);
|
|
1236
|
-
}
|
|
1237
|
-
}),
|
|
1496
|
+
list: listCmd,
|
|
1238
1497
|
path: defineCommand({
|
|
1239
1498
|
meta: {
|
|
1240
1499
|
name: "path",
|
|
@@ -1375,8 +1634,31 @@ const profileCommand = defineCommand({
|
|
|
1375
1634
|
logger.success(`Removed profile: ${name}`);
|
|
1376
1635
|
}
|
|
1377
1636
|
})
|
|
1637
|
+
},
|
|
1638
|
+
async run({ args }) {
|
|
1639
|
+
if (!noSubcommandSelected("profile", [
|
|
1640
|
+
"list",
|
|
1641
|
+
"path",
|
|
1642
|
+
"current",
|
|
1643
|
+
"use",
|
|
1644
|
+
"create",
|
|
1645
|
+
"clear",
|
|
1646
|
+
"delete"
|
|
1647
|
+
])) return;
|
|
1648
|
+
await listCmd.run?.({
|
|
1649
|
+
args,
|
|
1650
|
+
cmd: listCmd,
|
|
1651
|
+
rawArgs: []
|
|
1652
|
+
});
|
|
1378
1653
|
}
|
|
1379
1654
|
});
|
|
1655
|
+
const AUTH_SUBCOMMANDS = [
|
|
1656
|
+
"status",
|
|
1657
|
+
"login",
|
|
1658
|
+
"logout",
|
|
1659
|
+
"refresh",
|
|
1660
|
+
"scopes"
|
|
1661
|
+
];
|
|
1380
1662
|
const REQUIRED_SCOPES$1 = [
|
|
1381
1663
|
"https://www.googleapis.com/auth/webmasters",
|
|
1382
1664
|
"https://www.googleapis.com/auth/indexing",
|
|
@@ -1405,6 +1687,66 @@ async function resolveLiveAuthState() {
|
|
|
1405
1687
|
missing
|
|
1406
1688
|
};
|
|
1407
1689
|
}
|
|
1690
|
+
async function runStatus(args) {
|
|
1691
|
+
const { json } = applyOutputMode(args);
|
|
1692
|
+
const { byok, tokens, tokenInfo, scopes, missing } = await resolveLiveAuthState();
|
|
1693
|
+
const byokKind = byok ? typeof byok === "string" ? "access-token" : "refresh-token" : null;
|
|
1694
|
+
if (json) {
|
|
1695
|
+
console.log(JSON.stringify({
|
|
1696
|
+
authenticated: !!tokens || !!byok,
|
|
1697
|
+
source: byok ? "byok" : tokens ? "saved-tokens" : null,
|
|
1698
|
+
byokKind,
|
|
1699
|
+
scopes,
|
|
1700
|
+
tokenAccount: tokenInfo?.email ?? null,
|
|
1701
|
+
tokens: tokens ? {
|
|
1702
|
+
hasAccessToken: !!tokens.access_token,
|
|
1703
|
+
hasRefreshToken: !!tokens.refresh_token,
|
|
1704
|
+
expiry: tokens.expiry_date ? new Date(tokens.expiry_date).toISOString() : null,
|
|
1705
|
+
expired: tokens.expiry_date ? tokens.expiry_date < Date.now() : null
|
|
1706
|
+
} : null
|
|
1707
|
+
}, null, 2));
|
|
1708
|
+
return;
|
|
1709
|
+
}
|
|
1710
|
+
const reportScopes = () => {
|
|
1711
|
+
if (scopes.length === 0) return;
|
|
1712
|
+
console.log(` Scopes:`);
|
|
1713
|
+
for (const s of scopes) console.log(` \x1B[90m└─\x1B[0m ${s}`);
|
|
1714
|
+
if (missing.length > 0) {
|
|
1715
|
+
console.log(` \x1B[33mMissing scopes:\x1B[0m`);
|
|
1716
|
+
for (const s of missing) console.log(` \x1B[90m└─\x1B[0m ${s}`);
|
|
1717
|
+
console.log(` \x1B[90mRun \`gscdump auth login --force\` to re-consent.\x1B[0m`);
|
|
1718
|
+
}
|
|
1719
|
+
};
|
|
1720
|
+
if (byok) {
|
|
1721
|
+
logger.success(`Authenticated via BYOK (${byokKind})`);
|
|
1722
|
+
if (tokenInfo?.email) console.log(` Account: ${tokenInfo.email}`);
|
|
1723
|
+
reportScopes();
|
|
1724
|
+
return;
|
|
1725
|
+
}
|
|
1726
|
+
if (!tokens) {
|
|
1727
|
+
logger.warn("Not authenticated");
|
|
1728
|
+
logger.info("Run `gscdump init` (full setup) or `gscdump auth login` (OAuth only)");
|
|
1729
|
+
logger.info("Or set GSC_ACCESS_TOKEN / GSC_CLIENT_ID + GSC_CLIENT_SECRET + GSC_REFRESH_TOKEN env vars");
|
|
1730
|
+
return;
|
|
1731
|
+
}
|
|
1732
|
+
const hasAccess = !!tokens.access_token;
|
|
1733
|
+
const hasRefresh = !!tokens.refresh_token;
|
|
1734
|
+
const expiry = tokens.expiry_date ? new Date(tokens.expiry_date) : null;
|
|
1735
|
+
const isExpired = expiry && expiry < /* @__PURE__ */ new Date();
|
|
1736
|
+
if (isExpired && hasRefresh) logger.warn("Token expired; refresh available. Run `gscdump auth refresh`, or any live command will auto-refresh.");
|
|
1737
|
+
else if (isExpired) logger.error("Token expired and no refresh token present. Run `gscdump auth login`.");
|
|
1738
|
+
else logger.success("Authenticated (saved tokens)");
|
|
1739
|
+
console.log();
|
|
1740
|
+
console.log(` Access token: ${hasAccess ? "\x1B[32mpresent\x1B[0m" : "\x1B[31mmissing\x1B[0m"}`);
|
|
1741
|
+
console.log(` Refresh token: ${hasRefresh ? "\x1B[32mpresent\x1B[0m" : "\x1B[31mmissing\x1B[0m"}`);
|
|
1742
|
+
if (expiry) {
|
|
1743
|
+
const status = isExpired ? "\x1B[33mexpired\x1B[0m" : "\x1B[32mvalid\x1B[0m";
|
|
1744
|
+
console.log(` Expires: ${expiry.toISOString()} (${status})`);
|
|
1745
|
+
}
|
|
1746
|
+
if (tokenInfo?.email) console.log(` Account: ${tokenInfo.email}`);
|
|
1747
|
+
reportScopes();
|
|
1748
|
+
if (isExpired && !hasRefresh) process.exit(1);
|
|
1749
|
+
}
|
|
1408
1750
|
const statusCommand$1 = defineCommand({
|
|
1409
1751
|
meta: {
|
|
1410
1752
|
name: "status",
|
|
@@ -1412,61 +1754,7 @@ const statusCommand$1 = defineCommand({
|
|
|
1412
1754
|
},
|
|
1413
1755
|
args: { ...OUTPUT_ARGS },
|
|
1414
1756
|
async run({ args }) {
|
|
1415
|
-
|
|
1416
|
-
const { byok, tokens, tokenInfo, scopes, missing } = await resolveLiveAuthState();
|
|
1417
|
-
const byokKind = byok ? typeof byok === "string" ? "access-token" : "refresh-token" : null;
|
|
1418
|
-
if (json) {
|
|
1419
|
-
console.log(JSON.stringify({
|
|
1420
|
-
authenticated: !!tokens || !!byok,
|
|
1421
|
-
source: byok ? "byok" : tokens ? "saved-tokens" : null,
|
|
1422
|
-
byokKind,
|
|
1423
|
-
scopes,
|
|
1424
|
-
tokenAccount: tokenInfo?.email ?? null,
|
|
1425
|
-
tokens: tokens ? {
|
|
1426
|
-
hasAccessToken: !!tokens.access_token,
|
|
1427
|
-
hasRefreshToken: !!tokens.refresh_token,
|
|
1428
|
-
expiry: tokens.expiry_date ? new Date(tokens.expiry_date).toISOString() : null,
|
|
1429
|
-
expired: tokens.expiry_date ? tokens.expiry_date < Date.now() : null
|
|
1430
|
-
} : null
|
|
1431
|
-
}, null, 2));
|
|
1432
|
-
return;
|
|
1433
|
-
}
|
|
1434
|
-
const reportScopes = () => {
|
|
1435
|
-
if (scopes.length === 0) return;
|
|
1436
|
-
console.log(` Scopes:`);
|
|
1437
|
-
for (const s of scopes) console.log(` \x1B[90m└─\x1B[0m ${s}`);
|
|
1438
|
-
if (missing.length > 0) {
|
|
1439
|
-
console.log(` \x1B[33mMissing scopes:\x1B[0m`);
|
|
1440
|
-
for (const s of missing) console.log(` \x1B[90m└─\x1B[0m ${s}`);
|
|
1441
|
-
console.log(` \x1B[90mRun \`gscdump auth login --force\` to re-consent.\x1B[0m`);
|
|
1442
|
-
}
|
|
1443
|
-
};
|
|
1444
|
-
if (byok) {
|
|
1445
|
-
logger.success(`Authenticated via BYOK (${byokKind})`);
|
|
1446
|
-
if (tokenInfo?.email) console.log(` Account: ${tokenInfo.email}`);
|
|
1447
|
-
reportScopes();
|
|
1448
|
-
return;
|
|
1449
|
-
}
|
|
1450
|
-
if (!tokens) {
|
|
1451
|
-
logger.warn("Not authenticated");
|
|
1452
|
-
logger.info("Run `gscdump init` (full setup) or `gscdump auth login` (OAuth only)");
|
|
1453
|
-
logger.info("Or set GSC_ACCESS_TOKEN / GSC_CLIENT_ID + GSC_CLIENT_SECRET + GSC_REFRESH_TOKEN env vars");
|
|
1454
|
-
return;
|
|
1455
|
-
}
|
|
1456
|
-
const hasAccess = !!tokens.access_token;
|
|
1457
|
-
const hasRefresh = !!tokens.refresh_token;
|
|
1458
|
-
const expiry = tokens.expiry_date ? new Date(tokens.expiry_date) : null;
|
|
1459
|
-
const isExpired = expiry && expiry < /* @__PURE__ */ new Date();
|
|
1460
|
-
logger.success("Authenticated (saved tokens)");
|
|
1461
|
-
console.log();
|
|
1462
|
-
console.log(` Access token: ${hasAccess ? "\x1B[32mpresent\x1B[0m" : "\x1B[31mmissing\x1B[0m"}`);
|
|
1463
|
-
console.log(` Refresh token: ${hasRefresh ? "\x1B[32mpresent\x1B[0m" : "\x1B[31mmissing\x1B[0m"}`);
|
|
1464
|
-
if (expiry) {
|
|
1465
|
-
const status = isExpired ? "\x1B[33mexpired\x1B[0m" : "\x1B[32mvalid\x1B[0m";
|
|
1466
|
-
console.log(` Expires: ${expiry.toISOString()} (${status})`);
|
|
1467
|
-
}
|
|
1468
|
-
if (tokenInfo?.email) console.log(` Account: ${tokenInfo.email}`);
|
|
1469
|
-
reportScopes();
|
|
1757
|
+
await runStatus(args);
|
|
1470
1758
|
}
|
|
1471
1759
|
});
|
|
1472
1760
|
const refreshCommand = defineCommand({
|
|
@@ -1501,126 +1789,135 @@ const refreshCommand = defineCommand({
|
|
|
1501
1789
|
else logger.warn("Refresh completed but no new access token returned");
|
|
1502
1790
|
}
|
|
1503
1791
|
});
|
|
1504
|
-
const
|
|
1792
|
+
const loginCommand = defineCommand({
|
|
1505
1793
|
meta: {
|
|
1506
|
-
name: "
|
|
1507
|
-
description: "
|
|
1794
|
+
name: "login",
|
|
1795
|
+
description: "Run OAuth flow and persist tokens (skip if BYOK env vars set)"
|
|
1508
1796
|
},
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
logger.success("
|
|
1567
|
-
if (!resolveActiveProfile()) {
|
|
1568
|
-
const tokens = await loadTokens();
|
|
1569
|
-
const info = tokens?.access_token ? await fetchTokenInfo(tokens.access_token) : null;
|
|
1570
|
-
if (info?.email) {
|
|
1571
|
-
const name = profileNameFromEmail(info.email);
|
|
1572
|
-
if (await adoptCurrentConfigAsProfile(name).catch(() => null)) logger.success(`Saved as profile "${name}" (active)`);
|
|
1573
|
-
}
|
|
1574
|
-
}
|
|
1575
|
-
if (resolveBYOK()) {
|
|
1576
|
-
console.log();
|
|
1577
|
-
console.log(await formatAuthProvenance());
|
|
1578
|
-
}
|
|
1579
|
-
}
|
|
1580
|
-
}),
|
|
1581
|
-
logout: defineCommand({
|
|
1582
|
-
meta: {
|
|
1583
|
-
name: "logout",
|
|
1584
|
-
description: "Clear stored OAuth tokens"
|
|
1585
|
-
},
|
|
1586
|
-
args: { ...OUTPUT_ARGS },
|
|
1587
|
-
async run({ args }) {
|
|
1588
|
-
applyOutputMode(args);
|
|
1589
|
-
await clearTokens();
|
|
1590
|
-
const config = await loadConfig();
|
|
1591
|
-
if (config.serviceAccountPath) {
|
|
1592
|
-
delete config.serviceAccountPath;
|
|
1593
|
-
await saveConfig(config);
|
|
1594
|
-
logger.info("Cleared saved service-account path");
|
|
1595
|
-
}
|
|
1797
|
+
args: {
|
|
1798
|
+
...OUTPUT_ARGS,
|
|
1799
|
+
"force": {
|
|
1800
|
+
type: "boolean",
|
|
1801
|
+
alias: "f",
|
|
1802
|
+
default: false,
|
|
1803
|
+
description: "Re-run OAuth even if tokens already exist"
|
|
1804
|
+
},
|
|
1805
|
+
"browser": {
|
|
1806
|
+
type: "boolean",
|
|
1807
|
+
default: true,
|
|
1808
|
+
description: "Use loopback browser flow. Pass --no-browser for device-code (headless)."
|
|
1809
|
+
},
|
|
1810
|
+
"service-account": {
|
|
1811
|
+
type: "string",
|
|
1812
|
+
description: "Path to a service-account JSON key (skips OAuth)"
|
|
1813
|
+
}
|
|
1814
|
+
},
|
|
1815
|
+
async run({ args }) {
|
|
1816
|
+
applyOutputMode(args);
|
|
1817
|
+
if (resolveBYOK() && !args.force) {
|
|
1818
|
+
logger.info("BYOK env vars detected, no login needed (--force to override)");
|
|
1819
|
+
return;
|
|
1820
|
+
}
|
|
1821
|
+
if (args["service-account"]) {
|
|
1822
|
+
const saPath = path.resolve(String(args["service-account"]));
|
|
1823
|
+
const jwt = await loadServiceAccount(saPath).catch((e) => {
|
|
1824
|
+
logger.error(`Service-account load failed: ${e.message}`);
|
|
1825
|
+
process.exit(1);
|
|
1826
|
+
});
|
|
1827
|
+
await jwt.authorize().catch((e) => {
|
|
1828
|
+
logger.error(`Service-account auth failed: ${e.message}`);
|
|
1829
|
+
process.exit(1);
|
|
1830
|
+
});
|
|
1831
|
+
const config = await loadConfig();
|
|
1832
|
+
config.serviceAccountPath = saPath;
|
|
1833
|
+
await saveConfig(config);
|
|
1834
|
+
logger.success(`Service-account verified: ${jwt.email ?? "OK"}`);
|
|
1835
|
+
logger.info(`Saved path to config: ${saPath}`);
|
|
1836
|
+
return;
|
|
1837
|
+
}
|
|
1838
|
+
if (args.force) await clearTokens();
|
|
1839
|
+
const oauth = await getAuth({
|
|
1840
|
+
interactive: true,
|
|
1841
|
+
noBrowser: args.browser === false,
|
|
1842
|
+
force: Boolean(args.force)
|
|
1843
|
+
}).catch((e) => {
|
|
1844
|
+
logger.error(`Login failed: ${e.message}`);
|
|
1845
|
+
process.exit(1);
|
|
1846
|
+
});
|
|
1847
|
+
logger.success("Logged in");
|
|
1848
|
+
await runSmokeTest(oauth).catch(() => {});
|
|
1849
|
+
if (!resolveActiveProfile()) {
|
|
1850
|
+
const tokens = await loadTokens();
|
|
1851
|
+
const info = tokens?.access_token ? await fetchTokenInfo(tokens.access_token) : null;
|
|
1852
|
+
if (info?.email) {
|
|
1853
|
+
const name = profileNameFromEmail(info.email);
|
|
1854
|
+
if (await adoptCurrentConfigAsProfile(name).catch(() => null)) logger.success(`Saved as profile "${name}" (active)`);
|
|
1596
1855
|
}
|
|
1597
|
-
}
|
|
1856
|
+
}
|
|
1857
|
+
if (resolveBYOK()) {
|
|
1858
|
+
console.log();
|
|
1859
|
+
console.log(await formatAuthProvenance());
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
});
|
|
1863
|
+
const logoutCommand = defineCommand({
|
|
1864
|
+
meta: {
|
|
1865
|
+
name: "logout",
|
|
1866
|
+
description: "Clear stored OAuth tokens"
|
|
1867
|
+
},
|
|
1868
|
+
args: { ...OUTPUT_ARGS },
|
|
1869
|
+
async run({ args }) {
|
|
1870
|
+
applyOutputMode(args);
|
|
1871
|
+
await clearTokens();
|
|
1872
|
+
const config = await loadConfig();
|
|
1873
|
+
if (config.serviceAccountPath) {
|
|
1874
|
+
delete config.serviceAccountPath;
|
|
1875
|
+
await saveConfig(config);
|
|
1876
|
+
logger.info("Cleared saved service-account path");
|
|
1877
|
+
}
|
|
1878
|
+
}
|
|
1879
|
+
});
|
|
1880
|
+
const scopesCommand = defineCommand({
|
|
1881
|
+
meta: {
|
|
1882
|
+
name: "scopes",
|
|
1883
|
+
description: "Print granted OAuth scopes (one per line); exits 1 if any required scope is missing"
|
|
1884
|
+
},
|
|
1885
|
+
args: { ...OUTPUT_ARGS },
|
|
1886
|
+
async run({ args }) {
|
|
1887
|
+
const { json } = applyOutputMode(args);
|
|
1888
|
+
const { liveToken, scopes, missing } = await resolveLiveAuthState();
|
|
1889
|
+
if (!liveToken) {
|
|
1890
|
+
if (json) console.log(JSON.stringify({
|
|
1891
|
+
scopes: [],
|
|
1892
|
+
missing: null
|
|
1893
|
+
}, null, 2));
|
|
1894
|
+
else logger.error("Not authenticated");
|
|
1895
|
+
process.exit(1);
|
|
1896
|
+
}
|
|
1897
|
+
if (json) console.log(JSON.stringify({
|
|
1898
|
+
scopes,
|
|
1899
|
+
missing
|
|
1900
|
+
}, null, 2));
|
|
1901
|
+
else for (const s of scopes) console.log(s);
|
|
1902
|
+
if (missing.length > 0) process.exit(1);
|
|
1903
|
+
}
|
|
1904
|
+
});
|
|
1905
|
+
const authCommand = defineCommand({
|
|
1906
|
+
meta: {
|
|
1907
|
+
name: "auth",
|
|
1908
|
+
description: "Manage authentication"
|
|
1909
|
+
},
|
|
1910
|
+
args: { ...OUTPUT_ARGS },
|
|
1911
|
+
subCommands: {
|
|
1912
|
+
status: statusCommand$1,
|
|
1913
|
+
login: loginCommand,
|
|
1914
|
+
logout: logoutCommand,
|
|
1598
1915
|
refresh: refreshCommand,
|
|
1599
|
-
scopes:
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
args: { ...OUTPUT_ARGS },
|
|
1605
|
-
async run({ args }) {
|
|
1606
|
-
const { json } = applyOutputMode(args);
|
|
1607
|
-
const { liveToken, scopes, missing } = await resolveLiveAuthState();
|
|
1608
|
-
if (!liveToken) {
|
|
1609
|
-
if (json) console.log(JSON.stringify({
|
|
1610
|
-
scopes: [],
|
|
1611
|
-
missing: null
|
|
1612
|
-
}, null, 2));
|
|
1613
|
-
else logger.error("Not authenticated");
|
|
1614
|
-
process.exit(1);
|
|
1615
|
-
}
|
|
1616
|
-
if (json) console.log(JSON.stringify({
|
|
1617
|
-
scopes,
|
|
1618
|
-
missing
|
|
1619
|
-
}, null, 2));
|
|
1620
|
-
else for (const s of scopes) console.log(s);
|
|
1621
|
-
if (missing.length > 0) process.exit(1);
|
|
1622
|
-
}
|
|
1623
|
-
})
|
|
1916
|
+
scopes: scopesCommand
|
|
1917
|
+
},
|
|
1918
|
+
async run({ args }) {
|
|
1919
|
+
if (!noSubcommandSelected("auth", AUTH_SUBCOMMANDS)) return;
|
|
1920
|
+
await runStatus(args);
|
|
1624
1921
|
}
|
|
1625
1922
|
});
|
|
1626
1923
|
const showCommand = defineCommand({
|
|
@@ -1747,7 +2044,7 @@ const configCommand = defineCommand({
|
|
|
1747
2044
|
args: { ...OUTPUT_ARGS },
|
|
1748
2045
|
async run({ args }) {
|
|
1749
2046
|
const { json } = applyOutputMode(args);
|
|
1750
|
-
const { resolveDataDir } = await
|
|
2047
|
+
const { resolveDataDir } = await Promise.resolve().then(() => config_exports);
|
|
1751
2048
|
const fs = await import("node:fs/promises");
|
|
1752
2049
|
const config = await loadConfig();
|
|
1753
2050
|
const issues = [];
|
|
@@ -1834,6 +2131,20 @@ const configCommand = defineCommand({
|
|
|
1834
2131
|
if (issues.some((i) => i.level === "fail")) process.exit(1);
|
|
1835
2132
|
}
|
|
1836
2133
|
})
|
|
2134
|
+
},
|
|
2135
|
+
async run({ args }) {
|
|
2136
|
+
if (!noSubcommandSelected("config", [
|
|
2137
|
+
"show",
|
|
2138
|
+
"set",
|
|
2139
|
+
"unset",
|
|
2140
|
+
"path",
|
|
2141
|
+
"validate"
|
|
2142
|
+
])) return;
|
|
2143
|
+
await showCommand.run?.({
|
|
2144
|
+
args,
|
|
2145
|
+
cmd: showCommand,
|
|
2146
|
+
rawArgs: []
|
|
2147
|
+
});
|
|
1837
2148
|
}
|
|
1838
2149
|
});
|
|
1839
2150
|
const REQUIRED_SCOPES = [
|
|
@@ -2352,7 +2663,7 @@ async function dumpParquet(store, entries, outDir) {
|
|
|
2352
2663
|
const bytes = await store.engine.readObject(entry.objectKey);
|
|
2353
2664
|
const target = path.join(outDir, entry.objectKey);
|
|
2354
2665
|
await fs.mkdir(path.dirname(target), { recursive: true });
|
|
2355
|
-
await fs.writeFile(target, Buffer.from(bytes));
|
|
2666
|
+
await fs.writeFile(target, Buffer$1.from(bytes));
|
|
2356
2667
|
copied++;
|
|
2357
2668
|
}
|
|
2358
2669
|
return copied;
|
|
@@ -2414,7 +2725,7 @@ async function readUrlList(opts) {
|
|
|
2414
2725
|
if (opts.file) return (await readFile(opts.file, "utf8")).split("\n").map((l) => l.trim()).filter(Boolean);
|
|
2415
2726
|
const chunks = [];
|
|
2416
2727
|
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
2417
|
-
return Buffer.concat(chunks).toString("utf8").split("\n").map((l) => l.trim()).filter(Boolean);
|
|
2728
|
+
return Buffer$1.concat(chunks).toString("utf8").split("\n").map((l) => l.trim()).filter(Boolean);
|
|
2418
2729
|
}
|
|
2419
2730
|
const inspectSubCommand = defineCommand({
|
|
2420
2731
|
meta: {
|
|
@@ -3106,157 +3417,6 @@ const indexingCommand = defineCommand({
|
|
|
3106
3417
|
"quota": quotaCommand
|
|
3107
3418
|
}
|
|
3108
3419
|
});
|
|
3109
|
-
const ENV_LINE_RE = /^([^=]+)=(.*)$/;
|
|
3110
|
-
async function promptDataDir(existing) {
|
|
3111
|
-
const fallback = existing ?? defaultDataDir();
|
|
3112
|
-
const answer = await text({
|
|
3113
|
-
message: "Where should Parquet data be stored?",
|
|
3114
|
-
placeholder: fallback,
|
|
3115
|
-
defaultValue: fallback
|
|
3116
|
-
});
|
|
3117
|
-
if (isCancel(answer)) process.exit(1);
|
|
3118
|
-
return String(answer) || fallback;
|
|
3119
|
-
}
|
|
3120
|
-
async function loadEnvFile() {
|
|
3121
|
-
const envPath = path.join(process.cwd(), ".env");
|
|
3122
|
-
const content = await fs.readFile(envPath, "utf-8").catch(() => null);
|
|
3123
|
-
if (!content) return null;
|
|
3124
|
-
const env = {};
|
|
3125
|
-
for (const line of content.split("\n")) {
|
|
3126
|
-
const trimmed = line.trim();
|
|
3127
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
3128
|
-
const match = trimmed.match(ENV_LINE_RE);
|
|
3129
|
-
if (match) {
|
|
3130
|
-
const key = match[1].trim();
|
|
3131
|
-
let value = match[2].trim();
|
|
3132
|
-
if (value.startsWith("\"") && value.endsWith("\"") || value.startsWith("'") && value.endsWith("'")) value = value.slice(1, -1);
|
|
3133
|
-
env[key] = value;
|
|
3134
|
-
}
|
|
3135
|
-
}
|
|
3136
|
-
return env;
|
|
3137
|
-
}
|
|
3138
|
-
const initCommand = defineCommand({
|
|
3139
|
-
meta: {
|
|
3140
|
-
name: "init",
|
|
3141
|
-
description: "Set up GSCDump authentication"
|
|
3142
|
-
},
|
|
3143
|
-
args: {
|
|
3144
|
-
"force": {
|
|
3145
|
-
type: "boolean",
|
|
3146
|
-
alias: "f",
|
|
3147
|
-
description: "Force re-initialization"
|
|
3148
|
-
},
|
|
3149
|
-
"no-store": {
|
|
3150
|
-
type: "boolean",
|
|
3151
|
-
default: false,
|
|
3152
|
-
description: "Skip dataDir prompt (auth-only setup)"
|
|
3153
|
-
},
|
|
3154
|
-
...OUTPUT_ARGS
|
|
3155
|
-
},
|
|
3156
|
-
async run({ args }) {
|
|
3157
|
-
applyOutputMode(args);
|
|
3158
|
-
const config = await loadConfig();
|
|
3159
|
-
if (config.clientId && config.clientSecret && !args.force) {
|
|
3160
|
-
logger.info("Already configured");
|
|
3161
|
-
logger.info("Run with --force to reconfigure");
|
|
3162
|
-
return;
|
|
3163
|
-
}
|
|
3164
|
-
const byok = resolveBYOK();
|
|
3165
|
-
if (byok) {
|
|
3166
|
-
const dataDir = args["no-store"] ? void 0 : await promptDataDir(config.dataDir);
|
|
3167
|
-
await saveConfig({
|
|
3168
|
-
...config,
|
|
3169
|
-
dataDir: dataDir ?? config.dataDir
|
|
3170
|
-
});
|
|
3171
|
-
logger.success(`BYOK detected (${typeof byok === "string" ? "access-token" : "refresh-token"}) — auth setup skipped`);
|
|
3172
|
-
logger.success("Setup complete! Run gscdump to get started.");
|
|
3173
|
-
return;
|
|
3174
|
-
}
|
|
3175
|
-
const envFile = await loadEnvFile();
|
|
3176
|
-
const envCid = envFile?.GSC_CLIENT_ID ?? envFile?.GOOGLE_CLIENT_ID;
|
|
3177
|
-
const envSec = envFile?.GSC_CLIENT_SECRET ?? envFile?.GOOGLE_CLIENT_SECRET;
|
|
3178
|
-
const envRef = envFile?.GSC_REFRESH_TOKEN ?? envFile?.GOOGLE_REFRESH_TOKEN;
|
|
3179
|
-
const envAcc = envFile?.GSC_ACCESS_TOKEN ?? envFile?.GOOGLE_ACCESS_TOKEN;
|
|
3180
|
-
if (envCid && envSec && envRef) {
|
|
3181
|
-
logger.info("Found .env file with credentials");
|
|
3182
|
-
process.env.GOOGLE_CLIENT_ID = envCid;
|
|
3183
|
-
process.env.GOOGLE_CLIENT_SECRET = envSec;
|
|
3184
|
-
process.env.GOOGLE_REFRESH_TOKEN = envRef;
|
|
3185
|
-
if (envAcc) process.env.GOOGLE_ACCESS_TOKEN = envAcc;
|
|
3186
|
-
await saveConfig({
|
|
3187
|
-
...config,
|
|
3188
|
-
clientId: envCid,
|
|
3189
|
-
clientSecret: envSec,
|
|
3190
|
-
dataDir: config.dataDir ?? defaultDataDir()
|
|
3191
|
-
});
|
|
3192
|
-
const creds = (await authenticate({
|
|
3193
|
-
clientId: envCid,
|
|
3194
|
-
clientSecret: envSec
|
|
3195
|
-
}, false)).credentials;
|
|
3196
|
-
if (creds.access_token) await saveTokens({
|
|
3197
|
-
access_token: creds.access_token,
|
|
3198
|
-
refresh_token: creds.refresh_token || envRef,
|
|
3199
|
-
expiry_date: creds.expiry_date
|
|
3200
|
-
});
|
|
3201
|
-
console.log();
|
|
3202
|
-
logger.success("Setup complete using .env credentials! Run gscdump to get started.");
|
|
3203
|
-
return;
|
|
3204
|
-
}
|
|
3205
|
-
console.log();
|
|
3206
|
-
console.log(" \x1B[1mWelcome to GSCDump!\x1B[0m");
|
|
3207
|
-
console.log(" \x1B[90mGoogle Search Console data extraction CLI\x1B[0m");
|
|
3208
|
-
console.log();
|
|
3209
|
-
const dataDir = args["no-store"] ? void 0 : await promptDataDir(config.dataDir);
|
|
3210
|
-
const credentials = await getAuthCredentials(true);
|
|
3211
|
-
await saveConfig({
|
|
3212
|
-
...config,
|
|
3213
|
-
...dataDir ? { dataDir } : {},
|
|
3214
|
-
clientId: credentials.clientId,
|
|
3215
|
-
clientSecret: credentials.clientSecret
|
|
3216
|
-
});
|
|
3217
|
-
await smokeTest(await authenticate(credentials, true));
|
|
3218
|
-
await maybeWriteEnvFile(credentials.clientId, credentials.clientSecret);
|
|
3219
|
-
console.log();
|
|
3220
|
-
logger.success("Setup complete! Run gscdump to get started.");
|
|
3221
|
-
}
|
|
3222
|
-
});
|
|
3223
|
-
async function smokeTest(oauth) {
|
|
3224
|
-
const sites = await googleSearchConsole(oauth).sites().catch((e) => e);
|
|
3225
|
-
if (sites instanceof Error) {
|
|
3226
|
-
logger.warn(`Smoke test failed: ${sites.message}`);
|
|
3227
|
-
logger.info("Auth saved, but verify scopes via `gscdump auth status` / `gscdump doctor`.");
|
|
3228
|
-
return;
|
|
3229
|
-
}
|
|
3230
|
-
logger.success(`Verified: ${sites.length} GSC site(s) accessible`);
|
|
3231
|
-
}
|
|
3232
|
-
async function maybeWriteEnvFile(clientId, clientSecret) {
|
|
3233
|
-
const tokens = await loadTokens();
|
|
3234
|
-
if (!tokens?.refresh_token) return;
|
|
3235
|
-
const wants = await confirm({
|
|
3236
|
-
message: "Write a `.env` file with these credentials? (handy for CI / other machines)",
|
|
3237
|
-
initialValue: false
|
|
3238
|
-
});
|
|
3239
|
-
if (isCancel(wants) || !wants) return;
|
|
3240
|
-
const envPath = path.join(process.cwd(), ".env");
|
|
3241
|
-
if (await fs.stat(envPath).then(() => true).catch(() => false)) {
|
|
3242
|
-
const overwrite = await confirm({
|
|
3243
|
-
message: `${displayPath(envPath)} exists. Overwrite?`,
|
|
3244
|
-
initialValue: false
|
|
3245
|
-
});
|
|
3246
|
-
if (isCancel(overwrite) || !overwrite) {
|
|
3247
|
-
logger.info(`Skipped — keep credentials at ${displayPath(envPath)} manually if needed`);
|
|
3248
|
-
return;
|
|
3249
|
-
}
|
|
3250
|
-
}
|
|
3251
|
-
const content = [
|
|
3252
|
-
`GSC_CLIENT_ID=${clientId}`,
|
|
3253
|
-
`GSC_CLIENT_SECRET=${clientSecret}`,
|
|
3254
|
-
`GSC_REFRESH_TOKEN=${tokens.refresh_token}`,
|
|
3255
|
-
""
|
|
3256
|
-
].join("\n");
|
|
3257
|
-
await fs.writeFile(envPath, content, { mode: 384 });
|
|
3258
|
-
logger.success(`Wrote ${displayPath(envPath)}`);
|
|
3259
|
-
}
|
|
3260
3420
|
function verdictTone(verdict) {
|
|
3261
3421
|
if (verdict === "PASS") return "\x1B[32m";
|
|
3262
3422
|
if (verdict === "NEUTRAL" || verdict === "PARTIAL") return "\x1B[33m";
|
|
@@ -3505,6 +3665,7 @@ const DIMENSIONS = [
|
|
|
3505
3665
|
"page",
|
|
3506
3666
|
"query",
|
|
3507
3667
|
"date",
|
|
3668
|
+
"hour",
|
|
3508
3669
|
"country",
|
|
3509
3670
|
"device",
|
|
3510
3671
|
"searchAppearance"
|
|
@@ -3513,6 +3674,7 @@ const DIM_COLUMNS = {
|
|
|
3513
3674
|
page,
|
|
3514
3675
|
query,
|
|
3515
3676
|
date,
|
|
3677
|
+
hour,
|
|
3516
3678
|
country,
|
|
3517
3679
|
device,
|
|
3518
3680
|
searchAppearance
|
|
@@ -3570,7 +3732,7 @@ async function runLiveQuery(client, siteUrl, opts) {
|
|
|
3570
3732
|
dimensions: opts.dimensions,
|
|
3571
3733
|
rowLimit: opts.rowLimit
|
|
3572
3734
|
};
|
|
3573
|
-
if (opts.searchType) baseBody.
|
|
3735
|
+
if (opts.searchType) baseBody.type = opts.searchType;
|
|
3574
3736
|
if (opts.dataState) baseBody.dataState = opts.dataState;
|
|
3575
3737
|
if (opts.aggregationType) baseBody.aggregationType = opts.aggregationType;
|
|
3576
3738
|
if (opts.dimensionFilter) {
|
|
@@ -3756,7 +3918,7 @@ const queryCommand = defineCommand({
|
|
|
3756
3918
|
dimensions: dimNames,
|
|
3757
3919
|
rowLimit
|
|
3758
3920
|
};
|
|
3759
|
-
if (searchType) body.
|
|
3921
|
+
if (searchType) body.type = searchType;
|
|
3760
3922
|
if (dataState) body.dataState = dataState;
|
|
3761
3923
|
if (aggregationType) body.aggregationType = aggregationType;
|
|
3762
3924
|
if (dimensionFilter) body.dimensionFilterGroups = filterToGroups(dimensionFilter);
|
|
@@ -4215,69 +4377,70 @@ const reportCommand = defineCommand({
|
|
|
4215
4377
|
...Object.fromEntries(REPORT_IDS.map((id) => [id, makeReportCommand(defaultReportRegistry.getReport(id))]))
|
|
4216
4378
|
}
|
|
4217
4379
|
});
|
|
4380
|
+
const listCommand$1 = defineCommand({
|
|
4381
|
+
meta: {
|
|
4382
|
+
name: "list",
|
|
4383
|
+
description: "List sitemaps for a site"
|
|
4384
|
+
},
|
|
4385
|
+
args: {
|
|
4386
|
+
...OUTPUT_ARGS,
|
|
4387
|
+
site: {
|
|
4388
|
+
type: "string",
|
|
4389
|
+
alias: "s",
|
|
4390
|
+
description: "Site URL (e.g., sc-domain:example.com or https://example.com/)"
|
|
4391
|
+
},
|
|
4392
|
+
pending: {
|
|
4393
|
+
type: "boolean",
|
|
4394
|
+
default: false,
|
|
4395
|
+
description: "Show only sitemaps with isPending=true"
|
|
4396
|
+
},
|
|
4397
|
+
errored: {
|
|
4398
|
+
type: "boolean",
|
|
4399
|
+
default: false,
|
|
4400
|
+
description: "Show only sitemaps with errors > 0"
|
|
4401
|
+
}
|
|
4402
|
+
},
|
|
4403
|
+
async run({ args }) {
|
|
4404
|
+
const { json } = applyOutputMode(args);
|
|
4405
|
+
const ctx = await createCommandContext({ needsAuth: true });
|
|
4406
|
+
const siteUrl = await ctx.resolveSite(args.site ? String(args.site) : void 0);
|
|
4407
|
+
let sitemaps = (await ctx.client.sitemaps.list(siteUrl).catch(gscErrorHandler)).map((sm) => ({
|
|
4408
|
+
path: sm.path,
|
|
4409
|
+
type: sm.type || void 0,
|
|
4410
|
+
isPending: sm.isPending || false,
|
|
4411
|
+
errors: Number(sm.errors) || 0,
|
|
4412
|
+
warnings: Number(sm.warnings) || 0,
|
|
4413
|
+
lastDownloaded: sm.lastDownloaded || null,
|
|
4414
|
+
lastSubmitted: sm.lastSubmitted || null
|
|
4415
|
+
}));
|
|
4416
|
+
if (args.pending) sitemaps = sitemaps.filter((sm) => sm.isPending);
|
|
4417
|
+
if (args.errored) sitemaps = sitemaps.filter((sm) => sm.errors > 0);
|
|
4418
|
+
if (json) {
|
|
4419
|
+
console.log(JSON.stringify(sitemaps, null, 2));
|
|
4420
|
+
return;
|
|
4421
|
+
}
|
|
4422
|
+
if (sitemaps.length === 0) {
|
|
4423
|
+
logger.warn("No sitemaps found");
|
|
4424
|
+
return;
|
|
4425
|
+
}
|
|
4426
|
+
logger.success(`Found ${sitemaps.length} sitemaps:`);
|
|
4427
|
+
console.log();
|
|
4428
|
+
for (const sm of sitemaps) {
|
|
4429
|
+
const pending = sm.isPending ? " \x1B[33m(pending)\x1B[0m" : "";
|
|
4430
|
+
const errors = sm.errors ? ` \x1B[31m${sm.errors} errors\x1B[0m` : "";
|
|
4431
|
+
const warnings = sm.warnings ? ` \x1B[33m${sm.warnings} warnings\x1B[0m` : "";
|
|
4432
|
+
const submitted = sm.lastSubmitted ? ` \x1B[90msubmitted ${sm.lastSubmitted}\x1B[0m` : "";
|
|
4433
|
+
console.log(` ${sm.path}${pending}${errors}${warnings}${submitted}`);
|
|
4434
|
+
}
|
|
4435
|
+
}
|
|
4436
|
+
});
|
|
4218
4437
|
const sitemapsCommand = defineCommand({
|
|
4219
4438
|
meta: {
|
|
4220
4439
|
name: "sitemaps",
|
|
4221
4440
|
description: "Manage sitemaps"
|
|
4222
4441
|
},
|
|
4223
4442
|
subCommands: {
|
|
4224
|
-
list:
|
|
4225
|
-
meta: {
|
|
4226
|
-
name: "list",
|
|
4227
|
-
description: "List sitemaps for a site"
|
|
4228
|
-
},
|
|
4229
|
-
args: {
|
|
4230
|
-
...OUTPUT_ARGS,
|
|
4231
|
-
site: {
|
|
4232
|
-
type: "string",
|
|
4233
|
-
alias: "s",
|
|
4234
|
-
description: "Site URL (e.g., sc-domain:example.com or https://example.com/)"
|
|
4235
|
-
},
|
|
4236
|
-
pending: {
|
|
4237
|
-
type: "boolean",
|
|
4238
|
-
default: false,
|
|
4239
|
-
description: "Show only sitemaps with isPending=true"
|
|
4240
|
-
},
|
|
4241
|
-
errored: {
|
|
4242
|
-
type: "boolean",
|
|
4243
|
-
default: false,
|
|
4244
|
-
description: "Show only sitemaps with errors > 0"
|
|
4245
|
-
}
|
|
4246
|
-
},
|
|
4247
|
-
async run({ args }) {
|
|
4248
|
-
const { json } = applyOutputMode(args);
|
|
4249
|
-
const ctx = await createCommandContext({ needsAuth: true });
|
|
4250
|
-
const siteUrl = await ctx.resolveSite(args.site ? String(args.site) : void 0);
|
|
4251
|
-
let sitemaps = (await ctx.client.sitemaps.list(siteUrl).catch(gscErrorHandler)).map((sm) => ({
|
|
4252
|
-
path: sm.path,
|
|
4253
|
-
type: sm.type || void 0,
|
|
4254
|
-
isPending: sm.isPending || false,
|
|
4255
|
-
errors: Number(sm.errors) || 0,
|
|
4256
|
-
warnings: Number(sm.warnings) || 0,
|
|
4257
|
-
lastDownloaded: sm.lastDownloaded || null,
|
|
4258
|
-
lastSubmitted: sm.lastSubmitted || null
|
|
4259
|
-
}));
|
|
4260
|
-
if (args.pending) sitemaps = sitemaps.filter((sm) => sm.isPending);
|
|
4261
|
-
if (args.errored) sitemaps = sitemaps.filter((sm) => sm.errors > 0);
|
|
4262
|
-
if (json) {
|
|
4263
|
-
console.log(JSON.stringify(sitemaps, null, 2));
|
|
4264
|
-
return;
|
|
4265
|
-
}
|
|
4266
|
-
if (sitemaps.length === 0) {
|
|
4267
|
-
logger.warn("No sitemaps found");
|
|
4268
|
-
return;
|
|
4269
|
-
}
|
|
4270
|
-
logger.success(`Found ${sitemaps.length} sitemaps:`);
|
|
4271
|
-
console.log();
|
|
4272
|
-
for (const sm of sitemaps) {
|
|
4273
|
-
const pending = sm.isPending ? " \x1B[33m(pending)\x1B[0m" : "";
|
|
4274
|
-
const errors = sm.errors ? ` \x1B[31m${sm.errors} errors\x1B[0m` : "";
|
|
4275
|
-
const warnings = sm.warnings ? ` \x1B[33m${sm.warnings} warnings\x1B[0m` : "";
|
|
4276
|
-
const submitted = sm.lastSubmitted ? ` \x1B[90msubmitted ${sm.lastSubmitted}\x1B[0m` : "";
|
|
4277
|
-
console.log(` ${sm.path}${pending}${errors}${warnings}${submitted}`);
|
|
4278
|
-
}
|
|
4279
|
-
}
|
|
4280
|
-
}),
|
|
4443
|
+
list: listCommand$1,
|
|
4281
4444
|
get: defineCommand({
|
|
4282
4445
|
meta: {
|
|
4283
4446
|
name: "get",
|
|
@@ -4464,6 +4627,21 @@ const sitemapsCommand = defineCommand({
|
|
|
4464
4627
|
for (const u of urls) console.log(u);
|
|
4465
4628
|
}
|
|
4466
4629
|
})
|
|
4630
|
+
},
|
|
4631
|
+
async run({ args }) {
|
|
4632
|
+
if (!noSubcommandSelected("sitemaps", [
|
|
4633
|
+
"list",
|
|
4634
|
+
"get",
|
|
4635
|
+
"submit",
|
|
4636
|
+
"delete",
|
|
4637
|
+
"discover",
|
|
4638
|
+
"urls"
|
|
4639
|
+
])) return;
|
|
4640
|
+
await listCommand$1.run?.({
|
|
4641
|
+
args,
|
|
4642
|
+
cmd: listCommand$1,
|
|
4643
|
+
rawArgs: []
|
|
4644
|
+
});
|
|
4467
4645
|
}
|
|
4468
4646
|
});
|
|
4469
4647
|
const ALL_METHODS = [
|
|
@@ -5540,6 +5718,22 @@ const storeCommand = defineCommand({
|
|
|
5540
5718
|
console.log(` Sync states: ${result.syncStatesRemoved}`);
|
|
5541
5719
|
}
|
|
5542
5720
|
})
|
|
5721
|
+
},
|
|
5722
|
+
async run({ args }) {
|
|
5723
|
+
if (!noSubcommandSelected("store", [
|
|
5724
|
+
"stats",
|
|
5725
|
+
"compact",
|
|
5726
|
+
"gc",
|
|
5727
|
+
"export",
|
|
5728
|
+
"rollups",
|
|
5729
|
+
"rm-site",
|
|
5730
|
+
"reset"
|
|
5731
|
+
])) return;
|
|
5732
|
+
await statsCommand.run?.({
|
|
5733
|
+
args,
|
|
5734
|
+
cmd: statsCommand,
|
|
5735
|
+
rawArgs: []
|
|
5736
|
+
});
|
|
5543
5737
|
}
|
|
5544
5738
|
});
|
|
5545
5739
|
const DEFAULT_TABLES = [
|
|
@@ -6070,6 +6264,9 @@ runMain(defineCommand({
|
|
|
6070
6264
|
analyze: analyzeCommand,
|
|
6071
6265
|
report: reportCommand,
|
|
6072
6266
|
auth: authCommand,
|
|
6267
|
+
login: loginCommand,
|
|
6268
|
+
logout: logoutCommand,
|
|
6269
|
+
status: statusCommand$1,
|
|
6073
6270
|
config: configCommand,
|
|
6074
6271
|
profile: profileCommand,
|
|
6075
6272
|
doctor: doctorCommand,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gscdump/cli",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.18.0",
|
|
5
5
|
"description": "CLI for Google Search Console - dump, query, and run MCP server",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Harlan Wilton",
|
|
@@ -42,11 +42,11 @@
|
|
|
42
42
|
"google-auth-library": "^10.6.2",
|
|
43
43
|
"ofetch": "^1.5.1",
|
|
44
44
|
"open": "^11.0.0",
|
|
45
|
-
"@gscdump/
|
|
46
|
-
"@gscdump/
|
|
47
|
-
"gscdump": "0.
|
|
48
|
-
"
|
|
49
|
-
"@gscdump/engine
|
|
45
|
+
"@gscdump/engine-gsc-api": "0.18.0",
|
|
46
|
+
"@gscdump/analysis": "0.18.0",
|
|
47
|
+
"@gscdump/mcp": "0.18.0",
|
|
48
|
+
"gscdump": "0.18.0",
|
|
49
|
+
"@gscdump/engine": "0.18.0"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
52
|
"@duckdb/node-api": "1.5.1-r.2",
|
package/dist/_chunks/config.mjs
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import fs from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
import os from "node:os";
|
|
4
|
-
var __defProp = Object.defineProperty;
|
|
5
|
-
var __exportAll = (all, no_symbols) => {
|
|
6
|
-
let target = {};
|
|
7
|
-
for (var name in all) __defProp(target, name, {
|
|
8
|
-
get: all[name],
|
|
9
|
-
enumerable: true
|
|
10
|
-
});
|
|
11
|
-
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
12
|
-
return target;
|
|
13
|
-
};
|
|
14
|
-
var config_exports = /* @__PURE__ */ __exportAll({
|
|
15
|
-
defaultDataDir: () => defaultDataDir,
|
|
16
|
-
getConfigDir: () => getConfigDir,
|
|
17
|
-
getConfigPath: () => getConfigPath,
|
|
18
|
-
loadConfig: () => loadConfig,
|
|
19
|
-
resolveDataDir: () => resolveDataDir,
|
|
20
|
-
saveConfig: () => saveConfig,
|
|
21
|
-
setConfigDir: () => setConfigDir
|
|
22
|
-
});
|
|
23
|
-
let configDir = path.join(os.homedir(), ".config", "gscdump");
|
|
24
|
-
function setConfigDir(dir) {
|
|
25
|
-
configDir = dir;
|
|
26
|
-
}
|
|
27
|
-
function getConfigDir() {
|
|
28
|
-
return configDir;
|
|
29
|
-
}
|
|
30
|
-
function defaultDataDir() {
|
|
31
|
-
return path.join(os.homedir(), ".gscdump", "data");
|
|
32
|
-
}
|
|
33
|
-
function resolveDataDir(config) {
|
|
34
|
-
return expandTilde(config.dataDir ?? defaultDataDir());
|
|
35
|
-
}
|
|
36
|
-
function expandTilde(p) {
|
|
37
|
-
if (p === "~") return os.homedir();
|
|
38
|
-
if (p.startsWith("~/")) return path.join(os.homedir(), p.slice(2));
|
|
39
|
-
return p;
|
|
40
|
-
}
|
|
41
|
-
async function loadConfig() {
|
|
42
|
-
return fs.readFile(path.join(configDir, "config.json"), "utf-8").then((data) => JSON.parse(data)).catch(() => ({}));
|
|
43
|
-
}
|
|
44
|
-
async function saveConfig(config) {
|
|
45
|
-
await fs.mkdir(configDir, {
|
|
46
|
-
recursive: true,
|
|
47
|
-
mode: 448
|
|
48
|
-
});
|
|
49
|
-
await fs.writeFile(path.join(configDir, "config.json"), JSON.stringify(config, null, 2), { mode: 384 });
|
|
50
|
-
}
|
|
51
|
-
function getConfigPath() {
|
|
52
|
-
return path.join(configDir, "config.json");
|
|
53
|
-
}
|
|
54
|
-
export { loadConfig as a, setConfigDir as c, getConfigPath as i, __exportAll as l, defaultDataDir as n, resolveDataDir as o, getConfigDir as r, saveConfig as s, config_exports as t };
|