@gscdump/cli 2.0.6 → 2.1.1
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 +1 -1
- package/dist/analysis-local.mjs +2 -1
- package/dist/auth.mjs +1 -1
- package/dist/cli.mjs +2 -1
- package/dist/commands/analyze.mjs +19 -18
- package/dist/commands/config.mjs +172 -166
- package/dist/commands/doctor.mjs +1 -1
- package/dist/commands/dump.mjs +2 -1
- package/dist/commands/entities.mjs +241 -237
- package/dist/commands/indexing.mjs +159 -154
- package/dist/commands/inspect.mjs +0 -7
- package/dist/commands/profile.mjs +146 -140
- package/dist/commands/report.mjs +35 -32
- package/dist/commands/rollups.mjs +91 -90
- package/dist/commands/sitemaps.mjs +392 -382
- package/dist/commands/sites.mjs +23 -17
- package/dist/commands/stats.mjs +2 -2
- package/dist/mcp/handlers/diagnostics.mjs +1 -1
- package/dist/mcp/handlers/reports.mjs +12 -10
- package/dist/mcp/server/index.mjs +14 -7
- package/dist/package.mjs +1 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -48,7 +48,7 @@ gscdump mcp
|
|
|
48
48
|
| `sites add <url>` / `sites delete <url> [--yes]` | Register / remove a property in Search Console (add registers in unverified state) |
|
|
49
49
|
| `sites verify-token <url> [--method]` / `sites verify <url> [--method]` | Get a verification token, then trigger ownership verification (META/FILE/DNS_TXT/DNS_CNAME/ANALYTICS/TAG_MANAGER) |
|
|
50
50
|
| `sitemaps` | GSC CRUD, explicit live probes, and hosted canonical reads (`current`, `history`, `membership`, `lastmod`, `export`) |
|
|
51
|
-
| `inspect <url>` / `inspect batch [--concurrency]` | URL inspection (single URL or batch from file/stdin); renders index status, rich results, AMP
|
|
51
|
+
| `inspect <url>` / `inspect batch [--concurrency]` | URL inspection (single URL or batch from file/stdin); renders index status, rich results, and AMP |
|
|
52
52
|
| `indexing` | Notify Google about URL changes (`submit`, `remove`, `status`, `batch [--concurrency] [--yes]`); supports `--retries` |
|
|
53
53
|
| `sync` | Sync GSC data to the local Parquet store; `--retry-failed`, `--dry-run` |
|
|
54
54
|
| `query` | Run a search analytics query (local store by default; `--live` hits GSC API). Filters: `--query`, `--page`, `--country`, `--device`, `--search-appearance`, `--type`, `--data-state`, `--aggregation-type`. `--explain` previews the request body; `--output -` writes to stdout. |
|
package/dist/analysis-local.mjs
CHANGED
|
@@ -17,7 +17,8 @@ async function hasLocalData(store, siteUrl) {
|
|
|
17
17
|
})).length > 0;
|
|
18
18
|
}
|
|
19
19
|
async function listLocalSites(dataDir, userId = "local") {
|
|
20
|
-
|
|
20
|
+
const tenantDir = join(dataDir, `u_${userId}`);
|
|
21
|
+
return readdir(tenantDir, { withFileTypes: true }).then((entries) => entries.filter((e) => e.isDirectory() && (e.name.startsWith("d_") || e.name.startsWith("h_"))).map((e) => decodeSiteId(e.name))).catch(() => []);
|
|
21
22
|
}
|
|
22
23
|
function pickLocalSite(siteUrls, hint) {
|
|
23
24
|
if (siteUrls.length === 0) return null;
|
package/dist/auth.mjs
CHANGED
|
@@ -257,7 +257,7 @@ async function getAuthCodeViaLoopback(authUrl) {
|
|
|
257
257
|
server.on("error", (err) => settle(() => reject(err)));
|
|
258
258
|
timeoutId = setTimeout(() => {
|
|
259
259
|
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`).")));
|
|
260
|
-
},
|
|
260
|
+
}, 3e5);
|
|
261
261
|
});
|
|
262
262
|
}
|
|
263
263
|
async function authenticate(credentials, interactive, opts = {}) {
|
package/dist/cli.mjs
CHANGED
|
@@ -28,8 +28,9 @@ function prepareCliArgs(input) {
|
|
|
28
28
|
stderrIsTTY: Boolean(process.stderr.isTTY)
|
|
29
29
|
});
|
|
30
30
|
const profile = pluckArgValue(rawArgs, "--profile");
|
|
31
|
+
const configDir = pluckArgValue(rawArgs, "--config-dir") ?? env.configDir ?? null;
|
|
31
32
|
applyProfileFromCli({
|
|
32
|
-
configDir
|
|
33
|
+
configDir,
|
|
33
34
|
profile,
|
|
34
35
|
envProfile: env.profile
|
|
35
36
|
});
|
|
@@ -304,30 +304,31 @@ function renderResults(results, total, format) {
|
|
|
304
304
|
logger.success(`${results.length} results`);
|
|
305
305
|
if (total > results.length) logger.info(`Total: ${total} (showing ${results.length})`);
|
|
306
306
|
}
|
|
307
|
+
const listCommand = defineCommand({
|
|
308
|
+
meta: {
|
|
309
|
+
name: "list",
|
|
310
|
+
description: "List available analyzer ids"
|
|
311
|
+
},
|
|
312
|
+
args: { json: {
|
|
313
|
+
type: "boolean",
|
|
314
|
+
default: false,
|
|
315
|
+
description: "Output as JSON"
|
|
316
|
+
} },
|
|
317
|
+
async run({ args }) {
|
|
318
|
+
if (args.json) {
|
|
319
|
+
console.log(JSON.stringify(ANALYSIS_TOOLS, null, 2));
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
for (const id of ANALYSIS_TOOLS) console.log(id);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
307
325
|
const analyzeCommand = defineCommand({
|
|
308
326
|
meta: {
|
|
309
327
|
name: "analyze",
|
|
310
328
|
description: "SEO analysis tools"
|
|
311
329
|
},
|
|
312
330
|
subCommands: {
|
|
313
|
-
list:
|
|
314
|
-
meta: {
|
|
315
|
-
name: "list",
|
|
316
|
-
description: "List available analyzer ids"
|
|
317
|
-
},
|
|
318
|
-
args: { json: {
|
|
319
|
-
type: "boolean",
|
|
320
|
-
default: false,
|
|
321
|
-
description: "Output as JSON"
|
|
322
|
-
} },
|
|
323
|
-
async run({ args }) {
|
|
324
|
-
if (args.json) {
|
|
325
|
-
console.log(JSON.stringify(ANALYSIS_TOOLS, null, 2));
|
|
326
|
-
return;
|
|
327
|
-
}
|
|
328
|
-
for (const id of ANALYSIS_TOOLS) console.log(id);
|
|
329
|
-
}
|
|
330
|
-
}),
|
|
331
|
+
list: listCommand,
|
|
331
332
|
...Object.fromEntries(ANALYSIS_TOOLS.map((tool) => [tool, makeToolCommand(tool)]))
|
|
332
333
|
}
|
|
333
334
|
});
|
package/dist/commands/config.mjs
CHANGED
|
@@ -40,6 +40,174 @@ const VALID_KEYS = [
|
|
|
40
40
|
"serviceAccountPath"
|
|
41
41
|
];
|
|
42
42
|
const NUMERIC_KEYS = /* @__PURE__ */ new Set(["defaultLimit"]);
|
|
43
|
+
const setCommand = defineCommand({
|
|
44
|
+
meta: {
|
|
45
|
+
name: "set",
|
|
46
|
+
description: "Set a config value"
|
|
47
|
+
},
|
|
48
|
+
args: {
|
|
49
|
+
key: {
|
|
50
|
+
type: "positional",
|
|
51
|
+
description: `Config key (${VALID_KEYS.join(", ")})`,
|
|
52
|
+
required: true
|
|
53
|
+
},
|
|
54
|
+
value: {
|
|
55
|
+
type: "positional",
|
|
56
|
+
description: "Value to set",
|
|
57
|
+
required: true
|
|
58
|
+
},
|
|
59
|
+
...OUTPUT_ARGS
|
|
60
|
+
},
|
|
61
|
+
async run({ args }) {
|
|
62
|
+
applyOutputMode(args);
|
|
63
|
+
if (!VALID_KEYS.includes(args.key)) {
|
|
64
|
+
logger.error(`Invalid key: ${args.key}`);
|
|
65
|
+
logger.info(`Valid keys: ${VALID_KEYS.join(", ")}`);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
const config = await loadConfig();
|
|
69
|
+
const value = NUMERIC_KEYS.has(args.key) ? Number(args.value) : args.value;
|
|
70
|
+
if (NUMERIC_KEYS.has(args.key) && !Number.isFinite(value)) {
|
|
71
|
+
logger.error(`Invalid numeric value for ${args.key}: ${args.value}`);
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
config[args.key] = value;
|
|
75
|
+
await saveConfig(config);
|
|
76
|
+
logger.success(`Set ${args.key} = ${value}`);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
const unsetCommand = defineCommand({
|
|
80
|
+
meta: {
|
|
81
|
+
name: "unset",
|
|
82
|
+
description: "Remove a config value"
|
|
83
|
+
},
|
|
84
|
+
args: {
|
|
85
|
+
key: {
|
|
86
|
+
type: "positional",
|
|
87
|
+
description: `Config key to remove (${VALID_KEYS.join(", ")})`,
|
|
88
|
+
required: true
|
|
89
|
+
},
|
|
90
|
+
...OUTPUT_ARGS
|
|
91
|
+
},
|
|
92
|
+
async run({ args }) {
|
|
93
|
+
applyOutputMode(args);
|
|
94
|
+
if (!VALID_KEYS.includes(args.key)) {
|
|
95
|
+
logger.error(`Invalid key: ${args.key}`);
|
|
96
|
+
logger.info(`Valid keys: ${VALID_KEYS.join(", ")}`);
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
const config = await loadConfig();
|
|
100
|
+
delete config[args.key];
|
|
101
|
+
await saveConfig(config);
|
|
102
|
+
logger.success(`Removed ${args.key}`);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
const pathCommand = defineCommand({
|
|
106
|
+
meta: {
|
|
107
|
+
name: "path",
|
|
108
|
+
description: "Show config file path"
|
|
109
|
+
},
|
|
110
|
+
run() {
|
|
111
|
+
console.log(getConfigPath());
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
const validateCommand = defineCommand({
|
|
115
|
+
meta: {
|
|
116
|
+
name: "validate",
|
|
117
|
+
description: "Validate the saved config (defaultSite is verified, dataDir exists/writable)"
|
|
118
|
+
},
|
|
119
|
+
args: { ...OUTPUT_ARGS },
|
|
120
|
+
async run({ args }) {
|
|
121
|
+
const { json } = applyOutputMode(args);
|
|
122
|
+
const { resolveDataDir } = await import("../config.mjs");
|
|
123
|
+
const fs = await import("node:fs/promises");
|
|
124
|
+
const config = await loadConfig();
|
|
125
|
+
const issues = [];
|
|
126
|
+
const dataDir = resolveDataDir(config);
|
|
127
|
+
const dataDirDisplay = displayPath(dataDir);
|
|
128
|
+
const stat = await fs.stat(dataDir).catch(() => null);
|
|
129
|
+
if (stat && !stat.isDirectory()) issues.push({
|
|
130
|
+
key: "dataDir",
|
|
131
|
+
level: "fail",
|
|
132
|
+
message: `${dataDirDisplay} is not a directory`
|
|
133
|
+
});
|
|
134
|
+
else if (stat) {
|
|
135
|
+
const probe = `${dataDir}/.gscdump-config-probe`;
|
|
136
|
+
if (!await fs.writeFile(probe, "").then(() => fs.rm(probe)).then(() => true).catch(() => false)) issues.push({
|
|
137
|
+
key: "dataDir",
|
|
138
|
+
level: "fail",
|
|
139
|
+
message: `${dataDirDisplay} not writable`
|
|
140
|
+
});
|
|
141
|
+
} else issues.push({
|
|
142
|
+
key: "dataDir",
|
|
143
|
+
level: "warn",
|
|
144
|
+
message: `${dataDirDisplay} does not exist (will be created on first sync)`
|
|
145
|
+
});
|
|
146
|
+
if (config.defaultSite) {
|
|
147
|
+
if (!!config.clientId && !!config.clientSecret) {
|
|
148
|
+
const { createCommandContext } = await import("../context.mjs");
|
|
149
|
+
const ctx = await createCommandContext({ needsAuth: true }).catch(() => null);
|
|
150
|
+
if (ctx) {
|
|
151
|
+
const sites = await ctx.loadSites().catch(() => null);
|
|
152
|
+
if (sites && !sites.some((s) => s.siteUrl === config.defaultSite || s.siteUrl.includes(String(config.defaultSite)))) issues.push({
|
|
153
|
+
key: "defaultSite",
|
|
154
|
+
level: "fail",
|
|
155
|
+
message: `${config.defaultSite} is not in the verified site list`
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
} else issues.push({
|
|
159
|
+
key: "defaultSite",
|
|
160
|
+
level: "warn",
|
|
161
|
+
message: "set, but auth not configured — skipping verification"
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
if (config.defaultFormat && !["json", "csv"].includes(config.defaultFormat)) issues.push({
|
|
165
|
+
key: "defaultFormat",
|
|
166
|
+
level: "fail",
|
|
167
|
+
message: `unknown format: ${config.defaultFormat}`
|
|
168
|
+
});
|
|
169
|
+
const { SearchTypes } = await import("gscdump/query");
|
|
170
|
+
const allowedSearchTypes = Object.values(SearchTypes);
|
|
171
|
+
if (config.defaultSearchType && !allowedSearchTypes.includes(config.defaultSearchType)) issues.push({
|
|
172
|
+
key: "defaultSearchType",
|
|
173
|
+
level: "fail",
|
|
174
|
+
message: `unknown search type: ${config.defaultSearchType} (allowed: ${allowedSearchTypes.join(", ")})`
|
|
175
|
+
});
|
|
176
|
+
const allowedDataStates = [
|
|
177
|
+
"all",
|
|
178
|
+
"final",
|
|
179
|
+
"hourly_all"
|
|
180
|
+
];
|
|
181
|
+
if (config.defaultDataState && !allowedDataStates.includes(config.defaultDataState)) issues.push({
|
|
182
|
+
key: "defaultDataState",
|
|
183
|
+
level: "fail",
|
|
184
|
+
message: `unknown data state: ${config.defaultDataState} (allowed: ${allowedDataStates.join(", ")})`
|
|
185
|
+
});
|
|
186
|
+
if (config.serviceAccountPath) {
|
|
187
|
+
if (!await fs.stat(config.serviceAccountPath).catch(() => null)) issues.push({
|
|
188
|
+
key: "serviceAccountPath",
|
|
189
|
+
level: "fail",
|
|
190
|
+
message: `${displayPath(config.serviceAccountPath)} does not exist`
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
if (json) {
|
|
194
|
+
console.log(JSON.stringify({
|
|
195
|
+
ok: !issues.some((i) => i.level === "fail"),
|
|
196
|
+
issues
|
|
197
|
+
}, null, 2));
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (issues.length === 0) {
|
|
201
|
+
logger.success("Config OK");
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
for (const i of issues) {
|
|
205
|
+
const prefix = i.level === "fail" ? "\x1B[31m✗\x1B[0m" : "\x1B[33m!\x1B[0m";
|
|
206
|
+
console.log(` ${prefix} ${i.key}: ${i.message}`);
|
|
207
|
+
}
|
|
208
|
+
if (issues.some((i) => i.level === "fail")) process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
});
|
|
43
211
|
const configCommand = defineCommand({
|
|
44
212
|
meta: {
|
|
45
213
|
name: "config",
|
|
@@ -47,172 +215,10 @@ const configCommand = defineCommand({
|
|
|
47
215
|
},
|
|
48
216
|
subCommands: {
|
|
49
217
|
show: showCommand,
|
|
50
|
-
set:
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
},
|
|
55
|
-
args: {
|
|
56
|
-
key: {
|
|
57
|
-
type: "positional",
|
|
58
|
-
description: `Config key (${VALID_KEYS.join(", ")})`,
|
|
59
|
-
required: true
|
|
60
|
-
},
|
|
61
|
-
value: {
|
|
62
|
-
type: "positional",
|
|
63
|
-
description: "Value to set",
|
|
64
|
-
required: true
|
|
65
|
-
},
|
|
66
|
-
...OUTPUT_ARGS
|
|
67
|
-
},
|
|
68
|
-
async run({ args }) {
|
|
69
|
-
applyOutputMode(args);
|
|
70
|
-
if (!VALID_KEYS.includes(args.key)) {
|
|
71
|
-
logger.error(`Invalid key: ${args.key}`);
|
|
72
|
-
logger.info(`Valid keys: ${VALID_KEYS.join(", ")}`);
|
|
73
|
-
process.exit(1);
|
|
74
|
-
}
|
|
75
|
-
const config = await loadConfig();
|
|
76
|
-
const value = NUMERIC_KEYS.has(args.key) ? Number(args.value) : args.value;
|
|
77
|
-
if (NUMERIC_KEYS.has(args.key) && !Number.isFinite(value)) {
|
|
78
|
-
logger.error(`Invalid numeric value for ${args.key}: ${args.value}`);
|
|
79
|
-
process.exit(1);
|
|
80
|
-
}
|
|
81
|
-
config[args.key] = value;
|
|
82
|
-
await saveConfig(config);
|
|
83
|
-
logger.success(`Set ${args.key} = ${value}`);
|
|
84
|
-
}
|
|
85
|
-
}),
|
|
86
|
-
unset: defineCommand({
|
|
87
|
-
meta: {
|
|
88
|
-
name: "unset",
|
|
89
|
-
description: "Remove a config value"
|
|
90
|
-
},
|
|
91
|
-
args: {
|
|
92
|
-
key: {
|
|
93
|
-
type: "positional",
|
|
94
|
-
description: `Config key to remove (${VALID_KEYS.join(", ")})`,
|
|
95
|
-
required: true
|
|
96
|
-
},
|
|
97
|
-
...OUTPUT_ARGS
|
|
98
|
-
},
|
|
99
|
-
async run({ args }) {
|
|
100
|
-
applyOutputMode(args);
|
|
101
|
-
if (!VALID_KEYS.includes(args.key)) {
|
|
102
|
-
logger.error(`Invalid key: ${args.key}`);
|
|
103
|
-
logger.info(`Valid keys: ${VALID_KEYS.join(", ")}`);
|
|
104
|
-
process.exit(1);
|
|
105
|
-
}
|
|
106
|
-
const config = await loadConfig();
|
|
107
|
-
delete config[args.key];
|
|
108
|
-
await saveConfig(config);
|
|
109
|
-
logger.success(`Removed ${args.key}`);
|
|
110
|
-
}
|
|
111
|
-
}),
|
|
112
|
-
path: defineCommand({
|
|
113
|
-
meta: {
|
|
114
|
-
name: "path",
|
|
115
|
-
description: "Show config file path"
|
|
116
|
-
},
|
|
117
|
-
run() {
|
|
118
|
-
console.log(getConfigPath());
|
|
119
|
-
}
|
|
120
|
-
}),
|
|
121
|
-
validate: defineCommand({
|
|
122
|
-
meta: {
|
|
123
|
-
name: "validate",
|
|
124
|
-
description: "Validate the saved config (defaultSite is verified, dataDir exists/writable)"
|
|
125
|
-
},
|
|
126
|
-
args: { ...OUTPUT_ARGS },
|
|
127
|
-
async run({ args }) {
|
|
128
|
-
const { json } = applyOutputMode(args);
|
|
129
|
-
const { resolveDataDir } = await import("../config.mjs");
|
|
130
|
-
const fs = await import("node:fs/promises");
|
|
131
|
-
const config = await loadConfig();
|
|
132
|
-
const issues = [];
|
|
133
|
-
const dataDir = resolveDataDir(config);
|
|
134
|
-
const dataDirDisplay = displayPath(dataDir);
|
|
135
|
-
const stat = await fs.stat(dataDir).catch(() => null);
|
|
136
|
-
if (stat && !stat.isDirectory()) issues.push({
|
|
137
|
-
key: "dataDir",
|
|
138
|
-
level: "fail",
|
|
139
|
-
message: `${dataDirDisplay} is not a directory`
|
|
140
|
-
});
|
|
141
|
-
else if (stat) {
|
|
142
|
-
const probe = `${dataDir}/.gscdump-config-probe`;
|
|
143
|
-
if (!await fs.writeFile(probe, "").then(() => fs.rm(probe)).then(() => true).catch(() => false)) issues.push({
|
|
144
|
-
key: "dataDir",
|
|
145
|
-
level: "fail",
|
|
146
|
-
message: `${dataDirDisplay} not writable`
|
|
147
|
-
});
|
|
148
|
-
} else issues.push({
|
|
149
|
-
key: "dataDir",
|
|
150
|
-
level: "warn",
|
|
151
|
-
message: `${dataDirDisplay} does not exist (will be created on first sync)`
|
|
152
|
-
});
|
|
153
|
-
if (config.defaultSite) if (!!config.clientId && !!config.clientSecret) {
|
|
154
|
-
const { createCommandContext } = await import("../context.mjs");
|
|
155
|
-
const ctx = await createCommandContext({ needsAuth: true }).catch(() => null);
|
|
156
|
-
if (ctx) {
|
|
157
|
-
const sites = await ctx.loadSites().catch(() => null);
|
|
158
|
-
if (sites && !sites.some((s) => s.siteUrl === config.defaultSite || s.siteUrl.includes(String(config.defaultSite)))) issues.push({
|
|
159
|
-
key: "defaultSite",
|
|
160
|
-
level: "fail",
|
|
161
|
-
message: `${config.defaultSite} is not in the verified site list`
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
} else issues.push({
|
|
165
|
-
key: "defaultSite",
|
|
166
|
-
level: "warn",
|
|
167
|
-
message: "set, but auth not configured — skipping verification"
|
|
168
|
-
});
|
|
169
|
-
if (config.defaultFormat && !["json", "csv"].includes(config.defaultFormat)) issues.push({
|
|
170
|
-
key: "defaultFormat",
|
|
171
|
-
level: "fail",
|
|
172
|
-
message: `unknown format: ${config.defaultFormat}`
|
|
173
|
-
});
|
|
174
|
-
const { SearchTypes } = await import("gscdump/query");
|
|
175
|
-
const allowedSearchTypes = Object.values(SearchTypes);
|
|
176
|
-
if (config.defaultSearchType && !allowedSearchTypes.includes(config.defaultSearchType)) issues.push({
|
|
177
|
-
key: "defaultSearchType",
|
|
178
|
-
level: "fail",
|
|
179
|
-
message: `unknown search type: ${config.defaultSearchType} (allowed: ${allowedSearchTypes.join(", ")})`
|
|
180
|
-
});
|
|
181
|
-
const allowedDataStates = [
|
|
182
|
-
"all",
|
|
183
|
-
"final",
|
|
184
|
-
"hourly_all"
|
|
185
|
-
];
|
|
186
|
-
if (config.defaultDataState && !allowedDataStates.includes(config.defaultDataState)) issues.push({
|
|
187
|
-
key: "defaultDataState",
|
|
188
|
-
level: "fail",
|
|
189
|
-
message: `unknown data state: ${config.defaultDataState} (allowed: ${allowedDataStates.join(", ")})`
|
|
190
|
-
});
|
|
191
|
-
if (config.serviceAccountPath) {
|
|
192
|
-
if (!await fs.stat(config.serviceAccountPath).catch(() => null)) issues.push({
|
|
193
|
-
key: "serviceAccountPath",
|
|
194
|
-
level: "fail",
|
|
195
|
-
message: `${displayPath(config.serviceAccountPath)} does not exist`
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
if (json) {
|
|
199
|
-
console.log(JSON.stringify({
|
|
200
|
-
ok: !issues.some((i) => i.level === "fail"),
|
|
201
|
-
issues
|
|
202
|
-
}, null, 2));
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
if (issues.length === 0) {
|
|
206
|
-
logger.success("Config OK");
|
|
207
|
-
return;
|
|
208
|
-
}
|
|
209
|
-
for (const i of issues) {
|
|
210
|
-
const prefix = i.level === "fail" ? "\x1B[31m✗\x1B[0m" : "\x1B[33m!\x1B[0m";
|
|
211
|
-
console.log(` ${prefix} ${i.key}: ${i.message}`);
|
|
212
|
-
}
|
|
213
|
-
if (issues.some((i) => i.level === "fail")) process.exit(1);
|
|
214
|
-
}
|
|
215
|
-
})
|
|
218
|
+
set: setCommand,
|
|
219
|
+
unset: unsetCommand,
|
|
220
|
+
path: pathCommand,
|
|
221
|
+
validate: validateCommand
|
|
216
222
|
},
|
|
217
223
|
async run({ args }) {
|
|
218
224
|
if (!noSubcommandSelected("config", [
|
package/dist/commands/doctor.mjs
CHANGED
|
@@ -17,7 +17,7 @@ const REQUIRED_SCOPES = [
|
|
|
17
17
|
"https://www.googleapis.com/auth/siteverification"
|
|
18
18
|
];
|
|
19
19
|
const FETCH_TIMEOUT_MS = 5e3;
|
|
20
|
-
const TIME_SKEW_WARN_MS =
|
|
20
|
+
const TIME_SKEW_WARN_MS = 3e5;
|
|
21
21
|
const WATERMARK_STALE_DAYS_WARN = 7;
|
|
22
22
|
const RELEVANT_ENV_KEYS = [
|
|
23
23
|
"GSC_ACCESS_TOKEN",
|
package/dist/commands/dump.mjs
CHANGED
|
@@ -176,7 +176,8 @@ async function dumpRowFormat(store, entries, outDir, siteUrl, format) {
|
|
|
176
176
|
let files = 0;
|
|
177
177
|
let totalRows = 0;
|
|
178
178
|
for (const [table, tableEntries] of byTable) {
|
|
179
|
-
const
|
|
179
|
+
const filePaths = tableEntries.map((e) => path.join(store.dataDir, e.objectKey));
|
|
180
|
+
const rows = await readParquetRows(filePaths, table);
|
|
180
181
|
const ext = format === "csv" ? "csv" : format === "ndjson" ? "ndjson" : "json";
|
|
181
182
|
const target = path.join(siteDir, `${table}.${ext}`);
|
|
182
183
|
let body;
|