@siteoshq/cli 1.4.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 +15 -1
- package/dist/cli.js +446 -89
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
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: {
|
|
@@ -3133,11 +3133,33 @@ function resolveSiteOSFormsBaseUrl(env = process.env) {
|
|
|
3133
3133
|
env[SITEOS_FORMS_BASE_URL_ENV]?.trim() || DEFAULT_SITEOS_FORMS_BASE_URL
|
|
3134
3134
|
);
|
|
3135
3135
|
}
|
|
3136
|
+
var deploymentResponseSchema = z9.object({
|
|
3137
|
+
environmentId: opaqueIdentifierSchema,
|
|
3138
|
+
forms: z9.array(
|
|
3139
|
+
z9.object({
|
|
3140
|
+
formKey: opaqueIdentifierSchema,
|
|
3141
|
+
formId: opaqueIdentifierSchema,
|
|
3142
|
+
contractVersion: z9.string().regex(/^sha256:[a-f0-9]{64}$/),
|
|
3143
|
+
versionId: opaqueIdentifierSchema,
|
|
3144
|
+
version: z9.number().int().positive()
|
|
3145
|
+
})
|
|
3146
|
+
)
|
|
3147
|
+
});
|
|
3136
3148
|
function createSiteOSFormsApiClient(options) {
|
|
3137
3149
|
const apiBaseUrl = normalizeFormsOrigin(options.apiBaseUrl);
|
|
3138
3150
|
const inboxPath = (input) => `/api/v1/forms/environments/${encodeURIComponent(input.environmentId)}/forms/${encodeURIComponent(input.formId)}/submissions`;
|
|
3139
3151
|
const definitionPath = (input) => `/api/v1/forms/environments/${encodeURIComponent(input.environmentId)}/forms/${encodeURIComponent(input.formId)}`;
|
|
3152
|
+
const keyCollection = (input) => options.deploymentKeys ? `${environmentItemPath(input)}/deployment-keys` : credentialCollectionPath(input);
|
|
3140
3153
|
return {
|
|
3154
|
+
publishDefinitions: (input) => requestJson2({
|
|
3155
|
+
apiBaseUrl,
|
|
3156
|
+
fetchImpl: options.fetchImpl,
|
|
3157
|
+
method: "POST",
|
|
3158
|
+
path: "/api/forms/deployments",
|
|
3159
|
+
body: { definitions: input.definitions },
|
|
3160
|
+
headers: { "x-siteos-forms-deployment-key": input.credential },
|
|
3161
|
+
responseSchema: deploymentResponseSchema
|
|
3162
|
+
}),
|
|
3141
3163
|
listDefinitions: (input) => requestJson2({
|
|
3142
3164
|
apiBaseUrl,
|
|
3143
3165
|
fetchImpl: options.fetchImpl,
|
|
@@ -3212,8 +3234,10 @@ function createSiteOSFormsApiClient(options) {
|
|
|
3212
3234
|
fetchImpl: options.fetchImpl,
|
|
3213
3235
|
headers: managementHeaders(input),
|
|
3214
3236
|
method: "POST",
|
|
3215
|
-
path:
|
|
3216
|
-
responseSchema: formsCredentialExchangeSchema
|
|
3237
|
+
path: keyCollection(input),
|
|
3238
|
+
responseSchema: options.deploymentKeys ? formsCredentialExchangeSchema.extend({
|
|
3239
|
+
token: z9.string().regex(/^pfd_[A-Za-z0-9_-]{43}$/)
|
|
3240
|
+
}) : formsCredentialExchangeSchema
|
|
3217
3241
|
}),
|
|
3218
3242
|
listEnvironments: (input) => requestJson2({
|
|
3219
3243
|
apiBaseUrl,
|
|
@@ -3236,7 +3260,7 @@ function createSiteOSFormsApiClient(options) {
|
|
|
3236
3260
|
fetchImpl: options.fetchImpl,
|
|
3237
3261
|
headers: managementHeaders(input),
|
|
3238
3262
|
method: "GET",
|
|
3239
|
-
path:
|
|
3263
|
+
path: keyCollection(input),
|
|
3240
3264
|
responseSchema: formsCredentialListSchema
|
|
3241
3265
|
}),
|
|
3242
3266
|
revokeCredential: (input) => requestJson2({
|
|
@@ -3244,7 +3268,7 @@ function createSiteOSFormsApiClient(options) {
|
|
|
3244
3268
|
fetchImpl: options.fetchImpl,
|
|
3245
3269
|
headers: managementHeaders(input),
|
|
3246
3270
|
method: "DELETE",
|
|
3247
|
-
path: credentialItemPath(input),
|
|
3271
|
+
path: options.deploymentKeys ? `${keyCollection(input)}/${encodeURIComponent(input.credentialId)}` : credentialItemPath(input),
|
|
3248
3272
|
responseSchema: formsCredentialMetadataSchema
|
|
3249
3273
|
}),
|
|
3250
3274
|
rotateCredential: (input) => requestJson2({
|
|
@@ -3254,7 +3278,9 @@ function createSiteOSFormsApiClient(options) {
|
|
|
3254
3278
|
headers: managementHeaders(input),
|
|
3255
3279
|
method: "POST",
|
|
3256
3280
|
path: credentialRotatePath(input),
|
|
3257
|
-
responseSchema: formsCredentialExchangeSchema
|
|
3281
|
+
responseSchema: options.deploymentKeys ? formsCredentialExchangeSchema.extend({
|
|
3282
|
+
token: z9.string().regex(/^pfd_[A-Za-z0-9_-]{43}$/)
|
|
3283
|
+
}) : formsCredentialExchangeSchema
|
|
3258
3284
|
}),
|
|
3259
3285
|
submitForm: (input) => requestJson2({
|
|
3260
3286
|
apiBaseUrl,
|
|
@@ -3288,7 +3314,9 @@ async function requestJson2(options) {
|
|
|
3288
3314
|
...options.body === void 0 ? {} : { "Content-Type": "application/json" },
|
|
3289
3315
|
...options.headers
|
|
3290
3316
|
},
|
|
3291
|
-
method: options.method
|
|
3317
|
+
method: options.method,
|
|
3318
|
+
redirect: "error",
|
|
3319
|
+
signal: AbortSignal.timeout(3e4)
|
|
3292
3320
|
});
|
|
3293
3321
|
} catch {
|
|
3294
3322
|
throw new SiteOSFormsApiError({
|
|
@@ -3381,7 +3409,7 @@ import {
|
|
|
3381
3409
|
} from "fs/promises";
|
|
3382
3410
|
import path11 from "path";
|
|
3383
3411
|
var SITEOS_FORMS_SUBMISSION_CREDENTIAL_ENV = "SITEOS_FORMS_SUBMISSION_CREDENTIAL";
|
|
3384
|
-
var
|
|
3412
|
+
var SITEOS_FORMS_DEPLOYMENT_KEY_ENV = "SITEOS_FORMS_DEPLOYMENT_KEY";
|
|
3385
3413
|
var SiteOSFormsCredentialStorageError = class extends Error {
|
|
3386
3414
|
code;
|
|
3387
3415
|
constructor(code) {
|
|
@@ -3393,6 +3421,8 @@ var SiteOSFormsCredentialStorageError = class extends Error {
|
|
|
3393
3421
|
}
|
|
3394
3422
|
};
|
|
3395
3423
|
async function prepareSiteOSFormsCredentialInstallation(options) {
|
|
3424
|
+
const variable = options.deployment ? SITEOS_FORMS_DEPLOYMENT_KEY_ENV : SITEOS_FORMS_SUBMISSION_CREDENTIAL_ENV;
|
|
3425
|
+
const credentialPattern = options.deployment ? /^pfd_[A-Za-z0-9_-]{43}$/ : /^pfs_[A-Za-z0-9_-]{22}$/;
|
|
3396
3426
|
const projectRoot = path11.resolve(options.projectRoot);
|
|
3397
3427
|
const dotenvPath = path11.join(projectRoot, ".env");
|
|
3398
3428
|
try {
|
|
@@ -3419,7 +3449,7 @@ async function prepareSiteOSFormsCredentialInstallation(options) {
|
|
|
3419
3449
|
}
|
|
3420
3450
|
const currentContent = await readSafeDotenv(dotenvPath);
|
|
3421
3451
|
await writeCredentialAtomically({
|
|
3422
|
-
content: setCredential(currentContent, credential),
|
|
3452
|
+
content: setCredential(currentContent, credential, variable),
|
|
3423
3453
|
dotenvPath,
|
|
3424
3454
|
projectRoot
|
|
3425
3455
|
});
|
|
@@ -3457,7 +3487,10 @@ async function readSafeDotenv(dotenvPath) {
|
|
|
3457
3487
|
);
|
|
3458
3488
|
}
|
|
3459
3489
|
const content = dotenvStats ? await readFile4(dotenvPath, "utf8") : "";
|
|
3460
|
-
if (
|
|
3490
|
+
if ([
|
|
3491
|
+
SITEOS_FORMS_SUBMISSION_CREDENTIAL_ENV,
|
|
3492
|
+
SITEOS_FORMS_DEPLOYMENT_KEY_ENV
|
|
3493
|
+
].some((variable) => countCredentialEntries(content, variable) > 1)) {
|
|
3461
3494
|
throw new SiteOSFormsCredentialStorageError(
|
|
3462
3495
|
"FORMS_CREDENTIAL_STORAGE_INVALID"
|
|
3463
3496
|
);
|
|
@@ -3485,21 +3518,16 @@ async function writeCredentialAtomically(options) {
|
|
|
3485
3518
|
await rm3(temporaryPath, { force: true }).catch(() => void 0);
|
|
3486
3519
|
}
|
|
3487
3520
|
}
|
|
3488
|
-
function setCredential(content, credential) {
|
|
3489
|
-
const assignment = `${
|
|
3490
|
-
const existing = new RegExp(
|
|
3491
|
-
`^${SITEOS_FORMS_SUBMISSION_CREDENTIAL_ENV}=[^\\r\\n]*(?=\\r?$)`,
|
|
3492
|
-
"m"
|
|
3493
|
-
);
|
|
3521
|
+
function setCredential(content, credential, variable) {
|
|
3522
|
+
const assignment = `${variable}=${credential}`;
|
|
3523
|
+
const existing = new RegExp(`^${variable}=[^\\r\\n]*(?=\\r?$)`, "m");
|
|
3494
3524
|
if (existing.test(content)) return content.replace(existing, assignment);
|
|
3495
3525
|
const newline = content.includes("\r\n") ? "\r\n" : "\n";
|
|
3496
3526
|
if (content.length === 0) return `${assignment}${newline}`;
|
|
3497
3527
|
return `${content}${content.endsWith("\n") ? "" : newline}${assignment}${newline}`;
|
|
3498
3528
|
}
|
|
3499
|
-
function countCredentialEntries(content) {
|
|
3500
|
-
return content.split(/\r?\n/u).filter(
|
|
3501
|
-
(line) => line.startsWith(`${SITEOS_FORMS_SUBMISSION_CREDENTIAL_ENV}=`)
|
|
3502
|
-
).length;
|
|
3529
|
+
function countCredentialEntries(content, variable) {
|
|
3530
|
+
return content.split(/\r?\n/u).filter((line) => line.startsWith(`${variable}=`)).length;
|
|
3503
3531
|
}
|
|
3504
3532
|
function isDotenvIgnored(projectRoot) {
|
|
3505
3533
|
return new Promise((resolve, reject) => {
|
|
@@ -3670,6 +3698,10 @@ var FORMS_HELP = `Usage:
|
|
|
3670
3698
|
siteos forms submissions list --environment <slug> --form <form-id> [--query <text>] [--status <status>] [--from <ISO>] [--to <ISO>] [--limit <1-100>] [--cursor <cursor>] [--json]
|
|
3671
3699
|
siteos forms submissions read --environment <slug> --form <form-id> --submission <id> [--json]
|
|
3672
3700
|
siteos forms submissions status --environment <slug> --form <form-id> --submission <id> --status <new|read|archived|spam> --expected-status <status> [--json]
|
|
3701
|
+
siteos forms deploy --manifest <path> [--json]
|
|
3702
|
+
siteos forms deployment-key issue --environment <slug> --install [--name <name>] [--json]
|
|
3703
|
+
siteos forms deployment-key list --environment <slug> [--json]
|
|
3704
|
+
siteos forms deployment-key revoke --environment <slug> --credential <id> [--json]
|
|
3673
3705
|
siteos forms submit --input <path> [--json]
|
|
3674
3706
|
|
|
3675
3707
|
Manage SiteOS Forms Environments, definitions, credentials, and submission smoke tests.`;
|
|
@@ -3681,6 +3713,8 @@ async function runFormsCommand(options) {
|
|
|
3681
3713
|
stdout: FORMS_HELP
|
|
3682
3714
|
};
|
|
3683
3715
|
}
|
|
3716
|
+
if (args2[0] === "deploy")
|
|
3717
|
+
return runDeployment({ ...options, args: args2.slice(1) });
|
|
3684
3718
|
if (args2[0] !== "submit" && args2[0] !== "project") {
|
|
3685
3719
|
try {
|
|
3686
3720
|
args2 = await commonEnvironmentArguments(options, "forms", args2);
|
|
@@ -3717,6 +3751,13 @@ async function runFormsCommand(options) {
|
|
|
3717
3751
|
args: args2.slice(1)
|
|
3718
3752
|
});
|
|
3719
3753
|
}
|
|
3754
|
+
if (args2[0] === "deployment-key") {
|
|
3755
|
+
return runCredentialCommand({
|
|
3756
|
+
...options,
|
|
3757
|
+
args: args2.slice(1),
|
|
3758
|
+
deployment: true
|
|
3759
|
+
});
|
|
3760
|
+
}
|
|
3720
3761
|
if (args2[0] === "credential") {
|
|
3721
3762
|
return runCredentialCommand({
|
|
3722
3763
|
...options,
|
|
@@ -4052,6 +4093,11 @@ async function runCredentialCommand(options) {
|
|
|
4052
4093
|
if (!action || !["issue", "list", "revoke", "rotate"].includes(action)) {
|
|
4053
4094
|
return usageError2("Unknown SiteOS forms credential command.");
|
|
4054
4095
|
}
|
|
4096
|
+
if (options.deployment && action === "rotate")
|
|
4097
|
+
return usageError2(
|
|
4098
|
+
"Issue a new deployment key, update the release secret, then revoke the old key."
|
|
4099
|
+
);
|
|
4100
|
+
const variable = options.deployment ? SITEOS_FORMS_DEPLOYMENT_KEY_ENV : SITEOS_FORMS_SUBMISSION_CREDENTIAL_ENV;
|
|
4055
4101
|
const parsed = parseCredentialFlags(action, options.args.slice(1));
|
|
4056
4102
|
if (!parsed.ok) return parsed.error;
|
|
4057
4103
|
const context = await loadCredentialManagementContext({
|
|
@@ -4123,7 +4169,7 @@ Environment: ${parsed.environmentSlug}`
|
|
|
4123
4169
|
credential,
|
|
4124
4170
|
environment: parsed.environmentSlug,
|
|
4125
4171
|
installed: {
|
|
4126
|
-
variable
|
|
4172
|
+
variable
|
|
4127
4173
|
},
|
|
4128
4174
|
operation: action === "issue" ? "issued" : "rotated"
|
|
4129
4175
|
};
|
|
@@ -4133,7 +4179,7 @@ Environment: ${parsed.environmentSlug}`
|
|
|
4133
4179
|
`SiteOS Forms credential ${output.operation} and installed.`,
|
|
4134
4180
|
`Credential: ${credential.id}`,
|
|
4135
4181
|
`Environment: ${parsed.environmentSlug}`,
|
|
4136
|
-
`Variable: ${
|
|
4182
|
+
`Variable: ${variable}`
|
|
4137
4183
|
].join("\n")
|
|
4138
4184
|
};
|
|
4139
4185
|
} catch (error) {
|
|
@@ -4144,14 +4190,21 @@ async function loadCredentialManagementContext(options) {
|
|
|
4144
4190
|
try {
|
|
4145
4191
|
const local = await readLocalCommonProject(options);
|
|
4146
4192
|
const rootDir = local?.rootDir ?? (await requireFormsProjectReference(options.cwd ?? process.cwd())).rootDir;
|
|
4147
|
-
const installation = options.install ? await (options.prepareCredentialInstallation ?? prepareSiteOSFormsCredentialInstallation)({
|
|
4193
|
+
const installation = options.install ? await (options.prepareCredentialInstallation ?? prepareSiteOSFormsCredentialInstallation)({
|
|
4194
|
+
projectRoot: rootDir,
|
|
4195
|
+
...options.deployment ? { deployment: true } : {}
|
|
4196
|
+
}) : void 0;
|
|
4148
4197
|
const context = await loadFormsManagementContext(
|
|
4149
4198
|
options,
|
|
4150
4199
|
"forms:credential:manage"
|
|
4151
4200
|
);
|
|
4152
4201
|
if (!context.ok) return context;
|
|
4153
4202
|
return {
|
|
4154
|
-
client:
|
|
4203
|
+
client: options.deployment ? createSiteOSFormsApiClient({
|
|
4204
|
+
apiBaseUrl: context.apiBaseUrl,
|
|
4205
|
+
fetchImpl: options.fetchImpl ?? fetch,
|
|
4206
|
+
deploymentKeys: true
|
|
4207
|
+
}) : context.client,
|
|
4155
4208
|
grant: context.grant,
|
|
4156
4209
|
...installation ? { installation } : {},
|
|
4157
4210
|
ok: true,
|
|
@@ -5255,6 +5308,55 @@ function createFormsUiOutput(input) {
|
|
|
5255
5308
|
url
|
|
5256
5309
|
};
|
|
5257
5310
|
}
|
|
5311
|
+
async function runDeployment(options) {
|
|
5312
|
+
const parsed = parseDefinitionSourceFlags(options.args, {
|
|
5313
|
+
environmentRequired: false,
|
|
5314
|
+
usage: "Usage: siteos forms deploy --manifest <path> [--json]"
|
|
5315
|
+
});
|
|
5316
|
+
if (!parsed.ok) return parsed.error;
|
|
5317
|
+
const loaded = await loadFormDefinitions({
|
|
5318
|
+
cwd: options.cwd,
|
|
5319
|
+
source: parsed.source
|
|
5320
|
+
});
|
|
5321
|
+
if (!loaded.ok) return loaded.error;
|
|
5322
|
+
const env = options.env ?? process.env;
|
|
5323
|
+
const credential = env[SITEOS_FORMS_DEPLOYMENT_KEY_ENV]?.trim();
|
|
5324
|
+
const apiBaseUrl = env.SITEOS_FORMS_PUBLIC_URL?.trim();
|
|
5325
|
+
if (!credential || !/^pfd_[A-Za-z0-9_-]{43}$/.test(credential) || !apiBaseUrl)
|
|
5326
|
+
return {
|
|
5327
|
+
exitCode: 1,
|
|
5328
|
+
stderr: "Configure SITEOS_FORMS_PUBLIC_URL and SITEOS_FORMS_DEPLOYMENT_KEY in the release environment."
|
|
5329
|
+
};
|
|
5330
|
+
try {
|
|
5331
|
+
const client = createSiteOSFormsApiClient({
|
|
5332
|
+
apiBaseUrl,
|
|
5333
|
+
fetchImpl: options.fetchImpl ?? fetch
|
|
5334
|
+
});
|
|
5335
|
+
const result = await client.publishDefinitions({
|
|
5336
|
+
credential,
|
|
5337
|
+
definitions: loaded.definitions.map((item) => item.value)
|
|
5338
|
+
});
|
|
5339
|
+
const expected = new Map(
|
|
5340
|
+
loaded.definitions.map((item) => [
|
|
5341
|
+
item.value.formKey,
|
|
5342
|
+
item.value.sourceExportId
|
|
5343
|
+
])
|
|
5344
|
+
);
|
|
5345
|
+
if (result.forms.length !== expected.size || new Set(result.forms.map((item) => item.formKey)).size !== expected.size || result.forms.some(
|
|
5346
|
+
(item) => !expected.has(item.formKey) || expected.get(item.formKey) !== item.contractVersion
|
|
5347
|
+
))
|
|
5348
|
+
return {
|
|
5349
|
+
exitCode: 1,
|
|
5350
|
+
stderr: "The published Forms versions do not match this build."
|
|
5351
|
+
};
|
|
5352
|
+
return {
|
|
5353
|
+
exitCode: 0,
|
|
5354
|
+
stdout: parsed.json ? stringifySafeJson(result) : `Published ${result.forms.length} Forms contracts for Environment ${result.environmentId}.`
|
|
5355
|
+
};
|
|
5356
|
+
} catch (error) {
|
|
5357
|
+
return formatApiError(error);
|
|
5358
|
+
}
|
|
5359
|
+
}
|
|
5258
5360
|
|
|
5259
5361
|
// src/package-metadata.ts
|
|
5260
5362
|
import { readFileSync } from "fs";
|
|
@@ -5299,7 +5401,7 @@ var projectSchema = z11.object({
|
|
|
5299
5401
|
}).strict();
|
|
5300
5402
|
var projectListSchema = z11.object({ projects: z11.array(projectSchema) }).strict();
|
|
5301
5403
|
var projectResponseSchema = z11.object({ project: projectSchema }).strict();
|
|
5302
|
-
var
|
|
5404
|
+
var deploymentResponseSchema2 = z11.object({
|
|
5303
5405
|
checks: z11.array(
|
|
5304
5406
|
z11.object({
|
|
5305
5407
|
checkId: z11.string().min(1),
|
|
@@ -5404,7 +5506,7 @@ function createPulseApiClient(input) {
|
|
|
5404
5506
|
grant: options.grant,
|
|
5405
5507
|
method: "POST",
|
|
5406
5508
|
path: "/api/cli/v1/deployments",
|
|
5407
|
-
schema:
|
|
5509
|
+
schema: deploymentResponseSchema2
|
|
5408
5510
|
});
|
|
5409
5511
|
}
|
|
5410
5512
|
};
|
|
@@ -9735,10 +9837,261 @@ async function runServiceCommand(service, options) {
|
|
|
9735
9837
|
}
|
|
9736
9838
|
|
|
9737
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";
|
|
9738
9847
|
import { writeFile as writeFile8 } from "fs/promises";
|
|
9739
9848
|
import path29 from "path";
|
|
9849
|
+
import { setTimeout as setTimeout2 } from "timers/promises";
|
|
9740
9850
|
import { parseArgs as parseArgs4 } from "util";
|
|
9741
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
|
|
9742
10095
|
var SEO_HELP = `Audit public HTML in the selected Project environment.
|
|
9743
10096
|
|
|
9744
10097
|
Usage:
|
|
@@ -9759,19 +10112,23 @@ Usage:
|
|
|
9759
10112
|
siteos seo notifications destinations [--environment <slug>] [--json]
|
|
9760
10113
|
siteos seo notifications set --enabled <true|false> [--destination <candidate-id>] --severity <error|warning> --failures <true|false> --revision <number> [--environment <slug>] [--json]
|
|
9761
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]}
|
|
9762
10116
|
|
|
9763
10117
|
Schedule and notification writes require an owner/admin and the saved revision (initially 0).
|
|
9764
10118
|
Export writes all matching rows to a new file; existing files are never overwritten.
|
|
9765
10119
|
Runs are queued. Read audit show until terminal; an accepted run is not a completed check.
|
|
9766
10120
|
Recheck accepts a URL observed in the source audit. Cross-page rules require a full audit.
|
|
9767
10121
|
Read the current disposition revision before ignore/restore; use 0 if no decision exists.
|
|
9768
|
-
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")}`;
|
|
9769
10124
|
async function runSeoCommand(options) {
|
|
9770
10125
|
if (!options.args.length || options.args.some((arg) => ["--help", "-h"].includes(arg)))
|
|
9771
10126
|
return { exitCode: 0, stdout: SEO_HELP };
|
|
10127
|
+
if (options.args[0] === "performance")
|
|
10128
|
+
return runSeoPerformanceCommand(options);
|
|
9772
10129
|
const json = options.args.includes("--json");
|
|
9773
10130
|
try {
|
|
9774
|
-
const { positionals, values } =
|
|
10131
|
+
const { positionals, values } = parseArgs5({
|
|
9775
10132
|
args: options.args,
|
|
9776
10133
|
strict: true,
|
|
9777
10134
|
allowPositionals: true,
|
|
@@ -9984,18 +10341,18 @@ async function runSeoCommand(options) {
|
|
|
9984
10341
|
);
|
|
9985
10342
|
const text = await response.text();
|
|
9986
10343
|
if (values.format === "json")
|
|
9987
|
-
|
|
9988
|
-
contractVersion:
|
|
9989
|
-
audit:
|
|
9990
|
-
id:
|
|
9991
|
-
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)
|
|
9992
10349
|
}),
|
|
9993
|
-
kind:
|
|
9994
|
-
totalRows:
|
|
9995
|
-
rows:
|
|
10350
|
+
kind: z17.literal(values.kind),
|
|
10351
|
+
totalRows: z17.literal(rows),
|
|
10352
|
+
rows: z17.array(z17.unknown()).length(rows)
|
|
9996
10353
|
}).parse(JSON.parse(text));
|
|
9997
|
-
const output =
|
|
9998
|
-
await
|
|
10354
|
+
const output = path30.resolve(options.cwd ?? process.cwd(), values.output);
|
|
10355
|
+
await writeFile9(output, text, { flag: "wx", mode: 384 });
|
|
9999
10356
|
return {
|
|
10000
10357
|
exitCode: 0,
|
|
10001
10358
|
stdout: JSON.stringify(
|
|
@@ -10013,8 +10370,8 @@ async function runSeoCommand(options) {
|
|
|
10013
10370
|
}
|
|
10014
10371
|
const data = await response.json();
|
|
10015
10372
|
if (!response.ok) {
|
|
10016
|
-
const result =
|
|
10017
|
-
error:
|
|
10373
|
+
const result = z17.object({
|
|
10374
|
+
error: z17.object({ code: z17.string(), message: z17.string().max(500) })
|
|
10018
10375
|
}).safeParse(data);
|
|
10019
10376
|
throw new SiteOSAuthApiError({
|
|
10020
10377
|
code: result.success ? result.data.error.code : "SEO_REQUEST_FAILED",
|
|
@@ -10022,34 +10379,34 @@ async function runSeoCommand(options) {
|
|
|
10022
10379
|
status: response.status
|
|
10023
10380
|
});
|
|
10024
10381
|
}
|
|
10025
|
-
const record =
|
|
10382
|
+
const record = z17.object({ contractVersion: z17.literal(1) }).passthrough().parse(data);
|
|
10026
10383
|
if (automation) {
|
|
10027
|
-
|
|
10028
|
-
const schedule =
|
|
10029
|
-
enabled:
|
|
10030
|
-
weekday:
|
|
10031
|
-
time:
|
|
10032
|
-
timeZone:
|
|
10033
|
-
revision:
|
|
10034
|
-
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()
|
|
10035
10392
|
});
|
|
10036
|
-
const notificationRoute =
|
|
10037
|
-
enabled:
|
|
10038
|
-
minimumSeverity:
|
|
10039
|
-
includeFailures:
|
|
10040
|
-
revision:
|
|
10041
|
-
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()
|
|
10042
10399
|
});
|
|
10043
10400
|
if (retryNotification) {
|
|
10044
|
-
|
|
10045
|
-
|
|
10401
|
+
z17.literal(true).parse(record.retryQueued);
|
|
10402
|
+
z17.literal(positionals[2]).parse(record.notificationId);
|
|
10046
10403
|
} else if (route === "notifications destinations")
|
|
10047
|
-
|
|
10048
|
-
candidates:
|
|
10049
|
-
|
|
10050
|
-
candidateId:
|
|
10051
|
-
label:
|
|
10052
|
-
availability:
|
|
10404
|
+
z17.object({
|
|
10405
|
+
candidates: z17.array(
|
|
10406
|
+
z17.object({
|
|
10407
|
+
candidateId: z17.string(),
|
|
10408
|
+
label: z17.string(),
|
|
10409
|
+
availability: z17.literal("available")
|
|
10053
10410
|
})
|
|
10054
10411
|
)
|
|
10055
10412
|
}).parse(record);
|
|
@@ -10062,46 +10419,46 @@ async function runSeoCommand(options) {
|
|
|
10062
10419
|
notificationRoute.parse(record.route);
|
|
10063
10420
|
}
|
|
10064
10421
|
} else if (!writing) {
|
|
10065
|
-
const validated =
|
|
10066
|
-
resource:
|
|
10067
|
-
id:
|
|
10068
|
-
organizationId:
|
|
10422
|
+
const validated = z17.object({
|
|
10423
|
+
resource: z17.object({
|
|
10424
|
+
id: z17.literal(context.resourceId),
|
|
10425
|
+
organizationId: z17.literal(context.overview.project.organizationId)
|
|
10069
10426
|
}),
|
|
10070
|
-
audits:
|
|
10071
|
-
audit:
|
|
10072
|
-
id:
|
|
10073
|
-
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)
|
|
10074
10431
|
}).passthrough().nullable(),
|
|
10075
|
-
pages:
|
|
10076
|
-
issues:
|
|
10077
|
-
changes:
|
|
10078
|
-
totalChanges:
|
|
10079
|
-
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())
|
|
10080
10437
|
}).passthrough().parse(record);
|
|
10081
10438
|
const selected = query.get("audit");
|
|
10082
10439
|
if (selected && validated.audit?.id !== selected)
|
|
10083
10440
|
throw new Error("The SEO response does not match the requested audit.");
|
|
10084
10441
|
} else if (record.audit)
|
|
10085
|
-
|
|
10086
|
-
id:
|
|
10087
|
-
resourceId:
|
|
10088
|
-
organizationId:
|
|
10089
|
-
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")
|
|
10090
10447
|
}).parse(record.audit);
|
|
10091
|
-
else if (route === "audit cancel")
|
|
10448
|
+
else if (route === "audit cancel") z17.literal(true).parse(record.cancelled);
|
|
10092
10449
|
else if (action === "issue")
|
|
10093
|
-
|
|
10094
|
-
url:
|
|
10095
|
-
ruleId:
|
|
10096
|
-
ignored:
|
|
10097
|
-
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)
|
|
10098
10455
|
}).parse(record.disposition);
|
|
10099
10456
|
else throw new Error("The SEO service returned an invalid response.");
|
|
10100
10457
|
return { exitCode: 0, stdout: JSON.stringify(record, null, 2) };
|
|
10101
10458
|
} catch (cause) {
|
|
10102
10459
|
const error = {
|
|
10103
10460
|
code: cause instanceof SiteOSAuthApiError ? cause.code : "SEO_COMMAND_FAILED",
|
|
10104
|
-
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."
|
|
10105
10462
|
};
|
|
10106
10463
|
return {
|
|
10107
10464
|
exitCode: cause instanceof SiteOSAuthApiError ? 1 : 2,
|