@gscdump/cli 3.8.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +113 -30
- package/bin/gscdump.mjs +21 -1
- package/dist/analysis-local.mjs +172 -53
- package/dist/auth-state.mjs +21 -6
- package/dist/auth.mjs +27 -18
- package/dist/bing-data.mjs +6 -2
- package/dist/bing-hosted.mjs +1 -1
- package/dist/cli-args.mjs +8 -0
- package/dist/cli.d.mts +5 -1
- package/dist/cli.mjs +41 -10
- package/dist/cloud-google.mjs +8 -1
- package/dist/command-meta.mjs +4 -4
- package/dist/command-registry.mjs +86 -10
- package/dist/commands/analyze.mjs +84 -43
- package/dist/commands/auth.mjs +46 -37
- package/dist/commands/bing.mjs +3 -3
- package/dist/commands/compact.mjs +12 -8
- package/dist/commands/config.mjs +14 -14
- package/dist/commands/doctor.mjs +26 -26
- package/dist/commands/dump.mjs +203 -105
- package/dist/commands/entities.mjs +10 -113
- package/dist/commands/gc.mjs +4 -3
- package/dist/commands/indexing-urls.mjs +185 -0
- package/dist/commands/indexing.mjs +12 -8
- package/dist/commands/init.mjs +77 -18
- package/dist/commands/inspect.mjs +97 -110
- package/dist/commands/mcp.mjs +37 -57
- package/dist/commands/papercut.mjs +1 -1
- package/dist/commands/profile.mjs +9 -17
- package/dist/commands/query.mjs +219 -210
- package/dist/commands/report.mjs +68 -85
- package/dist/commands/rollups.mjs +9 -6
- package/dist/commands/sitemaps.mjs +51 -104
- package/dist/commands/sites.mjs +40 -24
- package/dist/commands/stats.mjs +14 -17
- package/dist/commands/store-purge.mjs +9 -5
- package/dist/commands/store.mjs +0 -12
- package/dist/commands/sync.mjs +774 -296
- package/dist/config.mjs +7 -4
- package/dist/context.mjs +51 -21
- package/dist/coverage.mjs +251 -0
- package/dist/dump-bing.mjs +71 -0
- package/dist/dump-writers.mjs +246 -0
- package/dist/error-handler.mjs +108 -38
- package/dist/filters.mjs +125 -0
- package/dist/hosted-site.mjs +91 -0
- package/dist/inspect-urls.mjs +76 -0
- package/dist/inspection-record.mjs +93 -0
- package/dist/local-entities.mjs +658 -0
- package/dist/mcp/errors.mjs +71 -18
- package/dist/mcp/handlers/diagnostics.mjs +15 -12
- package/dist/mcp/handlers/reports.mjs +33 -42
- package/dist/mcp/server/index.mjs +80 -38
- package/dist/mcp/types.mjs +7 -7
- package/dist/package.mjs +1 -1
- package/dist/quota-ledger.mjs +370 -0
- package/dist/render/analysis.mjs +8 -1
- package/dist/render/report.mjs +8 -1
- package/dist/request-pacer.mjs +29 -0
- package/dist/route.mjs +295 -0
- package/dist/sitemap.mjs +4 -0
- package/dist/sql-views.mjs +117 -0
- package/dist/store-sites.mjs +134 -0
- package/dist/sync-plan.mjs +129 -0
- package/dist/sync-run.mjs +96 -0
- package/dist/table-sources.mjs +57 -0
- package/dist/token-info.mjs +54 -0
- package/dist/utils.mjs +28 -19
- package/dist/window.mjs +159 -0
- package/package.json +17 -14
- package/skills/gscdump/SKILL.md +167 -37
- package/dist/commands/export.mjs +0 -76
- package/dist/native-duckdb.mjs +0 -46
package/dist/commands/auth.mjs
CHANGED
|
@@ -1,25 +1,18 @@
|
|
|
1
|
-
import { useCliRuntime } from "../runtime.mjs";
|
|
2
|
-
import { clearAuthentication, getCloudAccount, parseAuthMode, parseAuthentication, resolveAuthentication, saveAuthentication } from "../auth-state.mjs";
|
|
3
1
|
import { authCommandMeta } from "../command-meta.mjs";
|
|
2
|
+
import { useCliRuntime } from "../runtime.mjs";
|
|
3
|
+
import { clearAuthentication, formatHostedSync, getCloudAccount, getCloudSites, parseAuthMode, parseAuthentication, resolveAuthentication, saveAuthentication } from "../auth-state.mjs";
|
|
4
4
|
import { loadConfig, saveConfig } from "../config.mjs";
|
|
5
|
-
import { OUTPUT_ARGS, applyOutputMode, logger
|
|
6
|
-
import { clearTokens, formatAuthProvenance, getAuth, loadServiceAccount, loadTokens, resolveBYOK, saveTokens } from "../auth.mjs";
|
|
5
|
+
import { OUTPUT_ARGS, applyOutputMode, logger } from "../utils.mjs";
|
|
6
|
+
import { GOOGLE_NOT_CONNECTED, clearTokens, formatAuthProvenance, getAuth, loadServiceAccount, loadTokens, resolveBYOK, saveTokens } from "../auth.mjs";
|
|
7
7
|
import { missingRequiredScopes } from "../auth-scopes.mjs";
|
|
8
8
|
import { clearBingCredentials, getBingClient, inspectBingCredentials } from "../bing-auth.mjs";
|
|
9
|
+
import { currentAccessToken, fetchTokenInfo, redactTokens } from "../token-info.mjs";
|
|
9
10
|
import { runSmokeTest } from "./init.mjs";
|
|
10
11
|
import { adoptCurrentConfigAsProfile, profileNameFromEmail, resolveActiveProfile } from "./profile.mjs";
|
|
11
12
|
import process from "node:process";
|
|
12
13
|
import { defineCommand } from "citty";
|
|
13
14
|
import path from "node:path";
|
|
14
15
|
import { isCancel, password } from "@clack/prompts";
|
|
15
|
-
import { ofetch } from "ofetch";
|
|
16
|
-
const AUTH_SUBCOMMANDS = [
|
|
17
|
-
"status",
|
|
18
|
-
"login",
|
|
19
|
-
"logout",
|
|
20
|
-
"refresh",
|
|
21
|
-
"scopes"
|
|
22
|
-
];
|
|
23
16
|
const MODE_ARG = {
|
|
24
17
|
type: "string",
|
|
25
18
|
description: "Authentication mode: cloud or local"
|
|
@@ -52,24 +45,26 @@ async function loginCloud(args) {
|
|
|
52
45
|
logger.success(`Cloud authentication saved for ${account.user.email}`);
|
|
53
46
|
logger.info("Google and Bing commands use connections saved on gscdump.com.");
|
|
54
47
|
}
|
|
55
|
-
async function fetchTokenInfo(accessToken) {
|
|
56
|
-
return ofetch("https://oauth2.googleapis.com/tokeninfo", { query: { access_token: accessToken } }).catch(() => null);
|
|
57
|
-
}
|
|
58
48
|
async function resolveLiveAuthState() {
|
|
59
49
|
const tokens = await loadTokens();
|
|
60
50
|
const byok = resolveBYOK();
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
51
|
+
const source = byok ?? (tokens ? await getAuth({ interactive: false }).catch((error) => error instanceof Error ? error : new Error(String(error))) : null);
|
|
52
|
+
const current = source === null ? null : source instanceof Error ? {
|
|
53
|
+
kind: "failed",
|
|
54
|
+
detail: redactTokens(source.message)
|
|
55
|
+
} : await currentAccessToken(source);
|
|
56
|
+
const liveToken = current?.kind === "token" ? current.token : null;
|
|
57
|
+
const verified = liveToken ? await fetchTokenInfo(liveToken) : null;
|
|
58
|
+
const tokenInfo = verified?.kind === "valid" ? verified.info : null;
|
|
59
|
+
const failure = current?.kind === "failed" ? current.detail : verified?.kind === "invalid" ? verified.detail : null;
|
|
66
60
|
const scopes = tokenInfo?.scope ? tokenInfo.scope.split(/\s+/).filter(Boolean) : [];
|
|
67
61
|
const missing = missingRequiredScopes(scopes, byok ? void 0 : tokens?.provider);
|
|
68
62
|
return {
|
|
69
63
|
byok,
|
|
70
|
-
tokens,
|
|
64
|
+
tokens: tokens && !byok ? await loadTokens() : tokens,
|
|
71
65
|
liveToken,
|
|
72
66
|
tokenInfo,
|
|
67
|
+
failure,
|
|
73
68
|
scopes,
|
|
74
69
|
missing
|
|
75
70
|
};
|
|
@@ -127,25 +122,42 @@ async function runStatus(args) {
|
|
|
127
122
|
}
|
|
128
123
|
return;
|
|
129
124
|
}
|
|
125
|
+
const hosted = await getCloudSites(authentication).then((sites) => ({
|
|
126
|
+
_tag: "Ok",
|
|
127
|
+
sites: sites.filter((site) => site.registered)
|
|
128
|
+
}), (error) => ({
|
|
129
|
+
_tag: "Err",
|
|
130
|
+
detail: error instanceof Error ? error.message : "Hosted Site list failed."
|
|
131
|
+
}));
|
|
130
132
|
if (json) console.log(JSON.stringify({
|
|
131
133
|
authenticated: true,
|
|
132
134
|
mode: "cloud",
|
|
133
135
|
account: account.value.user.email,
|
|
134
136
|
apiRoot: authentication.apiRoot,
|
|
135
137
|
sites: account.value.sites,
|
|
138
|
+
...hosted._tag === "Ok" ? { hostedSync: hosted.sites.map((site) => ({
|
|
139
|
+
siteUrl: site.siteUrl,
|
|
140
|
+
syncStatus: site.syncStatus ?? null,
|
|
141
|
+
syncProgress: site.syncProgress ?? null,
|
|
142
|
+
oldestDateSynced: site.oldestDateSynced ?? null,
|
|
143
|
+
newestDateSynced: site.newestDateSynced ?? null
|
|
144
|
+
})) } : { hostedSyncError: hosted.detail },
|
|
136
145
|
capabilities
|
|
137
146
|
}, null, 2));
|
|
138
147
|
else {
|
|
139
148
|
logger.success(`Authenticated with cloud: ${account.value.user.email}`);
|
|
140
149
|
console.log(` API: ${authentication.apiRoot}`);
|
|
141
150
|
console.log(` Sites: ${account.value.sites.length}`);
|
|
151
|
+
if (hosted._tag === "Ok") for (const site of hosted.sites) console.log(` ${site.siteUrl} ${formatHostedSync(site)}`);
|
|
152
|
+
else console.log(` Hosted sync status is not available: ${hosted.detail}`);
|
|
142
153
|
console.log(" Google and Bing use connections saved on gscdump.com.");
|
|
143
154
|
console.log(` Cloud commands: ${capabilities.cloud.join(", ")}`);
|
|
144
155
|
console.log(" Google indexing and Site Verification require --mode local.");
|
|
145
156
|
}
|
|
146
157
|
return;
|
|
147
158
|
}
|
|
148
|
-
const { byok, tokens, tokenInfo, scopes, missing } = await resolveLiveAuthState();
|
|
159
|
+
const { byok, tokens, tokenInfo, failure, scopes, missing } = await resolveLiveAuthState();
|
|
160
|
+
const googleAuthenticated = tokenInfo !== null;
|
|
149
161
|
const bingCredentials = await inspectBingCredentials();
|
|
150
162
|
const bingResult = bingCredentials._tag === "Missing" ? null : await getBingClient().then((client) => client.getUserSites({ signal: AbortSignal.timeout(1e4) })).catch(() => null);
|
|
151
163
|
const bing = {
|
|
@@ -156,9 +168,10 @@ async function runStatus(args) {
|
|
|
156
168
|
const byokKind = byok ? typeof byok === "string" ? "access-token" : "refresh-token" : null;
|
|
157
169
|
if (json) {
|
|
158
170
|
console.log(JSON.stringify({
|
|
159
|
-
authenticated:
|
|
171
|
+
authenticated: googleAuthenticated || bing.authenticated,
|
|
160
172
|
mode: "local",
|
|
161
|
-
googleAuthenticated
|
|
173
|
+
googleAuthenticated,
|
|
174
|
+
googleError: failure,
|
|
162
175
|
bing,
|
|
163
176
|
source: byok ? "byok" : tokens ? "saved-tokens" : null,
|
|
164
177
|
byokKind,
|
|
@@ -186,7 +199,8 @@ async function runStatus(args) {
|
|
|
186
199
|
}
|
|
187
200
|
};
|
|
188
201
|
if (byok) {
|
|
189
|
-
logger.success(`Authenticated via BYOK (${byokKind})`);
|
|
202
|
+
if (googleAuthenticated) logger.success(`Authenticated via BYOK (${byokKind})`);
|
|
203
|
+
else logger.warn(`BYOK credentials (${byokKind}) failed verification: ${failure}`);
|
|
190
204
|
if (tokenInfo?.email) console.log(` Account: ${tokenInfo.email}`);
|
|
191
205
|
reportScopes();
|
|
192
206
|
return;
|
|
@@ -196,18 +210,15 @@ async function runStatus(args) {
|
|
|
196
210
|
logger.info("Google credentials are missing. Run `gscdump auth login --mode local` to connect Google.");
|
|
197
211
|
return;
|
|
198
212
|
}
|
|
199
|
-
logger.warn(
|
|
200
|
-
logger.info("Run `gscdump init` (full setup) or `gscdump auth login` (OAuth only)");
|
|
201
|
-
logger.info("Or set GSC_ACCESS_TOKEN / GSC_CLIENT_ID + GSC_CLIENT_SECRET + GSC_REFRESH_TOKEN env vars");
|
|
213
|
+
logger.warn(GOOGLE_NOT_CONNECTED);
|
|
202
214
|
return;
|
|
203
215
|
}
|
|
204
216
|
const hasAccess = !!tokens.access_token;
|
|
205
217
|
const hasRefresh = !!tokens.refresh_token;
|
|
206
218
|
const expiry = tokens.expiry_date ? new Date(tokens.expiry_date) : null;
|
|
207
219
|
const isExpired = expiry && expiry < /* @__PURE__ */ new Date();
|
|
208
|
-
if (
|
|
209
|
-
else
|
|
210
|
-
else logger.success("Authenticated (saved tokens)");
|
|
220
|
+
if (googleAuthenticated) logger.success("Authenticated (saved tokens)");
|
|
221
|
+
else logger.warn(`Saved credentials failed verification: ${failure}. Run \`gscdump auth login\` to sign in again.`);
|
|
211
222
|
console.log();
|
|
212
223
|
console.log(` Access token: ${hasAccess ? "\x1B[32mpresent\x1B[0m" : "\x1B[31mmissing\x1B[0m"}`);
|
|
213
224
|
console.log(` Refresh token: ${hasRefresh ? "\x1B[32mpresent\x1B[0m" : "\x1B[31mmissing\x1B[0m"}`);
|
|
@@ -217,7 +228,6 @@ async function runStatus(args) {
|
|
|
217
228
|
}
|
|
218
229
|
if (tokenInfo?.email) console.log(` Account: ${tokenInfo.email}`);
|
|
219
230
|
reportScopes();
|
|
220
|
-
if (isExpired && !hasRefresh) process.exit(1);
|
|
221
231
|
}
|
|
222
232
|
const statusCommand = defineCommand({
|
|
223
233
|
meta: authCommandMeta.status,
|
|
@@ -334,9 +344,9 @@ const loginCommand = defineCommand({
|
|
|
334
344
|
await saveAuthentication({ _tag: "Local" });
|
|
335
345
|
if (!resolveActiveProfile()) {
|
|
336
346
|
const tokens = await loadTokens();
|
|
337
|
-
const
|
|
338
|
-
if (info
|
|
339
|
-
const name = profileNameFromEmail(info.email);
|
|
347
|
+
const verified = tokens?.access_token ? await fetchTokenInfo(tokens.access_token) : null;
|
|
348
|
+
if (verified?.kind === "valid" && verified.info.email) {
|
|
349
|
+
const name = profileNameFromEmail(verified.info.email);
|
|
340
350
|
if (await adoptCurrentConfigAsProfile(name).catch((error) => {
|
|
341
351
|
const message = error instanceof Error ? error.message : String(error);
|
|
342
352
|
logger.warn(`Login succeeded, but the config could not be moved into profile "${name}": ${message}`);
|
|
@@ -409,8 +419,7 @@ const authCommand = defineCommand({
|
|
|
409
419
|
scopes: scopesCommand
|
|
410
420
|
},
|
|
411
421
|
async run({ args }) {
|
|
412
|
-
if (!noSubcommandSelected("auth", AUTH_SUBCOMMANDS)) return;
|
|
413
422
|
await runStatus(args);
|
|
414
423
|
}
|
|
415
424
|
});
|
|
416
|
-
export { authCommand, loginCommand, logoutCommand, statusCommand };
|
|
425
|
+
export { authCommand, loginCloud, loginCommand, logoutCommand, statusCommand };
|
package/dist/commands/bing.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { bingCommandMeta } from "../command-meta.mjs";
|
|
1
2
|
import { useCliRuntime } from "../runtime.mjs";
|
|
2
3
|
import { resolveAuthentication, saveAuthentication } from "../auth-state.mjs";
|
|
3
|
-
import { bingCommandMeta } from "../command-meta.mjs";
|
|
4
4
|
import { OUTPUT_ARGS, applyOutputMode, logger } from "../utils.mjs";
|
|
5
5
|
import { BING_REDIRECT_URI, clearBingCredentials, getBingClient, loginBingOAuth, saveBingCredentials } from "../bing-auth.mjs";
|
|
6
6
|
import { BING_DATASETS, dumpBingSite, parseBingDumpOptions, resolveBingSites, unwrapBing } from "../bing-data.mjs";
|
|
@@ -223,7 +223,7 @@ const bingCommand = defineCommand({
|
|
|
223
223
|
},
|
|
224
224
|
"datasets": {
|
|
225
225
|
type: "string",
|
|
226
|
-
description: `Comma-separated datasets: ${BING_DATASETS.join(", ")}. Default: all
|
|
226
|
+
description: `Comma-separated datasets: ${BING_DATASETS.join(", ")}. Default: all. Crawl issues have no dates, so --start, --end, and cloud mode leave them out`
|
|
227
227
|
},
|
|
228
228
|
"start": {
|
|
229
229
|
type: "string",
|
|
@@ -240,7 +240,7 @@ const bingCommand = defineCommand({
|
|
|
240
240
|
const options = parseBingDumpOptions(args);
|
|
241
241
|
const authentication = await resolveAuthentication();
|
|
242
242
|
if (authentication._tag === "Cloud") {
|
|
243
|
-
if (options.datasets.includes("crawl-issues")) throw new Error("Bing crawl issues require local authentication. Use --mode local.");
|
|
243
|
+
if (options.datasetsExplicit && options.datasets.includes("crawl-issues")) throw new Error("Bing crawl issues require local authentication. Use --mode local.");
|
|
244
244
|
if (options.start && options.end && Date.parse(options.end) - Date.parse(options.start) > 316224e5) throw new Error("Hosted Bing exports support at most 366 days. Use a shorter --start and --end range.");
|
|
245
245
|
const sites = await resolveHostedBingSites(authentication, {
|
|
246
246
|
site: args.site,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { OUTPUT_ARGS, applyOutputMode, logger } from "../utils.mjs";
|
|
2
2
|
import { allTables } from "../local-store.mjs";
|
|
3
|
+
import { readSiteMap, siteUrlForId } from "../store-sites.mjs";
|
|
3
4
|
import { createCommandContext } from "../context.mjs";
|
|
4
5
|
import { defineCommand } from "citty";
|
|
5
6
|
import { inferLegacyTier } from "@gscdump/engine";
|
|
@@ -12,7 +13,7 @@ const compactCommand = defineCommand({
|
|
|
12
13
|
"site": {
|
|
13
14
|
type: "string",
|
|
14
15
|
alias: "s",
|
|
15
|
-
description: "Restrict to
|
|
16
|
+
description: "Restrict to one Site, for example example.com (default: every Site with local data)"
|
|
16
17
|
},
|
|
17
18
|
"raw-days": {
|
|
18
19
|
type: "string",
|
|
@@ -35,8 +36,9 @@ const compactCommand = defineCommand({
|
|
|
35
36
|
},
|
|
36
37
|
async run({ args }) {
|
|
37
38
|
const { json } = applyOutputMode(args);
|
|
38
|
-
const
|
|
39
|
-
const
|
|
39
|
+
const ctx = await createCommandContext({ needsStore: true });
|
|
40
|
+
const store = ctx.store;
|
|
41
|
+
const siteId = args.site ? store.siteIdFor(await ctx.resolveSite(String(args.site), { scope: "store" })) : void 0;
|
|
40
42
|
const dryRun = Boolean(args["dry-run"]);
|
|
41
43
|
const thresholds = {};
|
|
42
44
|
if (args["raw-days"]) thresholds.raw = Number(args["raw-days"]);
|
|
@@ -46,13 +48,15 @@ const compactCommand = defineCommand({
|
|
|
46
48
|
userId: store.userId,
|
|
47
49
|
siteId
|
|
48
50
|
});
|
|
51
|
+
const siteMap = await readSiteMap(store.dataDir, store.userId);
|
|
52
|
+
const siteLabel = (id) => id ? siteUrlForId(siteMap, id) : "-";
|
|
49
53
|
if (dryRun) {
|
|
50
54
|
const report = [];
|
|
51
55
|
for (const table of allTables()) {
|
|
52
56
|
const bySite = groupBySite(liveEntries.filter((entry) => entry.table === table));
|
|
53
57
|
for (const [s, group] of bySite) report.push({
|
|
54
58
|
table,
|
|
55
|
-
|
|
59
|
+
siteUrl: s ? siteUrlForId(siteMap, s) : null,
|
|
56
60
|
...countByTier(group)
|
|
57
61
|
});
|
|
58
62
|
}
|
|
@@ -64,8 +68,8 @@ const compactCommand = defineCommand({
|
|
|
64
68
|
return;
|
|
65
69
|
}
|
|
66
70
|
console.log();
|
|
67
|
-
console.log(` table
|
|
68
|
-
for (const r of report) console.log(` ${r.table.padEnd(20)} ${(r.
|
|
71
|
+
console.log(` table Site raw d7 d30 d90`);
|
|
72
|
+
for (const r of report) console.log(` ${r.table.padEnd(20)} ${(r.siteUrl ?? "-").padEnd(30)} ${String(r.raw).padStart(4)} ${String(r.d7).padStart(4)} ${String(r.d30).padStart(4)} ${String(r.d90).padStart(4)}`);
|
|
69
73
|
console.log();
|
|
70
74
|
logger.info(`compact --dry-run: ${report.length} (table, site) pair(s) — pass without --dry-run to apply`);
|
|
71
75
|
return;
|
|
@@ -75,7 +79,7 @@ const compactCommand = defineCommand({
|
|
|
75
79
|
const entries = liveEntries.filter((entry) => entry.table === table);
|
|
76
80
|
const siteIds = new Set(entries.map((e) => e.siteId));
|
|
77
81
|
for (const targetSite of siteIds) {
|
|
78
|
-
logger.info(`Compacting ${table} [${targetSite
|
|
82
|
+
logger.info(`Compacting ${table} [${siteLabel(targetSite)}] (raw→d7→d30→d90)`);
|
|
79
83
|
await store.engine.compactTiered({
|
|
80
84
|
userId: store.userId,
|
|
81
85
|
siteId: targetSite,
|
|
@@ -83,7 +87,7 @@ const compactCommand = defineCommand({
|
|
|
83
87
|
}, thresholds);
|
|
84
88
|
summary.push({
|
|
85
89
|
table,
|
|
86
|
-
|
|
90
|
+
siteUrl: targetSite ? siteUrlForId(siteMap, targetSite) : null
|
|
87
91
|
});
|
|
88
92
|
}
|
|
89
93
|
}
|
package/dist/commands/config.mjs
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { configCommandMeta } from "../command-meta.mjs";
|
|
2
2
|
import { getConfigPath, loadConfig, resolveDataDir, saveConfig } from "../config.mjs";
|
|
3
|
-
import { OUTPUT_ARGS, applyOutputMode, displayPath, logger
|
|
4
|
-
import { createCommandContext } from "../context.mjs";
|
|
3
|
+
import { OUTPUT_ARGS, applyOutputMode, displayPath, logger } from "../utils.mjs";
|
|
4
|
+
import { createCommandContext, formatSiteResolution } from "../context.mjs";
|
|
5
5
|
import process from "node:process";
|
|
6
6
|
import { defineCommand } from "citty";
|
|
7
7
|
import fs from "node:fs/promises";
|
|
8
8
|
import path from "node:path";
|
|
9
|
+
import { resolveSiteInput } from "gscdump";
|
|
9
10
|
const showCommand = defineCommand({
|
|
10
11
|
meta: {
|
|
11
12
|
name: "show",
|
|
@@ -35,9 +36,7 @@ const showCommand = defineCommand({
|
|
|
35
36
|
});
|
|
36
37
|
const VALID_KEYS = [
|
|
37
38
|
"defaultSite",
|
|
38
|
-
"defaultPeriod",
|
|
39
39
|
"defaultFormat",
|
|
40
|
-
"defaultDb",
|
|
41
40
|
"dataDir",
|
|
42
41
|
"defaultLimit",
|
|
43
42
|
"defaultSearchType",
|
|
@@ -45,6 +44,13 @@ const VALID_KEYS = [
|
|
|
45
44
|
"serviceAccountPath"
|
|
46
45
|
];
|
|
47
46
|
const NUMERIC_KEYS = /* @__PURE__ */ new Set(["defaultLimit"]);
|
|
47
|
+
async function canonicalSite(input) {
|
|
48
|
+
const fromStore = await (await createCommandContext()).matchSite(input, { scope: "store" });
|
|
49
|
+
if (fromStore.kind === "resolved") return fromStore.siteUrl;
|
|
50
|
+
return (await createCommandContext({ needsAuth: true }).catch((error) => {
|
|
51
|
+
throw new Error(`${formatSiteResolution(fromStore, "store")} ${error.message}`);
|
|
52
|
+
})).resolveSite(input);
|
|
53
|
+
}
|
|
48
54
|
const setCommand = defineCommand({
|
|
49
55
|
meta: {
|
|
50
56
|
name: "set",
|
|
@@ -71,7 +77,7 @@ const setCommand = defineCommand({
|
|
|
71
77
|
process.exit(1);
|
|
72
78
|
}
|
|
73
79
|
const config = await loadConfig();
|
|
74
|
-
const value = NUMERIC_KEYS.has(args.key) ? Number(args.value) : args.value;
|
|
80
|
+
const value = NUMERIC_KEYS.has(args.key) ? Number(args.value) : args.key === "defaultSite" ? await canonicalSite(args.value) : args.value;
|
|
75
81
|
if (NUMERIC_KEYS.has(args.key) && !Number.isFinite(value)) {
|
|
76
82
|
logger.error(`Invalid numeric value for ${args.key}: ${args.value}`);
|
|
77
83
|
process.exit(1);
|
|
@@ -163,10 +169,11 @@ const validateCommand = defineCommand({
|
|
|
163
169
|
message: `Could not verify Site: ${message}`
|
|
164
170
|
});
|
|
165
171
|
});
|
|
166
|
-
|
|
172
|
+
const resolution = sites && resolveSiteInput(config.defaultSite, sites);
|
|
173
|
+
if (resolution && resolution.kind !== "resolved") issues.push({
|
|
167
174
|
key: "defaultSite",
|
|
168
175
|
level: "fail",
|
|
169
|
-
message:
|
|
176
|
+
message: formatSiteResolution(resolution, "account")
|
|
170
177
|
});
|
|
171
178
|
} else issues.push({
|
|
172
179
|
key: "defaultSite",
|
|
@@ -212,13 +219,6 @@ const configCommand = defineCommand({
|
|
|
212
219
|
validate: validateCommand
|
|
213
220
|
},
|
|
214
221
|
async run({ args }) {
|
|
215
|
-
if (!noSubcommandSelected("config", [
|
|
216
|
-
"show",
|
|
217
|
-
"set",
|
|
218
|
-
"unset",
|
|
219
|
-
"path",
|
|
220
|
-
"validate"
|
|
221
|
-
])) return;
|
|
222
222
|
await showCommand.run?.({
|
|
223
223
|
args,
|
|
224
224
|
cmd: showCommand,
|
package/dist/commands/doctor.mjs
CHANGED
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
import { getCloudAccount, resolveAuthentication } from "../auth-state.mjs";
|
|
2
1
|
import { doctorCommandMeta } from "../command-meta.mjs";
|
|
2
|
+
import { getCloudAccount, resolveAuthentication } from "../auth-state.mjs";
|
|
3
3
|
import { loadConfig } from "../config.mjs";
|
|
4
4
|
import { resolveCliEnvironment } from "../environment.mjs";
|
|
5
5
|
import { parseEnvFile } from "../env-file.mjs";
|
|
6
6
|
import { OUTPUT_ARGS, applyOutputMode, displayPath, logger } from "../utils.mjs";
|
|
7
|
-
import { loadTokens, resolveAuth, resolveBYOK } from "../auth.mjs";
|
|
7
|
+
import { getAuth, loadTokens, resolveAuth, resolveBYOK } from "../auth.mjs";
|
|
8
8
|
import { createLocalStore } from "../local-store.mjs";
|
|
9
|
-
import { createCommandContext } from "../context.mjs";
|
|
9
|
+
import { createCommandContext, formatSiteResolution } from "../context.mjs";
|
|
10
10
|
import { missingRequiredScopes } from "../auth-scopes.mjs";
|
|
11
|
+
import { currentAccessToken, fetchTokenInfo, redactTokens } from "../token-info.mjs";
|
|
11
12
|
import process from "node:process";
|
|
12
13
|
import { defineCommand } from "citty";
|
|
13
14
|
import fs from "node:fs/promises";
|
|
14
15
|
import path from "node:path";
|
|
15
16
|
import { googleSearchConsole } from "gscdump/client";
|
|
17
|
+
import { resolveSiteInput } from "gscdump";
|
|
16
18
|
import { ofetch } from "ofetch";
|
|
17
19
|
const FETCH_TIMEOUT_MS = 5e3;
|
|
18
20
|
const TIME_SKEW_WARN_MS = 3e5;
|
|
@@ -97,39 +99,36 @@ async function checkAuth(envKeys) {
|
|
|
97
99
|
status: "info",
|
|
98
100
|
detail: clientId
|
|
99
101
|
});
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
});
|
|
107
|
-
else if (tokens?.access_token) liveToken = tokens.access_token;
|
|
108
|
-
if (!liveToken) {
|
|
109
|
-
const source = describeAuthSource(envKeys, byok);
|
|
110
|
-
const detail = refreshError ? `${source} — refresh failed: ${refreshError} (token revoked / invalid — re-run \`gscdump auth login --force\` and update the source above)` : `${source} — no usable access token`;
|
|
102
|
+
const source = byok ?? await getAuth({ interactive: false }).catch((error) => error instanceof Error ? error : new Error(String(error)));
|
|
103
|
+
const current = source instanceof Error ? {
|
|
104
|
+
kind: "failed",
|
|
105
|
+
detail: redactTokens(source.message)
|
|
106
|
+
} : await currentAccessToken(source);
|
|
107
|
+
if (current.kind === "failed") {
|
|
111
108
|
checks.push({
|
|
112
109
|
name: "auth",
|
|
113
110
|
status: "fail",
|
|
114
|
-
detail
|
|
111
|
+
detail: `${describeAuthSource(envKeys, byok)}: refresh failed: ${current.detail}. Run \`gscdump auth login --force\` and update the source above.`
|
|
115
112
|
});
|
|
116
113
|
return {
|
|
117
114
|
checks,
|
|
118
115
|
liveToken: null
|
|
119
116
|
};
|
|
120
117
|
}
|
|
121
|
-
const
|
|
122
|
-
|
|
118
|
+
const liveToken = current.token;
|
|
119
|
+
const tokenInfo = await fetchTokenInfo(liveToken);
|
|
120
|
+
if (tokenInfo.kind === "invalid") {
|
|
123
121
|
checks.push({
|
|
124
122
|
name: "auth",
|
|
125
123
|
status: "fail",
|
|
126
|
-
detail: `tokeninfo failed: ${
|
|
124
|
+
detail: `tokeninfo failed: ${tokenInfo.detail}`
|
|
127
125
|
});
|
|
128
126
|
return {
|
|
129
127
|
checks,
|
|
130
128
|
liveToken: null
|
|
131
129
|
};
|
|
132
130
|
}
|
|
131
|
+
const info = tokenInfo.info;
|
|
133
132
|
checks.push({
|
|
134
133
|
name: "auth",
|
|
135
134
|
status: "pass",
|
|
@@ -152,12 +151,13 @@ async function checkAuth(envKeys) {
|
|
|
152
151
|
status: "pass",
|
|
153
152
|
detail: `${scopes.length} granted`
|
|
154
153
|
});
|
|
155
|
-
|
|
156
|
-
|
|
154
|
+
const savedAfterRefresh = byok ? null : await loadTokens();
|
|
155
|
+
if (savedAfterRefresh?.expiry_date) {
|
|
156
|
+
const expiresInMs = savedAfterRefresh.expiry_date - Date.now();
|
|
157
157
|
if (expiresInMs < 0) checks.push({
|
|
158
158
|
name: "auth.expiry",
|
|
159
159
|
status: "warn",
|
|
160
|
-
detail: `expired ${new Date(
|
|
160
|
+
detail: `expired ${new Date(savedAfterRefresh.expiry_date).toISOString()} — will refresh on next call`
|
|
161
161
|
});
|
|
162
162
|
else checks.push({
|
|
163
163
|
name: "auth.expiry",
|
|
@@ -298,7 +298,7 @@ async function checkGscSites() {
|
|
|
298
298
|
if (sites instanceof Error) return [{
|
|
299
299
|
name: "gsc.sites",
|
|
300
300
|
status: "fail",
|
|
301
|
-
detail: `sites() failed: ${sites.message}`
|
|
301
|
+
detail: `sites() failed: ${redactTokens(sites.message)}`
|
|
302
302
|
}];
|
|
303
303
|
return describeGscSites(sites, config.defaultSite);
|
|
304
304
|
}
|
|
@@ -311,15 +311,15 @@ function describeGscSites(sites, defaultSite) {
|
|
|
311
311
|
detail: `${sites.length} site(s) accessible (${verified} verified)`
|
|
312
312
|
});
|
|
313
313
|
if (defaultSite) {
|
|
314
|
-
const
|
|
315
|
-
checks.push(
|
|
314
|
+
const resolution = resolveSiteInput(defaultSite, sites.flatMap((s) => s.siteUrl ? [{ siteUrl: s.siteUrl }] : []));
|
|
315
|
+
checks.push(resolution.kind === "resolved" ? {
|
|
316
316
|
name: "config.defaultSite",
|
|
317
317
|
status: "pass",
|
|
318
|
-
detail: `${
|
|
318
|
+
detail: `${resolution.siteUrl} ✓`
|
|
319
319
|
} : {
|
|
320
320
|
name: "config.defaultSite",
|
|
321
321
|
status: "fail",
|
|
322
|
-
detail:
|
|
322
|
+
detail: formatSiteResolution(resolution, "account")
|
|
323
323
|
});
|
|
324
324
|
}
|
|
325
325
|
return checks;
|