@siteoshq/cli 1.2.0 → 1.3.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 +31 -2
- package/dist/cli.js +399 -12
- package/dist/cli.js.map +1 -1
- package/package.json +3 -2
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 and Integrations. This source prepares version 1.
|
|
4
|
+
Search, Trace, SEO and Integrations. This source prepares version 1.3.0; publishing is a separate release.
|
|
5
5
|
Node.js 22 or newer is required.
|
|
6
6
|
|
|
7
7
|
## Install and authenticate
|
|
@@ -69,7 +69,7 @@ siteos project environment connect forms --environment production --resource '<f
|
|
|
69
69
|
Omit `--resource` to create a new local environment. `siteos project environment list` reports the
|
|
70
70
|
common catalog and connections. Forms/Search/Trace operational `--environment` flags accept common
|
|
71
71
|
slugs and resolve explicit native bindings; without the flag, commands use the selected environment.
|
|
72
|
-
Pulse/Cookie use separate bound resources per environment. Missing bindings fail without a fallback.
|
|
72
|
+
Pulse/Cookie/SEO use separate bound resources per environment. Missing bindings fail without a fallback.
|
|
73
73
|
Manage names and website URLs with `project update` and `project environment update <slug>`.
|
|
74
74
|
Cookie/Trace published addresses remain unchanged until explicit republication.
|
|
75
75
|
|
|
@@ -136,6 +136,35 @@ snippet is unavailable until runtime publication succeeds. Integrations connecti
|
|
|
136
136
|
Organization; provider authorization opens the shared browser flow, and notification destinations
|
|
137
137
|
are connected explicitly by each service.
|
|
138
138
|
|
|
139
|
+
### SEO (prepared for release)
|
|
140
|
+
|
|
141
|
+
Check `siteos seo --help` before use; install a matching release only after it is published. An
|
|
142
|
+
SEO-capable server returns SEO in its Project service catalog, which older strict CLI parsers may
|
|
143
|
+
reject. Coordinate the CLI update before that server rollout, then publish the matching plugin.
|
|
144
|
+
|
|
145
|
+
```sh
|
|
146
|
+
siteos project connect seo --json
|
|
147
|
+
siteos seo status --environment production --json
|
|
148
|
+
siteos seo audit run --environment production --json
|
|
149
|
+
siteos seo audit list --json
|
|
150
|
+
siteos seo audit show '<audit-id>' --json
|
|
151
|
+
siteos seo pages --audit '<audit-id>' --json
|
|
152
|
+
siteos seo issues --audit '<audit-id>' --json
|
|
153
|
+
siteos seo changes --audit '<audit-id>' --state resolved --json
|
|
154
|
+
siteos seo recheck --audit '<audit-id>' --url https://example.com/pricing --json
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
Setup performs no crawl. `audit run` queues a full HTML audit; `recheck` queues one previously
|
|
158
|
+
observed URL. Read the returned audit ID until a terminal state; a queued response is not a result.
|
|
159
|
+
`audit cancel '<audit-id>'` revokes further work. Pages and changes are paginated with `--page`.
|
|
160
|
+
|
|
161
|
+
`issue ignore` and `issue restore` require `--audit`, `--url`, `--rule`, `--reason` and the current
|
|
162
|
+
`--revision` (0 before the first decision). They use a distinct issue-write scope. Read back the
|
|
163
|
+
decision after success; a revision conflict requires refreshing it. Ignore never means resolved.
|
|
164
|
+
All SEO commands resolve the selected common Project and explicit environment binding. Current
|
|
165
|
+
Auth policy permits CLI service-grant issuance to Organization owners/admins. JSON uses contract
|
|
166
|
+
version 1 and does not include credentials. The focused plugin skill is `siteos-seo`.
|
|
167
|
+
|
|
139
168
|
## Local state and diagnostics
|
|
140
169
|
|
|
141
170
|
```sh
|
package/dist/cli.js
CHANGED
|
@@ -483,7 +483,8 @@ var siteOSServiceAudienceSchema = z.enum([
|
|
|
483
483
|
"siteos-cookie",
|
|
484
484
|
"siteos-forms",
|
|
485
485
|
"siteos-pulse",
|
|
486
|
-
"siteos-search"
|
|
486
|
+
"siteos-search",
|
|
487
|
+
"siteos-seo"
|
|
487
488
|
]);
|
|
488
489
|
var siteOSServiceScopeSchema = z.string().min(1).max(160).regex(/^[a-z][a-z0-9-]*(?::[a-z][a-z0-9-]*){2,}$/);
|
|
489
490
|
var scopeNamespaceByAudience = {
|
|
@@ -493,7 +494,8 @@ var scopeNamespaceByAudience = {
|
|
|
493
494
|
"siteos-cookie": "cookie",
|
|
494
495
|
"siteos-forms": "forms",
|
|
495
496
|
"siteos-pulse": "pulse",
|
|
496
|
-
"siteos-search": "search"
|
|
497
|
+
"siteos-search": "search",
|
|
498
|
+
"siteos-seo": "seo"
|
|
497
499
|
};
|
|
498
500
|
function matchingServiceScopes(value, context) {
|
|
499
501
|
const prefix = `${scopeNamespaceByAudience[value.audience]}:`;
|
|
@@ -1442,7 +1444,8 @@ var PROJECT_SERVICES = [
|
|
|
1442
1444
|
"cookie",
|
|
1443
1445
|
"forms",
|
|
1444
1446
|
"search",
|
|
1445
|
-
"trace"
|
|
1447
|
+
"trace",
|
|
1448
|
+
"seo"
|
|
1446
1449
|
];
|
|
1447
1450
|
var ProjectSchema = z4.object({
|
|
1448
1451
|
id: z4.string().min(1),
|
|
@@ -1496,7 +1499,7 @@ var OverviewSchema = z4.object({
|
|
|
1496
1499
|
});
|
|
1497
1500
|
function createProjectApi(input) {
|
|
1498
1501
|
const origin = resolveSiteOSAuthBaseUrl(input.env);
|
|
1499
|
-
async function request(
|
|
1502
|
+
async function request(path30, schema, body, method) {
|
|
1500
1503
|
const scope = body === void 0 ? "projects:workspace:read" : "projects:workspace:write";
|
|
1501
1504
|
const grant = await input.grants.acquire({
|
|
1502
1505
|
audience: "siteos-projects",
|
|
@@ -1510,7 +1513,7 @@ function createProjectApi(input) {
|
|
|
1510
1513
|
message: "SiteOS API access is unavailable."
|
|
1511
1514
|
});
|
|
1512
1515
|
const response = await input.fetchImpl(
|
|
1513
|
-
`${origin}/api/projects/v1/projects${
|
|
1516
|
+
`${origin}/api/projects/v1/projects${path30}`,
|
|
1514
1517
|
{
|
|
1515
1518
|
method: method ?? (body === void 0 ? "GET" : "POST"),
|
|
1516
1519
|
headers: {
|
|
@@ -1706,14 +1709,14 @@ async function commonServiceContext(options, service, environmentSlug) {
|
|
|
1706
1709
|
const binding = attachment.environments.find(
|
|
1707
1710
|
(item) => item.environmentId === context.environment.id
|
|
1708
1711
|
);
|
|
1709
|
-
if (["pulse", "cookie"].includes(service) && !binding)
|
|
1712
|
+
if (["pulse", "cookie", "seo"].includes(service) && !binding)
|
|
1710
1713
|
throw new SiteOSAuthApiError({
|
|
1711
1714
|
code: "PROJECT_ENVIRONMENT_NOT_CONFIGURED",
|
|
1712
1715
|
message: `Set up ${service} in ${context.environment.name} with \`siteos project connect ${service}\`.`
|
|
1713
1716
|
});
|
|
1714
1717
|
return {
|
|
1715
1718
|
...context,
|
|
1716
|
-
resourceId: ["pulse", "cookie"].includes(service) ? binding.resourceId : attachment.resourceId,
|
|
1719
|
+
resourceId: ["pulse", "cookie", "seo"].includes(service) ? binding.resourceId : attachment.resourceId,
|
|
1717
1720
|
environmentBinding: binding
|
|
1718
1721
|
};
|
|
1719
1722
|
}
|
|
@@ -8752,13 +8755,13 @@ Usage:
|
|
|
8752
8755
|
siteos project use <id-or-slug> [--json]
|
|
8753
8756
|
siteos project update [--name <name>] [--slug <slug>] [--url <production-url>] [--json]
|
|
8754
8757
|
siteos project status [--json]
|
|
8755
|
-
siteos project connect <pulse|cookie|forms|search|trace> [--resource <id>] [--json]
|
|
8758
|
+
siteos project connect <pulse|cookie|forms|search|trace|seo> [--resource <id>] [--json]
|
|
8756
8759
|
siteos project environment list [--json]
|
|
8757
8760
|
siteos project environment create --name <name> --slug <slug> [--url <url>] [--json]
|
|
8758
8761
|
siteos project environment use <slug> [--json]
|
|
8759
8762
|
siteos project environment update <slug> [--name <name>] [--url <url>] [--json]
|
|
8760
|
-
siteos project environment resources <pulse|cookie|forms|search|trace> [--json]
|
|
8761
|
-
siteos project environment connect <pulse|cookie|forms|search|trace> --environment <slug> [--resource <id>] [--json]
|
|
8763
|
+
siteos project environment resources <pulse|cookie|forms|search|trace|seo> [--json]
|
|
8764
|
+
siteos project environment connect <pulse|cookie|forms|search|trace|seo> --environment <slug> [--resource <id>] [--json]
|
|
8762
8765
|
|
|
8763
8766
|
Select a Project once per repository. Service commands use its configured resources.
|
|
8764
8767
|
Connect creates a draft workspace, or explicitly attaches an existing resource.
|
|
@@ -8856,7 +8859,7 @@ Run \`siteos project status\` to inspect its services.`;
|
|
|
8856
8859
|
const service = parsed.positionals[0];
|
|
8857
8860
|
if (!PROJECT_SERVICES.includes(service))
|
|
8858
8861
|
throw new Error(
|
|
8859
|
-
"Choose pulse, cookie, forms, search or
|
|
8862
|
+
"Choose pulse, cookie, forms, search, trace or seo. Organization connections use `siteos integrations`. "
|
|
8860
8863
|
);
|
|
8861
8864
|
overview = await api.connect(
|
|
8862
8865
|
overview.project.id,
|
|
@@ -8912,7 +8915,7 @@ async function runEnvironmentCommand3(options) {
|
|
|
8912
8915
|
throw new Error("Environment create requires --name and --slug.");
|
|
8913
8916
|
const service = positionals[0];
|
|
8914
8917
|
if ((action === "connect" || action === "resources") && !PROJECT_SERVICES.includes(service))
|
|
8915
|
-
throw new Error("Choose pulse, cookie, forms, search or
|
|
8918
|
+
throw new Error("Choose pulse, cookie, forms, search, trace or seo.");
|
|
8916
8919
|
if (action === "connect" && !values.environment)
|
|
8917
8920
|
throw new Error("Choose a Project environment with --environment <slug>.");
|
|
8918
8921
|
const context = await readCommonProject(
|
|
@@ -9359,6 +9362,382 @@ async function runServiceCommand(service, options) {
|
|
|
9359
9362
|
}
|
|
9360
9363
|
}
|
|
9361
9364
|
|
|
9365
|
+
// src/services/seo-command.ts
|
|
9366
|
+
import { writeFile as writeFile8 } from "fs/promises";
|
|
9367
|
+
import path29 from "path";
|
|
9368
|
+
import { parseArgs as parseArgs4 } from "util";
|
|
9369
|
+
import { z as z16 } from "zod";
|
|
9370
|
+
var SEO_HELP = `Audit public HTML in the selected Project environment.
|
|
9371
|
+
|
|
9372
|
+
Usage:
|
|
9373
|
+
siteos seo status [--environment <slug>] [--json]
|
|
9374
|
+
siteos seo audit run [--environment <slug>] [--json]
|
|
9375
|
+
siteos seo audit list [--environment <slug>] [--json]
|
|
9376
|
+
siteos seo audit show <id> [--environment <slug>] [--json]
|
|
9377
|
+
siteos seo audit cancel <id> [--environment <slug>] [--json]
|
|
9378
|
+
siteos seo pages [--audit <id>] [--query <text>] [--url <url>] [--page <number>] [--environment <slug>] [--json]
|
|
9379
|
+
siteos seo issues [--audit <id>] [--rule <id>] [--page <number>] [--environment <slug>] [--json]
|
|
9380
|
+
siteos seo changes [--audit <id>] [--state <new|reopened|still_present|resolved|not_rechecked>] [--page <number>] [--environment <slug>] [--json]
|
|
9381
|
+
siteos seo recheck --audit <id> --url <url> [--environment <slug>] [--json]
|
|
9382
|
+
siteos seo issue <ignore|restore> --audit <id> --url <url> --rule <id> --reason <text> --revision <number> [--environment <slug>] [--json]
|
|
9383
|
+
siteos seo schedule show [--environment <slug>] [--json]
|
|
9384
|
+
siteos seo schedule set --enabled <true|false> --weekday <1-7> --time <HH:mm> --timezone <IANA> --revision <number> [--environment <slug>] [--json]
|
|
9385
|
+
siteos seo notifications retry <notification-id> [--environment <slug>] [--json]
|
|
9386
|
+
siteos seo notifications show [--environment <slug>] [--json]
|
|
9387
|
+
siteos seo notifications destinations [--environment <slug>] [--json]
|
|
9388
|
+
siteos seo notifications set --enabled <true|false> [--destination <candidate-id>] --severity <error|warning> --failures <true|false> --revision <number> [--environment <slug>] [--json]
|
|
9389
|
+
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]
|
|
9390
|
+
|
|
9391
|
+
Schedule and notification writes require an owner/admin and the saved revision (initially 0).
|
|
9392
|
+
Export writes all matching rows to a new file; existing files are never overwritten.
|
|
9393
|
+
Runs are queued. Read audit show until terminal; an accepted run is not a completed check.
|
|
9394
|
+
Recheck accepts a URL observed in the source audit. Cross-page rules require a full audit.
|
|
9395
|
+
Read the current disposition revision before ignore/restore; use 0 if no decision exists.
|
|
9396
|
+
Setup: siteos project connect seo. No crawl runs during setup.`;
|
|
9397
|
+
async function runSeoCommand(options) {
|
|
9398
|
+
if (!options.args.length || options.args.some((arg) => ["--help", "-h"].includes(arg)))
|
|
9399
|
+
return { exitCode: 0, stdout: SEO_HELP };
|
|
9400
|
+
const json = options.args.includes("--json");
|
|
9401
|
+
try {
|
|
9402
|
+
const { positionals, values } = parseArgs4({
|
|
9403
|
+
args: options.args,
|
|
9404
|
+
strict: true,
|
|
9405
|
+
allowPositionals: true,
|
|
9406
|
+
options: {
|
|
9407
|
+
json: { type: "boolean" },
|
|
9408
|
+
environment: { type: "string" },
|
|
9409
|
+
audit: { type: "string" },
|
|
9410
|
+
url: { type: "string" },
|
|
9411
|
+
rule: { type: "string" },
|
|
9412
|
+
reason: { type: "string" },
|
|
9413
|
+
revision: { type: "string" },
|
|
9414
|
+
query: { type: "string" },
|
|
9415
|
+
page: { type: "string" },
|
|
9416
|
+
state: { type: "string" },
|
|
9417
|
+
enabled: { type: "string" },
|
|
9418
|
+
weekday: { type: "string" },
|
|
9419
|
+
time: { type: "string" },
|
|
9420
|
+
timezone: { type: "string" },
|
|
9421
|
+
destination: { type: "string" },
|
|
9422
|
+
severity: { type: "string" },
|
|
9423
|
+
failures: { type: "string" },
|
|
9424
|
+
kind: { type: "string" },
|
|
9425
|
+
format: { type: "string" },
|
|
9426
|
+
output: { type: "string" }
|
|
9427
|
+
}
|
|
9428
|
+
});
|
|
9429
|
+
const route = positionals.slice(0, 2).join(" ");
|
|
9430
|
+
const action = positionals[0];
|
|
9431
|
+
const operations = {
|
|
9432
|
+
status: { flags: [], args: 1 },
|
|
9433
|
+
"schedule show": { flags: [], args: 2 },
|
|
9434
|
+
"schedule set": {
|
|
9435
|
+
flags: ["enabled", "weekday", "time", "timezone", "revision"],
|
|
9436
|
+
args: 2
|
|
9437
|
+
},
|
|
9438
|
+
"notifications retry": { flags: [], args: 3 },
|
|
9439
|
+
"notifications show": { flags: [], args: 2 },
|
|
9440
|
+
"notifications destinations": { flags: [], args: 2 },
|
|
9441
|
+
"notifications set": {
|
|
9442
|
+
flags: ["enabled", "destination", "severity", "failures", "revision"],
|
|
9443
|
+
args: 2
|
|
9444
|
+
},
|
|
9445
|
+
export: {
|
|
9446
|
+
flags: [
|
|
9447
|
+
"audit",
|
|
9448
|
+
"kind",
|
|
9449
|
+
"format",
|
|
9450
|
+
"output",
|
|
9451
|
+
"query",
|
|
9452
|
+
"rule",
|
|
9453
|
+
"severity",
|
|
9454
|
+
"state"
|
|
9455
|
+
],
|
|
9456
|
+
args: 1
|
|
9457
|
+
},
|
|
9458
|
+
"audit run": { flags: [], args: 2 },
|
|
9459
|
+
"audit list": { flags: [], args: 2 },
|
|
9460
|
+
"audit show": { flags: [], args: 3 },
|
|
9461
|
+
"audit cancel": { flags: [], args: 3 },
|
|
9462
|
+
pages: { flags: ["audit", "url", "query", "page"], args: 1 },
|
|
9463
|
+
issues: { flags: ["audit", "rule", "page"], args: 1 },
|
|
9464
|
+
changes: { flags: ["audit", "state", "page"], args: 1 },
|
|
9465
|
+
recheck: { flags: ["audit", "url"], args: 1 },
|
|
9466
|
+
"issue ignore": {
|
|
9467
|
+
flags: ["audit", "url", "rule", "reason", "revision"],
|
|
9468
|
+
args: 2
|
|
9469
|
+
},
|
|
9470
|
+
"issue restore": {
|
|
9471
|
+
flags: ["audit", "url", "rule", "reason", "revision"],
|
|
9472
|
+
args: 2
|
|
9473
|
+
}
|
|
9474
|
+
};
|
|
9475
|
+
const operation = operations[route];
|
|
9476
|
+
if (!operation || positionals.length !== operation.args || Object.keys(values).some(
|
|
9477
|
+
(key) => !["json", "environment", ...operation.flags].includes(key)
|
|
9478
|
+
))
|
|
9479
|
+
throw new Error(
|
|
9480
|
+
"Invalid SEO operation or flags. Run `siteos seo --help`."
|
|
9481
|
+
);
|
|
9482
|
+
if (action === "recheck" && (!values.audit || !values.url))
|
|
9483
|
+
throw new Error("Recheck requires --audit and --url.");
|
|
9484
|
+
if (action === "issue" && ["audit", "url", "rule", "reason", "revision"].some(
|
|
9485
|
+
(key) => !values[key]
|
|
9486
|
+
))
|
|
9487
|
+
throw new Error(
|
|
9488
|
+
"Issue decisions require --audit, --url, --rule, --reason and --revision."
|
|
9489
|
+
);
|
|
9490
|
+
if (values.page && !/^[1-9]\d{0,5}$/u.test(values.page))
|
|
9491
|
+
throw new Error("Use a positive page number.");
|
|
9492
|
+
if (values.revision && !/^\d{1,9}$/u.test(values.revision))
|
|
9493
|
+
throw new Error("Use a non-negative revision.");
|
|
9494
|
+
if (values.reason && (values.reason.trim().length < 3 || values.reason.length > 500))
|
|
9495
|
+
throw new Error("Use a reason between 3 and 500 characters.");
|
|
9496
|
+
const pageStateFilter = action === "export" && values.kind !== "changes";
|
|
9497
|
+
if (values.state && !(pageStateFilter ? ["analyzed", "unavailable", "excluded", "non_html"] : ["new", "reopened", "still_present", "resolved", "not_rechecked"]).includes(values.state))
|
|
9498
|
+
throw new Error("Use a documented page or change state for this export.");
|
|
9499
|
+
const automation = ["schedule", "notifications"].includes(action ?? "");
|
|
9500
|
+
const setting = automation && positionals[1] === "set";
|
|
9501
|
+
if (setting && (!values.revision || !["true", "false"].includes(values.enabled ?? "")))
|
|
9502
|
+
throw new Error("Settings require --enabled true|false and --revision.");
|
|
9503
|
+
if (route === "schedule set") {
|
|
9504
|
+
if (!/^[1-7]$/u.test(values.weekday ?? "") || !/^([01]\d|2[0-3]):[0-5]\d$/u.test(values.time ?? "") || !values.timezone)
|
|
9505
|
+
throw new Error("Use weekday 1\u20137, HH:mm and an IANA time zone.");
|
|
9506
|
+
try {
|
|
9507
|
+
new Intl.DateTimeFormat("en", { timeZone: values.timezone });
|
|
9508
|
+
} catch {
|
|
9509
|
+
throw new Error("Use a valid IANA time zone.");
|
|
9510
|
+
}
|
|
9511
|
+
}
|
|
9512
|
+
if (route === "notifications set" && (!["error", "warning"].includes(values.severity ?? "") || !["true", "false"].includes(values.failures ?? "") || values.enabled === "true" && !values.destination))
|
|
9513
|
+
throw new Error(
|
|
9514
|
+
"Notifications require --severity, --failures and an available --destination when enabled."
|
|
9515
|
+
);
|
|
9516
|
+
if (action === "export" && (!values.audit || !["pages", "issues", "changes"].includes(values.kind ?? "") || !["csv", "json"].includes(values.format ?? "") || !values.output))
|
|
9517
|
+
throw new Error(
|
|
9518
|
+
"Export requires --audit, --kind, --format and --output."
|
|
9519
|
+
);
|
|
9520
|
+
if (values.severity && !["error", "warning", "notice"].includes(values.severity))
|
|
9521
|
+
throw new Error("Use a documented severity.");
|
|
9522
|
+
const context = await commonServiceContext(
|
|
9523
|
+
options,
|
|
9524
|
+
"seo",
|
|
9525
|
+
values.environment
|
|
9526
|
+
);
|
|
9527
|
+
if (!context)
|
|
9528
|
+
throw new Error(
|
|
9529
|
+
"Select a SiteOS Project with `siteos project use` first."
|
|
9530
|
+
);
|
|
9531
|
+
const runtime = commonProjectRuntime(options);
|
|
9532
|
+
const retryNotification = route === "notifications retry";
|
|
9533
|
+
const writing = retryNotification || setting || [
|
|
9534
|
+
"audit run",
|
|
9535
|
+
"audit cancel",
|
|
9536
|
+
"recheck",
|
|
9537
|
+
"issue ignore",
|
|
9538
|
+
"issue restore"
|
|
9539
|
+
].includes(route);
|
|
9540
|
+
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";
|
|
9541
|
+
const grant = await runtime.grants.acquire({
|
|
9542
|
+
audience: "siteos-seo",
|
|
9543
|
+
scopes: [scope]
|
|
9544
|
+
});
|
|
9545
|
+
if (grant.grant.audience !== "siteos-seo" || grant.grant.scopes.length !== 1 || grant.grant.scopes[0] !== scope || grant.grant.organizationId !== context.overview.project.organizationId)
|
|
9546
|
+
throw new SiteOSAuthApiError({
|
|
9547
|
+
code: "AUTH_INVALID_RESPONSE",
|
|
9548
|
+
message: "The SEO grant does not match this Project and operation."
|
|
9549
|
+
});
|
|
9550
|
+
const query = new URLSearchParams();
|
|
9551
|
+
for (const [flag, name] of [
|
|
9552
|
+
["audit", "audit"],
|
|
9553
|
+
["rule", "rule"],
|
|
9554
|
+
["query", "q"],
|
|
9555
|
+
["url", "pageUrl"],
|
|
9556
|
+
["page", "page"],
|
|
9557
|
+
["state", "change"],
|
|
9558
|
+
["kind", "kind"],
|
|
9559
|
+
["format", "format"],
|
|
9560
|
+
["severity", "severity"]
|
|
9561
|
+
])
|
|
9562
|
+
if (values[flag] && !writing) query.set(name, values[flag]);
|
|
9563
|
+
if (pageStateFilter && values.state) {
|
|
9564
|
+
query.delete("change");
|
|
9565
|
+
query.set("state", values.state);
|
|
9566
|
+
}
|
|
9567
|
+
if (route === "audit show") query.set("audit", positionals[2]);
|
|
9568
|
+
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 === "recheck" ? "/rechecks" : action === "issue" ? "/dispositions" : `?${query}`;
|
|
9569
|
+
const body = retryNotification ? { notificationId: positionals[2] } : route === "schedule set" ? {
|
|
9570
|
+
enabled: values.enabled === "true",
|
|
9571
|
+
weekday: Number(values.weekday),
|
|
9572
|
+
time: values.time,
|
|
9573
|
+
timeZone: values.timezone,
|
|
9574
|
+
expectedRevision: Number(values.revision)
|
|
9575
|
+
} : route === "notifications set" ? {
|
|
9576
|
+
enabled: values.enabled === "true",
|
|
9577
|
+
candidateId: values.destination ?? null,
|
|
9578
|
+
minimumSeverity: values.severity,
|
|
9579
|
+
includeFailures: values.failures === "true",
|
|
9580
|
+
expectedRevision: Number(values.revision)
|
|
9581
|
+
} : action === "recheck" ? { auditId: values.audit, urls: [values.url] } : action === "issue" ? {
|
|
9582
|
+
auditId: values.audit,
|
|
9583
|
+
url: values.url,
|
|
9584
|
+
ruleId: values.rule,
|
|
9585
|
+
reason: values.reason,
|
|
9586
|
+
expectedRevision: Number(values.revision),
|
|
9587
|
+
ignored: positionals[1] === "ignore"
|
|
9588
|
+
} : void 0;
|
|
9589
|
+
if (!options.fetchImpl)
|
|
9590
|
+
throw new Error("SiteOS API access is unavailable.");
|
|
9591
|
+
const response = await options.fetchImpl(
|
|
9592
|
+
`${runtime.api.origin}/api/seo/v1/resources/${encodeURIComponent(context.resourceId)}${suffix}`,
|
|
9593
|
+
{
|
|
9594
|
+
method: setting ? "PATCH" : writing ? "POST" : "GET",
|
|
9595
|
+
headers: {
|
|
9596
|
+
Accept: "application/json",
|
|
9597
|
+
Authorization: `Bearer ${grant.accessToken}`,
|
|
9598
|
+
...body ? { "Content-Type": "application/json" } : {}
|
|
9599
|
+
},
|
|
9600
|
+
...body ? { body: JSON.stringify(body) } : {},
|
|
9601
|
+
signal: AbortSignal.timeout(3e4)
|
|
9602
|
+
}
|
|
9603
|
+
);
|
|
9604
|
+
if (action === "export" && response.ok) {
|
|
9605
|
+
if (!(response instanceof Response))
|
|
9606
|
+
throw new Error("The API transport does not support file exports.");
|
|
9607
|
+
const mime = values.format === "csv" ? "text/csv" : "application/json";
|
|
9608
|
+
const rows = Number(response.headers.get("X-SEO-Export-Rows"));
|
|
9609
|
+
if (response.headers.get("X-SEO-Audit-Id") !== values.audit || !response.headers.get("Content-Type")?.startsWith(mime) || !response.headers.has("X-SEO-Export-Rows") || !Number.isSafeInteger(rows) || rows < 0)
|
|
9610
|
+
throw new Error(
|
|
9611
|
+
"The export response does not match the requested audit."
|
|
9612
|
+
);
|
|
9613
|
+
const text = await response.text();
|
|
9614
|
+
if (values.format === "json")
|
|
9615
|
+
z16.object({
|
|
9616
|
+
contractVersion: z16.literal(1),
|
|
9617
|
+
audit: z16.object({
|
|
9618
|
+
id: z16.literal(values.audit),
|
|
9619
|
+
resourceId: z16.literal(context.resourceId)
|
|
9620
|
+
}),
|
|
9621
|
+
kind: z16.literal(values.kind),
|
|
9622
|
+
totalRows: z16.literal(rows),
|
|
9623
|
+
rows: z16.array(z16.unknown()).length(rows)
|
|
9624
|
+
}).parse(JSON.parse(text));
|
|
9625
|
+
const output = path29.resolve(options.cwd ?? process.cwd(), values.output);
|
|
9626
|
+
await writeFile8(output, text, { flag: "wx", mode: 384 });
|
|
9627
|
+
return {
|
|
9628
|
+
exitCode: 0,
|
|
9629
|
+
stdout: JSON.stringify(
|
|
9630
|
+
{
|
|
9631
|
+
auditId: values.audit,
|
|
9632
|
+
kind: values.kind,
|
|
9633
|
+
format: values.format,
|
|
9634
|
+
rows,
|
|
9635
|
+
output
|
|
9636
|
+
},
|
|
9637
|
+
null,
|
|
9638
|
+
2
|
|
9639
|
+
)
|
|
9640
|
+
};
|
|
9641
|
+
}
|
|
9642
|
+
const data = await response.json();
|
|
9643
|
+
if (!response.ok) {
|
|
9644
|
+
const result = z16.object({
|
|
9645
|
+
error: z16.object({ code: z16.string(), message: z16.string().max(500) })
|
|
9646
|
+
}).safeParse(data);
|
|
9647
|
+
throw new SiteOSAuthApiError({
|
|
9648
|
+
code: result.success ? result.data.error.code : "SEO_REQUEST_FAILED",
|
|
9649
|
+
message: result.success ? result.data.error.message : "The SEO request failed.",
|
|
9650
|
+
status: response.status
|
|
9651
|
+
});
|
|
9652
|
+
}
|
|
9653
|
+
const record = z16.object({ contractVersion: z16.literal(1) }).passthrough().parse(data);
|
|
9654
|
+
if (automation) {
|
|
9655
|
+
z16.literal(context.resourceId).parse(record.resourceId);
|
|
9656
|
+
const schedule = z16.object({
|
|
9657
|
+
enabled: z16.boolean(),
|
|
9658
|
+
weekday: z16.number().int().min(1).max(7),
|
|
9659
|
+
time: z16.string(),
|
|
9660
|
+
timeZone: z16.string(),
|
|
9661
|
+
revision: z16.number().int().min(0),
|
|
9662
|
+
nextRunAt: z16.string().nullable()
|
|
9663
|
+
});
|
|
9664
|
+
const notificationRoute = z16.object({
|
|
9665
|
+
enabled: z16.boolean(),
|
|
9666
|
+
minimumSeverity: z16.enum(["error", "warning"]),
|
|
9667
|
+
includeFailures: z16.boolean(),
|
|
9668
|
+
revision: z16.number().int().min(0),
|
|
9669
|
+
destinationId: z16.string().nullable()
|
|
9670
|
+
});
|
|
9671
|
+
if (retryNotification) {
|
|
9672
|
+
z16.literal(true).parse(record.retryQueued);
|
|
9673
|
+
z16.literal(positionals[2]).parse(record.notificationId);
|
|
9674
|
+
} else if (route === "notifications destinations")
|
|
9675
|
+
z16.object({
|
|
9676
|
+
candidates: z16.array(
|
|
9677
|
+
z16.object({
|
|
9678
|
+
candidateId: z16.string(),
|
|
9679
|
+
label: z16.string(),
|
|
9680
|
+
availability: z16.literal("available")
|
|
9681
|
+
})
|
|
9682
|
+
)
|
|
9683
|
+
}).parse(record);
|
|
9684
|
+
else if (setting) {
|
|
9685
|
+
const value = action === "schedule" ? schedule.parse(record.schedule) : notificationRoute.parse(record.route);
|
|
9686
|
+
if (value.revision !== Number(values.revision) + 1 || value.enabled !== (values.enabled === "true"))
|
|
9687
|
+
throw new Error("The saved settings do not match this change.");
|
|
9688
|
+
} else {
|
|
9689
|
+
schedule.parse(record.schedule);
|
|
9690
|
+
notificationRoute.parse(record.route);
|
|
9691
|
+
}
|
|
9692
|
+
} else if (!writing) {
|
|
9693
|
+
const validated = z16.object({
|
|
9694
|
+
resource: z16.object({
|
|
9695
|
+
id: z16.literal(context.resourceId),
|
|
9696
|
+
organizationId: z16.literal(context.overview.project.organizationId)
|
|
9697
|
+
}),
|
|
9698
|
+
audits: z16.array(z16.object({ id: z16.string() }).passthrough()),
|
|
9699
|
+
audit: z16.object({
|
|
9700
|
+
id: z16.string(),
|
|
9701
|
+
resourceId: z16.literal(context.resourceId)
|
|
9702
|
+
}).passthrough().nullable(),
|
|
9703
|
+
pages: z16.array(z16.unknown()),
|
|
9704
|
+
issues: z16.array(z16.unknown()),
|
|
9705
|
+
changes: z16.array(z16.unknown()),
|
|
9706
|
+
totalChanges: z16.number(),
|
|
9707
|
+
dispositions: z16.array(z16.unknown())
|
|
9708
|
+
}).passthrough().parse(record);
|
|
9709
|
+
const selected = query.get("audit");
|
|
9710
|
+
if (selected && validated.audit?.id !== selected)
|
|
9711
|
+
throw new Error("The SEO response does not match the requested audit.");
|
|
9712
|
+
} else if (record.audit)
|
|
9713
|
+
z16.object({
|
|
9714
|
+
id: z16.string(),
|
|
9715
|
+
resourceId: z16.literal(context.resourceId),
|
|
9716
|
+
organizationId: z16.literal(context.overview.project.organizationId),
|
|
9717
|
+
state: z16.literal("queued")
|
|
9718
|
+
}).parse(record.audit);
|
|
9719
|
+
else if (route === "audit cancel") z16.literal(true).parse(record.cancelled);
|
|
9720
|
+
else if (action === "issue")
|
|
9721
|
+
z16.object({
|
|
9722
|
+
url: z16.literal(values.url),
|
|
9723
|
+
ruleId: z16.literal(values.rule),
|
|
9724
|
+
ignored: z16.literal(positionals[1] === "ignore"),
|
|
9725
|
+
revision: z16.literal(Number(values.revision) + 1)
|
|
9726
|
+
}).parse(record.disposition);
|
|
9727
|
+
else throw new Error("The SEO service returned an invalid response.");
|
|
9728
|
+
return { exitCode: 0, stdout: JSON.stringify(record, null, 2) };
|
|
9729
|
+
} catch (cause) {
|
|
9730
|
+
const error = {
|
|
9731
|
+
code: cause instanceof SiteOSAuthApiError ? cause.code : "SEO_COMMAND_FAILED",
|
|
9732
|
+
message: cause instanceof z16.ZodError ? "The SEO service returned an invalid response." : cause instanceof Error ? cause.message : "The SEO command failed."
|
|
9733
|
+
};
|
|
9734
|
+
return {
|
|
9735
|
+
exitCode: cause instanceof SiteOSAuthApiError ? 1 : 2,
|
|
9736
|
+
...json ? { stdout: JSON.stringify({ error }) } : { stderr: error.message }
|
|
9737
|
+
};
|
|
9738
|
+
}
|
|
9739
|
+
}
|
|
9740
|
+
|
|
9362
9741
|
// src/cli.ts
|
|
9363
9742
|
var [command, ...args] = process.argv.slice(2);
|
|
9364
9743
|
var HELP_FLAGS5 = /* @__PURE__ */ new Set(["--help", "-h"]);
|
|
@@ -9392,6 +9771,12 @@ if (!command || command === "--help" || command === "-h") {
|
|
|
9392
9771
|
}
|
|
9393
9772
|
function rootCommandRegistry() {
|
|
9394
9773
|
return createCommandRegistry({
|
|
9774
|
+
seo: (args2) => runSeoCommand({
|
|
9775
|
+
args: args2,
|
|
9776
|
+
cwd: process.cwd(),
|
|
9777
|
+
env: process.env,
|
|
9778
|
+
fetchImpl: globalThis.fetch
|
|
9779
|
+
}),
|
|
9395
9780
|
"health-check": runHealthCheck,
|
|
9396
9781
|
project: (args2) => runProjectCommand4({
|
|
9397
9782
|
args: args2,
|
|
@@ -9499,6 +9884,7 @@ Usage:
|
|
|
9499
9884
|
siteos project --help
|
|
9500
9885
|
siteos cookie --help
|
|
9501
9886
|
siteos trace --help
|
|
9887
|
+
siteos seo --help
|
|
9502
9888
|
siteos integrations --help
|
|
9503
9889
|
siteos pulse --help
|
|
9504
9890
|
siteos search --help
|
|
@@ -9510,6 +9896,7 @@ Commands:
|
|
|
9510
9896
|
project Select one Project and configure its services and environments.
|
|
9511
9897
|
cookie Configure, publish, and inspect the Project\u2019s cookie banner.
|
|
9512
9898
|
trace Configure analytics observation and inspect evidence.
|
|
9899
|
+
seo Audit HTML, inspect changes and verify fixes.
|
|
9513
9900
|
integrations Manage Organization connections and destinations.
|
|
9514
9901
|
pulse Manage monitoring checks, tests, and deployments.
|
|
9515
9902
|
search Run SiteOS search operations for a project environment.
|