@gscdump/cli 0.38.1 → 0.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_chunks/dump.mjs +43 -32
- package/dist/_chunks/store.mjs +35 -59
- package/dist/_chunks/sync.mjs +7 -9
- package/dist/_chunks/utils.mjs +1 -1
- package/package.json +5 -5
package/dist/_chunks/dump.mjs
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import { ALL_SEARCH_TYPES, OUTPUT_ARGS, applyOutputMode, displayPath, logger, parseSearchType, toCSV } from "./utils.mjs";
|
|
1
|
+
import { ALL_SEARCH_TYPES, OUTPUT_ARGS, applyOutputMode, displayPath, logger, parseSearchType, runWithConcurrency, toCSV } from "./utils.mjs";
|
|
2
2
|
import { allTables, createCommandContext } from "./context.mjs";
|
|
3
3
|
import { readParquetRows } from "./native-duckdb.mjs";
|
|
4
4
|
import process from "node:process";
|
|
5
5
|
import { defineCommand } from "citty";
|
|
6
6
|
import fs from "node:fs/promises";
|
|
7
7
|
import path from "node:path";
|
|
8
|
-
import { Buffer } from "node:buffer";
|
|
9
8
|
const DEFAULT_OUT = "./gscdump-export";
|
|
10
9
|
const FORMATS = [
|
|
11
10
|
"parquet",
|
|
@@ -72,32 +71,48 @@ const dumpCommand = defineCommand({
|
|
|
72
71
|
});
|
|
73
72
|
const store = ctx.store;
|
|
74
73
|
const outDir = path.resolve(String(args.out));
|
|
75
|
-
|
|
74
|
+
let preloadedEntries = args["all-sites"] ? await store.engine.listLive({
|
|
75
|
+
userId: store.userId,
|
|
76
|
+
...searchType !== void 0 ? { searchType } : {}
|
|
77
|
+
}) : void 0;
|
|
78
|
+
const targets = args["all-sites"] ? [...new Set(preloadedEntries.flatMap((entry) => entry.siteId ? [entry.siteId] : []))].map((siteId) => ({
|
|
79
|
+
site: siteId,
|
|
80
|
+
siteId
|
|
81
|
+
})) : await ctx.resolveSite(args.site ? String(args.site) : void 0).then((site) => [{
|
|
82
|
+
site,
|
|
83
|
+
siteId: store.siteIdFor(site)
|
|
84
|
+
}]);
|
|
76
85
|
if (targets.length === 0) {
|
|
77
86
|
logger.warn("No sites with local data. Run `gscdump sync` first.");
|
|
78
87
|
process.exit(0);
|
|
79
88
|
}
|
|
80
|
-
if (args.compact)
|
|
89
|
+
if (args.compact) {
|
|
90
|
+
for (const target of targets) await compactClosedMonths(store, target.siteId, quiet);
|
|
91
|
+
if (preloadedEntries) preloadedEntries = await store.engine.listLive({
|
|
92
|
+
userId: store.userId,
|
|
93
|
+
...searchType !== void 0 ? { searchType } : {}
|
|
94
|
+
});
|
|
95
|
+
}
|
|
81
96
|
const summary = [];
|
|
82
|
-
for (const
|
|
83
|
-
const entries = (await listLiveEntries(store,
|
|
97
|
+
for (const target of targets) {
|
|
98
|
+
const entries = (preloadedEntries ? preloadedEntries.filter((entry) => entry.siteId === target.siteId) : await listLiveEntries(store, target.siteId, searchType)).filter((e) => !tablesFilter || tablesFilter.has(e.table));
|
|
84
99
|
if (entries.length === 0) {
|
|
85
|
-
if (!quiet) logger.warn(`No data for ${
|
|
100
|
+
if (!quiet) logger.warn(`No data for ${target.site}; skipping`);
|
|
86
101
|
continue;
|
|
87
102
|
}
|
|
88
103
|
if (format === "parquet") {
|
|
89
104
|
const written = await dumpParquet(store, entries, outDir);
|
|
90
105
|
summary.push({
|
|
91
|
-
site:
|
|
106
|
+
site: target.site,
|
|
92
107
|
files: written,
|
|
93
108
|
rows: 0,
|
|
94
109
|
format,
|
|
95
110
|
outPath: outDir
|
|
96
111
|
});
|
|
97
112
|
} else {
|
|
98
|
-
const written = await dumpRowFormat(store, entries, outDir,
|
|
113
|
+
const written = await dumpRowFormat(store, entries, outDir, target.site, format);
|
|
99
114
|
summary.push({
|
|
100
|
-
site:
|
|
115
|
+
site: target.site,
|
|
101
116
|
files: written.files,
|
|
102
117
|
rows: written.rows,
|
|
103
118
|
format,
|
|
@@ -118,36 +133,33 @@ const dumpCommand = defineCommand({
|
|
|
118
133
|
}
|
|
119
134
|
}
|
|
120
135
|
});
|
|
121
|
-
async function
|
|
122
|
-
|
|
123
|
-
for (const table of allTables()) {
|
|
124
|
-
const entries = await store.engine.listLive({
|
|
125
|
-
userId: store.userId,
|
|
126
|
-
table
|
|
127
|
-
});
|
|
128
|
-
for (const e of entries) if (e.siteId) siteIds.add(e.siteId);
|
|
129
|
-
}
|
|
130
|
-
return Array.from(siteIds);
|
|
131
|
-
}
|
|
132
|
-
async function listLiveEntries(store, siteUrl, searchType) {
|
|
133
|
-
const siteId = store.siteIdFor(siteUrl);
|
|
134
|
-
return (await Promise.all(allTables().map((table) => store.engine.listLive({
|
|
136
|
+
async function listLiveEntries(store, siteId, searchType) {
|
|
137
|
+
return store.engine.listLive({
|
|
135
138
|
userId: store.userId,
|
|
136
139
|
siteId,
|
|
137
|
-
table,
|
|
138
140
|
...searchType !== void 0 ? { searchType } : {}
|
|
139
|
-
})
|
|
141
|
+
});
|
|
140
142
|
}
|
|
141
143
|
async function dumpParquet(store, entries, outDir) {
|
|
142
144
|
await fs.mkdir(outDir, { recursive: true });
|
|
145
|
+
const readyDirectories = /* @__PURE__ */ new Map();
|
|
146
|
+
async function ensureDirectory(dir) {
|
|
147
|
+
let ready = readyDirectories.get(dir);
|
|
148
|
+
if (!ready) {
|
|
149
|
+
ready = fs.mkdir(dir, { recursive: true }).then(() => void 0);
|
|
150
|
+
readyDirectories.set(dir, ready);
|
|
151
|
+
ready.catch(() => readyDirectories.delete(dir));
|
|
152
|
+
}
|
|
153
|
+
await ready;
|
|
154
|
+
}
|
|
143
155
|
let copied = 0;
|
|
144
|
-
|
|
156
|
+
await runWithConcurrency(entries, 8, async (entry) => {
|
|
145
157
|
const bytes = await store.engine.readObject(entry.objectKey);
|
|
146
158
|
const target = path.join(outDir, entry.objectKey);
|
|
147
|
-
await
|
|
148
|
-
await fs.writeFile(target,
|
|
159
|
+
await ensureDirectory(path.dirname(target));
|
|
160
|
+
await fs.writeFile(target, bytes);
|
|
149
161
|
copied++;
|
|
150
|
-
}
|
|
162
|
+
});
|
|
151
163
|
return copied;
|
|
152
164
|
}
|
|
153
165
|
async function dumpRowFormat(store, entries, outDir, siteUrl, format) {
|
|
@@ -179,8 +191,7 @@ async function dumpRowFormat(store, entries, outDir, siteUrl, format) {
|
|
|
179
191
|
rows: totalRows
|
|
180
192
|
};
|
|
181
193
|
}
|
|
182
|
-
async function compactClosedMonths(store,
|
|
183
|
-
const siteId = store.siteIdFor(siteUrl);
|
|
194
|
+
async function compactClosedMonths(store, siteId, quiet) {
|
|
184
195
|
for (const table of allTables()) {
|
|
185
196
|
if (!quiet) logger.info(`Compacting ${table} (raw→d7→d30→d90)`);
|
|
186
197
|
await store.engine.compactTiered({
|
package/dist/_chunks/store.mjs
CHANGED
|
@@ -49,14 +49,14 @@ const compactCommand = defineCommand({
|
|
|
49
49
|
if (args["raw-days"]) thresholds.raw = Number(args["raw-days"]);
|
|
50
50
|
if (args["d7-days"]) thresholds.d7 = Number(args["d7-days"]);
|
|
51
51
|
if (args["d30-days"]) thresholds.d30 = Number(args["d30-days"]);
|
|
52
|
+
const liveEntries = await store.engine.listLive({
|
|
53
|
+
userId: store.userId,
|
|
54
|
+
siteId
|
|
55
|
+
});
|
|
52
56
|
if (dryRun) {
|
|
53
57
|
const report = [];
|
|
54
58
|
for (const table of allTables()) {
|
|
55
|
-
const bySite = groupBySite(
|
|
56
|
-
userId: store.userId,
|
|
57
|
-
siteId,
|
|
58
|
-
table
|
|
59
|
-
}));
|
|
59
|
+
const bySite = groupBySite(liveEntries.filter((entry) => entry.table === table));
|
|
60
60
|
for (const [s, group] of bySite) report.push({
|
|
61
61
|
table,
|
|
62
62
|
siteId: s,
|
|
@@ -79,11 +79,7 @@ const compactCommand = defineCommand({
|
|
|
79
79
|
}
|
|
80
80
|
const summary = [];
|
|
81
81
|
for (const table of allTables()) {
|
|
82
|
-
const entries =
|
|
83
|
-
userId: store.userId,
|
|
84
|
-
siteId,
|
|
85
|
-
table
|
|
86
|
-
});
|
|
82
|
+
const entries = liveEntries.filter((entry) => entry.table === table);
|
|
87
83
|
const siteIds = new Set(entries.map((e) => e.siteId));
|
|
88
84
|
for (const targetSite of siteIds) {
|
|
89
85
|
logger.info(`Compacting ${table} [${targetSite ?? "-"}] (raw→d7→d30→d90)`);
|
|
@@ -138,16 +134,16 @@ function countByTier(entries) {
|
|
|
138
134
|
}
|
|
139
135
|
async function exportToDuckDB(opts) {
|
|
140
136
|
const outPath = path.resolve(opts.outPath);
|
|
137
|
+
const entries = await opts.engine.listLive({
|
|
138
|
+
userId: opts.userId,
|
|
139
|
+
siteId: opts.siteId
|
|
140
|
+
});
|
|
141
141
|
const inputs = [];
|
|
142
142
|
for (const table of allTables()) {
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
siteId: opts.siteId,
|
|
146
|
-
table
|
|
147
|
-
});
|
|
148
|
-
if (entries.length > 0) inputs.push({
|
|
143
|
+
const tableEntries = entries.filter((entry) => entry.table === table);
|
|
144
|
+
if (tableEntries.length > 0) inputs.push({
|
|
149
145
|
table,
|
|
150
|
-
filePaths:
|
|
146
|
+
filePaths: tableEntries.map((entry) => path.join(opts.dataDir, entry.objectKey))
|
|
151
147
|
});
|
|
152
148
|
}
|
|
153
149
|
const tables = await materializeParquetTables(outPath, inputs, opts.force);
|
|
@@ -237,20 +233,17 @@ const gcCommand = defineCommand({
|
|
|
237
233
|
if (args["dry-run"]) {
|
|
238
234
|
const cutoff = Date.now() - graceMs;
|
|
239
235
|
const candidates = [];
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
objectKey: e.objectKey
|
|
252
|
-
});
|
|
253
|
-
}
|
|
236
|
+
const all = await store.engine.listAll({
|
|
237
|
+
userId: store.userId,
|
|
238
|
+
siteId
|
|
239
|
+
});
|
|
240
|
+
for (const e of all) if (e.retiredAt && e.retiredAt < cutoff) candidates.push({
|
|
241
|
+
table: e.table,
|
|
242
|
+
siteId: e.siteId,
|
|
243
|
+
partition: e.partition,
|
|
244
|
+
retiredAt: e.retiredAt,
|
|
245
|
+
objectKey: e.objectKey
|
|
246
|
+
});
|
|
254
247
|
if (json) {
|
|
255
248
|
console.log(JSON.stringify({
|
|
256
249
|
graceHours: Number(args["grace-hours"]),
|
|
@@ -337,11 +330,8 @@ const rollupsCommand = defineCommand({
|
|
|
337
330
|
const explicitSiteId = args.site ? store.siteIdFor(String(args.site)) : void 0;
|
|
338
331
|
const allSiteIds = /* @__PURE__ */ new Set();
|
|
339
332
|
if (explicitSiteId) allSiteIds.add(explicitSiteId);
|
|
340
|
-
else
|
|
341
|
-
const entries = await store.engine.listLive({
|
|
342
|
-
userId: store.userId,
|
|
343
|
-
table
|
|
344
|
-
});
|
|
333
|
+
else {
|
|
334
|
+
const entries = await store.engine.listLive({ userId: store.userId });
|
|
345
335
|
for (const e of entries) if (e.siteId) allSiteIds.add(e.siteId);
|
|
346
336
|
}
|
|
347
337
|
if (allSiteIds.size === 0) {
|
|
@@ -423,9 +413,10 @@ const statsCommand = defineCommand({
|
|
|
423
413
|
async run({ args }) {
|
|
424
414
|
const { json } = applyOutputMode(args);
|
|
425
415
|
const store = (await createCommandContext({ needsStore: true })).store;
|
|
416
|
+
const allEntries = await store.engine.listAll({ userId: store.userId });
|
|
426
417
|
let siteId;
|
|
427
418
|
if (args.site) {
|
|
428
|
-
const known =
|
|
419
|
+
const known = new Set(allEntries.filter((entry) => entry.retiredAt === void 0 && entry.siteId !== void 0).map((entry) => entry.siteId));
|
|
429
420
|
const candidate = store.siteIdFor(args.site);
|
|
430
421
|
if (!known.has(candidate)) {
|
|
431
422
|
logger.error(`No local data for --site=${args.site}. Known site IDs: ${known.size === 0 ? "(none — run `gscdump sync` first)" : Array.from(known).join(", ")}`);
|
|
@@ -433,26 +424,22 @@ const statsCommand = defineCommand({
|
|
|
433
424
|
}
|
|
434
425
|
siteId = candidate;
|
|
435
426
|
}
|
|
436
|
-
const
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
siteId,
|
|
440
|
-
table
|
|
441
|
-
});
|
|
427
|
+
const selectedEntries = siteId === void 0 ? allEntries : allEntries.filter((entry) => entry.siteId === siteId);
|
|
428
|
+
const perTable = allTables().map((table) => {
|
|
429
|
+
const all = selectedEntries.filter((entry) => entry.table === table);
|
|
442
430
|
return {
|
|
443
431
|
table,
|
|
444
432
|
live: all.filter((e) => e.retiredAt === void 0),
|
|
445
433
|
retired: all.filter((e) => e.retiredAt !== void 0)
|
|
446
434
|
};
|
|
447
|
-
})
|
|
448
|
-
const watermarks = await store.engine.getWatermarks({
|
|
435
|
+
});
|
|
436
|
+
const [watermarks, disk] = await Promise.all([store.engine.getWatermarks({
|
|
449
437
|
userId: store.userId,
|
|
450
438
|
siteId
|
|
451
|
-
})
|
|
452
|
-
const disk = await filesystemStats(store.dataDir).catch(() => ({
|
|
439
|
+
}), filesystemStats(store.dataDir).catch(() => ({
|
|
453
440
|
files: 0,
|
|
454
441
|
bytes: 0
|
|
455
|
-
}));
|
|
442
|
+
}))]);
|
|
456
443
|
if (json) {
|
|
457
444
|
const payload = {
|
|
458
445
|
dataDir: store.dataDir,
|
|
@@ -504,17 +491,6 @@ const statsCommand = defineCommand({
|
|
|
504
491
|
console.log();
|
|
505
492
|
}
|
|
506
493
|
});
|
|
507
|
-
async function listKnownSiteIds(store) {
|
|
508
|
-
const ids = /* @__PURE__ */ new Set();
|
|
509
|
-
for (const table of allTables()) {
|
|
510
|
-
const entries = await store.engine.listLive({
|
|
511
|
-
userId: store.userId,
|
|
512
|
-
table
|
|
513
|
-
});
|
|
514
|
-
for (const e of entries) if (e.siteId) ids.add(e.siteId);
|
|
515
|
-
}
|
|
516
|
-
return ids;
|
|
517
|
-
}
|
|
518
494
|
function sortWatermarks(ws) {
|
|
519
495
|
return [...ws].sort((a, b) => {
|
|
520
496
|
if (a.table !== b.table) return a.table.localeCompare(b.table);
|
package/dist/_chunks/sync.mjs
CHANGED
|
@@ -269,15 +269,13 @@ const syncCommand = defineCommand({
|
|
|
269
269
|
const store = ctx.store;
|
|
270
270
|
if (args["retry-failed"]) {
|
|
271
271
|
const failedSet = /* @__PURE__ */ new Set();
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
for (const s of states) if (s.state === "failed" && s.date >= startDate && s.date <= endDate) failedSet.add(s.date);
|
|
280
|
-
}
|
|
272
|
+
const selectedTables = new Set(tables);
|
|
273
|
+
const selectedTypes = new Set(types);
|
|
274
|
+
const states = await store.engine.getSyncStates({
|
|
275
|
+
userId: store.userId,
|
|
276
|
+
siteId
|
|
277
|
+
});
|
|
278
|
+
for (const s of states) if (selectedTables.has(s.table) && selectedTypes.has(s.searchType ?? "web") && s.state === "failed" && s.date >= startDate && s.date <= endDate) failedSet.add(s.date);
|
|
281
279
|
dates = dates.filter((d) => failedSet.has(d));
|
|
282
280
|
if (dates.length === 0) {
|
|
283
281
|
logger.success("No failed dates in range — nothing to retry.");
|
package/dist/_chunks/utils.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import fs from "node:fs/promises";
|
|
|
4
4
|
import os from "node:os";
|
|
5
5
|
import { Buffer } from "node:buffer";
|
|
6
6
|
import { SearchTypes } from "gscdump/query";
|
|
7
|
-
var version = "0.
|
|
7
|
+
var version = "0.39.0";
|
|
8
8
|
const ALL_SEARCH_TYPES = Object.values(SearchTypes);
|
|
9
9
|
const VERSION = version;
|
|
10
10
|
function noSubcommandSelected(parent, subNames) {
|
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.39.0",
|
|
5
5
|
"description": "CLI for Google Search Console - dump, query, and run MCP server",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Harlan Wilton",
|
|
@@ -52,10 +52,10 @@
|
|
|
52
52
|
"ofetch": "^1.5.1",
|
|
53
53
|
"open": "^11.0.0",
|
|
54
54
|
"zod": "^4.4.3",
|
|
55
|
-
"@gscdump/analysis": "0.
|
|
56
|
-
"@gscdump/engine": "0.
|
|
57
|
-
"@gscdump/engine-gsc-api": "0.
|
|
58
|
-
"gscdump": "0.
|
|
55
|
+
"@gscdump/analysis": "0.39.0",
|
|
56
|
+
"@gscdump/engine": "0.39.0",
|
|
57
|
+
"@gscdump/engine-gsc-api": "0.39.0",
|
|
58
|
+
"gscdump": "0.39.0"
|
|
59
59
|
},
|
|
60
60
|
"devDependencies": {
|
|
61
61
|
"vitest": "^4.1.10"
|