@siteoshq/cli 2.0.0 → 2.1.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 +9 -4
- package/dist/cli.js +281 -188
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -8710,16 +8710,8 @@ async function runAnalyticsCommand(options) {
|
|
|
8710
8710
|
}
|
|
8711
8711
|
}
|
|
8712
8712
|
|
|
8713
|
-
// src/services/seo-command.ts
|
|
8714
|
-
import {
|
|
8715
|
-
import path28 from "path";
|
|
8716
|
-
import { parseArgs as parseArgs8 } from "util";
|
|
8717
|
-
import { z as z19 } from "zod";
|
|
8718
|
-
|
|
8719
|
-
// src/services/seo-research-command.ts
|
|
8720
|
-
import { randomUUID as randomUUID6 } from "crypto";
|
|
8721
|
-
import { setTimeout as setTimeout2 } from "timers/promises";
|
|
8722
|
-
import { isDeepStrictEqual, parseArgs as parseArgs5 } from "util";
|
|
8713
|
+
// src/services/seo-repair-command.ts
|
|
8714
|
+
import { parseArgs as parseArgs5 } from "util";
|
|
8723
8715
|
import { z as z16 } from "zod";
|
|
8724
8716
|
|
|
8725
8717
|
// src/services/seo-report-client.ts
|
|
@@ -8823,7 +8815,110 @@ function seoReportFailure(cause, json, idempotencyKey) {
|
|
|
8823
8815
|
};
|
|
8824
8816
|
}
|
|
8825
8817
|
|
|
8818
|
+
// src/services/seo-repair-command.ts
|
|
8819
|
+
async function runSeoRepairCommand(options) {
|
|
8820
|
+
const json = options.args.includes("--json");
|
|
8821
|
+
try {
|
|
8822
|
+
const { positionals, values } = parseArgs5({
|
|
8823
|
+
args: options.args,
|
|
8824
|
+
allowPositionals: true,
|
|
8825
|
+
strict: true,
|
|
8826
|
+
options: {
|
|
8827
|
+
json: { type: "boolean" },
|
|
8828
|
+
environment: { type: "string" },
|
|
8829
|
+
resource: { type: "string" },
|
|
8830
|
+
audit: { type: "string" },
|
|
8831
|
+
url: { type: "string", multiple: true },
|
|
8832
|
+
rule: { type: "string" }
|
|
8833
|
+
}
|
|
8834
|
+
});
|
|
8835
|
+
const recheck = positionals[0] === "recheck";
|
|
8836
|
+
if (positionals.length !== 1 || !recheck && positionals[0] !== "repair" || recheck && values.rule)
|
|
8837
|
+
throw new Error(
|
|
8838
|
+
"Use siteos seo repair or siteos seo recheck with documented flags."
|
|
8839
|
+
);
|
|
8840
|
+
if (!values.audit || values.audit.length > 160 || !values.url?.length || values.url.length > 20)
|
|
8841
|
+
throw new Error(
|
|
8842
|
+
"Choose a source --audit and between 1 and 20 --url values."
|
|
8843
|
+
);
|
|
8844
|
+
if (values.rule && values.rule.length > 120)
|
|
8845
|
+
throw new Error("Use a documented rule ID.");
|
|
8846
|
+
for (const value of values.url) {
|
|
8847
|
+
const url = new URL(value);
|
|
8848
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.port || value.length > 2048)
|
|
8849
|
+
throw new Error("Use eligible public HTTP(S) URLs.");
|
|
8850
|
+
}
|
|
8851
|
+
const client = await seoReportClient(options, values.environment);
|
|
8852
|
+
if (values.resource && client.resourceId !== values.resource)
|
|
8853
|
+
throw new Error(
|
|
8854
|
+
"The selected Project environment does not match --resource. Select the environment from the repair brief first."
|
|
8855
|
+
);
|
|
8856
|
+
const urls = [...new Set(values.url)];
|
|
8857
|
+
const response = recheck ? await client.request("/rechecks", "seo:audits:write", {
|
|
8858
|
+
auditId: values.audit,
|
|
8859
|
+
urls
|
|
8860
|
+
}) : await client.request("/repair-brief", "seo:workspace:read", {
|
|
8861
|
+
audit: values.audit,
|
|
8862
|
+
urls,
|
|
8863
|
+
...values.rule ? { rule: values.rule } : {}
|
|
8864
|
+
});
|
|
8865
|
+
const record = versionedReport.parse(await response.json());
|
|
8866
|
+
if (recheck) {
|
|
8867
|
+
z16.object({
|
|
8868
|
+
id: z16.string(),
|
|
8869
|
+
resourceId: z16.literal(client.resourceId),
|
|
8870
|
+
organizationId: z16.literal(client.organizationId),
|
|
8871
|
+
state: z16.literal("queued")
|
|
8872
|
+
}).parse(record.audit);
|
|
8873
|
+
return { exitCode: 0, stdout: JSON.stringify(record, null, 2) };
|
|
8874
|
+
}
|
|
8875
|
+
const brief = z16.object({
|
|
8876
|
+
schemaVersion: z16.literal(1),
|
|
8877
|
+
context: z16.object({
|
|
8878
|
+
auditId: z16.literal(values.audit),
|
|
8879
|
+
resourceId: z16.literal(client.resourceId),
|
|
8880
|
+
organizationId: z16.literal(client.organizationId),
|
|
8881
|
+
selectedUrls: z16.array(z16.string()).min(1).max(20),
|
|
8882
|
+
ruleId: z16.literal(values.rule ?? null)
|
|
8883
|
+
}),
|
|
8884
|
+
evidence: z16.array(
|
|
8885
|
+
z16.object({
|
|
8886
|
+
url: z16.string(),
|
|
8887
|
+
findings: z16.array(z16.unknown()).max(12)
|
|
8888
|
+
})
|
|
8889
|
+
).min(1).max(20),
|
|
8890
|
+
suggestedGroups: z16.array(z16.unknown()),
|
|
8891
|
+
verificationCommand: z16.string().min(1),
|
|
8892
|
+
text: z16.string().min(1)
|
|
8893
|
+
}).parse(record);
|
|
8894
|
+
const canonical = (value) => {
|
|
8895
|
+
const url = new URL(value);
|
|
8896
|
+
url.hash = "";
|
|
8897
|
+
return url.href;
|
|
8898
|
+
};
|
|
8899
|
+
const expected = new Set(urls.map(canonical));
|
|
8900
|
+
if (brief.context.selectedUrls.length !== expected.size || new Set(brief.context.selectedUrls).size !== expected.size || brief.context.selectedUrls.some((url) => !expected.has(url)) || brief.evidence.length !== expected.size || new Set(brief.evidence.map((page) => page.url)).size !== expected.size || brief.evidence.some((page) => !expected.has(page.url)))
|
|
8901
|
+
throw new Error("The repair brief does not match the selected URLs.");
|
|
8902
|
+
return {
|
|
8903
|
+
exitCode: 0,
|
|
8904
|
+
stdout: json ? JSON.stringify(record, null, 2) : brief.text
|
|
8905
|
+
};
|
|
8906
|
+
} catch (cause) {
|
|
8907
|
+
return seoReportFailure(cause, json);
|
|
8908
|
+
}
|
|
8909
|
+
}
|
|
8910
|
+
|
|
8911
|
+
// src/services/seo-command.ts
|
|
8912
|
+
import { writeFile as writeFile10 } from "fs/promises";
|
|
8913
|
+
import path28 from "path";
|
|
8914
|
+
import { parseArgs as parseArgs9 } from "util";
|
|
8915
|
+
import { z as z20 } from "zod";
|
|
8916
|
+
|
|
8826
8917
|
// src/services/seo-research-command.ts
|
|
8918
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
8919
|
+
import { setTimeout as setTimeout2 } from "timers/promises";
|
|
8920
|
+
import { isDeepStrictEqual, parseArgs as parseArgs6 } from "util";
|
|
8921
|
+
import { z as z17 } from "zod";
|
|
8827
8922
|
var RESEARCH_HELP = `
|
|
8828
8923
|
siteos seo research summary [--environment <slug>] [--json]
|
|
8829
8924
|
siteos seo research status --kind <kind> [--environment <slug>] [--json]
|
|
@@ -8864,11 +8959,11 @@ var kinds = [
|
|
|
8864
8959
|
"brand",
|
|
8865
8960
|
"ai-visibility"
|
|
8866
8961
|
];
|
|
8867
|
-
var requestSchema =
|
|
8868
|
-
kind:
|
|
8869
|
-
target:
|
|
8962
|
+
var requestSchema = z17.object({
|
|
8963
|
+
kind: z17.enum(["domain", ...kinds]).transform((kind) => kind === "domain" ? "competitors" : kind),
|
|
8964
|
+
target: z17.string().min(1).max(253)
|
|
8870
8965
|
}).passthrough();
|
|
8871
|
-
var runStates =
|
|
8966
|
+
var runStates = z17.enum([
|
|
8872
8967
|
"queued",
|
|
8873
8968
|
"running",
|
|
8874
8969
|
"completed",
|
|
@@ -8884,7 +8979,7 @@ var csvCell = (value) => {
|
|
|
8884
8979
|
async function runSeoResearchCommand(options) {
|
|
8885
8980
|
let idempotencyKey;
|
|
8886
8981
|
try {
|
|
8887
|
-
const { values, positionals } =
|
|
8982
|
+
const { values, positionals } = parseArgs6({
|
|
8888
8983
|
args: options.args.slice(1),
|
|
8889
8984
|
strict: true,
|
|
8890
8985
|
allowPositionals: true,
|
|
@@ -8965,26 +9060,26 @@ async function runSeoResearchCommand(options) {
|
|
|
8965
9060
|
);
|
|
8966
9061
|
}
|
|
8967
9062
|
const client = await seoReportClient(options, values.environment);
|
|
8968
|
-
const resourceSchema =
|
|
8969
|
-
id:
|
|
8970
|
-
organizationId:
|
|
9063
|
+
const resourceSchema = z17.object({
|
|
9064
|
+
id: z17.literal(client.resourceId),
|
|
9065
|
+
organizationId: z17.literal(client.organizationId)
|
|
8971
9066
|
}).passthrough();
|
|
8972
|
-
const runSchema =
|
|
8973
|
-
id:
|
|
8974
|
-
resourceId:
|
|
8975
|
-
organizationId:
|
|
8976
|
-
websiteUrl:
|
|
9067
|
+
const runSchema = z17.object({
|
|
9068
|
+
id: z17.string(),
|
|
9069
|
+
resourceId: z17.literal(client.resourceId),
|
|
9070
|
+
organizationId: z17.literal(client.organizationId),
|
|
9071
|
+
websiteUrl: z17.string(),
|
|
8977
9072
|
request: requestSchema,
|
|
8978
9073
|
state: runStates,
|
|
8979
|
-
createdAt:
|
|
8980
|
-
finishedAt:
|
|
8981
|
-
parts:
|
|
8982
|
-
|
|
8983
|
-
key:
|
|
8984
|
-
state:
|
|
8985
|
-
error:
|
|
8986
|
-
dataset:
|
|
8987
|
-
observedAt:
|
|
9074
|
+
createdAt: z17.string(),
|
|
9075
|
+
finishedAt: z17.string().nullable(),
|
|
9076
|
+
parts: z17.array(
|
|
9077
|
+
z17.object({
|
|
9078
|
+
key: z17.string(),
|
|
9079
|
+
state: z17.enum(["completed", "failed"]),
|
|
9080
|
+
error: z17.string().nullable(),
|
|
9081
|
+
dataset: z17.record(z17.unknown()).nullable(),
|
|
9082
|
+
observedAt: z17.string()
|
|
8988
9083
|
}).passthrough()
|
|
8989
9084
|
)
|
|
8990
9085
|
}).passthrough();
|
|
@@ -8998,36 +9093,36 @@ async function runSeoResearchCommand(options) {
|
|
|
8998
9093
|
const query = `?kind=${encodeURIComponent(values.kind ?? "rankings")}`;
|
|
8999
9094
|
let record;
|
|
9000
9095
|
if (action.startsWith("serp ")) {
|
|
9001
|
-
const snapshotSchema =
|
|
9002
|
-
id:
|
|
9003
|
-
organizationId:
|
|
9004
|
-
resourceId:
|
|
9005
|
-
sourceRunId:
|
|
9096
|
+
const snapshotSchema = z17.object({
|
|
9097
|
+
id: z17.string(),
|
|
9098
|
+
organizationId: z17.literal(client.organizationId),
|
|
9099
|
+
resourceId: z17.literal(client.resourceId),
|
|
9100
|
+
sourceRunId: z17.literal(id),
|
|
9006
9101
|
request: requestSchema,
|
|
9007
|
-
keywordKey:
|
|
9102
|
+
keywordKey: z17.literal(
|
|
9008
9103
|
values.keyword.normalize("NFKC").trim().toLowerCase().replace(/\s+/gu, " ")
|
|
9009
9104
|
),
|
|
9010
|
-
state:
|
|
9105
|
+
state: z17.enum([
|
|
9011
9106
|
"queued",
|
|
9012
9107
|
"running",
|
|
9013
9108
|
"completed",
|
|
9014
9109
|
"failed",
|
|
9015
9110
|
"cancelled"
|
|
9016
9111
|
]),
|
|
9017
|
-
dataset:
|
|
9018
|
-
type:
|
|
9019
|
-
keyword:
|
|
9020
|
-
country:
|
|
9021
|
-
language:
|
|
9022
|
-
device:
|
|
9023
|
-
observedAt:
|
|
9024
|
-
rows:
|
|
9025
|
-
|
|
9026
|
-
position:
|
|
9027
|
-
title:
|
|
9028
|
-
url:
|
|
9029
|
-
domain:
|
|
9030
|
-
description:
|
|
9112
|
+
dataset: z17.object({
|
|
9113
|
+
type: z17.literal("serp"),
|
|
9114
|
+
keyword: z17.string(),
|
|
9115
|
+
country: z17.string(),
|
|
9116
|
+
language: z17.string(),
|
|
9117
|
+
device: z17.enum(["desktop", "mobile"]),
|
|
9118
|
+
observedAt: z17.string(),
|
|
9119
|
+
rows: z17.array(
|
|
9120
|
+
z17.object({
|
|
9121
|
+
position: z17.number(),
|
|
9122
|
+
title: z17.string(),
|
|
9123
|
+
url: z17.string(),
|
|
9124
|
+
domain: z17.string(),
|
|
9125
|
+
description: z17.string()
|
|
9031
9126
|
})
|
|
9032
9127
|
)
|
|
9033
9128
|
}).nullable()
|
|
@@ -9120,9 +9215,9 @@ async function runSeoResearchCommand(options) {
|
|
|
9120
9215
|
name: values.name.trim(),
|
|
9121
9216
|
...values.id ? { id: values.id } : {}
|
|
9122
9217
|
});
|
|
9123
|
-
const saved =
|
|
9124
|
-
id:
|
|
9125
|
-
name:
|
|
9218
|
+
const saved = z17.object({
|
|
9219
|
+
id: z17.string(),
|
|
9220
|
+
name: z17.literal(values.name.trim()),
|
|
9126
9221
|
request: requestSchema
|
|
9127
9222
|
}).parse(record.saved);
|
|
9128
9223
|
if (values.id && saved.id !== values.id || !isDeepStrictEqual(saved.request, plan.request))
|
|
@@ -9136,23 +9231,23 @@ async function runSeoResearchCommand(options) {
|
|
|
9136
9231
|
action === "cancel" ? "run" : "write",
|
|
9137
9232
|
{}
|
|
9138
9233
|
);
|
|
9139
|
-
|
|
9234
|
+
z17.literal(true).parse(
|
|
9140
9235
|
record[action === "cancel" ? "cancelled" : "removed"]
|
|
9141
9236
|
);
|
|
9142
9237
|
} else if (action === "summary") {
|
|
9143
9238
|
record = await get("/summary");
|
|
9144
9239
|
resourceSchema.parse(record.resource);
|
|
9145
|
-
|
|
9240
|
+
z17.array(z17.object({ kind: z17.enum(kinds), summary: z17.unknown() })).parse(
|
|
9146
9241
|
record.checks
|
|
9147
9242
|
);
|
|
9148
9243
|
} else if (["status", "history", "saved list"].includes(action)) {
|
|
9149
9244
|
const view = await get(query);
|
|
9150
9245
|
resourceSchema.parse(view.resource);
|
|
9151
|
-
const runs =
|
|
9152
|
-
const saved =
|
|
9153
|
-
|
|
9154
|
-
id:
|
|
9155
|
-
name:
|
|
9246
|
+
const runs = z17.array(runSchema).parse(view.runs);
|
|
9247
|
+
const saved = z17.array(
|
|
9248
|
+
z17.object({
|
|
9249
|
+
id: z17.string(),
|
|
9250
|
+
name: z17.string(),
|
|
9156
9251
|
request: requestSchema
|
|
9157
9252
|
}).passthrough()
|
|
9158
9253
|
).parse(view.saved);
|
|
@@ -9268,8 +9363,8 @@ async function runSeoResearchCommand(options) {
|
|
|
9268
9363
|
}
|
|
9269
9364
|
|
|
9270
9365
|
// src/services/seo-gsc-command.ts
|
|
9271
|
-
import { parseArgs as
|
|
9272
|
-
import { z as
|
|
9366
|
+
import { parseArgs as parseArgs7 } from "util";
|
|
9367
|
+
import { z as z18 } from "zod";
|
|
9273
9368
|
var GSC_HELP = `
|
|
9274
9369
|
siteos seo gsc status [--environment <slug>] [--json]
|
|
9275
9370
|
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]
|
|
@@ -9287,7 +9382,7 @@ Bind/disconnect require a current revision and separate settings authority. Disc
|
|
|
9287
9382
|
`;
|
|
9288
9383
|
async function runSeoGscCommand(options) {
|
|
9289
9384
|
try {
|
|
9290
|
-
const { values, positionals } =
|
|
9385
|
+
const { values, positionals } = parseArgs7({
|
|
9291
9386
|
args: options.args.slice(1),
|
|
9292
9387
|
strict: true,
|
|
9293
9388
|
allowPositionals: true,
|
|
@@ -9369,13 +9464,13 @@ async function runSeoGscCommand(options) {
|
|
|
9369
9464
|
`seo:search:${action === "sync" ? "sync" : writing ? "write" : "read"}`,
|
|
9370
9465
|
body
|
|
9371
9466
|
);
|
|
9372
|
-
const runSchema =
|
|
9373
|
-
id:
|
|
9374
|
-
resourceId:
|
|
9375
|
-
organizationId:
|
|
9376
|
-
websiteUrl:
|
|
9377
|
-
siteUrl:
|
|
9378
|
-
state:
|
|
9467
|
+
const runSchema = z18.object({
|
|
9468
|
+
id: z18.string(),
|
|
9469
|
+
resourceId: z18.literal(client.resourceId),
|
|
9470
|
+
organizationId: z18.literal(client.organizationId),
|
|
9471
|
+
websiteUrl: z18.string(),
|
|
9472
|
+
siteUrl: z18.string(),
|
|
9473
|
+
state: z18.enum([
|
|
9379
9474
|
"queued",
|
|
9380
9475
|
"running",
|
|
9381
9476
|
"completed",
|
|
@@ -9383,32 +9478,32 @@ async function runSeoGscCommand(options) {
|
|
|
9383
9478
|
"failed",
|
|
9384
9479
|
"cancelled"
|
|
9385
9480
|
]),
|
|
9386
|
-
dates:
|
|
9481
|
+
dates: z18.unknown()
|
|
9387
9482
|
}).passthrough();
|
|
9388
|
-
const bindingSchema =
|
|
9389
|
-
resourceId:
|
|
9390
|
-
organizationId:
|
|
9391
|
-
websiteUrl:
|
|
9392
|
-
siteUrl:
|
|
9393
|
-
revision:
|
|
9483
|
+
const bindingSchema = z18.object({
|
|
9484
|
+
resourceId: z18.literal(client.resourceId),
|
|
9485
|
+
organizationId: z18.literal(client.organizationId),
|
|
9486
|
+
websiteUrl: z18.string(),
|
|
9487
|
+
siteUrl: z18.string(),
|
|
9488
|
+
revision: z18.number().int().positive()
|
|
9394
9489
|
}).passthrough();
|
|
9395
|
-
const metrics =
|
|
9396
|
-
clicks:
|
|
9397
|
-
impressions:
|
|
9398
|
-
ctr:
|
|
9399
|
-
position:
|
|
9490
|
+
const metrics = z18.object({
|
|
9491
|
+
clicks: z18.number(),
|
|
9492
|
+
impressions: z18.number(),
|
|
9493
|
+
ctr: z18.number(),
|
|
9494
|
+
position: z18.number()
|
|
9400
9495
|
}).passthrough().nullable();
|
|
9401
9496
|
const validateView = (data) => {
|
|
9402
9497
|
const record2 = versionedReport.parse(data);
|
|
9403
|
-
|
|
9498
|
+
z18.literal(client.resourceId).parse(record2.resourceId);
|
|
9404
9499
|
bindingSchema.nullable().parse(record2.binding);
|
|
9405
9500
|
runSchema.nullable().parse(record2.latest);
|
|
9406
9501
|
runSchema.nullable().parse(record2.report);
|
|
9407
|
-
|
|
9408
|
-
|
|
9502
|
+
z18.array(
|
|
9503
|
+
z18.object({ key: z18.string(), current: metrics, previous: metrics }).passthrough()
|
|
9409
9504
|
).parse(record2.rows);
|
|
9410
|
-
|
|
9411
|
-
|
|
9505
|
+
z18.number().int().nonnegative().parse(record2.total);
|
|
9506
|
+
z18.literal(values.dataset ?? "pages").parse(record2.dataset);
|
|
9412
9507
|
return record2;
|
|
9413
9508
|
};
|
|
9414
9509
|
if (action === "export") {
|
|
@@ -9424,9 +9519,9 @@ async function runSeoGscCommand(options) {
|
|
|
9424
9519
|
const text = await response.text();
|
|
9425
9520
|
if (values.format === "json") {
|
|
9426
9521
|
const report = validateView(JSON.parse(text));
|
|
9427
|
-
|
|
9428
|
-
|
|
9429
|
-
|
|
9522
|
+
z18.literal(reportId).parse(runSchema.parse(report.report).id);
|
|
9523
|
+
z18.array(z18.unknown()).length(rows).parse(report.rows);
|
|
9524
|
+
z18.literal(truncated === "true").parse(report.truncated);
|
|
9430
9525
|
}
|
|
9431
9526
|
const output = await writeSeoReport(options, values.output, text);
|
|
9432
9527
|
return {
|
|
@@ -9447,7 +9542,7 @@ async function runSeoGscCommand(options) {
|
|
|
9447
9542
|
};
|
|
9448
9543
|
}
|
|
9449
9544
|
const record = versionedReport.parse(await response.json());
|
|
9450
|
-
if (action === "disconnect")
|
|
9545
|
+
if (action === "disconnect") z18.literal(true).parse(record.unbound);
|
|
9451
9546
|
else if (writing) {
|
|
9452
9547
|
runSchema.parse(record.run);
|
|
9453
9548
|
if (action === "bind") {
|
|
@@ -9469,8 +9564,8 @@ import { randomUUID as randomUUID7 } from "crypto";
|
|
|
9469
9564
|
import { writeFile as writeFile9 } from "fs/promises";
|
|
9470
9565
|
import path27 from "path";
|
|
9471
9566
|
import { setTimeout as setTimeout3 } from "timers/promises";
|
|
9472
|
-
import { parseArgs as
|
|
9473
|
-
import { z as
|
|
9567
|
+
import { parseArgs as parseArgs8 } from "util";
|
|
9568
|
+
import { z as z19 } from "zod";
|
|
9474
9569
|
var PERFORMANCE_HELP = `
|
|
9475
9570
|
siteos seo performance run --audit <id> --url <url> [--url <url>...] [--device <mobile|desktop>] [--idempotency-key <key>] [--environment <slug>] [--json]
|
|
9476
9571
|
siteos seo performance list [--device <mobile|desktop>] [--environment <slug>] [--json]
|
|
@@ -9487,7 +9582,7 @@ Reuse the returned idempotency key after an uncertain run response. Wait default
|
|
|
9487
9582
|
async function runSeoPerformanceCommand(options) {
|
|
9488
9583
|
let idempotencyKey;
|
|
9489
9584
|
try {
|
|
9490
|
-
const { values, positionals } =
|
|
9585
|
+
const { values, positionals } = parseArgs8({
|
|
9491
9586
|
args: options.args.slice(1),
|
|
9492
9587
|
strict: true,
|
|
9493
9588
|
allowPositionals: true,
|
|
@@ -9558,12 +9653,12 @@ async function runSeoPerformanceCommand(options) {
|
|
|
9558
9653
|
const runtime = commonProjectRuntime(options);
|
|
9559
9654
|
const writing = ["run", "cancel"].includes(action);
|
|
9560
9655
|
const scope = writing ? "seo:audits:write" : "seo:workspace:read";
|
|
9561
|
-
const batchSchema =
|
|
9562
|
-
id:
|
|
9563
|
-
resourceId:
|
|
9564
|
-
organizationId:
|
|
9565
|
-
sourceAuditId:
|
|
9566
|
-
state:
|
|
9656
|
+
const batchSchema = z19.object({
|
|
9657
|
+
id: z19.string(),
|
|
9658
|
+
resourceId: z19.literal(context.resourceId),
|
|
9659
|
+
organizationId: z19.literal(context.overview.project.organizationId),
|
|
9660
|
+
sourceAuditId: z19.string(),
|
|
9661
|
+
state: z19.enum([
|
|
9567
9662
|
"queued",
|
|
9568
9663
|
"running",
|
|
9569
9664
|
"completed",
|
|
@@ -9571,8 +9666,8 @@ async function runSeoPerformanceCommand(options) {
|
|
|
9571
9666
|
"failed",
|
|
9572
9667
|
"cancelled"
|
|
9573
9668
|
]),
|
|
9574
|
-
device:
|
|
9575
|
-
urls:
|
|
9669
|
+
device: z19.enum(["mobile", "desktop"]),
|
|
9670
|
+
urls: z19.array(z19.string()).min(1).max(10)
|
|
9576
9671
|
}).passthrough();
|
|
9577
9672
|
const query = new URLSearchParams();
|
|
9578
9673
|
if (values.device) query.set("device", values.device);
|
|
@@ -9620,10 +9715,10 @@ async function runSeoPerformanceCommand(options) {
|
|
|
9620
9715
|
);
|
|
9621
9716
|
const text = await response.text();
|
|
9622
9717
|
if (values.format === "json") {
|
|
9623
|
-
const parsed =
|
|
9624
|
-
contractVersion:
|
|
9718
|
+
const parsed = z19.object({
|
|
9719
|
+
contractVersion: z19.literal(1),
|
|
9625
9720
|
batch: batchSchema,
|
|
9626
|
-
pages:
|
|
9721
|
+
pages: z19.array(z19.unknown())
|
|
9627
9722
|
}).parse(JSON.parse(text));
|
|
9628
9723
|
if (parsed.batch.id !== id)
|
|
9629
9724
|
throw new Error(
|
|
@@ -9646,8 +9741,8 @@ async function runSeoPerformanceCommand(options) {
|
|
|
9646
9741
|
}
|
|
9647
9742
|
const data = await response.json();
|
|
9648
9743
|
if (!response.ok) {
|
|
9649
|
-
const error =
|
|
9650
|
-
error:
|
|
9744
|
+
const error = z19.object({
|
|
9745
|
+
error: z19.object({ code: z19.string(), message: z19.string().max(500) })
|
|
9651
9746
|
}).safeParse(data);
|
|
9652
9747
|
throw new SiteOSAuthApiError({
|
|
9653
9748
|
code: error.success ? error.data.error.code : "SEO_REQUEST_FAILED",
|
|
@@ -9655,8 +9750,8 @@ async function runSeoPerformanceCommand(options) {
|
|
|
9655
9750
|
status: response.status
|
|
9656
9751
|
});
|
|
9657
9752
|
}
|
|
9658
|
-
const record =
|
|
9659
|
-
if (action === "cancel")
|
|
9753
|
+
const record = z19.object({ contractVersion: z19.literal(1) }).passthrough().parse(data);
|
|
9754
|
+
if (action === "cancel") z19.literal(true).parse(record.cancelled);
|
|
9660
9755
|
else if (action === "run") {
|
|
9661
9756
|
const accepted = batchSchema.parse(record.batch);
|
|
9662
9757
|
const normalize = (url) => {
|
|
@@ -9669,8 +9764,8 @@ async function runSeoPerformanceCommand(options) {
|
|
|
9669
9764
|
"The queued check does not match the requested source, device and URLs."
|
|
9670
9765
|
);
|
|
9671
9766
|
} else {
|
|
9672
|
-
|
|
9673
|
-
|
|
9767
|
+
z19.literal(context.resourceId).parse(record.resourceId);
|
|
9768
|
+
z19.array(batchSchema).parse(record.batches);
|
|
9674
9769
|
const selected = batchSchema.nullable().parse(record.batch);
|
|
9675
9770
|
if (id && selected?.id !== id)
|
|
9676
9771
|
throw new Error(
|
|
@@ -9697,7 +9792,7 @@ async function runSeoPerformanceCommand(options) {
|
|
|
9697
9792
|
} catch (cause) {
|
|
9698
9793
|
const error = {
|
|
9699
9794
|
code: cause instanceof SiteOSAuthApiError ? cause.code : "SEO_COMMAND_FAILED",
|
|
9700
|
-
message: cause instanceof
|
|
9795
|
+
message: cause instanceof z19.ZodError ? "The SEO service returned an invalid response." : cause instanceof Error ? cause.message : "The performance command failed."
|
|
9701
9796
|
};
|
|
9702
9797
|
return {
|
|
9703
9798
|
exitCode: cause instanceof SiteOSAuthApiError ? 1 : 2,
|
|
@@ -9733,7 +9828,8 @@ Usage:
|
|
|
9733
9828
|
siteos seo pages [--audit <id>] [--query <text>] [--url <url>] [--page <number>] [--environment <slug>] [--json]
|
|
9734
9829
|
siteos seo issues [--audit <id>] [--rule <id>] [--page <number>] [--environment <slug>] [--json]
|
|
9735
9830
|
siteos seo changes [--audit <id>] [--state <new|reopened|still_present|resolved|not_rechecked>] [--page <number>] [--environment <slug>] [--json]
|
|
9736
|
-
siteos seo
|
|
9831
|
+
siteos seo repair --audit <id> --url <url> [--url <url>...] [--rule <id>] [--resource <expected-id>] [--environment <slug>] [--json]
|
|
9832
|
+
siteos seo recheck --audit <id> --url <url> [--url <url>...] [--resource <expected-id>] [--environment <slug>] [--json]
|
|
9737
9833
|
siteos seo issue <ignore|restore> --audit <id> --url <url> --rule <id> --reason <text> --revision <number> [--environment <slug>] [--json]
|
|
9738
9834
|
siteos seo schedule show [--environment <slug>] [--json]
|
|
9739
9835
|
siteos seo schedule set --enabled <true|false> --weekday <1-7> --time <HH:mm> --timezone <IANA> --revision <number> [--environment <slug>] [--json]
|
|
@@ -9749,7 +9845,9 @@ ${GSC_HELP.trim().split("\n\n")[0]}
|
|
|
9749
9845
|
Schedule and notification writes require an owner/admin and the saved revision (initially 0).
|
|
9750
9846
|
Export writes all matching rows to a new file; existing files are never overwritten.
|
|
9751
9847
|
Runs are queued. Read audit show until terminal; an accepted run is not a completed check.
|
|
9752
|
-
|
|
9848
|
+
Repair reads a bounded brief for selected saved evidence; it does not start work.
|
|
9849
|
+
Recheck accepts 1\u201320 URLs observed in the source audit. Cross-page rules require a full audit.
|
|
9850
|
+
--resource verifies the selected environment binding before requesting a brief or recheck.
|
|
9753
9851
|
Read the current disposition revision before ignore/restore; use 0 if no decision exists.
|
|
9754
9852
|
Setup: siteos project connect seo. No crawl runs during setup.
|
|
9755
9853
|
${PERFORMANCE_HELP.trim().split("\n\n").slice(1).join("\n\n")}
|
|
@@ -9762,9 +9860,11 @@ async function runSeoCommand(options) {
|
|
|
9762
9860
|
return runSeoPerformanceCommand(options);
|
|
9763
9861
|
if (options.args[0] === "research") return runSeoResearchCommand(options);
|
|
9764
9862
|
if (options.args[0] === "gsc") return runSeoGscCommand(options);
|
|
9863
|
+
if (["repair", "recheck"].includes(options.args[0] ?? ""))
|
|
9864
|
+
return runSeoRepairCommand(options);
|
|
9765
9865
|
const json = options.args.includes("--json");
|
|
9766
9866
|
try {
|
|
9767
|
-
const { positionals, values } =
|
|
9867
|
+
const { positionals, values } = parseArgs9({
|
|
9768
9868
|
args: options.args,
|
|
9769
9869
|
strict: true,
|
|
9770
9870
|
allowPositionals: true,
|
|
@@ -9827,7 +9927,6 @@ async function runSeoCommand(options) {
|
|
|
9827
9927
|
pages: { flags: ["audit", "url", "query", "page"], args: 1 },
|
|
9828
9928
|
issues: { flags: ["audit", "rule", "page"], args: 1 },
|
|
9829
9929
|
changes: { flags: ["audit", "state", "page"], args: 1 },
|
|
9830
|
-
recheck: { flags: ["audit", "url"], args: 1 },
|
|
9831
9930
|
"issue ignore": {
|
|
9832
9931
|
flags: ["audit", "url", "rule", "reason", "revision"],
|
|
9833
9932
|
args: 2
|
|
@@ -9844,8 +9943,6 @@ async function runSeoCommand(options) {
|
|
|
9844
9943
|
throw new Error(
|
|
9845
9944
|
"Invalid SEO operation or flags. Run `siteos seo --help`."
|
|
9846
9945
|
);
|
|
9847
|
-
if (action === "recheck" && (!values.audit || !values.url))
|
|
9848
|
-
throw new Error("Recheck requires --audit and --url.");
|
|
9849
9946
|
if (action === "issue" && ["audit", "url", "rule", "reason", "revision"].some(
|
|
9850
9947
|
(key) => !values[key]
|
|
9851
9948
|
))
|
|
@@ -9895,13 +9992,9 @@ async function runSeoCommand(options) {
|
|
|
9895
9992
|
);
|
|
9896
9993
|
const runtime = commonProjectRuntime(options);
|
|
9897
9994
|
const retryNotification = route === "notifications retry";
|
|
9898
|
-
const writing = retryNotification || setting || [
|
|
9899
|
-
|
|
9900
|
-
|
|
9901
|
-
"recheck",
|
|
9902
|
-
"issue ignore",
|
|
9903
|
-
"issue restore"
|
|
9904
|
-
].includes(route);
|
|
9995
|
+
const writing = retryNotification || setting || ["audit run", "audit cancel", "issue ignore", "issue restore"].includes(
|
|
9996
|
+
route
|
|
9997
|
+
);
|
|
9905
9998
|
const scope = retryNotification ? "seo:notifications:write" : setting ? action === "schedule" ? "seo:schedule:write" : "seo:notifications:write" : !writing ? "seo:workspace:read" : action === "issue" ? "seo:issues:write" : "seo:audits:write";
|
|
9906
9999
|
const grant = await runtime.grants.acquire({
|
|
9907
10000
|
audience: "siteos-seo",
|
|
@@ -9930,7 +10023,7 @@ async function runSeoCommand(options) {
|
|
|
9930
10023
|
query.set("state", values.state);
|
|
9931
10024
|
}
|
|
9932
10025
|
if (route === "audit show") query.set("audit", positionals[2]);
|
|
9933
|
-
const suffix = retryNotification ? "/notification-retries" : automation ? setting ? `/${action}` : route === "notifications destinations" ? "/destinations" : "/automation" : action === "export" ? `/export?${query}` : route === "audit run" ? "/audits" : route === "audit cancel" ? `/audits/${encodeURIComponent(positionals[2])}/cancel` : action === "
|
|
10026
|
+
const suffix = retryNotification ? "/notification-retries" : automation ? setting ? `/${action}` : route === "notifications destinations" ? "/destinations" : "/automation" : action === "export" ? `/export?${query}` : route === "audit run" ? "/audits" : route === "audit cancel" ? `/audits/${encodeURIComponent(positionals[2])}/cancel` : action === "issue" ? "/dispositions" : `?${query}`;
|
|
9934
10027
|
const body = retryNotification ? { notificationId: positionals[2] } : route === "schedule set" ? {
|
|
9935
10028
|
enabled: values.enabled === "true",
|
|
9936
10029
|
weekday: Number(values.weekday),
|
|
@@ -9943,7 +10036,7 @@ async function runSeoCommand(options) {
|
|
|
9943
10036
|
minimumSeverity: values.severity,
|
|
9944
10037
|
includeFailures: values.failures === "true",
|
|
9945
10038
|
expectedRevision: Number(values.revision)
|
|
9946
|
-
} : action === "
|
|
10039
|
+
} : action === "issue" ? {
|
|
9947
10040
|
auditId: values.audit,
|
|
9948
10041
|
url: values.url,
|
|
9949
10042
|
ruleId: values.rule,
|
|
@@ -9977,15 +10070,15 @@ async function runSeoCommand(options) {
|
|
|
9977
10070
|
);
|
|
9978
10071
|
const text = await response.text();
|
|
9979
10072
|
if (values.format === "json")
|
|
9980
|
-
|
|
9981
|
-
contractVersion:
|
|
9982
|
-
audit:
|
|
9983
|
-
id:
|
|
9984
|
-
resourceId:
|
|
10073
|
+
z20.object({
|
|
10074
|
+
contractVersion: z20.literal(1),
|
|
10075
|
+
audit: z20.object({
|
|
10076
|
+
id: z20.literal(values.audit),
|
|
10077
|
+
resourceId: z20.literal(context.resourceId)
|
|
9985
10078
|
}),
|
|
9986
|
-
kind:
|
|
9987
|
-
totalRows:
|
|
9988
|
-
rows:
|
|
10079
|
+
kind: z20.literal(values.kind),
|
|
10080
|
+
totalRows: z20.literal(rows),
|
|
10081
|
+
rows: z20.array(z20.unknown()).length(rows)
|
|
9989
10082
|
}).parse(JSON.parse(text));
|
|
9990
10083
|
const output = path28.resolve(options.cwd ?? process.cwd(), values.output);
|
|
9991
10084
|
await writeFile10(output, text, { flag: "wx", mode: 384 });
|
|
@@ -10006,8 +10099,8 @@ async function runSeoCommand(options) {
|
|
|
10006
10099
|
}
|
|
10007
10100
|
const data = await response.json();
|
|
10008
10101
|
if (!response.ok) {
|
|
10009
|
-
const result =
|
|
10010
|
-
error:
|
|
10102
|
+
const result = z20.object({
|
|
10103
|
+
error: z20.object({ code: z20.string(), message: z20.string().max(500) })
|
|
10011
10104
|
}).safeParse(data);
|
|
10012
10105
|
throw new SiteOSAuthApiError({
|
|
10013
10106
|
code: result.success ? result.data.error.code : "SEO_REQUEST_FAILED",
|
|
@@ -10015,34 +10108,34 @@ async function runSeoCommand(options) {
|
|
|
10015
10108
|
status: response.status
|
|
10016
10109
|
});
|
|
10017
10110
|
}
|
|
10018
|
-
const record =
|
|
10111
|
+
const record = z20.object({ contractVersion: z20.literal(1) }).passthrough().parse(data);
|
|
10019
10112
|
if (automation) {
|
|
10020
|
-
|
|
10021
|
-
const schedule =
|
|
10022
|
-
enabled:
|
|
10023
|
-
weekday:
|
|
10024
|
-
time:
|
|
10025
|
-
timeZone:
|
|
10026
|
-
revision:
|
|
10027
|
-
nextRunAt:
|
|
10113
|
+
z20.literal(context.resourceId).parse(record.resourceId);
|
|
10114
|
+
const schedule = z20.object({
|
|
10115
|
+
enabled: z20.boolean(),
|
|
10116
|
+
weekday: z20.number().int().min(1).max(7),
|
|
10117
|
+
time: z20.string(),
|
|
10118
|
+
timeZone: z20.string(),
|
|
10119
|
+
revision: z20.number().int().min(0),
|
|
10120
|
+
nextRunAt: z20.string().nullable()
|
|
10028
10121
|
});
|
|
10029
|
-
const notificationRoute =
|
|
10030
|
-
enabled:
|
|
10031
|
-
minimumSeverity:
|
|
10032
|
-
includeFailures:
|
|
10033
|
-
revision:
|
|
10034
|
-
destinationId:
|
|
10122
|
+
const notificationRoute = z20.object({
|
|
10123
|
+
enabled: z20.boolean(),
|
|
10124
|
+
minimumSeverity: z20.enum(["error", "warning"]),
|
|
10125
|
+
includeFailures: z20.boolean(),
|
|
10126
|
+
revision: z20.number().int().min(0),
|
|
10127
|
+
destinationId: z20.string().nullable()
|
|
10035
10128
|
});
|
|
10036
10129
|
if (retryNotification) {
|
|
10037
|
-
|
|
10038
|
-
|
|
10130
|
+
z20.literal(true).parse(record.retryQueued);
|
|
10131
|
+
z20.literal(positionals[2]).parse(record.notificationId);
|
|
10039
10132
|
} else if (route === "notifications destinations")
|
|
10040
|
-
|
|
10041
|
-
candidates:
|
|
10042
|
-
|
|
10043
|
-
candidateId:
|
|
10044
|
-
label:
|
|
10045
|
-
availability:
|
|
10133
|
+
z20.object({
|
|
10134
|
+
candidates: z20.array(
|
|
10135
|
+
z20.object({
|
|
10136
|
+
candidateId: z20.string(),
|
|
10137
|
+
label: z20.string(),
|
|
10138
|
+
availability: z20.literal("available")
|
|
10046
10139
|
})
|
|
10047
10140
|
)
|
|
10048
10141
|
}).parse(record);
|
|
@@ -10055,46 +10148,46 @@ async function runSeoCommand(options) {
|
|
|
10055
10148
|
notificationRoute.parse(record.route);
|
|
10056
10149
|
}
|
|
10057
10150
|
} else if (!writing) {
|
|
10058
|
-
const validated =
|
|
10059
|
-
resource:
|
|
10060
|
-
id:
|
|
10061
|
-
organizationId:
|
|
10151
|
+
const validated = z20.object({
|
|
10152
|
+
resource: z20.object({
|
|
10153
|
+
id: z20.literal(context.resourceId),
|
|
10154
|
+
organizationId: z20.literal(context.overview.project.organizationId)
|
|
10062
10155
|
}),
|
|
10063
|
-
audits:
|
|
10064
|
-
audit:
|
|
10065
|
-
id:
|
|
10066
|
-
resourceId:
|
|
10156
|
+
audits: z20.array(z20.object({ id: z20.string() }).passthrough()),
|
|
10157
|
+
audit: z20.object({
|
|
10158
|
+
id: z20.string(),
|
|
10159
|
+
resourceId: z20.literal(context.resourceId)
|
|
10067
10160
|
}).passthrough().nullable(),
|
|
10068
|
-
pages:
|
|
10069
|
-
issues:
|
|
10070
|
-
changes:
|
|
10071
|
-
totalChanges:
|
|
10072
|
-
dispositions:
|
|
10161
|
+
pages: z20.array(z20.unknown()),
|
|
10162
|
+
issues: z20.array(z20.unknown()),
|
|
10163
|
+
changes: z20.array(z20.unknown()),
|
|
10164
|
+
totalChanges: z20.number(),
|
|
10165
|
+
dispositions: z20.array(z20.unknown())
|
|
10073
10166
|
}).passthrough().parse(record);
|
|
10074
10167
|
const selected = query.get("audit");
|
|
10075
10168
|
if (selected && validated.audit?.id !== selected)
|
|
10076
10169
|
throw new Error("The SEO response does not match the requested audit.");
|
|
10077
10170
|
} else if (record.audit)
|
|
10078
|
-
|
|
10079
|
-
id:
|
|
10080
|
-
resourceId:
|
|
10081
|
-
organizationId:
|
|
10082
|
-
state:
|
|
10171
|
+
z20.object({
|
|
10172
|
+
id: z20.string(),
|
|
10173
|
+
resourceId: z20.literal(context.resourceId),
|
|
10174
|
+
organizationId: z20.literal(context.overview.project.organizationId),
|
|
10175
|
+
state: z20.literal("queued")
|
|
10083
10176
|
}).parse(record.audit);
|
|
10084
|
-
else if (route === "audit cancel")
|
|
10177
|
+
else if (route === "audit cancel") z20.literal(true).parse(record.cancelled);
|
|
10085
10178
|
else if (action === "issue")
|
|
10086
|
-
|
|
10087
|
-
url:
|
|
10088
|
-
ruleId:
|
|
10089
|
-
ignored:
|
|
10090
|
-
revision:
|
|
10179
|
+
z20.object({
|
|
10180
|
+
url: z20.literal(values.url),
|
|
10181
|
+
ruleId: z20.literal(values.rule),
|
|
10182
|
+
ignored: z20.literal(positionals[1] === "ignore"),
|
|
10183
|
+
revision: z20.literal(Number(values.revision) + 1)
|
|
10091
10184
|
}).parse(record.disposition);
|
|
10092
10185
|
else throw new Error("The SEO service returned an invalid response.");
|
|
10093
10186
|
return { exitCode: 0, stdout: JSON.stringify(record, null, 2) };
|
|
10094
10187
|
} catch (cause) {
|
|
10095
10188
|
const error = {
|
|
10096
10189
|
code: cause instanceof SiteOSAuthApiError ? cause.code : "SEO_COMMAND_FAILED",
|
|
10097
|
-
message: cause instanceof
|
|
10190
|
+
message: cause instanceof z20.ZodError ? "The SEO service returned an invalid response." : cause instanceof Error ? cause.message : "The SEO command failed."
|
|
10098
10191
|
};
|
|
10099
10192
|
return {
|
|
10100
10193
|
exitCode: cause instanceof SiteOSAuthApiError ? 1 : 2,
|