@siteoshq/cli 1.5.0 → 1.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 +1 -1
- package/dist/cli.js +319 -64
- package/dist/cli.js.map +1 -1
- package/package.json +20 -18
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# SiteOS CLI
|
|
2
2
|
|
|
3
3
|
`@siteoshq/cli` exposes one `siteos` binary for Auth, common Projects, Pulse, Cookie, Forms,
|
|
4
|
-
Search, Trace, SEO and Integrations. This source prepares version 1.
|
|
4
|
+
Search, Trace, SEO and Integrations. This source prepares version 1.6.0; publishing is a separate release.
|
|
5
5
|
Node.js 22 or newer is required.
|
|
6
6
|
|
|
7
7
|
## Install and authenticate
|
package/dist/cli.js
CHANGED
|
@@ -1499,7 +1499,7 @@ var OverviewSchema = z4.object({
|
|
|
1499
1499
|
});
|
|
1500
1500
|
function createProjectApi(input) {
|
|
1501
1501
|
const origin = resolveSiteOSAuthBaseUrl(input.env);
|
|
1502
|
-
async function request(
|
|
1502
|
+
async function request(path31, schema, body, method) {
|
|
1503
1503
|
const scope = body === void 0 ? "projects:workspace:read" : "projects:workspace:write";
|
|
1504
1504
|
const grant = await input.grants.acquire({
|
|
1505
1505
|
audience: "siteos-projects",
|
|
@@ -1513,7 +1513,7 @@ function createProjectApi(input) {
|
|
|
1513
1513
|
message: "SiteOS API access is unavailable."
|
|
1514
1514
|
});
|
|
1515
1515
|
const response = await input.fetchImpl(
|
|
1516
|
-
`${origin}/api/projects/v1/projects${
|
|
1516
|
+
`${origin}/api/projects/v1/projects${path31}`,
|
|
1517
1517
|
{
|
|
1518
1518
|
method: method ?? (body === void 0 ? "GET" : "POST"),
|
|
1519
1519
|
headers: {
|
|
@@ -9837,10 +9837,261 @@ async function runServiceCommand(service, options) {
|
|
|
9837
9837
|
}
|
|
9838
9838
|
|
|
9839
9839
|
// src/services/seo-command.ts
|
|
9840
|
+
import { writeFile as writeFile9 } from "fs/promises";
|
|
9841
|
+
import path30 from "path";
|
|
9842
|
+
import { parseArgs as parseArgs5 } from "util";
|
|
9843
|
+
import { z as z17 } from "zod";
|
|
9844
|
+
|
|
9845
|
+
// src/services/seo-performance-command.ts
|
|
9846
|
+
import { randomUUID as randomUUID6 } from "crypto";
|
|
9840
9847
|
import { writeFile as writeFile8 } from "fs/promises";
|
|
9841
9848
|
import path29 from "path";
|
|
9849
|
+
import { setTimeout as setTimeout2 } from "timers/promises";
|
|
9842
9850
|
import { parseArgs as parseArgs4 } from "util";
|
|
9843
9851
|
import { z as z16 } from "zod";
|
|
9852
|
+
var PERFORMANCE_HELP = `
|
|
9853
|
+
siteos seo performance run --audit <id> --url <url> [--url <url>...] [--device <mobile|desktop>] [--idempotency-key <key>] [--environment <slug>] [--json]
|
|
9854
|
+
siteos seo performance list [--device <mobile|desktop>] [--environment <slug>] [--json]
|
|
9855
|
+
siteos seo performance show <id> [--url <url>] [--environment <slug>] [--json]
|
|
9856
|
+
siteos seo performance wait <id> [--timeout <seconds>] [--environment <slug>] [--json]
|
|
9857
|
+
siteos seo performance cancel <id> [--environment <slug>] [--json]
|
|
9858
|
+
siteos seo performance history --url <url> [--device <mobile|desktop>] [--environment <slug>] [--json]
|
|
9859
|
+
siteos seo performance export <id> --format <csv|json> --output <new-file> [--environment <slug>] [--json]
|
|
9860
|
+
|
|
9861
|
+
Performance checks use up to 10 explicitly selected URLs from a saved full HTML audit.
|
|
9862
|
+
Mobile is the default for a new run. Scores are Lighthouse lab data, not real-user Core Web Vitals.
|
|
9863
|
+
Reuse the returned idempotency key after an uncertain run response. Wait defaults to 120 seconds (maximum 900).
|
|
9864
|
+
`;
|
|
9865
|
+
async function runSeoPerformanceCommand(options) {
|
|
9866
|
+
let idempotencyKey;
|
|
9867
|
+
try {
|
|
9868
|
+
const { values, positionals } = parseArgs4({
|
|
9869
|
+
args: options.args.slice(1),
|
|
9870
|
+
strict: true,
|
|
9871
|
+
allowPositionals: true,
|
|
9872
|
+
options: {
|
|
9873
|
+
json: { type: "boolean" },
|
|
9874
|
+
environment: { type: "string" },
|
|
9875
|
+
audit: { type: "string" },
|
|
9876
|
+
url: { type: "string", multiple: true },
|
|
9877
|
+
device: { type: "string" },
|
|
9878
|
+
"idempotency-key": { type: "string" },
|
|
9879
|
+
timeout: { type: "string" },
|
|
9880
|
+
format: { type: "string" },
|
|
9881
|
+
output: { type: "string" }
|
|
9882
|
+
}
|
|
9883
|
+
});
|
|
9884
|
+
const action = positionals[0] ?? "";
|
|
9885
|
+
const id = positionals[1];
|
|
9886
|
+
const operations = {
|
|
9887
|
+
run: { args: 1, flags: ["audit", "url", "device", "idempotency-key"] },
|
|
9888
|
+
list: { args: 1, flags: ["device"] },
|
|
9889
|
+
show: { args: 2, flags: ["url"] },
|
|
9890
|
+
wait: { args: 2, flags: ["timeout"] },
|
|
9891
|
+
cancel: { args: 2, flags: [] },
|
|
9892
|
+
history: { args: 1, flags: ["url", "device"] },
|
|
9893
|
+
export: { args: 2, flags: ["format", "output"] }
|
|
9894
|
+
};
|
|
9895
|
+
const operation = operations[action];
|
|
9896
|
+
if (!operation || positionals.length !== operation.args || Object.keys(values).some(
|
|
9897
|
+
(key) => !["environment", "json", ...operation.flags].includes(key)
|
|
9898
|
+
))
|
|
9899
|
+
throw new Error(
|
|
9900
|
+
"Invalid performance operation or flags. Run siteos seo --help."
|
|
9901
|
+
);
|
|
9902
|
+
if (values.device && !["mobile", "desktop"].includes(values.device))
|
|
9903
|
+
throw new Error("Use --device mobile or desktop.");
|
|
9904
|
+
if (action === "run" && (!values.audit || !values.url?.length || values.url.length > 10))
|
|
9905
|
+
throw new Error(
|
|
9906
|
+
"Choose a source --audit and between 1 and 10 --url values."
|
|
9907
|
+
);
|
|
9908
|
+
if (action === "history" && values.url?.length !== 1 || action === "show" && values.url && values.url.length !== 1)
|
|
9909
|
+
throw new Error("Use one --url for page evidence or history.");
|
|
9910
|
+
for (const value of values.url ?? []) {
|
|
9911
|
+
const url = new URL(value);
|
|
9912
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.port || value.length > 2048)
|
|
9913
|
+
throw new Error("Use eligible public HTTP(S) URLs.");
|
|
9914
|
+
}
|
|
9915
|
+
const timeout = Number(values.timeout ?? 120);
|
|
9916
|
+
if (!Number.isInteger(timeout) || timeout < 1 || timeout > 900)
|
|
9917
|
+
throw new Error("Use a wait timeout between 1 and 900 seconds.");
|
|
9918
|
+
if (action === "export" && (!values.output || !["csv", "json"].includes(values.format ?? "")))
|
|
9919
|
+
throw new Error(
|
|
9920
|
+
"Export requires --format csv|json and --output for a new file."
|
|
9921
|
+
);
|
|
9922
|
+
if (action === "run") {
|
|
9923
|
+
idempotencyKey = values["idempotency-key"] ?? randomUUID6();
|
|
9924
|
+
if (!/^[a-zA-Z0-9_-]{16,100}$/u.test(idempotencyKey))
|
|
9925
|
+
throw new Error(
|
|
9926
|
+
"Use an idempotency key of 16 to 100 letters, digits, underscores or hyphens."
|
|
9927
|
+
);
|
|
9928
|
+
}
|
|
9929
|
+
const context = await commonServiceContext(
|
|
9930
|
+
options,
|
|
9931
|
+
"seo",
|
|
9932
|
+
values.environment
|
|
9933
|
+
);
|
|
9934
|
+
if (!context)
|
|
9935
|
+
throw new Error("Select a SiteOS Project with siteos project use first.");
|
|
9936
|
+
const runtime = commonProjectRuntime(options);
|
|
9937
|
+
const writing = ["run", "cancel"].includes(action);
|
|
9938
|
+
const scope = writing ? "seo:audits:write" : "seo:workspace:read";
|
|
9939
|
+
const batchSchema = z16.object({
|
|
9940
|
+
id: z16.string(),
|
|
9941
|
+
resourceId: z16.literal(context.resourceId),
|
|
9942
|
+
organizationId: z16.literal(context.overview.project.organizationId),
|
|
9943
|
+
sourceAuditId: z16.string(),
|
|
9944
|
+
state: z16.enum([
|
|
9945
|
+
"queued",
|
|
9946
|
+
"running",
|
|
9947
|
+
"completed",
|
|
9948
|
+
"partial",
|
|
9949
|
+
"failed",
|
|
9950
|
+
"cancelled"
|
|
9951
|
+
]),
|
|
9952
|
+
device: z16.enum(["mobile", "desktop"]),
|
|
9953
|
+
urls: z16.array(z16.string()).min(1).max(10)
|
|
9954
|
+
}).passthrough();
|
|
9955
|
+
const query = new URLSearchParams();
|
|
9956
|
+
if (values.device) query.set("device", values.device);
|
|
9957
|
+
if (!writing && values.url?.[0]) query.set("pageUrl", values.url[0]);
|
|
9958
|
+
const suffix = id ? `/${encodeURIComponent(id)}${action === "cancel" ? "/cancel" : action === "export" ? "/export" : ""}` : "";
|
|
9959
|
+
if (action === "export") query.set("format", values.format);
|
|
9960
|
+
const deadline = Date.now() + timeout * 1e3;
|
|
9961
|
+
while (true) {
|
|
9962
|
+
const grant = await runtime.grants.acquire({
|
|
9963
|
+
audience: "siteos-seo",
|
|
9964
|
+
scopes: [scope]
|
|
9965
|
+
});
|
|
9966
|
+
if (grant.grant.audience !== "siteos-seo" || grant.grant.organizationId !== context.overview.project.organizationId || grant.grant.scopes.length !== 1 || grant.grant.scopes[0] !== scope)
|
|
9967
|
+
throw new Error(
|
|
9968
|
+
"The SEO grant does not match this Project and operation."
|
|
9969
|
+
);
|
|
9970
|
+
if (!options.fetchImpl)
|
|
9971
|
+
throw new Error("SiteOS API access is unavailable.");
|
|
9972
|
+
const response = await options.fetchImpl(
|
|
9973
|
+
`${runtime.api.origin}/api/seo/v1/resources/${encodeURIComponent(context.resourceId)}/browser-checks${suffix}?${query}`,
|
|
9974
|
+
{
|
|
9975
|
+
method: writing ? "POST" : "GET",
|
|
9976
|
+
headers: {
|
|
9977
|
+
Accept: "application/json",
|
|
9978
|
+
Authorization: `Bearer ${grant.accessToken}`,
|
|
9979
|
+
...action === "run" ? { "Content-Type": "application/json" } : {}
|
|
9980
|
+
},
|
|
9981
|
+
...action === "run" ? {
|
|
9982
|
+
body: JSON.stringify({
|
|
9983
|
+
auditId: values.audit,
|
|
9984
|
+
urls: values.url,
|
|
9985
|
+
device: values.device ?? "mobile",
|
|
9986
|
+
idempotencyKey
|
|
9987
|
+
})
|
|
9988
|
+
} : {},
|
|
9989
|
+
signal: AbortSignal.timeout(3e4)
|
|
9990
|
+
}
|
|
9991
|
+
);
|
|
9992
|
+
if (action === "export" && response.ok) {
|
|
9993
|
+
if (!(response instanceof Response) || response.headers.get("X-SEO-Browser-Id") !== id || !response.headers.get("Content-Type")?.startsWith(
|
|
9994
|
+
values.format === "csv" ? "text/csv" : "application/json"
|
|
9995
|
+
))
|
|
9996
|
+
throw new Error(
|
|
9997
|
+
"The export response does not match the selected check."
|
|
9998
|
+
);
|
|
9999
|
+
const text = await response.text();
|
|
10000
|
+
if (values.format === "json") {
|
|
10001
|
+
const parsed = z16.object({
|
|
10002
|
+
contractVersion: z16.literal(1),
|
|
10003
|
+
batch: batchSchema,
|
|
10004
|
+
pages: z16.array(z16.unknown())
|
|
10005
|
+
}).parse(JSON.parse(text));
|
|
10006
|
+
if (parsed.batch.id !== id)
|
|
10007
|
+
throw new Error(
|
|
10008
|
+
"The export response does not match the selected check."
|
|
10009
|
+
);
|
|
10010
|
+
}
|
|
10011
|
+
const output = path29.resolve(
|
|
10012
|
+
options.cwd ?? process.cwd(),
|
|
10013
|
+
values.output
|
|
10014
|
+
);
|
|
10015
|
+
await writeFile8(output, text, { flag: "wx", mode: 384 });
|
|
10016
|
+
return {
|
|
10017
|
+
exitCode: 0,
|
|
10018
|
+
stdout: JSON.stringify(
|
|
10019
|
+
{ batchId: id, output, format: values.format },
|
|
10020
|
+
null,
|
|
10021
|
+
2
|
|
10022
|
+
)
|
|
10023
|
+
};
|
|
10024
|
+
}
|
|
10025
|
+
const data = await response.json();
|
|
10026
|
+
if (!response.ok) {
|
|
10027
|
+
const error = z16.object({
|
|
10028
|
+
error: z16.object({ code: z16.string(), message: z16.string().max(500) })
|
|
10029
|
+
}).safeParse(data);
|
|
10030
|
+
throw new SiteOSAuthApiError({
|
|
10031
|
+
code: error.success ? error.data.error.code : "SEO_REQUEST_FAILED",
|
|
10032
|
+
message: error.success ? error.data.error.message : "The performance request failed.",
|
|
10033
|
+
status: response.status
|
|
10034
|
+
});
|
|
10035
|
+
}
|
|
10036
|
+
const record = z16.object({ contractVersion: z16.literal(1) }).passthrough().parse(data);
|
|
10037
|
+
if (action === "cancel") z16.literal(true).parse(record.cancelled);
|
|
10038
|
+
else if (action === "run") {
|
|
10039
|
+
const accepted = batchSchema.parse(record.batch);
|
|
10040
|
+
const normalize = (url) => {
|
|
10041
|
+
const value = new URL(url);
|
|
10042
|
+
value.hash = "";
|
|
10043
|
+
return value.href;
|
|
10044
|
+
};
|
|
10045
|
+
if (accepted.sourceAuditId !== values.audit || accepted.device !== (values.device ?? "mobile") || JSON.stringify([...new Set(accepted.urls.map(normalize))].sort()) !== JSON.stringify([...new Set(values.url.map(normalize))].sort()))
|
|
10046
|
+
throw new Error(
|
|
10047
|
+
"The queued check does not match the requested source, device and URLs."
|
|
10048
|
+
);
|
|
10049
|
+
} else {
|
|
10050
|
+
z16.literal(context.resourceId).parse(record.resourceId);
|
|
10051
|
+
z16.array(batchSchema).parse(record.batches);
|
|
10052
|
+
const selected = batchSchema.nullable().parse(record.batch);
|
|
10053
|
+
if (id && selected?.id !== id)
|
|
10054
|
+
throw new Error(
|
|
10055
|
+
"The response does not match the selected performance check."
|
|
10056
|
+
);
|
|
10057
|
+
}
|
|
10058
|
+
const batch = record.batch ? batchSchema.parse(record.batch) : null;
|
|
10059
|
+
if (action !== "wait" || !batch || !["queued", "running"].includes(batch.state))
|
|
10060
|
+
return {
|
|
10061
|
+
exitCode: 0,
|
|
10062
|
+
stdout: JSON.stringify(
|
|
10063
|
+
{ ...record, ...idempotencyKey ? { idempotencyKey } : {} },
|
|
10064
|
+
null,
|
|
10065
|
+
2
|
|
10066
|
+
)
|
|
10067
|
+
};
|
|
10068
|
+
if (Date.now() >= deadline)
|
|
10069
|
+
return {
|
|
10070
|
+
exitCode: 3,
|
|
10071
|
+
stdout: JSON.stringify({ ...record, timedOut: true }, null, 2)
|
|
10072
|
+
};
|
|
10073
|
+
await setTimeout2(Math.min(3e3, Math.max(0, deadline - Date.now())));
|
|
10074
|
+
}
|
|
10075
|
+
} catch (cause) {
|
|
10076
|
+
const error = {
|
|
10077
|
+
code: cause instanceof SiteOSAuthApiError ? cause.code : "SEO_COMMAND_FAILED",
|
|
10078
|
+
message: cause instanceof z16.ZodError ? "The SEO service returned an invalid response." : cause instanceof Error ? cause.message : "The performance command failed."
|
|
10079
|
+
};
|
|
10080
|
+
return {
|
|
10081
|
+
exitCode: cause instanceof SiteOSAuthApiError ? 1 : 2,
|
|
10082
|
+
...options.args.includes("--json") ? {
|
|
10083
|
+
stdout: JSON.stringify({
|
|
10084
|
+
error,
|
|
10085
|
+
...idempotencyKey ? { idempotencyKey } : {}
|
|
10086
|
+
})
|
|
10087
|
+
} : {
|
|
10088
|
+
stderr: `${error.message}${idempotencyKey ? ` Retry key: ${idempotencyKey}` : ""}`
|
|
10089
|
+
}
|
|
10090
|
+
};
|
|
10091
|
+
}
|
|
10092
|
+
}
|
|
10093
|
+
|
|
10094
|
+
// src/services/seo-command.ts
|
|
9844
10095
|
var SEO_HELP = `Audit public HTML in the selected Project environment.
|
|
9845
10096
|
|
|
9846
10097
|
Usage:
|
|
@@ -9861,19 +10112,23 @@ Usage:
|
|
|
9861
10112
|
siteos seo notifications destinations [--environment <slug>] [--json]
|
|
9862
10113
|
siteos seo notifications set --enabled <true|false> [--destination <candidate-id>] --severity <error|warning> --failures <true|false> --revision <number> [--environment <slug>] [--json]
|
|
9863
10114
|
siteos seo export --audit <id> --kind <pages|issues|changes> --format <csv|json> --output <new-file> [--query <text>] [--rule <id>] [--severity <error|warning|notice>] [--state <page-or-change-state>] [--environment <slug>] [--json]
|
|
10115
|
+
${PERFORMANCE_HELP.trim().split("\n\n")[0]}
|
|
9864
10116
|
|
|
9865
10117
|
Schedule and notification writes require an owner/admin and the saved revision (initially 0).
|
|
9866
10118
|
Export writes all matching rows to a new file; existing files are never overwritten.
|
|
9867
10119
|
Runs are queued. Read audit show until terminal; an accepted run is not a completed check.
|
|
9868
10120
|
Recheck accepts a URL observed in the source audit. Cross-page rules require a full audit.
|
|
9869
10121
|
Read the current disposition revision before ignore/restore; use 0 if no decision exists.
|
|
9870
|
-
Setup: siteos project connect seo. No crawl runs during setup
|
|
10122
|
+
Setup: siteos project connect seo. No crawl runs during setup.
|
|
10123
|
+
${PERFORMANCE_HELP.trim().split("\n\n").slice(1).join("\n\n")}`;
|
|
9871
10124
|
async function runSeoCommand(options) {
|
|
9872
10125
|
if (!options.args.length || options.args.some((arg) => ["--help", "-h"].includes(arg)))
|
|
9873
10126
|
return { exitCode: 0, stdout: SEO_HELP };
|
|
10127
|
+
if (options.args[0] === "performance")
|
|
10128
|
+
return runSeoPerformanceCommand(options);
|
|
9874
10129
|
const json = options.args.includes("--json");
|
|
9875
10130
|
try {
|
|
9876
|
-
const { positionals, values } =
|
|
10131
|
+
const { positionals, values } = parseArgs5({
|
|
9877
10132
|
args: options.args,
|
|
9878
10133
|
strict: true,
|
|
9879
10134
|
allowPositionals: true,
|
|
@@ -10086,18 +10341,18 @@ async function runSeoCommand(options) {
|
|
|
10086
10341
|
);
|
|
10087
10342
|
const text = await response.text();
|
|
10088
10343
|
if (values.format === "json")
|
|
10089
|
-
|
|
10090
|
-
contractVersion:
|
|
10091
|
-
audit:
|
|
10092
|
-
id:
|
|
10093
|
-
resourceId:
|
|
10344
|
+
z17.object({
|
|
10345
|
+
contractVersion: z17.literal(1),
|
|
10346
|
+
audit: z17.object({
|
|
10347
|
+
id: z17.literal(values.audit),
|
|
10348
|
+
resourceId: z17.literal(context.resourceId)
|
|
10094
10349
|
}),
|
|
10095
|
-
kind:
|
|
10096
|
-
totalRows:
|
|
10097
|
-
rows:
|
|
10350
|
+
kind: z17.literal(values.kind),
|
|
10351
|
+
totalRows: z17.literal(rows),
|
|
10352
|
+
rows: z17.array(z17.unknown()).length(rows)
|
|
10098
10353
|
}).parse(JSON.parse(text));
|
|
10099
|
-
const output =
|
|
10100
|
-
await
|
|
10354
|
+
const output = path30.resolve(options.cwd ?? process.cwd(), values.output);
|
|
10355
|
+
await writeFile9(output, text, { flag: "wx", mode: 384 });
|
|
10101
10356
|
return {
|
|
10102
10357
|
exitCode: 0,
|
|
10103
10358
|
stdout: JSON.stringify(
|
|
@@ -10115,8 +10370,8 @@ async function runSeoCommand(options) {
|
|
|
10115
10370
|
}
|
|
10116
10371
|
const data = await response.json();
|
|
10117
10372
|
if (!response.ok) {
|
|
10118
|
-
const result =
|
|
10119
|
-
error:
|
|
10373
|
+
const result = z17.object({
|
|
10374
|
+
error: z17.object({ code: z17.string(), message: z17.string().max(500) })
|
|
10120
10375
|
}).safeParse(data);
|
|
10121
10376
|
throw new SiteOSAuthApiError({
|
|
10122
10377
|
code: result.success ? result.data.error.code : "SEO_REQUEST_FAILED",
|
|
@@ -10124,34 +10379,34 @@ async function runSeoCommand(options) {
|
|
|
10124
10379
|
status: response.status
|
|
10125
10380
|
});
|
|
10126
10381
|
}
|
|
10127
|
-
const record =
|
|
10382
|
+
const record = z17.object({ contractVersion: z17.literal(1) }).passthrough().parse(data);
|
|
10128
10383
|
if (automation) {
|
|
10129
|
-
|
|
10130
|
-
const schedule =
|
|
10131
|
-
enabled:
|
|
10132
|
-
weekday:
|
|
10133
|
-
time:
|
|
10134
|
-
timeZone:
|
|
10135
|
-
revision:
|
|
10136
|
-
nextRunAt:
|
|
10384
|
+
z17.literal(context.resourceId).parse(record.resourceId);
|
|
10385
|
+
const schedule = z17.object({
|
|
10386
|
+
enabled: z17.boolean(),
|
|
10387
|
+
weekday: z17.number().int().min(1).max(7),
|
|
10388
|
+
time: z17.string(),
|
|
10389
|
+
timeZone: z17.string(),
|
|
10390
|
+
revision: z17.number().int().min(0),
|
|
10391
|
+
nextRunAt: z17.string().nullable()
|
|
10137
10392
|
});
|
|
10138
|
-
const notificationRoute =
|
|
10139
|
-
enabled:
|
|
10140
|
-
minimumSeverity:
|
|
10141
|
-
includeFailures:
|
|
10142
|
-
revision:
|
|
10143
|
-
destinationId:
|
|
10393
|
+
const notificationRoute = z17.object({
|
|
10394
|
+
enabled: z17.boolean(),
|
|
10395
|
+
minimumSeverity: z17.enum(["error", "warning"]),
|
|
10396
|
+
includeFailures: z17.boolean(),
|
|
10397
|
+
revision: z17.number().int().min(0),
|
|
10398
|
+
destinationId: z17.string().nullable()
|
|
10144
10399
|
});
|
|
10145
10400
|
if (retryNotification) {
|
|
10146
|
-
|
|
10147
|
-
|
|
10401
|
+
z17.literal(true).parse(record.retryQueued);
|
|
10402
|
+
z17.literal(positionals[2]).parse(record.notificationId);
|
|
10148
10403
|
} else if (route === "notifications destinations")
|
|
10149
|
-
|
|
10150
|
-
candidates:
|
|
10151
|
-
|
|
10152
|
-
candidateId:
|
|
10153
|
-
label:
|
|
10154
|
-
availability:
|
|
10404
|
+
z17.object({
|
|
10405
|
+
candidates: z17.array(
|
|
10406
|
+
z17.object({
|
|
10407
|
+
candidateId: z17.string(),
|
|
10408
|
+
label: z17.string(),
|
|
10409
|
+
availability: z17.literal("available")
|
|
10155
10410
|
})
|
|
10156
10411
|
)
|
|
10157
10412
|
}).parse(record);
|
|
@@ -10164,46 +10419,46 @@ async function runSeoCommand(options) {
|
|
|
10164
10419
|
notificationRoute.parse(record.route);
|
|
10165
10420
|
}
|
|
10166
10421
|
} else if (!writing) {
|
|
10167
|
-
const validated =
|
|
10168
|
-
resource:
|
|
10169
|
-
id:
|
|
10170
|
-
organizationId:
|
|
10422
|
+
const validated = z17.object({
|
|
10423
|
+
resource: z17.object({
|
|
10424
|
+
id: z17.literal(context.resourceId),
|
|
10425
|
+
organizationId: z17.literal(context.overview.project.organizationId)
|
|
10171
10426
|
}),
|
|
10172
|
-
audits:
|
|
10173
|
-
audit:
|
|
10174
|
-
id:
|
|
10175
|
-
resourceId:
|
|
10427
|
+
audits: z17.array(z17.object({ id: z17.string() }).passthrough()),
|
|
10428
|
+
audit: z17.object({
|
|
10429
|
+
id: z17.string(),
|
|
10430
|
+
resourceId: z17.literal(context.resourceId)
|
|
10176
10431
|
}).passthrough().nullable(),
|
|
10177
|
-
pages:
|
|
10178
|
-
issues:
|
|
10179
|
-
changes:
|
|
10180
|
-
totalChanges:
|
|
10181
|
-
dispositions:
|
|
10432
|
+
pages: z17.array(z17.unknown()),
|
|
10433
|
+
issues: z17.array(z17.unknown()),
|
|
10434
|
+
changes: z17.array(z17.unknown()),
|
|
10435
|
+
totalChanges: z17.number(),
|
|
10436
|
+
dispositions: z17.array(z17.unknown())
|
|
10182
10437
|
}).passthrough().parse(record);
|
|
10183
10438
|
const selected = query.get("audit");
|
|
10184
10439
|
if (selected && validated.audit?.id !== selected)
|
|
10185
10440
|
throw new Error("The SEO response does not match the requested audit.");
|
|
10186
10441
|
} else if (record.audit)
|
|
10187
|
-
|
|
10188
|
-
id:
|
|
10189
|
-
resourceId:
|
|
10190
|
-
organizationId:
|
|
10191
|
-
state:
|
|
10442
|
+
z17.object({
|
|
10443
|
+
id: z17.string(),
|
|
10444
|
+
resourceId: z17.literal(context.resourceId),
|
|
10445
|
+
organizationId: z17.literal(context.overview.project.organizationId),
|
|
10446
|
+
state: z17.literal("queued")
|
|
10192
10447
|
}).parse(record.audit);
|
|
10193
|
-
else if (route === "audit cancel")
|
|
10448
|
+
else if (route === "audit cancel") z17.literal(true).parse(record.cancelled);
|
|
10194
10449
|
else if (action === "issue")
|
|
10195
|
-
|
|
10196
|
-
url:
|
|
10197
|
-
ruleId:
|
|
10198
|
-
ignored:
|
|
10199
|
-
revision:
|
|
10450
|
+
z17.object({
|
|
10451
|
+
url: z17.literal(values.url),
|
|
10452
|
+
ruleId: z17.literal(values.rule),
|
|
10453
|
+
ignored: z17.literal(positionals[1] === "ignore"),
|
|
10454
|
+
revision: z17.literal(Number(values.revision) + 1)
|
|
10200
10455
|
}).parse(record.disposition);
|
|
10201
10456
|
else throw new Error("The SEO service returned an invalid response.");
|
|
10202
10457
|
return { exitCode: 0, stdout: JSON.stringify(record, null, 2) };
|
|
10203
10458
|
} catch (cause) {
|
|
10204
10459
|
const error = {
|
|
10205
10460
|
code: cause instanceof SiteOSAuthApiError ? cause.code : "SEO_COMMAND_FAILED",
|
|
10206
|
-
message: cause instanceof
|
|
10461
|
+
message: cause instanceof z17.ZodError ? "The SEO service returned an invalid response." : cause instanceof Error ? cause.message : "The SEO command failed."
|
|
10207
10462
|
};
|
|
10208
10463
|
return {
|
|
10209
10464
|
exitCode: cause instanceof SiteOSAuthApiError ? 1 : 2,
|