@gscdump/cli 0.37.3 → 0.37.5
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/bin/gscdump.mjs +3 -0
- package/dist/_chunks/analysis-local.mjs +88 -0
- package/dist/_chunks/analyze.mjs +332 -0
- package/dist/_chunks/auth.mjs +476 -0
- package/dist/_chunks/auth2.mjs +293 -0
- package/dist/_chunks/config.mjs +93 -0
- package/dist/_chunks/config2.mjs +232 -0
- package/dist/_chunks/context.mjs +69 -0
- package/dist/_chunks/doctor.mjs +400 -0
- package/dist/_chunks/dump.mjs +193 -0
- package/dist/_chunks/entities.mjs +414 -0
- package/dist/_chunks/env-file.mjs +53 -0
- package/dist/_chunks/environment.mjs +30 -0
- package/dist/_chunks/error-handler.mjs +86 -0
- package/dist/_chunks/indexing.mjs +314 -0
- package/dist/_chunks/init.mjs +190 -0
- package/dist/_chunks/inspect.mjs +203 -0
- package/dist/_chunks/mcp.mjs +867 -0
- package/dist/_chunks/native-duckdb.mjs +46 -0
- package/dist/_chunks/profile.mjs +290 -0
- package/dist/_chunks/query.mjs +537 -0
- package/dist/_chunks/report.mjs +243 -0
- package/dist/_chunks/sitemaps.mjs +274 -0
- package/dist/_chunks/sites.mjs +500 -0
- package/dist/_chunks/store.mjs +652 -0
- package/dist/_chunks/sync.mjs +489 -0
- package/dist/_chunks/utils.mjs +191 -0
- package/dist/cli.d.mts +28 -0
- package/dist/cli.mjs +104 -0
- package/package.json +15 -7
- package/dist/index.d.mts +0 -1
- package/dist/index.mjs +0 -7330
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { logger } from "./utils.mjs";
|
|
2
|
+
import { resolveAnalysisSource } from "./analysis-local.mjs";
|
|
3
|
+
import { defineCommand } from "citty";
|
|
4
|
+
import { defaultAnalyzerRegistry } from "@gscdump/analysis/registry";
|
|
5
|
+
import { err, ok, unwrapResult } from "gscdump/result";
|
|
6
|
+
import { defaultReportRegistry, dryRunReport, formatReport, runReport } from "@gscdump/analysis/report";
|
|
7
|
+
import { resolveWindow } from "@gscdump/engine/period";
|
|
8
|
+
function reportFlagErrorToException(error) {
|
|
9
|
+
const exception = new Error(error.message);
|
|
10
|
+
exception.reportFlagError = error;
|
|
11
|
+
return exception;
|
|
12
|
+
}
|
|
13
|
+
const REPORT_IDS = defaultReportRegistry.listReportIds();
|
|
14
|
+
const PERIOD_ALIASES = {
|
|
15
|
+
"7d": "last-7d",
|
|
16
|
+
"28d": "last-28d",
|
|
17
|
+
"30d": "last-30d",
|
|
18
|
+
"90d": "last-90d",
|
|
19
|
+
"180d": "last-180d",
|
|
20
|
+
"365d": "last-365d",
|
|
21
|
+
"last-7d": "last-7d",
|
|
22
|
+
"last-28d": "last-28d",
|
|
23
|
+
"last-30d": "last-30d",
|
|
24
|
+
"last-90d": "last-90d",
|
|
25
|
+
"last-180d": "last-180d",
|
|
26
|
+
"last-365d": "last-365d",
|
|
27
|
+
"mtd": "mtd",
|
|
28
|
+
"ytd": "ytd",
|
|
29
|
+
"custom": "custom"
|
|
30
|
+
};
|
|
31
|
+
const COMPARISON_ALIASES = {
|
|
32
|
+
"none": "none",
|
|
33
|
+
"prev": "prev-period",
|
|
34
|
+
"prev-period": "prev-period",
|
|
35
|
+
"prior": "prev-period",
|
|
36
|
+
"prior-period": "prev-period",
|
|
37
|
+
"yoy": "yoy"
|
|
38
|
+
};
|
|
39
|
+
function resolvePeriodResult(input, fallback) {
|
|
40
|
+
if (!input) return ok(fallback);
|
|
41
|
+
const preset = PERIOD_ALIASES[input.toLowerCase()];
|
|
42
|
+
if (!preset) return err({
|
|
43
|
+
kind: "unknown-period",
|
|
44
|
+
value: input,
|
|
45
|
+
message: `Unknown --period "${input}". Supported: 7d, 28d, 30d, 90d, 180d, 365d, mtd, ytd, custom.`
|
|
46
|
+
});
|
|
47
|
+
return ok(preset);
|
|
48
|
+
}
|
|
49
|
+
function resolvePeriod(input, fallback) {
|
|
50
|
+
return unwrapResult(resolvePeriodResult(input, fallback), reportFlagErrorToException);
|
|
51
|
+
}
|
|
52
|
+
function resolveComparisonResult(input, fallback) {
|
|
53
|
+
if (!input) return ok(fallback);
|
|
54
|
+
const mode = COMPARISON_ALIASES[input.toLowerCase()];
|
|
55
|
+
if (!mode) return err({
|
|
56
|
+
kind: "unknown-comparison",
|
|
57
|
+
value: input,
|
|
58
|
+
message: `Unknown --vs "${input}". Supported: none, prev-period, yoy.`
|
|
59
|
+
});
|
|
60
|
+
return ok(mode);
|
|
61
|
+
}
|
|
62
|
+
function resolveComparison(input, fallback) {
|
|
63
|
+
return unwrapResult(resolveComparisonResult(input, fallback), reportFlagErrorToException);
|
|
64
|
+
}
|
|
65
|
+
function reportArgsToCitty(spec) {
|
|
66
|
+
const out = {};
|
|
67
|
+
for (const [key, def] of Object.entries(spec)) out[key] = {
|
|
68
|
+
type: def.type === "boolean" ? "boolean" : "string",
|
|
69
|
+
description: def.description,
|
|
70
|
+
default: def.default == null ? void 0 : String(def.default),
|
|
71
|
+
alias: def.alias,
|
|
72
|
+
required: def.required
|
|
73
|
+
};
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
function buildReportParams(report, args) {
|
|
77
|
+
const params = {};
|
|
78
|
+
for (const [key, def] of Object.entries(report.argsSpec)) {
|
|
79
|
+
const raw = args[key];
|
|
80
|
+
if (raw == null || raw === "") continue;
|
|
81
|
+
if (def.type === "number") {
|
|
82
|
+
const n = Number(raw);
|
|
83
|
+
if (Number.isFinite(n)) params[toCamel(key)] = n;
|
|
84
|
+
} else if (def.type === "boolean") params[toCamel(key)] = !!raw;
|
|
85
|
+
else params[toCamel(key)] = raw;
|
|
86
|
+
}
|
|
87
|
+
return params;
|
|
88
|
+
}
|
|
89
|
+
function toCamel(kebab) {
|
|
90
|
+
return kebab.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
91
|
+
}
|
|
92
|
+
function makeReportCommand(report) {
|
|
93
|
+
const reportArgs = reportArgsToCitty(report.argsSpec);
|
|
94
|
+
return defineCommand({
|
|
95
|
+
meta: {
|
|
96
|
+
name: report.id,
|
|
97
|
+
description: report.description
|
|
98
|
+
},
|
|
99
|
+
args: {
|
|
100
|
+
"site": {
|
|
101
|
+
type: "string",
|
|
102
|
+
alias: "s",
|
|
103
|
+
description: "Site URL"
|
|
104
|
+
},
|
|
105
|
+
"period": {
|
|
106
|
+
type: "string",
|
|
107
|
+
description: "Window: 7d|28d|90d|mtd|ytd|custom",
|
|
108
|
+
default: presetToFlag(report.defaultPeriod)
|
|
109
|
+
},
|
|
110
|
+
"vs": {
|
|
111
|
+
type: "string",
|
|
112
|
+
description: "Comparison: none|prev-period|yoy",
|
|
113
|
+
default: report.defaultComparison
|
|
114
|
+
},
|
|
115
|
+
"start": {
|
|
116
|
+
type: "string",
|
|
117
|
+
description: "Custom start date (YYYY-MM-DD)"
|
|
118
|
+
},
|
|
119
|
+
"end": {
|
|
120
|
+
type: "string",
|
|
121
|
+
description: "Custom end date (YYYY-MM-DD)"
|
|
122
|
+
},
|
|
123
|
+
"prev-start": {
|
|
124
|
+
type: "string",
|
|
125
|
+
description: "Override comparison start"
|
|
126
|
+
},
|
|
127
|
+
"prev-end": {
|
|
128
|
+
type: "string",
|
|
129
|
+
description: "Override comparison end"
|
|
130
|
+
},
|
|
131
|
+
"live": {
|
|
132
|
+
type: "boolean",
|
|
133
|
+
default: false,
|
|
134
|
+
description: "Force live GSC API; bypass local store"
|
|
135
|
+
},
|
|
136
|
+
"json": {
|
|
137
|
+
type: "boolean",
|
|
138
|
+
default: false,
|
|
139
|
+
description: "Emit full ReportResult JSON"
|
|
140
|
+
},
|
|
141
|
+
"explain": {
|
|
142
|
+
type: "boolean",
|
|
143
|
+
default: false,
|
|
144
|
+
description: "Print plan steps + window without executing"
|
|
145
|
+
},
|
|
146
|
+
"dry-run": {
|
|
147
|
+
type: "boolean",
|
|
148
|
+
default: false,
|
|
149
|
+
description: "Alias for --explain"
|
|
150
|
+
},
|
|
151
|
+
...reportArgs
|
|
152
|
+
},
|
|
153
|
+
async run({ args }) {
|
|
154
|
+
const preset = resolvePeriod(args.period, report.defaultPeriod);
|
|
155
|
+
const comparison = resolveComparison(args.vs, report.defaultComparison);
|
|
156
|
+
const window = resolveWindow({
|
|
157
|
+
preset,
|
|
158
|
+
comparison,
|
|
159
|
+
start: args.start,
|
|
160
|
+
end: args.end
|
|
161
|
+
});
|
|
162
|
+
if (args["prev-start"] && args["prev-end"]) window.comparison = {
|
|
163
|
+
start: String(args["prev-start"]),
|
|
164
|
+
end: String(args["prev-end"])
|
|
165
|
+
};
|
|
166
|
+
const params = buildReportParams(report, args);
|
|
167
|
+
if (args.explain || args["dry-run"]) {
|
|
168
|
+
const dry = await dryRunReport(report, {
|
|
169
|
+
site: args.site ? String(args.site) : "(unresolved)",
|
|
170
|
+
window,
|
|
171
|
+
params,
|
|
172
|
+
registryVersion: defaultReportRegistry.version
|
|
173
|
+
});
|
|
174
|
+
console.log(JSON.stringify({
|
|
175
|
+
id: report.id,
|
|
176
|
+
window,
|
|
177
|
+
comparison,
|
|
178
|
+
plan: dry.steps
|
|
179
|
+
}, null, 2));
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
const { source, siteUrl } = await resolveAnalysisSource({
|
|
183
|
+
site: args.site,
|
|
184
|
+
live: !!args.live,
|
|
185
|
+
json: !!args.json
|
|
186
|
+
});
|
|
187
|
+
const result = await runReport(report, {
|
|
188
|
+
source,
|
|
189
|
+
analyzers: defaultAnalyzerRegistry,
|
|
190
|
+
ctx: {
|
|
191
|
+
site: siteUrl,
|
|
192
|
+
window,
|
|
193
|
+
params,
|
|
194
|
+
registryVersion: defaultReportRegistry.version
|
|
195
|
+
}
|
|
196
|
+
});
|
|
197
|
+
if (args.json) {
|
|
198
|
+
console.log(JSON.stringify(result, null, 2));
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
console.log(formatReport(result));
|
|
202
|
+
if (result.meta.degraded) logger.warn(`degraded: ${result.meta.steps.filter((s) => s.status === "error").map((s) => `${s.key}(${s.error})`).join(", ")}`);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
function presetToFlag(preset) {
|
|
207
|
+
if (preset === "mtd" || preset === "ytd" || preset === "custom") return preset;
|
|
208
|
+
return preset.replace(/^last-/, "");
|
|
209
|
+
}
|
|
210
|
+
const reportCommand = defineCommand({
|
|
211
|
+
meta: {
|
|
212
|
+
name: "report",
|
|
213
|
+
description: "Run an intent-keyed report (composes analyzers into bounded sections)"
|
|
214
|
+
},
|
|
215
|
+
subCommands: {
|
|
216
|
+
list: defineCommand({
|
|
217
|
+
meta: {
|
|
218
|
+
name: "list",
|
|
219
|
+
description: "List available report ids"
|
|
220
|
+
},
|
|
221
|
+
args: { json: {
|
|
222
|
+
type: "boolean",
|
|
223
|
+
default: false,
|
|
224
|
+
description: "Output as JSON"
|
|
225
|
+
} },
|
|
226
|
+
async run({ args }) {
|
|
227
|
+
const reports = defaultReportRegistry.listReports().map((r) => ({
|
|
228
|
+
id: r.id,
|
|
229
|
+
description: r.description,
|
|
230
|
+
defaultPeriod: r.defaultPeriod,
|
|
231
|
+
defaultComparison: r.defaultComparison
|
|
232
|
+
}));
|
|
233
|
+
if (args.json) {
|
|
234
|
+
console.log(JSON.stringify(reports, null, 2));
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
for (const r of reports) console.log(`${r.id.padEnd(16)} ${r.description}`);
|
|
238
|
+
}
|
|
239
|
+
}),
|
|
240
|
+
...Object.fromEntries(REPORT_IDS.map((id) => [id, makeReportCommand(defaultReportRegistry.getReport(id))]))
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
export { reportCommand };
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { OUTPUT_ARGS, applyOutputMode, logger, noSubcommandSelected } from "./utils.mjs";
|
|
2
|
+
import { createCommandContext } from "./context.mjs";
|
|
3
|
+
import { gscErrorHandler } from "./error-handler.mjs";
|
|
4
|
+
import process from "node:process";
|
|
5
|
+
import { defineCommand } from "citty";
|
|
6
|
+
import { discoverSitemap, fetchSitemap, fetchSitemapUrls } from "gscdump/api";
|
|
7
|
+
const listCommand = defineCommand({
|
|
8
|
+
meta: {
|
|
9
|
+
name: "list",
|
|
10
|
+
description: "List sitemaps for a site"
|
|
11
|
+
},
|
|
12
|
+
args: {
|
|
13
|
+
...OUTPUT_ARGS,
|
|
14
|
+
site: {
|
|
15
|
+
type: "string",
|
|
16
|
+
alias: "s",
|
|
17
|
+
description: "Site URL (e.g., sc-domain:example.com or https://example.com/)"
|
|
18
|
+
},
|
|
19
|
+
pending: {
|
|
20
|
+
type: "boolean",
|
|
21
|
+
default: false,
|
|
22
|
+
description: "Show only sitemaps with isPending=true"
|
|
23
|
+
},
|
|
24
|
+
errored: {
|
|
25
|
+
type: "boolean",
|
|
26
|
+
default: false,
|
|
27
|
+
description: "Show only sitemaps with errors > 0"
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
async run({ args }) {
|
|
31
|
+
const { json } = applyOutputMode(args);
|
|
32
|
+
const ctx = await createCommandContext({ needsAuth: true });
|
|
33
|
+
const siteUrl = await ctx.resolveSite(args.site ? String(args.site) : void 0);
|
|
34
|
+
let sitemaps = (await ctx.client.sitemaps.list(siteUrl).catch(gscErrorHandler)).map((sm) => ({
|
|
35
|
+
path: sm.path,
|
|
36
|
+
type: sm.type || void 0,
|
|
37
|
+
isPending: sm.isPending || false,
|
|
38
|
+
errors: Number(sm.errors) || 0,
|
|
39
|
+
warnings: Number(sm.warnings) || 0,
|
|
40
|
+
lastDownloaded: sm.lastDownloaded || null,
|
|
41
|
+
lastSubmitted: sm.lastSubmitted || null
|
|
42
|
+
}));
|
|
43
|
+
if (args.pending) sitemaps = sitemaps.filter((sm) => sm.isPending);
|
|
44
|
+
if (args.errored) sitemaps = sitemaps.filter((sm) => sm.errors > 0);
|
|
45
|
+
if (json) {
|
|
46
|
+
console.log(JSON.stringify(sitemaps, null, 2));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (sitemaps.length === 0) {
|
|
50
|
+
logger.warn("No sitemaps found");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
logger.success(`Found ${sitemaps.length} sitemaps:`);
|
|
54
|
+
console.log();
|
|
55
|
+
for (const sm of sitemaps) {
|
|
56
|
+
const pending = sm.isPending ? " \x1B[33m(pending)\x1B[0m" : "";
|
|
57
|
+
const errors = sm.errors ? ` \x1B[31m${sm.errors} errors\x1B[0m` : "";
|
|
58
|
+
const warnings = sm.warnings ? ` \x1B[33m${sm.warnings} warnings\x1B[0m` : "";
|
|
59
|
+
const submitted = sm.lastSubmitted ? ` \x1B[90msubmitted ${sm.lastSubmitted}\x1B[0m` : "";
|
|
60
|
+
console.log(` ${sm.path}${pending}${errors}${warnings}${submitted}`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
const sitemapsCommand = defineCommand({
|
|
65
|
+
meta: {
|
|
66
|
+
name: "sitemaps",
|
|
67
|
+
description: "Manage sitemaps"
|
|
68
|
+
},
|
|
69
|
+
subCommands: {
|
|
70
|
+
list: listCommand,
|
|
71
|
+
get: defineCommand({
|
|
72
|
+
meta: {
|
|
73
|
+
name: "get",
|
|
74
|
+
description: "Get details for a specific sitemap"
|
|
75
|
+
},
|
|
76
|
+
args: {
|
|
77
|
+
...OUTPUT_ARGS,
|
|
78
|
+
site: {
|
|
79
|
+
type: "string",
|
|
80
|
+
alias: "s",
|
|
81
|
+
description: "Site URL (defaults to config.defaultSite or prompt)"
|
|
82
|
+
},
|
|
83
|
+
url: {
|
|
84
|
+
type: "positional",
|
|
85
|
+
required: true,
|
|
86
|
+
description: "Sitemap URL"
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
async run({ args }) {
|
|
90
|
+
const { json } = applyOutputMode(args);
|
|
91
|
+
const ctx = await createCommandContext({ needsAuth: true });
|
|
92
|
+
const siteUrl = await ctx.resolveSite(args.site ? String(args.site) : void 0);
|
|
93
|
+
const client = ctx.client;
|
|
94
|
+
const sitemap = await fetchSitemap(client, siteUrl, args.url).catch(gscErrorHandler);
|
|
95
|
+
if (json) {
|
|
96
|
+
console.log(JSON.stringify(sitemap, null, 2));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
console.log();
|
|
100
|
+
console.log(` \x1B[1mPath:\x1B[0m ${sitemap.path}`);
|
|
101
|
+
console.log(` \x1B[1mType:\x1B[0m ${sitemap.type || "sitemap"}`);
|
|
102
|
+
console.log(` \x1B[1mLast Submitted:\x1B[0m ${sitemap.lastSubmitted || "N/A"}`);
|
|
103
|
+
console.log(` \x1B[1mLast Downloaded:\x1B[0m ${sitemap.lastDownloaded || "N/A"}`);
|
|
104
|
+
console.log(` \x1B[1mPending:\x1B[0m ${sitemap.isPending ? "Yes" : "No"}`);
|
|
105
|
+
console.log(` \x1B[1mErrors:\x1B[0m ${sitemap.errors || 0}`);
|
|
106
|
+
console.log(` \x1B[1mWarnings:\x1B[0m ${sitemap.warnings || 0}`);
|
|
107
|
+
if (sitemap.contents?.length) {
|
|
108
|
+
console.log();
|
|
109
|
+
console.log(" \x1B[1mContents:\x1B[0m");
|
|
110
|
+
for (const c of sitemap.contents) console.log(` ${c.type}: ${c.submitted} submitted, ${c.indexed} indexed`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}),
|
|
114
|
+
submit: defineCommand({
|
|
115
|
+
meta: {
|
|
116
|
+
name: "submit",
|
|
117
|
+
description: "Submit a sitemap to GSC"
|
|
118
|
+
},
|
|
119
|
+
args: {
|
|
120
|
+
...OUTPUT_ARGS,
|
|
121
|
+
site: {
|
|
122
|
+
type: "string",
|
|
123
|
+
alias: "s",
|
|
124
|
+
description: "Site URL (defaults to config.defaultSite or prompt)"
|
|
125
|
+
},
|
|
126
|
+
url: {
|
|
127
|
+
type: "positional",
|
|
128
|
+
required: true,
|
|
129
|
+
description: "Sitemap URL to submit"
|
|
130
|
+
}
|
|
131
|
+
},
|
|
132
|
+
async run({ args }) {
|
|
133
|
+
const { json } = applyOutputMode(args);
|
|
134
|
+
const ctx = await createCommandContext({ needsAuth: true });
|
|
135
|
+
const siteUrl = await ctx.resolveSite(args.site ? String(args.site) : void 0);
|
|
136
|
+
await ctx.client.sitemaps.submit(siteUrl, args.url).catch(gscErrorHandler);
|
|
137
|
+
if (json) {
|
|
138
|
+
console.log(JSON.stringify({
|
|
139
|
+
siteUrl,
|
|
140
|
+
feedpath: args.url,
|
|
141
|
+
status: "submitted"
|
|
142
|
+
}, null, 2));
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
logger.success(`Submitted sitemap: ${args.url}`);
|
|
146
|
+
}
|
|
147
|
+
}),
|
|
148
|
+
delete: defineCommand({
|
|
149
|
+
meta: {
|
|
150
|
+
name: "delete",
|
|
151
|
+
description: "Delete a sitemap from GSC"
|
|
152
|
+
},
|
|
153
|
+
args: {
|
|
154
|
+
...OUTPUT_ARGS,
|
|
155
|
+
site: {
|
|
156
|
+
type: "string",
|
|
157
|
+
alias: "s",
|
|
158
|
+
description: "Site URL (defaults to config.defaultSite or prompt)"
|
|
159
|
+
},
|
|
160
|
+
url: {
|
|
161
|
+
type: "positional",
|
|
162
|
+
required: true,
|
|
163
|
+
description: "Sitemap URL to delete"
|
|
164
|
+
}
|
|
165
|
+
},
|
|
166
|
+
async run({ args }) {
|
|
167
|
+
const { json } = applyOutputMode(args);
|
|
168
|
+
const ctx = await createCommandContext({ needsAuth: true });
|
|
169
|
+
const siteUrl = await ctx.resolveSite(args.site ? String(args.site) : void 0);
|
|
170
|
+
await ctx.client.sitemaps.delete(siteUrl, args.url).catch(gscErrorHandler);
|
|
171
|
+
if (json) {
|
|
172
|
+
console.log(JSON.stringify({
|
|
173
|
+
siteUrl,
|
|
174
|
+
feedpath: args.url,
|
|
175
|
+
status: "deleted"
|
|
176
|
+
}, null, 2));
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
logger.success(`Deleted sitemap: ${args.url}`);
|
|
180
|
+
}
|
|
181
|
+
}),
|
|
182
|
+
discover: defineCommand({
|
|
183
|
+
meta: {
|
|
184
|
+
name: "discover",
|
|
185
|
+
description: "Probe a domain's robots.txt + common paths for an advertised sitemap (no auth needed)"
|
|
186
|
+
},
|
|
187
|
+
args: {
|
|
188
|
+
...OUTPUT_ARGS,
|
|
189
|
+
domain: {
|
|
190
|
+
type: "positional",
|
|
191
|
+
required: true,
|
|
192
|
+
description: "Domain (e.g., example.com)"
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
async run({ args }) {
|
|
196
|
+
const { json } = applyOutputMode(args);
|
|
197
|
+
const domain = String(args.domain).replace(/^https?:\/\//, "").replace(/\/.*$/, "");
|
|
198
|
+
const url = await discoverSitemap(domain).catch(() => null);
|
|
199
|
+
if (json) {
|
|
200
|
+
console.log(JSON.stringify({
|
|
201
|
+
domain,
|
|
202
|
+
sitemap: url
|
|
203
|
+
}, null, 2));
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (!url) {
|
|
207
|
+
logger.warn(`No sitemap discovered for ${domain}`);
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
logger.success(`Discovered sitemap: ${url}`);
|
|
211
|
+
}
|
|
212
|
+
}),
|
|
213
|
+
urls: defineCommand({
|
|
214
|
+
meta: {
|
|
215
|
+
name: "urls",
|
|
216
|
+
description: "Fetch a sitemap (or sitemap index) and dump its <loc> URLs (no auth needed)"
|
|
217
|
+
},
|
|
218
|
+
args: {
|
|
219
|
+
...OUTPUT_ARGS,
|
|
220
|
+
"url": {
|
|
221
|
+
type: "positional",
|
|
222
|
+
required: true,
|
|
223
|
+
description: "Sitemap URL (index files are followed)"
|
|
224
|
+
},
|
|
225
|
+
"limit": {
|
|
226
|
+
type: "string",
|
|
227
|
+
alias: "l",
|
|
228
|
+
description: "Stop after N URLs across all nested sitemaps"
|
|
229
|
+
},
|
|
230
|
+
"max-depth": {
|
|
231
|
+
type: "string",
|
|
232
|
+
description: "Max sitemap-index nesting depth (default: 3)"
|
|
233
|
+
}
|
|
234
|
+
},
|
|
235
|
+
async run({ args }) {
|
|
236
|
+
const { json } = applyOutputMode(args);
|
|
237
|
+
const limit = args.limit ? Number.parseInt(String(args.limit), 10) : void 0;
|
|
238
|
+
const maxDepth = args["max-depth"] ? Number.parseInt(String(args["max-depth"]), 10) : void 0;
|
|
239
|
+
const urls = await fetchSitemapUrls(String(args.url), {
|
|
240
|
+
limit,
|
|
241
|
+
maxDepth
|
|
242
|
+
}).catch((e) => {
|
|
243
|
+
logger.error(`Sitemap fetch failed: ${e.message}`);
|
|
244
|
+
process.exit(1);
|
|
245
|
+
});
|
|
246
|
+
if (json) {
|
|
247
|
+
console.log(JSON.stringify({
|
|
248
|
+
sitemap: args.url,
|
|
249
|
+
count: urls.length,
|
|
250
|
+
urls
|
|
251
|
+
}, null, 2));
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
for (const u of urls) console.log(u);
|
|
255
|
+
}
|
|
256
|
+
})
|
|
257
|
+
},
|
|
258
|
+
async run({ args }) {
|
|
259
|
+
if (!noSubcommandSelected("sitemaps", [
|
|
260
|
+
"list",
|
|
261
|
+
"get",
|
|
262
|
+
"submit",
|
|
263
|
+
"delete",
|
|
264
|
+
"discover",
|
|
265
|
+
"urls"
|
|
266
|
+
])) return;
|
|
267
|
+
await listCommand.run?.({
|
|
268
|
+
args,
|
|
269
|
+
cmd: listCommand,
|
|
270
|
+
rawArgs: []
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
export { sitemapsCommand };
|