@siteoshq/cli 2.5.0 → 2.6.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 +4 -4
- package/dist/cli.js +547 -252
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1506,7 +1506,7 @@ var OverviewSchema = z4.object({
|
|
|
1506
1506
|
});
|
|
1507
1507
|
function createProjectApi(input) {
|
|
1508
1508
|
const origin = resolveSiteOSAuthBaseUrl(input.env);
|
|
1509
|
-
async function request(
|
|
1509
|
+
async function request(path30, schema, body, method) {
|
|
1510
1510
|
const scope = body === void 0 ? "projects:workspace:read" : "projects:workspace:write";
|
|
1511
1511
|
const grant = await input.grants.acquire({
|
|
1512
1512
|
audience: "siteos-projects",
|
|
@@ -1520,7 +1520,7 @@ function createProjectApi(input) {
|
|
|
1520
1520
|
message: "SiteOS API access is unavailable."
|
|
1521
1521
|
});
|
|
1522
1522
|
const response = await input.fetchImpl(
|
|
1523
|
-
`${origin}/api/projects/v1/projects${
|
|
1523
|
+
`${origin}/api/projects/v1/projects${path30}`,
|
|
1524
1524
|
{
|
|
1525
1525
|
method: method ?? (body === void 0 ? "GET" : "POST"),
|
|
1526
1526
|
headers: {
|
|
@@ -8418,11 +8418,301 @@ async function runServiceCommand(service, options) {
|
|
|
8418
8418
|
}
|
|
8419
8419
|
}
|
|
8420
8420
|
|
|
8421
|
-
// src/services/analytics-
|
|
8421
|
+
// src/services/analytics-google-csv.ts
|
|
8422
|
+
import { z as z14 } from "zod";
|
|
8423
|
+
var table = z14.object({
|
|
8424
|
+
dimensions: z14.array(z14.string()),
|
|
8425
|
+
metrics: z14.array(z14.object({ name: z14.string() })),
|
|
8426
|
+
rows: z14.array(
|
|
8427
|
+
z14.object({ dimensions: z14.array(z14.string()), metrics: z14.array(z14.string()) })
|
|
8428
|
+
)
|
|
8429
|
+
});
|
|
8430
|
+
function googleReportCsv(value, fetchedAt) {
|
|
8431
|
+
const report = z14.object({
|
|
8432
|
+
request: z14.object({
|
|
8433
|
+
kind: z14.string(),
|
|
8434
|
+
propertyId: z14.string(),
|
|
8435
|
+
streamId: z14.string(),
|
|
8436
|
+
startDate: z14.string(),
|
|
8437
|
+
endDate: z14.string(),
|
|
8438
|
+
filters: z14.array(z14.unknown()),
|
|
8439
|
+
minuteRange: z14.unknown().optional(),
|
|
8440
|
+
comparison: z14.object({ startDate: z14.string(), endDate: z14.string() }).optional()
|
|
8441
|
+
}).passthrough(),
|
|
8442
|
+
timeZone: z14.string(),
|
|
8443
|
+
limitations: z14.array(z14.string()),
|
|
8444
|
+
quality: z14.unknown(),
|
|
8445
|
+
table,
|
|
8446
|
+
previous: table.optional()
|
|
8447
|
+
}).parse(value);
|
|
8448
|
+
const escape = (v) => `"${(/^[\s]*[=+\-@\t\r]/u.test(v) ? "'" : "") + v.replaceAll('"', '""')}"`;
|
|
8449
|
+
const rows = [
|
|
8450
|
+
["Report", report.request.kind],
|
|
8451
|
+
["Definition", JSON.stringify(report.request)],
|
|
8452
|
+
["Property", report.request.propertyId],
|
|
8453
|
+
["Stream", report.request.streamId],
|
|
8454
|
+
["From", report.request.startDate],
|
|
8455
|
+
["To", report.request.endDate],
|
|
8456
|
+
["Timezone", report.timeZone],
|
|
8457
|
+
["Fetched at", fetchedAt ?? ""],
|
|
8458
|
+
["Filters", JSON.stringify(report.request.filters)],
|
|
8459
|
+
["Minute boundaries", JSON.stringify(report.request.minuteRange ?? null)],
|
|
8460
|
+
["Limitations", report.limitations.join(", ")],
|
|
8461
|
+
["Quality", JSON.stringify(report.quality)],
|
|
8462
|
+
[]
|
|
8463
|
+
];
|
|
8464
|
+
const add = (t, label) => {
|
|
8465
|
+
rows.push(
|
|
8466
|
+
[label],
|
|
8467
|
+
[...t.dimensions, ...t.metrics.map((m) => m.name)],
|
|
8468
|
+
...t.rows.map((r) => [...r.dimensions, ...r.metrics])
|
|
8469
|
+
);
|
|
8470
|
+
};
|
|
8471
|
+
add(report.table, "Selected period");
|
|
8472
|
+
if (report.previous) {
|
|
8473
|
+
rows.push(
|
|
8474
|
+
["Comparison from", report.request.comparison?.startDate ?? ""],
|
|
8475
|
+
["Comparison to", report.request.comparison?.endDate ?? ""]
|
|
8476
|
+
);
|
|
8477
|
+
add(report.previous, "Comparison period");
|
|
8478
|
+
}
|
|
8479
|
+
return rows.map((r) => r.map(escape).join(",")).join("\r\n");
|
|
8480
|
+
}
|
|
8481
|
+
|
|
8482
|
+
// src/services/analytics-google-command.ts
|
|
8422
8483
|
import { readFile as readFile16, stat as stat2 } from "fs/promises";
|
|
8423
8484
|
import path25 from "path";
|
|
8424
8485
|
import { parseArgs as parseArgs4 } from "util";
|
|
8425
|
-
import { z as
|
|
8486
|
+
import { z as z15 } from "zod";
|
|
8487
|
+
var GOOGLE_ANALYTICS_HELP = `Read GA4 reports in the selected Project environment.
|
|
8488
|
+
|
|
8489
|
+
Usage:
|
|
8490
|
+
siteos analytics ga4 report [--period <today|yesterday|24h|7d|28d|month|last-month|custom>] [--from <date>] [--to <date>] [--compare <previous|year|none>] [--filters <json>] [--section <totals|trend|events|channels|sources|pages|countries|devices|quality>] [--offset <number>] [--limit <1..100>] [--refresh] [--environment <slug>] [--json]
|
|
8491
|
+
siteos analytics ga4 realtime [--filters <json>] [--environment <slug>] [--json]
|
|
8492
|
+
siteos analytics ga4 explore --file <selection.json> [--refresh] [--environment <slug>] [--json]
|
|
8493
|
+
siteos analytics ga4 export --file <selection.json> [--environment <slug>]
|
|
8494
|
+
siteos analytics ga4 saved list [--environment <slug>] [--json]
|
|
8495
|
+
siteos analytics ga4 saved save --file <saved-report.json> [--environment <slug>] [--json]
|
|
8496
|
+
siteos analytics ga4 saved remove <id> --revision <number> [--environment <slug>] [--json]
|
|
8497
|
+
|
|
8498
|
+
A missing or stale report is queued in the shared cache; rerun to read completion.
|
|
8499
|
+
Exploration selections support campaigns, registered event dimensions, funnels and cohorts.
|
|
8500
|
+
Export writes CSV to stdout from a completed cached snapshot. It does not wait for Google.
|
|
8501
|
+
Saved report writes require an owner/admin and analytics:reports:write.
|
|
8502
|
+
No command connects Google, changes tracking or enables notifications.`;
|
|
8503
|
+
async function runAnalyticsGoogleCommand(options) {
|
|
8504
|
+
const json = options.args.includes("--json");
|
|
8505
|
+
if (options.args.length <= 1 || options.args.includes("--help"))
|
|
8506
|
+
return { exitCode: 0, stdout: GOOGLE_ANALYTICS_HELP };
|
|
8507
|
+
try {
|
|
8508
|
+
const { values, positionals } = parseArgs4({
|
|
8509
|
+
args: options.args.slice(1),
|
|
8510
|
+
allowPositionals: true,
|
|
8511
|
+
strict: true,
|
|
8512
|
+
options: {
|
|
8513
|
+
json: { type: "boolean" },
|
|
8514
|
+
environment: { type: "string" },
|
|
8515
|
+
period: { type: "string" },
|
|
8516
|
+
from: { type: "string" },
|
|
8517
|
+
to: { type: "string" },
|
|
8518
|
+
compare: { type: "string" },
|
|
8519
|
+
filters: { type: "string" },
|
|
8520
|
+
section: { type: "string" },
|
|
8521
|
+
offset: { type: "string" },
|
|
8522
|
+
limit: { type: "string" },
|
|
8523
|
+
file: { type: "string" },
|
|
8524
|
+
revision: { type: "string" },
|
|
8525
|
+
refresh: { type: "boolean" }
|
|
8526
|
+
}
|
|
8527
|
+
});
|
|
8528
|
+
const action = positionals[0];
|
|
8529
|
+
const sub = positionals[1];
|
|
8530
|
+
const operations = {
|
|
8531
|
+
report: {
|
|
8532
|
+
args: 1,
|
|
8533
|
+
flags: [
|
|
8534
|
+
"period",
|
|
8535
|
+
"from",
|
|
8536
|
+
"to",
|
|
8537
|
+
"compare",
|
|
8538
|
+
"filters",
|
|
8539
|
+
"refresh",
|
|
8540
|
+
"section",
|
|
8541
|
+
"offset",
|
|
8542
|
+
"limit"
|
|
8543
|
+
]
|
|
8544
|
+
},
|
|
8545
|
+
realtime: { args: 1, flags: ["filters"] },
|
|
8546
|
+
explore: { args: 1, flags: ["file", "refresh"] },
|
|
8547
|
+
export: { args: 1, flags: ["file"] },
|
|
8548
|
+
"saved list": { args: 2, flags: [] },
|
|
8549
|
+
"saved save": { args: 2, flags: ["file"], write: true },
|
|
8550
|
+
"saved remove": { args: 3, flags: ["revision"], write: true }
|
|
8551
|
+
};
|
|
8552
|
+
const operation = operations[action === "saved" ? `${action} ${sub}` : action ?? ""];
|
|
8553
|
+
if (!operation || positionals.length !== operation.args || Object.keys(values).some(
|
|
8554
|
+
(k) => !["json", "environment", ...operation.flags].includes(k)
|
|
8555
|
+
))
|
|
8556
|
+
throw new Error(
|
|
8557
|
+
"Invalid GA4 operation or flags. Run siteos analytics ga4 --help."
|
|
8558
|
+
);
|
|
8559
|
+
const context = await commonServiceContext(
|
|
8560
|
+
options,
|
|
8561
|
+
"analytics",
|
|
8562
|
+
values.environment
|
|
8563
|
+
);
|
|
8564
|
+
if (!context) throw new Error("Select a SiteOS Project first.");
|
|
8565
|
+
const runtime = commonProjectRuntime(options);
|
|
8566
|
+
const scope = operation.write ? "analytics:reports:write" : "analytics:workspace:read";
|
|
8567
|
+
const grant = await runtime.grants.acquire({
|
|
8568
|
+
audience: "siteos-analytics",
|
|
8569
|
+
scopes: [scope]
|
|
8570
|
+
});
|
|
8571
|
+
if (grant.grant.audience !== "siteos-analytics" || grant.grant.organizationId !== context.overview.project.organizationId || grant.grant.scopes.length !== 1 || grant.grant.scopes[0] !== scope)
|
|
8572
|
+
throw new Error(
|
|
8573
|
+
"The Analytics grant does not match this Project and operation."
|
|
8574
|
+
);
|
|
8575
|
+
if (!options.fetchImpl)
|
|
8576
|
+
throw new Error("SiteOS API access is unavailable.");
|
|
8577
|
+
const send = async (suffix, method = "GET", body) => {
|
|
8578
|
+
const response = await options.fetchImpl(
|
|
8579
|
+
`${runtime.api.origin}/api/analytics/v1/resources/${encodeURIComponent(context.resourceId)}/ga4${suffix}`,
|
|
8580
|
+
{
|
|
8581
|
+
method,
|
|
8582
|
+
headers: {
|
|
8583
|
+
Accept: "application/json",
|
|
8584
|
+
Authorization: `Bearer ${grant.accessToken}`,
|
|
8585
|
+
...body ? { "Content-Type": "application/json" } : {}
|
|
8586
|
+
},
|
|
8587
|
+
...body ? { body: JSON.stringify(body) } : {},
|
|
8588
|
+
signal: AbortSignal.timeout(3e4)
|
|
8589
|
+
}
|
|
8590
|
+
);
|
|
8591
|
+
const value = await response.json();
|
|
8592
|
+
if (!response.ok) {
|
|
8593
|
+
const error = z15.object({
|
|
8594
|
+
error: z15.object({ code: z15.string(), message: z15.string().max(500) })
|
|
8595
|
+
}).safeParse(value);
|
|
8596
|
+
throw new SiteOSAuthApiError({
|
|
8597
|
+
code: error.success ? error.data.error.code : "ANALYTICS_REQUEST_FAILED",
|
|
8598
|
+
message: error.success ? error.data.error.message : "The GA4 request failed.",
|
|
8599
|
+
status: response.status
|
|
8600
|
+
});
|
|
8601
|
+
}
|
|
8602
|
+
return z15.object({
|
|
8603
|
+
contractVersion: z15.literal(1),
|
|
8604
|
+
resourceId: z15.literal(context.resourceId)
|
|
8605
|
+
}).passthrough().parse(value);
|
|
8606
|
+
};
|
|
8607
|
+
const file = async () => {
|
|
8608
|
+
if (!values.file)
|
|
8609
|
+
throw new Error("Provide --file with a report definition.");
|
|
8610
|
+
const name = path25.resolve(options.cwd ?? process.cwd(), values.file);
|
|
8611
|
+
if ((await stat2(name)).size > 16384)
|
|
8612
|
+
throw new Error("Definition exceeds 16 KiB.");
|
|
8613
|
+
const text = await readFile16(name, "utf8");
|
|
8614
|
+
if (Buffer.byteLength(text) > 16384)
|
|
8615
|
+
throw new Error("Definition exceeds 16 KiB.");
|
|
8616
|
+
return z15.record(z15.unknown()).parse(JSON.parse(text));
|
|
8617
|
+
};
|
|
8618
|
+
let result;
|
|
8619
|
+
if (action === "saved") {
|
|
8620
|
+
result = sub === "list" ? await send("/saved") : sub === "save" ? await send("/saved", "POST", await file()) : await send("/saved", "DELETE", {
|
|
8621
|
+
id: z15.string().uuid().parse(positionals[2]),
|
|
8622
|
+
revision: z15.number().int().positive().parse(Number(values.revision))
|
|
8623
|
+
});
|
|
8624
|
+
} else {
|
|
8625
|
+
const base = await send("");
|
|
8626
|
+
const binding = z15.object({ revision: z15.number().int().positive(), state: z15.string() }).parse(base.binding);
|
|
8627
|
+
if (binding.state === "disconnected")
|
|
8628
|
+
throw new Error("Connect GA4 in Analytics Setup first.");
|
|
8629
|
+
if (action === "report") {
|
|
8630
|
+
const params = new URLSearchParams({ period: values.period ?? "7d" });
|
|
8631
|
+
for (const k of [
|
|
8632
|
+
"from",
|
|
8633
|
+
"to",
|
|
8634
|
+
"compare",
|
|
8635
|
+
"filters",
|
|
8636
|
+
"section",
|
|
8637
|
+
"offset",
|
|
8638
|
+
"limit"
|
|
8639
|
+
])
|
|
8640
|
+
if (values[k] && values[k] !== "none") params.set(k, values[k]);
|
|
8641
|
+
if (!values.compare) params.set("compare", "previous");
|
|
8642
|
+
result = await send(`?${params}`);
|
|
8643
|
+
const cached = result.query;
|
|
8644
|
+
if (result.range && ((values.section ? !result.report || result.stale : !cached || cached.state === "ready" && cached.stale) || values.refresh)) {
|
|
8645
|
+
await send("/report", "POST", {
|
|
8646
|
+
period: 7,
|
|
8647
|
+
revision: binding.revision,
|
|
8648
|
+
filters: values.filters ? JSON.parse(values.filters) : [],
|
|
8649
|
+
range: Object.fromEntries(
|
|
8650
|
+
[...params].filter(
|
|
8651
|
+
([k]) => ["period", "from", "to", "compare"].includes(k)
|
|
8652
|
+
)
|
|
8653
|
+
),
|
|
8654
|
+
refresh: values.refresh ?? false
|
|
8655
|
+
});
|
|
8656
|
+
result = await send(`?${params}`);
|
|
8657
|
+
}
|
|
8658
|
+
} else if (action === "realtime") {
|
|
8659
|
+
const filters2 = values.filters ? JSON.parse(values.filters) : [];
|
|
8660
|
+
const params = new URLSearchParams({
|
|
8661
|
+
filters: JSON.stringify(filters2)
|
|
8662
|
+
});
|
|
8663
|
+
result = await send(`/realtime?${params}`);
|
|
8664
|
+
const live = result.realtime;
|
|
8665
|
+
if (!live?.report || live.stale) {
|
|
8666
|
+
await send("/realtime", "POST", {
|
|
8667
|
+
revision: binding.revision,
|
|
8668
|
+
filters: filters2
|
|
8669
|
+
});
|
|
8670
|
+
result = await send(`/realtime?${params}`);
|
|
8671
|
+
}
|
|
8672
|
+
} else {
|
|
8673
|
+
const selection = await file();
|
|
8674
|
+
const suffix = `/explore?${new URLSearchParams({ selection: JSON.stringify(selection) })}`;
|
|
8675
|
+
result = await send(suffix);
|
|
8676
|
+
const query = result.query;
|
|
8677
|
+
if (action === "export") {
|
|
8678
|
+
if (!query?.report)
|
|
8679
|
+
throw new Error(
|
|
8680
|
+
"No cached report. Run ga4 explore first, then export after it completes."
|
|
8681
|
+
);
|
|
8682
|
+
const csv = googleReportCsv(query.report, query.fetchedAt ?? null);
|
|
8683
|
+
return {
|
|
8684
|
+
exitCode: 0,
|
|
8685
|
+
stdout: csv
|
|
8686
|
+
};
|
|
8687
|
+
}
|
|
8688
|
+
if (!query || query.state === "ready" && query.stale || values.refresh) {
|
|
8689
|
+
await send("/explore", "POST", {
|
|
8690
|
+
selection,
|
|
8691
|
+
revision: binding.revision,
|
|
8692
|
+
refresh: values.refresh ?? false
|
|
8693
|
+
});
|
|
8694
|
+
result = await send(suffix);
|
|
8695
|
+
}
|
|
8696
|
+
}
|
|
8697
|
+
}
|
|
8698
|
+
return { exitCode: 0, stdout: JSON.stringify(result, null, 2) };
|
|
8699
|
+
} catch (cause) {
|
|
8700
|
+
const error = {
|
|
8701
|
+
code: cause instanceof SiteOSAuthApiError ? cause.code : "ANALYTICS_COMMAND_FAILED",
|
|
8702
|
+
message: cause instanceof z15.ZodError ? "The input or server response does not match the GA4 contract." : cause instanceof Error ? cause.message : "GA4 request failed."
|
|
8703
|
+
};
|
|
8704
|
+
return {
|
|
8705
|
+
exitCode: cause instanceof SiteOSAuthApiError ? 1 : 2,
|
|
8706
|
+
...json ? { stdout: JSON.stringify({ error }) } : { stderr: error.message }
|
|
8707
|
+
};
|
|
8708
|
+
}
|
|
8709
|
+
}
|
|
8710
|
+
|
|
8711
|
+
// src/services/analytics-command.ts
|
|
8712
|
+
import { readFile as readFile17, stat as stat3 } from "fs/promises";
|
|
8713
|
+
import path26 from "path";
|
|
8714
|
+
import { parseArgs as parseArgs5 } from "util";
|
|
8715
|
+
import { z as z16 } from "zod";
|
|
8426
8716
|
var ANALYTICS_HELP = `Set up website Analytics in the selected Project environment.
|
|
8427
8717
|
|
|
8428
8718
|
Usage:
|
|
@@ -8444,6 +8734,7 @@ Usage:
|
|
|
8444
8734
|
siteos analytics funnels create --file <funnel.json> [--environment <slug>] [--json]
|
|
8445
8735
|
siteos analytics funnels archive <id> [--environment <slug>] [--json]
|
|
8446
8736
|
siteos analytics monitoring prepare [--environment <slug>] [--json]
|
|
8737
|
+
${GOOGLE_ANALYTICS_HELP.split("Usage:\n")[1].split("\n\n")[0]}
|
|
8447
8738
|
|
|
8448
8739
|
Connect first: siteos project connect analytics. Cookie and Trace are optional.
|
|
8449
8740
|
Use project environment use <slug> to select an environment; no resource fallback is allowed.
|
|
@@ -8453,27 +8744,27 @@ Settings require an owner/admin, the current revision and at least one change.
|
|
|
8453
8744
|
Catalog files are bounded JSON using the documented event/campaign/goal/funnel contracts.
|
|
8454
8745
|
Monitoring preparation creates a Trace draft only; publication remains a separate operation.
|
|
8455
8746
|
Analytics is Unlimited during early access. No synthetic events are sent by these commands.`;
|
|
8456
|
-
var EventInput =
|
|
8457
|
-
name:
|
|
8458
|
-
label:
|
|
8459
|
-
properties:
|
|
8460
|
-
|
|
8461
|
-
|
|
8462
|
-
|
|
8747
|
+
var EventInput = z16.object({
|
|
8748
|
+
name: z16.string().regex(/^[a-z][a-z0-9_]{0,63}$/u).refine((v) => v !== "pageview" && !v.startsWith("cookie_")),
|
|
8749
|
+
label: z16.string().trim().min(1).max(80),
|
|
8750
|
+
properties: z16.record(
|
|
8751
|
+
z16.string().regex(/^[a-z][a-z0-9_]{0,39}$/u),
|
|
8752
|
+
z16.array(
|
|
8753
|
+
z16.string().min(1).max(80).regex(/^[a-zA-Z0-9 _./:-]+$/u)
|
|
8463
8754
|
).min(1).max(20)
|
|
8464
8755
|
).refine((v) => Object.keys(v).length <= 8)
|
|
8465
8756
|
}).strict();
|
|
8466
|
-
var Resource =
|
|
8467
|
-
id:
|
|
8468
|
-
organizationId:
|
|
8469
|
-
name:
|
|
8470
|
-
slug:
|
|
8471
|
-
origin:
|
|
8472
|
-
publicKey:
|
|
8473
|
-
revision:
|
|
8474
|
-
enabled:
|
|
8475
|
-
cookieEvents:
|
|
8476
|
-
minimalRealtime:
|
|
8757
|
+
var Resource = z16.object({
|
|
8758
|
+
id: z16.string(),
|
|
8759
|
+
organizationId: z16.string(),
|
|
8760
|
+
name: z16.string(),
|
|
8761
|
+
slug: z16.string(),
|
|
8762
|
+
origin: z16.string().url(),
|
|
8763
|
+
publicKey: z16.string(),
|
|
8764
|
+
revision: z16.number().int().positive(),
|
|
8765
|
+
enabled: z16.boolean(),
|
|
8766
|
+
cookieEvents: z16.boolean(),
|
|
8767
|
+
minimalRealtime: z16.boolean()
|
|
8477
8768
|
});
|
|
8478
8769
|
var readKeys = [
|
|
8479
8770
|
"days",
|
|
@@ -8518,10 +8809,14 @@ var pick = (value, keys) => Object.fromEntries(
|
|
|
8518
8809
|
);
|
|
8519
8810
|
async function runAnalyticsCommand(options) {
|
|
8520
8811
|
if (!options.args.length || options.args.some((v) => ["--help", "-h"].includes(v)))
|
|
8521
|
-
return {
|
|
8812
|
+
return {
|
|
8813
|
+
exitCode: 0,
|
|
8814
|
+
stdout: ANALYTICS_HELP + "\n\n" + GOOGLE_ANALYTICS_HELP
|
|
8815
|
+
};
|
|
8816
|
+
if (options.args[0] === "ga4") return runAnalyticsGoogleCommand(options);
|
|
8522
8817
|
const json = options.args.includes("--json");
|
|
8523
8818
|
try {
|
|
8524
|
-
const { positionals, values } =
|
|
8819
|
+
const { positionals, values } = parseArgs5({
|
|
8525
8820
|
args: options.args,
|
|
8526
8821
|
strict: true,
|
|
8527
8822
|
allowPositionals: true,
|
|
@@ -8603,7 +8898,7 @@ async function runAnalyticsCommand(options) {
|
|
|
8603
8898
|
throw new Error("Use --days 1, 7 or 28.");
|
|
8604
8899
|
if (values.country && !/^(?:[A-Z]{2}|unknown)$/u.test(values.country))
|
|
8605
8900
|
throw new Error("Use an uppercase ISO country code or unknown.");
|
|
8606
|
-
if (values.campaign)
|
|
8901
|
+
if (values.campaign) z16.string().uuid().parse(values.campaign);
|
|
8607
8902
|
const setting = route === "settings set", creating = positionals[1] === "create", archive = positionals[1] === "archive";
|
|
8608
8903
|
let body;
|
|
8609
8904
|
if (setting) {
|
|
@@ -8627,10 +8922,10 @@ async function runAnalyticsCommand(options) {
|
|
|
8627
8922
|
} else if (creating) {
|
|
8628
8923
|
if (!values.file)
|
|
8629
8924
|
throw new Error("Creation requires --file with a JSON definition.");
|
|
8630
|
-
const filename =
|
|
8631
|
-
if ((await
|
|
8925
|
+
const filename = path26.resolve(options.cwd ?? process.cwd(), values.file);
|
|
8926
|
+
if ((await stat3(filename)).size > 16384)
|
|
8632
8927
|
throw new Error("The definition must not exceed 16 KiB.");
|
|
8633
|
-
const text = await
|
|
8928
|
+
const text = await readFile17(filename, "utf8");
|
|
8634
8929
|
if (Buffer.byteLength(text) > 16384)
|
|
8635
8930
|
throw new Error("The definition must not exceed 16 KiB.");
|
|
8636
8931
|
let definition;
|
|
@@ -8639,9 +8934,9 @@ async function runAnalyticsCommand(options) {
|
|
|
8639
8934
|
} catch {
|
|
8640
8935
|
throw new Error("The definition must contain valid JSON.");
|
|
8641
8936
|
}
|
|
8642
|
-
body =
|
|
8937
|
+
body = z16.record(z16.unknown()).parse(definition);
|
|
8643
8938
|
if (action === "events") body = EventInput.parse(body);
|
|
8644
|
-
} else if (archive) body = { id:
|
|
8939
|
+
} else if (archive) body = { id: z16.string().uuid().parse(positionals[2]) };
|
|
8645
8940
|
const context = await commonServiceContext(
|
|
8646
8941
|
options,
|
|
8647
8942
|
"analytics",
|
|
@@ -8688,8 +8983,8 @@ async function runAnalyticsCommand(options) {
|
|
|
8688
8983
|
);
|
|
8689
8984
|
const value = await response.json();
|
|
8690
8985
|
if (!response.ok) {
|
|
8691
|
-
const error =
|
|
8692
|
-
error:
|
|
8986
|
+
const error = z16.object({
|
|
8987
|
+
error: z16.object({ code: z16.string(), message: z16.string().max(500) })
|
|
8693
8988
|
}).safeParse(value);
|
|
8694
8989
|
throw new SiteOSAuthApiError({
|
|
8695
8990
|
code: error.success ? error.data.error.code : "ANALYTICS_REQUEST_FAILED",
|
|
@@ -8697,9 +8992,9 @@ async function runAnalyticsCommand(options) {
|
|
|
8697
8992
|
status: response.status
|
|
8698
8993
|
});
|
|
8699
8994
|
}
|
|
8700
|
-
const record =
|
|
8701
|
-
contractVersion:
|
|
8702
|
-
resourceId:
|
|
8995
|
+
const record = z16.object({
|
|
8996
|
+
contractVersion: z16.literal(1),
|
|
8997
|
+
resourceId: z16.literal(context.resourceId)
|
|
8703
8998
|
}).passthrough().parse(value);
|
|
8704
8999
|
let output;
|
|
8705
9000
|
if (setting) {
|
|
@@ -8718,16 +9013,16 @@ async function runAnalyticsCommand(options) {
|
|
|
8718
9013
|
throw new Error("The saved event does not match this definition.");
|
|
8719
9014
|
} else
|
|
8720
9015
|
output = {
|
|
8721
|
-
id:
|
|
9016
|
+
id: z16.string().uuid().parse(record.id),
|
|
8722
9017
|
...pick(
|
|
8723
9018
|
record,
|
|
8724
9019
|
action === "campaigns" ? ["label", "source", "medium", "campaign"] : action === "goals" ? ["label", "match"] : ["label", "steps"]
|
|
8725
9020
|
)
|
|
8726
9021
|
};
|
|
8727
9022
|
} else if (archive)
|
|
8728
|
-
output = { archived:
|
|
9023
|
+
output = { archived: z16.literal(true).parse(record.archived) };
|
|
8729
9024
|
else if (route === "monitoring prepare")
|
|
8730
|
-
output = { state:
|
|
9025
|
+
output = { state: z16.literal("prepared").parse(record.state) };
|
|
8731
9026
|
else if (action === "realtime") output = pick(record, liveKeys);
|
|
8732
9027
|
else {
|
|
8733
9028
|
const resource = Resource.parse(record.resource);
|
|
@@ -8739,8 +9034,8 @@ async function runAnalyticsCommand(options) {
|
|
|
8739
9034
|
output = {
|
|
8740
9035
|
resource,
|
|
8741
9036
|
lastReceivedAt: record.lastReceivedAt,
|
|
8742
|
-
consentSource:
|
|
8743
|
-
consentSource:
|
|
9037
|
+
consentSource: z16.object({
|
|
9038
|
+
consentSource: z16.enum([
|
|
8744
9039
|
"cookie",
|
|
8745
9040
|
"external",
|
|
8746
9041
|
"waiting_for_cookie",
|
|
@@ -8752,11 +9047,11 @@ async function runAnalyticsCommand(options) {
|
|
|
8752
9047
|
else if (action === "installation")
|
|
8753
9048
|
output = {
|
|
8754
9049
|
resource,
|
|
8755
|
-
installation:
|
|
9050
|
+
installation: z16.string().parse(record.installation),
|
|
8756
9051
|
configuration: record.configuration
|
|
8757
9052
|
};
|
|
8758
9053
|
else if (action === "events") {
|
|
8759
|
-
const definitions =
|
|
9054
|
+
const definitions = z16.array(EventInput).parse(record.definitions);
|
|
8760
9055
|
if (route === "events snippet") {
|
|
8761
9056
|
const definition = definitions.find((d) => d.name === positionals[2]);
|
|
8762
9057
|
if (!definition)
|
|
@@ -8770,13 +9065,13 @@ async function runAnalyticsCommand(options) {
|
|
|
8770
9065
|
};
|
|
8771
9066
|
} else output = { definitions, eventCounts: record.eventCounts };
|
|
8772
9067
|
} else
|
|
8773
|
-
output = action === "report" ? pick(record, readKeys) : { [action]:
|
|
9068
|
+
output = action === "report" ? pick(record, readKeys) : { [action]: z16.array(z16.unknown()).parse(record[action]) };
|
|
8774
9069
|
}
|
|
8775
9070
|
return { exitCode: 0, stdout: JSON.stringify(output, null, 2) };
|
|
8776
9071
|
} catch (cause) {
|
|
8777
9072
|
const error = cause instanceof SiteOSAuthApiError ? { code: cause.code, message: cause.message } : {
|
|
8778
9073
|
code: "ANALYTICS_COMMAND_FAILED",
|
|
8779
|
-
message: cause instanceof
|
|
9074
|
+
message: cause instanceof z16.ZodError ? "The Analytics input or response does not match the supported contract." : cause instanceof Error ? cause.message : "The Analytics command failed."
|
|
8780
9075
|
};
|
|
8781
9076
|
return {
|
|
8782
9077
|
exitCode: cause instanceof SiteOSAuthApiError ? 1 : 2,
|
|
@@ -8786,13 +9081,13 @@ async function runAnalyticsCommand(options) {
|
|
|
8786
9081
|
}
|
|
8787
9082
|
|
|
8788
9083
|
// src/services/seo-repair-command.ts
|
|
8789
|
-
import { parseArgs as
|
|
8790
|
-
import { z as
|
|
9084
|
+
import { parseArgs as parseArgs6 } from "util";
|
|
9085
|
+
import { z as z18 } from "zod";
|
|
8791
9086
|
|
|
8792
9087
|
// src/services/seo-report-client.ts
|
|
8793
9088
|
import { open, writeFile as writeFile8 } from "fs/promises";
|
|
8794
|
-
import
|
|
8795
|
-
import { z as
|
|
9089
|
+
import path27 from "path";
|
|
9090
|
+
import { z as z17 } from "zod";
|
|
8796
9091
|
async function seoReportClient(options, environment) {
|
|
8797
9092
|
const context = await commonServiceContext(options, "seo", environment);
|
|
8798
9093
|
if (!context)
|
|
@@ -8827,8 +9122,8 @@ async function seoReportClient(options, environment) {
|
|
|
8827
9122
|
}
|
|
8828
9123
|
);
|
|
8829
9124
|
if (!response.ok) {
|
|
8830
|
-
const result =
|
|
8831
|
-
error:
|
|
9125
|
+
const result = z17.object({
|
|
9126
|
+
error: z17.object({ code: z17.string(), message: z17.string().max(500) })
|
|
8832
9127
|
}).safeParse(await response.json());
|
|
8833
9128
|
throw new SiteOSAuthApiError({
|
|
8834
9129
|
code: result.success ? result.data.error.code : "SEO_REQUEST_FAILED",
|
|
@@ -8842,12 +9137,12 @@ async function seoReportClient(options, environment) {
|
|
|
8842
9137
|
}
|
|
8843
9138
|
};
|
|
8844
9139
|
}
|
|
8845
|
-
var versionedReport =
|
|
9140
|
+
var versionedReport = z17.object({ contractVersion: z17.literal(1) }).passthrough();
|
|
8846
9141
|
async function readSeoInput(cwd, filename) {
|
|
8847
|
-
const file = await open(
|
|
9142
|
+
const file = await open(path27.resolve(cwd ?? process.cwd(), filename), "r");
|
|
8848
9143
|
try {
|
|
8849
|
-
const
|
|
8850
|
-
if (!
|
|
9144
|
+
const stat4 = await file.stat();
|
|
9145
|
+
if (!stat4.isFile() || stat4.size > 32e3)
|
|
8851
9146
|
throw new Error("Use a JSON request file of at most 32,000 bytes.");
|
|
8852
9147
|
const buffer = Buffer.alloc(32001);
|
|
8853
9148
|
let bytes = 0;
|
|
@@ -8868,14 +9163,14 @@ async function readSeoInput(cwd, filename) {
|
|
|
8868
9163
|
}
|
|
8869
9164
|
}
|
|
8870
9165
|
async function writeSeoReport(options, filename, content) {
|
|
8871
|
-
const output =
|
|
9166
|
+
const output = path27.resolve(options.cwd ?? process.cwd(), filename);
|
|
8872
9167
|
await writeFile8(output, content, { flag: "wx", mode: 384 });
|
|
8873
9168
|
return output;
|
|
8874
9169
|
}
|
|
8875
9170
|
function seoReportFailure(cause, json, idempotencyKey) {
|
|
8876
9171
|
const error = {
|
|
8877
9172
|
code: cause instanceof SiteOSAuthApiError ? cause.code : "SEO_COMMAND_FAILED",
|
|
8878
|
-
message: cause instanceof
|
|
9173
|
+
message: cause instanceof z17.ZodError ? "The SEO service returned an invalid response." : cause instanceof Error ? cause.message : "The SEO command failed."
|
|
8879
9174
|
};
|
|
8880
9175
|
return {
|
|
8881
9176
|
exitCode: cause instanceof SiteOSAuthApiError ? 1 : 2,
|
|
@@ -8894,7 +9189,7 @@ function seoReportFailure(cause, json, idempotencyKey) {
|
|
|
8894
9189
|
async function runSeoRepairCommand(options) {
|
|
8895
9190
|
const json = options.args.includes("--json");
|
|
8896
9191
|
try {
|
|
8897
|
-
const { positionals, values } =
|
|
9192
|
+
const { positionals, values } = parseArgs6({
|
|
8898
9193
|
args: options.args,
|
|
8899
9194
|
allowPositionals: true,
|
|
8900
9195
|
strict: true,
|
|
@@ -8939,32 +9234,32 @@ async function runSeoRepairCommand(options) {
|
|
|
8939
9234
|
});
|
|
8940
9235
|
const record = versionedReport.parse(await response.json());
|
|
8941
9236
|
if (recheck) {
|
|
8942
|
-
|
|
8943
|
-
id:
|
|
8944
|
-
resourceId:
|
|
8945
|
-
organizationId:
|
|
8946
|
-
state:
|
|
9237
|
+
z18.object({
|
|
9238
|
+
id: z18.string(),
|
|
9239
|
+
resourceId: z18.literal(client.resourceId),
|
|
9240
|
+
organizationId: z18.literal(client.organizationId),
|
|
9241
|
+
state: z18.literal("queued")
|
|
8947
9242
|
}).parse(record.audit);
|
|
8948
9243
|
return { exitCode: 0, stdout: JSON.stringify(record, null, 2) };
|
|
8949
9244
|
}
|
|
8950
|
-
const brief =
|
|
8951
|
-
schemaVersion:
|
|
8952
|
-
context:
|
|
8953
|
-
auditId:
|
|
8954
|
-
resourceId:
|
|
8955
|
-
organizationId:
|
|
8956
|
-
selectedUrls:
|
|
8957
|
-
ruleId:
|
|
9245
|
+
const brief = z18.object({
|
|
9246
|
+
schemaVersion: z18.literal(1),
|
|
9247
|
+
context: z18.object({
|
|
9248
|
+
auditId: z18.literal(values.audit),
|
|
9249
|
+
resourceId: z18.literal(client.resourceId),
|
|
9250
|
+
organizationId: z18.literal(client.organizationId),
|
|
9251
|
+
selectedUrls: z18.array(z18.string()).min(1).max(20),
|
|
9252
|
+
ruleId: z18.literal(values.rule ?? null)
|
|
8958
9253
|
}),
|
|
8959
|
-
evidence:
|
|
8960
|
-
|
|
8961
|
-
url:
|
|
8962
|
-
findings:
|
|
9254
|
+
evidence: z18.array(
|
|
9255
|
+
z18.object({
|
|
9256
|
+
url: z18.string(),
|
|
9257
|
+
findings: z18.array(z18.unknown()).max(12)
|
|
8963
9258
|
})
|
|
8964
9259
|
).min(1).max(20),
|
|
8965
|
-
suggestedGroups:
|
|
8966
|
-
verificationCommand:
|
|
8967
|
-
text:
|
|
9260
|
+
suggestedGroups: z18.array(z18.unknown()),
|
|
9261
|
+
verificationCommand: z18.string().min(1),
|
|
9262
|
+
text: z18.string().min(1)
|
|
8968
9263
|
}).parse(record);
|
|
8969
9264
|
const canonical = (value) => {
|
|
8970
9265
|
const url = new URL(value);
|
|
@@ -8985,15 +9280,15 @@ async function runSeoRepairCommand(options) {
|
|
|
8985
9280
|
|
|
8986
9281
|
// src/services/seo-command.ts
|
|
8987
9282
|
import { writeFile as writeFile10 } from "fs/promises";
|
|
8988
|
-
import
|
|
8989
|
-
import { parseArgs as
|
|
8990
|
-
import { z as
|
|
9283
|
+
import path29 from "path";
|
|
9284
|
+
import { parseArgs as parseArgs10 } from "util";
|
|
9285
|
+
import { z as z22 } from "zod";
|
|
8991
9286
|
|
|
8992
9287
|
// src/services/seo-research-command.ts
|
|
8993
9288
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
8994
9289
|
import { setTimeout as setTimeout2 } from "timers/promises";
|
|
8995
|
-
import { isDeepStrictEqual, parseArgs as
|
|
8996
|
-
import { z as
|
|
9290
|
+
import { isDeepStrictEqual, parseArgs as parseArgs7 } from "util";
|
|
9291
|
+
import { z as z19 } from "zod";
|
|
8997
9292
|
var RESEARCH_HELP = `
|
|
8998
9293
|
siteos seo research summary [--environment <slug>] [--json]
|
|
8999
9294
|
siteos seo research status --kind <kind> [--environment <slug>] [--json]
|
|
@@ -9015,15 +9310,15 @@ var RESEARCH_HELP = `
|
|
|
9015
9310
|
|
|
9016
9311
|
Research request kinds: keywords, domain, rankings, backlinks, brand, ai-visibility.
|
|
9017
9312
|
Read sections also accept --kind ai-rankings for category comparisons; --kind ai-visibility selects Prompt checks.
|
|
9018
|
-
Category run/plan inputs use kind ai-visibility with category-
|
|
9313
|
+
Category run/plan inputs use kind ai-visibility with category-mentions-v3; both sections share AI request execution.
|
|
9019
9314
|
Domain Overview: {"kind":"domain","target":"example.com","country":"US","language":"en"}.
|
|
9020
9315
|
Domain collects summary, ranking keywords, top pages and competitors. The legacy competitors kind retains its two-part plan; domain history includes those reports.
|
|
9021
9316
|
Request JSON: {"kind":"keywords","target":"example.com","keywords":["website analytics"],"country":"US","language":"en"}.
|
|
9022
|
-
Rankings accepts up to 10 keywords and 5 competitors. AI visibility accepts brand, platforms and either prompt or a category-
|
|
9317
|
+
Rankings accepts up to 10 keywords and 5 competitors. AI visibility accepts brand, platforms and either prompt or a category-mentions-v3 category with products and questions.
|
|
9023
9318
|
Markets: US, GB, ES, DE, FR, CA, AU. Languages: en, es, de, fr, pt, it, nl, ru.
|
|
9024
9319
|
Platforms: chat_gpt, claude, gemini, perplexity. Brand lookup uses brandPlatform chat_gpt|google and brandMatch domain|brand.
|
|
9025
9320
|
Plan validates the request and shows planned parts and available research credits without enqueueing work.
|
|
9026
|
-
Retry creates a continuation using the original questions and
|
|
9321
|
+
Retry creates a continuation using the original questions and platforms; category continuations collect ordinary text with category-mentions-v3; it preserves usable saved answers and only requests missing parts. It consumes credits and requires explicit scope authorization, like Run. Original reports and observation dates remain available.
|
|
9027
9322
|
Run consumes Organization research credits through the same worker as the interface. Preserve the retry key after an uncertain response.
|
|
9028
9323
|
SERP ensure collects one saved organic snapshot for a keyword returned by the selected completed Keyword Research report. It uses research credits once; repeating the same report/word returns the saved job, including failures. Show/wait/export only read and never spend. New reports can collect fresh snapshots.
|
|
9029
9324
|
History returns the latest 30 checks per section; show/export can address older retained run IDs. Saved checks are limited to 100 per resource.
|
|
@@ -9040,11 +9335,11 @@ var kinds = [
|
|
|
9040
9335
|
];
|
|
9041
9336
|
var sections = [...kinds, "ai-rankings"];
|
|
9042
9337
|
var sectionOf = (request) => request.kind === "ai-visibility" && request.category ? "ai-rankings" : request.kind;
|
|
9043
|
-
var requestSchema =
|
|
9044
|
-
kind:
|
|
9045
|
-
target:
|
|
9338
|
+
var requestSchema = z19.object({
|
|
9339
|
+
kind: z19.enum(["domain", ...kinds]).transform((kind) => kind === "domain" ? "competitors" : kind),
|
|
9340
|
+
target: z19.string().min(1).max(253)
|
|
9046
9341
|
}).passthrough();
|
|
9047
|
-
var runStates =
|
|
9342
|
+
var runStates = z19.enum([
|
|
9048
9343
|
"queued",
|
|
9049
9344
|
"running",
|
|
9050
9345
|
"completed",
|
|
@@ -9060,7 +9355,7 @@ var csvCell = (value) => {
|
|
|
9060
9355
|
async function runSeoResearchCommand(options) {
|
|
9061
9356
|
let idempotencyKey;
|
|
9062
9357
|
try {
|
|
9063
|
-
const { values, positionals } =
|
|
9358
|
+
const { values, positionals } = parseArgs7({
|
|
9064
9359
|
args: options.args.slice(1),
|
|
9065
9360
|
strict: true,
|
|
9066
9361
|
allowPositionals: true,
|
|
@@ -9142,26 +9437,26 @@ async function runSeoResearchCommand(options) {
|
|
|
9142
9437
|
);
|
|
9143
9438
|
}
|
|
9144
9439
|
const client = await seoReportClient(options, values.environment);
|
|
9145
|
-
const resourceSchema =
|
|
9146
|
-
id:
|
|
9147
|
-
organizationId:
|
|
9440
|
+
const resourceSchema = z19.object({
|
|
9441
|
+
id: z19.literal(client.resourceId),
|
|
9442
|
+
organizationId: z19.literal(client.organizationId)
|
|
9148
9443
|
}).passthrough();
|
|
9149
|
-
const runSchema =
|
|
9150
|
-
id:
|
|
9151
|
-
resourceId:
|
|
9152
|
-
organizationId:
|
|
9153
|
-
websiteUrl:
|
|
9444
|
+
const runSchema = z19.object({
|
|
9445
|
+
id: z19.string(),
|
|
9446
|
+
resourceId: z19.literal(client.resourceId),
|
|
9447
|
+
organizationId: z19.literal(client.organizationId),
|
|
9448
|
+
websiteUrl: z19.string(),
|
|
9154
9449
|
request: requestSchema,
|
|
9155
9450
|
state: runStates,
|
|
9156
|
-
createdAt:
|
|
9157
|
-
finishedAt:
|
|
9158
|
-
parts:
|
|
9159
|
-
|
|
9160
|
-
key:
|
|
9161
|
-
state:
|
|
9162
|
-
error:
|
|
9163
|
-
dataset:
|
|
9164
|
-
observedAt:
|
|
9451
|
+
createdAt: z19.string(),
|
|
9452
|
+
finishedAt: z19.string().nullable(),
|
|
9453
|
+
parts: z19.array(
|
|
9454
|
+
z19.object({
|
|
9455
|
+
key: z19.string(),
|
|
9456
|
+
state: z19.enum(["completed", "failed"]),
|
|
9457
|
+
error: z19.string().nullable(),
|
|
9458
|
+
dataset: z19.record(z19.unknown()).nullable(),
|
|
9459
|
+
observedAt: z19.string()
|
|
9165
9460
|
}).passthrough()
|
|
9166
9461
|
)
|
|
9167
9462
|
}).passthrough();
|
|
@@ -9175,36 +9470,36 @@ async function runSeoResearchCommand(options) {
|
|
|
9175
9470
|
const query = `?kind=${encodeURIComponent(values.kind ?? "rankings")}`;
|
|
9176
9471
|
let record;
|
|
9177
9472
|
if (action.startsWith("serp ")) {
|
|
9178
|
-
const snapshotSchema =
|
|
9179
|
-
id:
|
|
9180
|
-
organizationId:
|
|
9181
|
-
resourceId:
|
|
9182
|
-
sourceRunId:
|
|
9473
|
+
const snapshotSchema = z19.object({
|
|
9474
|
+
id: z19.string(),
|
|
9475
|
+
organizationId: z19.literal(client.organizationId),
|
|
9476
|
+
resourceId: z19.literal(client.resourceId),
|
|
9477
|
+
sourceRunId: z19.literal(id),
|
|
9183
9478
|
request: requestSchema,
|
|
9184
|
-
keywordKey:
|
|
9479
|
+
keywordKey: z19.literal(
|
|
9185
9480
|
values.keyword.normalize("NFKC").trim().toLowerCase().replace(/\s+/gu, " ")
|
|
9186
9481
|
),
|
|
9187
|
-
state:
|
|
9482
|
+
state: z19.enum([
|
|
9188
9483
|
"queued",
|
|
9189
9484
|
"running",
|
|
9190
9485
|
"completed",
|
|
9191
9486
|
"failed",
|
|
9192
9487
|
"cancelled"
|
|
9193
9488
|
]),
|
|
9194
|
-
dataset:
|
|
9195
|
-
type:
|
|
9196
|
-
keyword:
|
|
9197
|
-
country:
|
|
9198
|
-
language:
|
|
9199
|
-
device:
|
|
9200
|
-
observedAt:
|
|
9201
|
-
rows:
|
|
9202
|
-
|
|
9203
|
-
position:
|
|
9204
|
-
title:
|
|
9205
|
-
url:
|
|
9206
|
-
domain:
|
|
9207
|
-
description:
|
|
9489
|
+
dataset: z19.object({
|
|
9490
|
+
type: z19.literal("serp"),
|
|
9491
|
+
keyword: z19.string(),
|
|
9492
|
+
country: z19.string(),
|
|
9493
|
+
language: z19.string(),
|
|
9494
|
+
device: z19.enum(["desktop", "mobile"]),
|
|
9495
|
+
observedAt: z19.string(),
|
|
9496
|
+
rows: z19.array(
|
|
9497
|
+
z19.object({
|
|
9498
|
+
position: z19.number(),
|
|
9499
|
+
title: z19.string(),
|
|
9500
|
+
url: z19.string(),
|
|
9501
|
+
domain: z19.string(),
|
|
9502
|
+
description: z19.string()
|
|
9208
9503
|
})
|
|
9209
9504
|
)
|
|
9210
9505
|
}).nullable()
|
|
@@ -9314,9 +9609,9 @@ async function runSeoResearchCommand(options) {
|
|
|
9314
9609
|
name: values.name.trim(),
|
|
9315
9610
|
...values.id ? { id: values.id } : {}
|
|
9316
9611
|
});
|
|
9317
|
-
const saved =
|
|
9318
|
-
id:
|
|
9319
|
-
name:
|
|
9612
|
+
const saved = z19.object({
|
|
9613
|
+
id: z19.string(),
|
|
9614
|
+
name: z19.literal(values.name.trim()),
|
|
9320
9615
|
request: requestSchema
|
|
9321
9616
|
}).parse(record.saved);
|
|
9322
9617
|
if (values.id && saved.id !== values.id || !isDeepStrictEqual(saved.request, plan.request))
|
|
@@ -9330,23 +9625,23 @@ async function runSeoResearchCommand(options) {
|
|
|
9330
9625
|
action === "cancel" ? "run" : "write",
|
|
9331
9626
|
{}
|
|
9332
9627
|
);
|
|
9333
|
-
|
|
9628
|
+
z19.literal(true).parse(
|
|
9334
9629
|
record[action === "cancel" ? "cancelled" : "removed"]
|
|
9335
9630
|
);
|
|
9336
9631
|
} else if (action === "summary") {
|
|
9337
9632
|
record = await get("/summary");
|
|
9338
9633
|
resourceSchema.parse(record.resource);
|
|
9339
|
-
|
|
9634
|
+
z19.array(z19.object({ kind: z19.enum(sections), summary: z19.unknown() })).parse(
|
|
9340
9635
|
record.checks
|
|
9341
9636
|
);
|
|
9342
9637
|
} else if (["status", "history", "saved list"].includes(action)) {
|
|
9343
9638
|
const view = await get(query);
|
|
9344
9639
|
resourceSchema.parse(view.resource);
|
|
9345
|
-
const runs =
|
|
9346
|
-
const saved =
|
|
9347
|
-
|
|
9348
|
-
id:
|
|
9349
|
-
name:
|
|
9640
|
+
const runs = z19.array(runSchema).parse(view.runs);
|
|
9641
|
+
const saved = z19.array(
|
|
9642
|
+
z19.object({
|
|
9643
|
+
id: z19.string(),
|
|
9644
|
+
name: z19.string(),
|
|
9350
9645
|
request: requestSchema
|
|
9351
9646
|
}).passthrough()
|
|
9352
9647
|
).parse(view.saved);
|
|
@@ -9481,8 +9776,8 @@ async function runSeoResearchCommand(options) {
|
|
|
9481
9776
|
}
|
|
9482
9777
|
|
|
9483
9778
|
// src/services/seo-gsc-command.ts
|
|
9484
|
-
import { parseArgs as
|
|
9485
|
-
import { z as
|
|
9779
|
+
import { parseArgs as parseArgs8 } from "util";
|
|
9780
|
+
import { z as z20 } from "zod";
|
|
9486
9781
|
var GSC_HELP = `
|
|
9487
9782
|
siteos seo gsc status [--environment <slug>] [--json]
|
|
9488
9783
|
siteos seo gsc report [--dataset <pages|queries>] [--query <text>] [--filter <all|issues|declining>] [--sort <clicks|change|impressions>] [--page <number>] [--url <page-url>] [--environment <slug>] [--json]
|
|
@@ -9500,7 +9795,7 @@ Bind/disconnect require a current revision and separate settings authority. Disc
|
|
|
9500
9795
|
`;
|
|
9501
9796
|
async function runSeoGscCommand(options) {
|
|
9502
9797
|
try {
|
|
9503
|
-
const { values, positionals } =
|
|
9798
|
+
const { values, positionals } = parseArgs8({
|
|
9504
9799
|
args: options.args.slice(1),
|
|
9505
9800
|
strict: true,
|
|
9506
9801
|
allowPositionals: true,
|
|
@@ -9582,13 +9877,13 @@ async function runSeoGscCommand(options) {
|
|
|
9582
9877
|
`seo:search:${action === "sync" ? "sync" : writing ? "write" : "read"}`,
|
|
9583
9878
|
body
|
|
9584
9879
|
);
|
|
9585
|
-
const runSchema =
|
|
9586
|
-
id:
|
|
9587
|
-
resourceId:
|
|
9588
|
-
organizationId:
|
|
9589
|
-
websiteUrl:
|
|
9590
|
-
siteUrl:
|
|
9591
|
-
state:
|
|
9880
|
+
const runSchema = z20.object({
|
|
9881
|
+
id: z20.string(),
|
|
9882
|
+
resourceId: z20.literal(client.resourceId),
|
|
9883
|
+
organizationId: z20.literal(client.organizationId),
|
|
9884
|
+
websiteUrl: z20.string(),
|
|
9885
|
+
siteUrl: z20.string(),
|
|
9886
|
+
state: z20.enum([
|
|
9592
9887
|
"queued",
|
|
9593
9888
|
"running",
|
|
9594
9889
|
"completed",
|
|
@@ -9596,32 +9891,32 @@ async function runSeoGscCommand(options) {
|
|
|
9596
9891
|
"failed",
|
|
9597
9892
|
"cancelled"
|
|
9598
9893
|
]),
|
|
9599
|
-
dates:
|
|
9894
|
+
dates: z20.unknown()
|
|
9600
9895
|
}).passthrough();
|
|
9601
|
-
const bindingSchema =
|
|
9602
|
-
resourceId:
|
|
9603
|
-
organizationId:
|
|
9604
|
-
websiteUrl:
|
|
9605
|
-
siteUrl:
|
|
9606
|
-
revision:
|
|
9896
|
+
const bindingSchema = z20.object({
|
|
9897
|
+
resourceId: z20.literal(client.resourceId),
|
|
9898
|
+
organizationId: z20.literal(client.organizationId),
|
|
9899
|
+
websiteUrl: z20.string(),
|
|
9900
|
+
siteUrl: z20.string(),
|
|
9901
|
+
revision: z20.number().int().positive()
|
|
9607
9902
|
}).passthrough();
|
|
9608
|
-
const metrics =
|
|
9609
|
-
clicks:
|
|
9610
|
-
impressions:
|
|
9611
|
-
ctr:
|
|
9612
|
-
position:
|
|
9903
|
+
const metrics = z20.object({
|
|
9904
|
+
clicks: z20.number(),
|
|
9905
|
+
impressions: z20.number(),
|
|
9906
|
+
ctr: z20.number(),
|
|
9907
|
+
position: z20.number()
|
|
9613
9908
|
}).passthrough().nullable();
|
|
9614
9909
|
const validateView = (data) => {
|
|
9615
9910
|
const record2 = versionedReport.parse(data);
|
|
9616
|
-
|
|
9911
|
+
z20.literal(client.resourceId).parse(record2.resourceId);
|
|
9617
9912
|
bindingSchema.nullable().parse(record2.binding);
|
|
9618
9913
|
runSchema.nullable().parse(record2.latest);
|
|
9619
9914
|
runSchema.nullable().parse(record2.report);
|
|
9620
|
-
|
|
9621
|
-
|
|
9915
|
+
z20.array(
|
|
9916
|
+
z20.object({ key: z20.string(), current: metrics, previous: metrics }).passthrough()
|
|
9622
9917
|
).parse(record2.rows);
|
|
9623
|
-
|
|
9624
|
-
|
|
9918
|
+
z20.number().int().nonnegative().parse(record2.total);
|
|
9919
|
+
z20.literal(values.dataset ?? "pages").parse(record2.dataset);
|
|
9625
9920
|
return record2;
|
|
9626
9921
|
};
|
|
9627
9922
|
if (action === "export") {
|
|
@@ -9637,9 +9932,9 @@ async function runSeoGscCommand(options) {
|
|
|
9637
9932
|
const text = await response.text();
|
|
9638
9933
|
if (values.format === "json") {
|
|
9639
9934
|
const report = validateView(JSON.parse(text));
|
|
9640
|
-
|
|
9641
|
-
|
|
9642
|
-
|
|
9935
|
+
z20.literal(reportId).parse(runSchema.parse(report.report).id);
|
|
9936
|
+
z20.array(z20.unknown()).length(rows).parse(report.rows);
|
|
9937
|
+
z20.literal(truncated === "true").parse(report.truncated);
|
|
9643
9938
|
}
|
|
9644
9939
|
const output = await writeSeoReport(options, values.output, text);
|
|
9645
9940
|
return {
|
|
@@ -9660,7 +9955,7 @@ async function runSeoGscCommand(options) {
|
|
|
9660
9955
|
};
|
|
9661
9956
|
}
|
|
9662
9957
|
const record = versionedReport.parse(await response.json());
|
|
9663
|
-
if (action === "disconnect")
|
|
9958
|
+
if (action === "disconnect") z20.literal(true).parse(record.unbound);
|
|
9664
9959
|
else if (writing) {
|
|
9665
9960
|
runSchema.parse(record.run);
|
|
9666
9961
|
if (action === "bind") {
|
|
@@ -9680,10 +9975,10 @@ async function runSeoGscCommand(options) {
|
|
|
9680
9975
|
// src/services/seo-performance-command.ts
|
|
9681
9976
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
9682
9977
|
import { writeFile as writeFile9 } from "fs/promises";
|
|
9683
|
-
import
|
|
9978
|
+
import path28 from "path";
|
|
9684
9979
|
import { setTimeout as setTimeout3 } from "timers/promises";
|
|
9685
|
-
import { parseArgs as
|
|
9686
|
-
import { z as
|
|
9980
|
+
import { parseArgs as parseArgs9 } from "util";
|
|
9981
|
+
import { z as z21 } from "zod";
|
|
9687
9982
|
var PERFORMANCE_HELP = `
|
|
9688
9983
|
siteos seo performance run --audit <id> --url <url> [--url <url>...] [--device <mobile|desktop>] [--idempotency-key <key>] [--environment <slug>] [--json]
|
|
9689
9984
|
siteos seo performance list [--device <mobile|desktop>] [--environment <slug>] [--json]
|
|
@@ -9700,7 +9995,7 @@ Reuse the returned idempotency key after an uncertain run response. Wait default
|
|
|
9700
9995
|
async function runSeoPerformanceCommand(options) {
|
|
9701
9996
|
let idempotencyKey;
|
|
9702
9997
|
try {
|
|
9703
|
-
const { values, positionals } =
|
|
9998
|
+
const { values, positionals } = parseArgs9({
|
|
9704
9999
|
args: options.args.slice(1),
|
|
9705
10000
|
strict: true,
|
|
9706
10001
|
allowPositionals: true,
|
|
@@ -9771,12 +10066,12 @@ async function runSeoPerformanceCommand(options) {
|
|
|
9771
10066
|
const runtime = commonProjectRuntime(options);
|
|
9772
10067
|
const writing = ["run", "cancel"].includes(action);
|
|
9773
10068
|
const scope = writing ? "seo:audits:write" : "seo:workspace:read";
|
|
9774
|
-
const batchSchema =
|
|
9775
|
-
id:
|
|
9776
|
-
resourceId:
|
|
9777
|
-
organizationId:
|
|
9778
|
-
sourceAuditId:
|
|
9779
|
-
state:
|
|
10069
|
+
const batchSchema = z21.object({
|
|
10070
|
+
id: z21.string(),
|
|
10071
|
+
resourceId: z21.literal(context.resourceId),
|
|
10072
|
+
organizationId: z21.literal(context.overview.project.organizationId),
|
|
10073
|
+
sourceAuditId: z21.string(),
|
|
10074
|
+
state: z21.enum([
|
|
9780
10075
|
"queued",
|
|
9781
10076
|
"running",
|
|
9782
10077
|
"completed",
|
|
@@ -9784,8 +10079,8 @@ async function runSeoPerformanceCommand(options) {
|
|
|
9784
10079
|
"failed",
|
|
9785
10080
|
"cancelled"
|
|
9786
10081
|
]),
|
|
9787
|
-
device:
|
|
9788
|
-
urls:
|
|
10082
|
+
device: z21.enum(["mobile", "desktop"]),
|
|
10083
|
+
urls: z21.array(z21.string()).min(1).max(10)
|
|
9789
10084
|
}).passthrough();
|
|
9790
10085
|
const query = new URLSearchParams();
|
|
9791
10086
|
if (values.device) query.set("device", values.device);
|
|
@@ -9833,17 +10128,17 @@ async function runSeoPerformanceCommand(options) {
|
|
|
9833
10128
|
);
|
|
9834
10129
|
const text = await response.text();
|
|
9835
10130
|
if (values.format === "json") {
|
|
9836
|
-
const parsed =
|
|
9837
|
-
contractVersion:
|
|
10131
|
+
const parsed = z21.object({
|
|
10132
|
+
contractVersion: z21.literal(1),
|
|
9838
10133
|
batch: batchSchema,
|
|
9839
|
-
pages:
|
|
10134
|
+
pages: z21.array(z21.unknown())
|
|
9840
10135
|
}).parse(JSON.parse(text));
|
|
9841
10136
|
if (parsed.batch.id !== id)
|
|
9842
10137
|
throw new Error(
|
|
9843
10138
|
"The export response does not match the selected check."
|
|
9844
10139
|
);
|
|
9845
10140
|
}
|
|
9846
|
-
const output =
|
|
10141
|
+
const output = path28.resolve(
|
|
9847
10142
|
options.cwd ?? process.cwd(),
|
|
9848
10143
|
values.output
|
|
9849
10144
|
);
|
|
@@ -9859,8 +10154,8 @@ async function runSeoPerformanceCommand(options) {
|
|
|
9859
10154
|
}
|
|
9860
10155
|
const data = await response.json();
|
|
9861
10156
|
if (!response.ok) {
|
|
9862
|
-
const error =
|
|
9863
|
-
error:
|
|
10157
|
+
const error = z21.object({
|
|
10158
|
+
error: z21.object({ code: z21.string(), message: z21.string().max(500) })
|
|
9864
10159
|
}).safeParse(data);
|
|
9865
10160
|
throw new SiteOSAuthApiError({
|
|
9866
10161
|
code: error.success ? error.data.error.code : "SEO_REQUEST_FAILED",
|
|
@@ -9868,8 +10163,8 @@ async function runSeoPerformanceCommand(options) {
|
|
|
9868
10163
|
status: response.status
|
|
9869
10164
|
});
|
|
9870
10165
|
}
|
|
9871
|
-
const record =
|
|
9872
|
-
if (action === "cancel")
|
|
10166
|
+
const record = z21.object({ contractVersion: z21.literal(1) }).passthrough().parse(data);
|
|
10167
|
+
if (action === "cancel") z21.literal(true).parse(record.cancelled);
|
|
9873
10168
|
else if (action === "run") {
|
|
9874
10169
|
const accepted = batchSchema.parse(record.batch);
|
|
9875
10170
|
const normalize = (url) => {
|
|
@@ -9882,8 +10177,8 @@ async function runSeoPerformanceCommand(options) {
|
|
|
9882
10177
|
"The queued check does not match the requested source, device and URLs."
|
|
9883
10178
|
);
|
|
9884
10179
|
} else {
|
|
9885
|
-
|
|
9886
|
-
|
|
10180
|
+
z21.literal(context.resourceId).parse(record.resourceId);
|
|
10181
|
+
z21.array(batchSchema).parse(record.batches);
|
|
9887
10182
|
const selected = batchSchema.nullable().parse(record.batch);
|
|
9888
10183
|
if (id && selected?.id !== id)
|
|
9889
10184
|
throw new Error(
|
|
@@ -9910,7 +10205,7 @@ async function runSeoPerformanceCommand(options) {
|
|
|
9910
10205
|
} catch (cause) {
|
|
9911
10206
|
const error = {
|
|
9912
10207
|
code: cause instanceof SiteOSAuthApiError ? cause.code : "SEO_COMMAND_FAILED",
|
|
9913
|
-
message: cause instanceof
|
|
10208
|
+
message: cause instanceof z21.ZodError ? "The SEO service returned an invalid response." : cause instanceof Error ? cause.message : "The performance command failed."
|
|
9914
10209
|
};
|
|
9915
10210
|
return {
|
|
9916
10211
|
exitCode: cause instanceof SiteOSAuthApiError ? 1 : 2,
|
|
@@ -9982,7 +10277,7 @@ async function runSeoCommand(options) {
|
|
|
9982
10277
|
return runSeoRepairCommand(options);
|
|
9983
10278
|
const json = options.args.includes("--json");
|
|
9984
10279
|
try {
|
|
9985
|
-
const { positionals, values } =
|
|
10280
|
+
const { positionals, values } = parseArgs10({
|
|
9986
10281
|
args: options.args,
|
|
9987
10282
|
strict: true,
|
|
9988
10283
|
allowPositionals: true,
|
|
@@ -10188,17 +10483,17 @@ async function runSeoCommand(options) {
|
|
|
10188
10483
|
);
|
|
10189
10484
|
const text = await response.text();
|
|
10190
10485
|
if (values.format === "json")
|
|
10191
|
-
|
|
10192
|
-
contractVersion:
|
|
10193
|
-
audit:
|
|
10194
|
-
id:
|
|
10195
|
-
resourceId:
|
|
10486
|
+
z22.object({
|
|
10487
|
+
contractVersion: z22.literal(1),
|
|
10488
|
+
audit: z22.object({
|
|
10489
|
+
id: z22.literal(values.audit),
|
|
10490
|
+
resourceId: z22.literal(context.resourceId)
|
|
10196
10491
|
}),
|
|
10197
|
-
kind:
|
|
10198
|
-
totalRows:
|
|
10199
|
-
rows:
|
|
10492
|
+
kind: z22.literal(values.kind),
|
|
10493
|
+
totalRows: z22.literal(rows),
|
|
10494
|
+
rows: z22.array(z22.unknown()).length(rows)
|
|
10200
10495
|
}).parse(JSON.parse(text));
|
|
10201
|
-
const output =
|
|
10496
|
+
const output = path29.resolve(options.cwd ?? process.cwd(), values.output);
|
|
10202
10497
|
await writeFile10(output, text, { flag: "wx", mode: 384 });
|
|
10203
10498
|
return {
|
|
10204
10499
|
exitCode: 0,
|
|
@@ -10217,8 +10512,8 @@ async function runSeoCommand(options) {
|
|
|
10217
10512
|
}
|
|
10218
10513
|
const data = await response.json();
|
|
10219
10514
|
if (!response.ok) {
|
|
10220
|
-
const result =
|
|
10221
|
-
error:
|
|
10515
|
+
const result = z22.object({
|
|
10516
|
+
error: z22.object({ code: z22.string(), message: z22.string().max(500) })
|
|
10222
10517
|
}).safeParse(data);
|
|
10223
10518
|
throw new SiteOSAuthApiError({
|
|
10224
10519
|
code: result.success ? result.data.error.code : "SEO_REQUEST_FAILED",
|
|
@@ -10226,34 +10521,34 @@ async function runSeoCommand(options) {
|
|
|
10226
10521
|
status: response.status
|
|
10227
10522
|
});
|
|
10228
10523
|
}
|
|
10229
|
-
const record =
|
|
10524
|
+
const record = z22.object({ contractVersion: z22.literal(1) }).passthrough().parse(data);
|
|
10230
10525
|
if (automation) {
|
|
10231
|
-
|
|
10232
|
-
const schedule =
|
|
10233
|
-
enabled:
|
|
10234
|
-
weekday:
|
|
10235
|
-
time:
|
|
10236
|
-
timeZone:
|
|
10237
|
-
revision:
|
|
10238
|
-
nextRunAt:
|
|
10526
|
+
z22.literal(context.resourceId).parse(record.resourceId);
|
|
10527
|
+
const schedule = z22.object({
|
|
10528
|
+
enabled: z22.boolean(),
|
|
10529
|
+
weekday: z22.number().int().min(1).max(7),
|
|
10530
|
+
time: z22.string(),
|
|
10531
|
+
timeZone: z22.string(),
|
|
10532
|
+
revision: z22.number().int().min(0),
|
|
10533
|
+
nextRunAt: z22.string().nullable()
|
|
10239
10534
|
});
|
|
10240
|
-
const notificationRoute =
|
|
10241
|
-
enabled:
|
|
10242
|
-
minimumSeverity:
|
|
10243
|
-
includeFailures:
|
|
10244
|
-
revision:
|
|
10245
|
-
destinationId:
|
|
10535
|
+
const notificationRoute = z22.object({
|
|
10536
|
+
enabled: z22.boolean(),
|
|
10537
|
+
minimumSeverity: z22.enum(["error", "warning"]),
|
|
10538
|
+
includeFailures: z22.boolean(),
|
|
10539
|
+
revision: z22.number().int().min(0),
|
|
10540
|
+
destinationId: z22.string().nullable()
|
|
10246
10541
|
});
|
|
10247
10542
|
if (retryNotification) {
|
|
10248
|
-
|
|
10249
|
-
|
|
10543
|
+
z22.literal(true).parse(record.retryQueued);
|
|
10544
|
+
z22.literal(positionals[2]).parse(record.notificationId);
|
|
10250
10545
|
} else if (route === "notifications destinations")
|
|
10251
|
-
|
|
10252
|
-
candidates:
|
|
10253
|
-
|
|
10254
|
-
candidateId:
|
|
10255
|
-
label:
|
|
10256
|
-
availability:
|
|
10546
|
+
z22.object({
|
|
10547
|
+
candidates: z22.array(
|
|
10548
|
+
z22.object({
|
|
10549
|
+
candidateId: z22.string(),
|
|
10550
|
+
label: z22.string(),
|
|
10551
|
+
availability: z22.literal("available")
|
|
10257
10552
|
})
|
|
10258
10553
|
)
|
|
10259
10554
|
}).parse(record);
|
|
@@ -10266,46 +10561,46 @@ async function runSeoCommand(options) {
|
|
|
10266
10561
|
notificationRoute.parse(record.route);
|
|
10267
10562
|
}
|
|
10268
10563
|
} else if (!writing) {
|
|
10269
|
-
const validated =
|
|
10270
|
-
resource:
|
|
10271
|
-
id:
|
|
10272
|
-
organizationId:
|
|
10564
|
+
const validated = z22.object({
|
|
10565
|
+
resource: z22.object({
|
|
10566
|
+
id: z22.literal(context.resourceId),
|
|
10567
|
+
organizationId: z22.literal(context.overview.project.organizationId)
|
|
10273
10568
|
}),
|
|
10274
|
-
audits:
|
|
10275
|
-
audit:
|
|
10276
|
-
id:
|
|
10277
|
-
resourceId:
|
|
10569
|
+
audits: z22.array(z22.object({ id: z22.string() }).passthrough()),
|
|
10570
|
+
audit: z22.object({
|
|
10571
|
+
id: z22.string(),
|
|
10572
|
+
resourceId: z22.literal(context.resourceId)
|
|
10278
10573
|
}).passthrough().nullable(),
|
|
10279
|
-
pages:
|
|
10280
|
-
issues:
|
|
10281
|
-
changes:
|
|
10282
|
-
totalChanges:
|
|
10283
|
-
dispositions:
|
|
10574
|
+
pages: z22.array(z22.unknown()),
|
|
10575
|
+
issues: z22.array(z22.unknown()),
|
|
10576
|
+
changes: z22.array(z22.unknown()),
|
|
10577
|
+
totalChanges: z22.number(),
|
|
10578
|
+
dispositions: z22.array(z22.unknown())
|
|
10284
10579
|
}).passthrough().parse(record);
|
|
10285
10580
|
const selected = query.get("audit");
|
|
10286
10581
|
if (selected && validated.audit?.id !== selected)
|
|
10287
10582
|
throw new Error("The SEO response does not match the requested audit.");
|
|
10288
10583
|
} else if (record.audit)
|
|
10289
|
-
|
|
10290
|
-
id:
|
|
10291
|
-
resourceId:
|
|
10292
|
-
organizationId:
|
|
10293
|
-
state:
|
|
10584
|
+
z22.object({
|
|
10585
|
+
id: z22.string(),
|
|
10586
|
+
resourceId: z22.literal(context.resourceId),
|
|
10587
|
+
organizationId: z22.literal(context.overview.project.organizationId),
|
|
10588
|
+
state: z22.literal("queued")
|
|
10294
10589
|
}).parse(record.audit);
|
|
10295
|
-
else if (route === "audit cancel")
|
|
10590
|
+
else if (route === "audit cancel") z22.literal(true).parse(record.cancelled);
|
|
10296
10591
|
else if (action === "issue")
|
|
10297
|
-
|
|
10298
|
-
url:
|
|
10299
|
-
ruleId:
|
|
10300
|
-
ignored:
|
|
10301
|
-
revision:
|
|
10592
|
+
z22.object({
|
|
10593
|
+
url: z22.literal(values.url),
|
|
10594
|
+
ruleId: z22.literal(values.rule),
|
|
10595
|
+
ignored: z22.literal(positionals[1] === "ignore"),
|
|
10596
|
+
revision: z22.literal(Number(values.revision) + 1)
|
|
10302
10597
|
}).parse(record.disposition);
|
|
10303
10598
|
else throw new Error("The SEO service returned an invalid response.");
|
|
10304
10599
|
return { exitCode: 0, stdout: JSON.stringify(record, null, 2) };
|
|
10305
10600
|
} catch (cause) {
|
|
10306
10601
|
const error = {
|
|
10307
10602
|
code: cause instanceof SiteOSAuthApiError ? cause.code : "SEO_COMMAND_FAILED",
|
|
10308
|
-
message: cause instanceof
|
|
10603
|
+
message: cause instanceof z22.ZodError ? "The SEO service returned an invalid response." : cause instanceof Error ? cause.message : "The SEO command failed."
|
|
10309
10604
|
};
|
|
10310
10605
|
return {
|
|
10311
10606
|
exitCode: cause instanceof SiteOSAuthApiError ? 1 : 2,
|