@siteoshq/cli 1.3.0 → 1.4.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 +36 -0
- package/dist/cli.js +387 -15
- package/dist/cli.js.map +1 -1
- package/package.json +22 -18
package/README.md
CHANGED
|
@@ -231,3 +231,39 @@ A report records the revision, runtime, observed Edge location, scenario scope a
|
|
|
231
231
|
The check covers one route and a bounded observation window, not hidden first-party/server-side
|
|
232
232
|
tracking, every delayed interaction, visual accessibility or legal compliance. Repeat after
|
|
233
233
|
website/publication changes and preserve a separate GTM Tag Assistant check where applicable.
|
|
234
|
+
|
|
235
|
+
### Forms inbox
|
|
236
|
+
|
|
237
|
+
Use `siteos forms submissions list --environment <slug> --form <form-id> --json` to search complete
|
|
238
|
+
history. Filters include `--query`, `--status`, `--from`, `--to`, `--limit` (1–100), and `--cursor`.
|
|
239
|
+
The response contains the filtered total and a continuation cursor. Full answers and the saved
|
|
240
|
+
version's labels are available with `siteos forms submissions read --environment <slug> --form
|
|
241
|
+
<form-id> --submission <id> --json`.
|
|
242
|
+
|
|
243
|
+
`siteos forms submissions status --environment <slug> --form <form-id> --submission <id> --status
|
|
244
|
+
read --expected-status new --json` changes status without overwriting concurrent triage. Statuses
|
|
245
|
+
are `new`, `read`, `archived`, and `spam`. These management commands require the matching Forms inbox
|
|
246
|
+
server release. They use the selected common Project/environment and a scoped Auth grant.
|
|
247
|
+
`forms definition check` compiles the schema locally using the same strict Ajv policy as sync.
|
|
248
|
+
|
|
249
|
+
### Form archive and deletion
|
|
250
|
+
|
|
251
|
+
These commands require the lifecycle API on the selected server. Owner/admin rights are required
|
|
252
|
+
for changes; members can list and read. Read a form to obtain its current revision before changing it.
|
|
253
|
+
|
|
254
|
+
```sh
|
|
255
|
+
siteos forms definition list --environment production --status all --json
|
|
256
|
+
siteos forms definition read --environment production --form <form-id> --json
|
|
257
|
+
siteos forms definition archive --environment production --form <form-id> --expected-revision <revision> --json
|
|
258
|
+
siteos forms definition restore --environment production --form <form-id> --expected-revision <revision> --json
|
|
259
|
+
siteos forms definition delete --environment production --form <form-id> --json
|
|
260
|
+
siteos forms definition delete --environment production --form <form-id> --apply --confirm <form-key> --expected-revision <revision> --expected-submissions <count> --json
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Delete without `--apply` only previews the exact Project, Environment, form and submitted-answer
|
|
264
|
+
count. Use the values from that reviewed preview when applying; a stale revision or count conflicts.
|
|
265
|
+
Archive stops new submissions and preserves history. Delete permanently removes versions and
|
|
266
|
+
answers from the working database, retaining only a reserved-key tombstone. That form key cannot be
|
|
267
|
+
reused. Shared Environment credentials remain active for other forms. Sync refuses archived/deleted
|
|
268
|
+
keys and never removes forms missing from a local manifest. Backups follow deployment retention;
|
|
269
|
+
they are not an in-app restore mechanism.
|
package/dist/cli.js
CHANGED
|
@@ -2936,6 +2936,8 @@ function isCliExitCode(value) {
|
|
|
2936
2936
|
import { readFile as readFile5 } from "fs/promises";
|
|
2937
2937
|
import path12 from "path";
|
|
2938
2938
|
import { z as z10 } from "zod";
|
|
2939
|
+
import { Ajv } from "ajv";
|
|
2940
|
+
import addFormats from "ajv-formats";
|
|
2939
2941
|
|
|
2940
2942
|
// src/siteos-forms-api.ts
|
|
2941
2943
|
import { z as z9 } from "zod";
|
|
@@ -2971,12 +2973,82 @@ var formDefinitionSyncResponseSchema = z9.object({
|
|
|
2971
2973
|
success: z9.literal(true),
|
|
2972
2974
|
version: z9.number().int().positive()
|
|
2973
2975
|
}).strict();
|
|
2976
|
+
var definitionSchema = z9.object({
|
|
2977
|
+
id: opaqueIdentifierSchema,
|
|
2978
|
+
environmentId: opaqueIdentifierSchema,
|
|
2979
|
+
formKey: opaqueIdentifierSchema,
|
|
2980
|
+
name: z9.string(),
|
|
2981
|
+
sourcePagePath: z9.string().nullable(),
|
|
2982
|
+
activeVersion: z9.number().int().positive(),
|
|
2983
|
+
activeVersionId: opaqueIdentifierSchema,
|
|
2984
|
+
status: z9.enum(["active", "inactive"]),
|
|
2985
|
+
submissionCount: z9.number().int().nonnegative(),
|
|
2986
|
+
lastSubmittedAt: z9.string().datetime({ offset: true }).nullable(),
|
|
2987
|
+
createdAt: z9.string().datetime({ offset: true }),
|
|
2988
|
+
updatedAt: z9.string().datetime({ offset: true })
|
|
2989
|
+
});
|
|
2990
|
+
var definitionListSchema = z9.object({
|
|
2991
|
+
definitions: z9.array(definitionSchema)
|
|
2992
|
+
});
|
|
2993
|
+
var definitionContextSchema = z9.object({
|
|
2994
|
+
definition: definitionSchema,
|
|
2995
|
+
revision: z9.number().int().positive(),
|
|
2996
|
+
canManage: z9.boolean(),
|
|
2997
|
+
project: z9.object({ id: opaqueIdentifierSchema, name: z9.string() }),
|
|
2998
|
+
environment: z9.object({
|
|
2999
|
+
id: opaqueIdentifierSchema,
|
|
3000
|
+
name: z9.string(),
|
|
3001
|
+
slug: z9.string()
|
|
3002
|
+
})
|
|
3003
|
+
});
|
|
3004
|
+
var definitionChangeResponseSchema = z9.object({
|
|
3005
|
+
deleted: z9.boolean(),
|
|
3006
|
+
formId: opaqueIdentifierSchema
|
|
3007
|
+
});
|
|
2974
3008
|
var formSubmissionResponseSchema = z9.object({
|
|
2975
3009
|
formId: opaqueIdentifierSchema,
|
|
2976
3010
|
receivedAt: z9.string().datetime({ offset: true }),
|
|
2977
3011
|
submissionId: opaqueIdentifierSchema,
|
|
2978
3012
|
success: z9.literal(true)
|
|
2979
3013
|
}).strict();
|
|
3014
|
+
var formsSubmissionStatusSchema = z9.enum([
|
|
3015
|
+
"new",
|
|
3016
|
+
"read",
|
|
3017
|
+
"archived",
|
|
3018
|
+
"spam"
|
|
3019
|
+
]);
|
|
3020
|
+
var submissionSummarySchema = z9.object({
|
|
3021
|
+
id: opaqueIdentifierSchema,
|
|
3022
|
+
formId: opaqueIdentifierSchema,
|
|
3023
|
+
normalized: z9.record(
|
|
3024
|
+
z9.union([z9.string(), z9.number(), z9.boolean(), z9.null()])
|
|
3025
|
+
),
|
|
3026
|
+
status: formsSubmissionStatusSchema,
|
|
3027
|
+
submittedAt: z9.string().datetime({ offset: true })
|
|
3028
|
+
});
|
|
3029
|
+
var submissionDetailSchema = submissionSummarySchema.extend({
|
|
3030
|
+
payload: z9.record(z9.unknown()),
|
|
3031
|
+
fields: z9.array(
|
|
3032
|
+
z9.object({
|
|
3033
|
+
key: z9.string(),
|
|
3034
|
+
label: z9.string().optional(),
|
|
3035
|
+
kind: z9.string().optional(),
|
|
3036
|
+
displayRole: z9.enum(["primary", "secondary"]).optional(),
|
|
3037
|
+
multiple: z9.boolean().optional(),
|
|
3038
|
+
options: z9.array(z9.object({ value: z9.string(), label: z9.string() })).optional()
|
|
3039
|
+
})
|
|
3040
|
+
),
|
|
3041
|
+
version: z9.number().int().positive(),
|
|
3042
|
+
versionId: opaqueIdentifierSchema
|
|
3043
|
+
});
|
|
3044
|
+
var submissionPageSchema = z9.object({
|
|
3045
|
+
submissions: z9.array(submissionSummarySchema),
|
|
3046
|
+
total: z9.number().int().nonnegative(),
|
|
3047
|
+
nextCursor: z9.string().nullable()
|
|
3048
|
+
});
|
|
3049
|
+
var submissionDetailResponseSchema = z9.object({
|
|
3050
|
+
submission: submissionDetailSchema
|
|
3051
|
+
});
|
|
2980
3052
|
var formsCredentialMetadataSchema = z9.object({
|
|
2981
3053
|
createdAt: z9.string().datetime({ offset: true }),
|
|
2982
3054
|
id: z9.string().trim().min(1).max(255),
|
|
@@ -2993,6 +3065,9 @@ var formsCredentialExchangeSchema = formsCredentialMetadataSchema.extend({
|
|
|
2993
3065
|
token: z9.string().regex(/^pfs_[A-Za-z0-9_-]{22}$/)
|
|
2994
3066
|
});
|
|
2995
3067
|
var formsErrorCodeSchema = z9.enum([
|
|
3068
|
+
"AUTHENTICATION_REQUIRED",
|
|
3069
|
+
"FORBIDDEN",
|
|
3070
|
+
"CONFLICT",
|
|
2996
3071
|
"INVALID_REQUEST",
|
|
2997
3072
|
"INVALID_INPUT",
|
|
2998
3073
|
"VALIDATION_FAILED",
|
|
@@ -3000,6 +3075,8 @@ var formsErrorCodeSchema = z9.enum([
|
|
|
3000
3075
|
"UNAUTHORIZED",
|
|
3001
3076
|
"IDENTITY_REQUIRED",
|
|
3002
3077
|
"NOT_ALLOWED",
|
|
3078
|
+
"FORM_ARCHIVED",
|
|
3079
|
+
"FORM_DELETED",
|
|
3003
3080
|
"NOT_FOUND",
|
|
3004
3081
|
"PROJECT_NOT_FOUND",
|
|
3005
3082
|
"PROJECT_CONFLICT",
|
|
@@ -3012,6 +3089,9 @@ var formsErrorCodeSchema = z9.enum([
|
|
|
3012
3089
|
"INTERNAL_ERROR"
|
|
3013
3090
|
]);
|
|
3014
3091
|
var safeFormsErrorMessages = {
|
|
3092
|
+
AUTHENTICATION_REQUIRED: "Sign in to SiteOS before reading Forms submissions.",
|
|
3093
|
+
FORBIDDEN: "Forms management access is denied.",
|
|
3094
|
+
CONFLICT: "The resource changed or this idempotency key belongs to different data. Read the current state before retrying.",
|
|
3015
3095
|
INVALID_REQUEST: "The Forms request is invalid.",
|
|
3016
3096
|
INVALID_INPUT: "Invalid input data.",
|
|
3017
3097
|
VALIDATION_FAILED: "Validation failed.",
|
|
@@ -3019,6 +3099,8 @@ var safeFormsErrorMessages = {
|
|
|
3019
3099
|
UNAUTHORIZED: "Forms management authorization is invalid.",
|
|
3020
3100
|
IDENTITY_REQUIRED: "Forms browser identity is required.",
|
|
3021
3101
|
NOT_ALLOWED: "Forms management access is denied.",
|
|
3102
|
+
FORM_ARCHIVED: "This form is archived. Restore it explicitly before syncing or sending new submissions.",
|
|
3103
|
+
FORM_DELETED: "This form was permanently deleted. Use a new form key for a new form.",
|
|
3022
3104
|
NOT_FOUND: "The requested resource was not found.",
|
|
3023
3105
|
PROJECT_NOT_FOUND: "The Forms Project was not found.",
|
|
3024
3106
|
PROJECT_CONFLICT: "The Forms Project already exists.",
|
|
@@ -3053,7 +3135,59 @@ function resolveSiteOSFormsBaseUrl(env = process.env) {
|
|
|
3053
3135
|
}
|
|
3054
3136
|
function createSiteOSFormsApiClient(options) {
|
|
3055
3137
|
const apiBaseUrl = normalizeFormsOrigin(options.apiBaseUrl);
|
|
3138
|
+
const inboxPath = (input) => `/api/v1/forms/environments/${encodeURIComponent(input.environmentId)}/forms/${encodeURIComponent(input.formId)}/submissions`;
|
|
3139
|
+
const definitionPath = (input) => `/api/v1/forms/environments/${encodeURIComponent(input.environmentId)}/forms/${encodeURIComponent(input.formId)}`;
|
|
3056
3140
|
return {
|
|
3141
|
+
listDefinitions: (input) => requestJson2({
|
|
3142
|
+
apiBaseUrl,
|
|
3143
|
+
fetchImpl: options.fetchImpl,
|
|
3144
|
+
headers: managementHeaders(input),
|
|
3145
|
+
method: "GET",
|
|
3146
|
+
path: `/api/v1/forms/environments/${encodeURIComponent(input.environmentId)}/definitions?status=${input.status}`,
|
|
3147
|
+
responseSchema: definitionListSchema
|
|
3148
|
+
}),
|
|
3149
|
+
readDefinition: (input) => requestJson2({
|
|
3150
|
+
apiBaseUrl,
|
|
3151
|
+
fetchImpl: options.fetchImpl,
|
|
3152
|
+
headers: managementHeaders(input),
|
|
3153
|
+
method: "GET",
|
|
3154
|
+
path: definitionPath(input),
|
|
3155
|
+
responseSchema: definitionContextSchema
|
|
3156
|
+
}),
|
|
3157
|
+
changeDefinition: (input) => requestJson2({
|
|
3158
|
+
apiBaseUrl,
|
|
3159
|
+
fetchImpl: options.fetchImpl,
|
|
3160
|
+
headers: managementHeaders(input),
|
|
3161
|
+
method: "PATCH",
|
|
3162
|
+
path: definitionPath(input),
|
|
3163
|
+
body: input.change,
|
|
3164
|
+
responseSchema: definitionChangeResponseSchema
|
|
3165
|
+
}),
|
|
3166
|
+
listSubmissions: (input) => requestJson2({
|
|
3167
|
+
apiBaseUrl,
|
|
3168
|
+
fetchImpl: options.fetchImpl,
|
|
3169
|
+
headers: managementHeaders(input),
|
|
3170
|
+
method: "GET",
|
|
3171
|
+
path: `${inboxPath(input)}?${input.filters}`,
|
|
3172
|
+
responseSchema: submissionPageSchema
|
|
3173
|
+
}),
|
|
3174
|
+
readSubmission: (input) => requestJson2({
|
|
3175
|
+
apiBaseUrl,
|
|
3176
|
+
fetchImpl: options.fetchImpl,
|
|
3177
|
+
headers: managementHeaders(input),
|
|
3178
|
+
method: "GET",
|
|
3179
|
+
path: `${inboxPath(input)}/${encodeURIComponent(input.submissionId)}`,
|
|
3180
|
+
responseSchema: submissionDetailResponseSchema
|
|
3181
|
+
}),
|
|
3182
|
+
updateSubmissionStatus: (input) => requestJson2({
|
|
3183
|
+
apiBaseUrl,
|
|
3184
|
+
fetchImpl: options.fetchImpl,
|
|
3185
|
+
headers: managementHeaders(input),
|
|
3186
|
+
method: "PATCH",
|
|
3187
|
+
path: `${inboxPath(input)}/${encodeURIComponent(input.submissionId)}`,
|
|
3188
|
+
body: { status: input.status, expectedStatus: input.expectedStatus },
|
|
3189
|
+
responseSchema: submissionDetailResponseSchema
|
|
3190
|
+
}),
|
|
3057
3191
|
createProject: (input) => requestJson2({
|
|
3058
3192
|
apiBaseUrl,
|
|
3059
3193
|
body: { name: input.name, slug: input.slug },
|
|
@@ -3407,25 +3541,40 @@ var SLUG_FLAG = "--slug";
|
|
|
3407
3541
|
var CREDENTIAL_FLAG = "--credential";
|
|
3408
3542
|
var REPLACE_FLAG = "--replace";
|
|
3409
3543
|
var normalizedFieldOptionSchema = z10.object({
|
|
3410
|
-
label: z10.string().trim().min(1),
|
|
3411
|
-
value: z10.string().trim().min(1)
|
|
3544
|
+
label: z10.string().trim().min(1).max(255),
|
|
3545
|
+
value: z10.string().trim().min(1).max(255)
|
|
3412
3546
|
});
|
|
3413
3547
|
var normalizedFieldSchema = z10.object({
|
|
3414
|
-
key: z10.string().trim().min(1),
|
|
3415
|
-
kind: z10.string().trim().min(1).optional(),
|
|
3416
|
-
label: z10.string().trim().min(1).optional(),
|
|
3548
|
+
key: z10.string().trim().min(1).max(255),
|
|
3549
|
+
kind: z10.string().trim().min(1).max(64).optional(),
|
|
3550
|
+
label: z10.string().trim().min(1).max(255).optional(),
|
|
3417
3551
|
displayRole: z10.enum(["primary", "secondary"]).optional(),
|
|
3418
3552
|
multiple: z10.boolean().optional(),
|
|
3419
|
-
options: z10.array(normalizedFieldOptionSchema).optional()
|
|
3420
|
-
}).
|
|
3553
|
+
options: z10.array(normalizedFieldOptionSchema).max(500).optional()
|
|
3554
|
+
}).strict();
|
|
3421
3555
|
var definitionSyncInputSchema = z10.object({
|
|
3422
|
-
formKey: z10.string().trim().min(1),
|
|
3423
|
-
name: z10.string().trim().min(1),
|
|
3424
|
-
normalizedFieldsJson: z10.array(normalizedFieldSchema).min(1),
|
|
3556
|
+
formKey: z10.string().trim().min(1).max(255),
|
|
3557
|
+
name: z10.string().trim().min(1).max(255),
|
|
3558
|
+
normalizedFieldsJson: z10.array(normalizedFieldSchema).min(1).max(500),
|
|
3425
3559
|
schemaJson: z10.record(z10.unknown()),
|
|
3426
|
-
sourceExportId: z10.string().trim().min(1).optional(),
|
|
3427
|
-
sourcePagePath: z10.string().trim().min(1).optional()
|
|
3428
|
-
}).
|
|
3560
|
+
sourceExportId: z10.string().trim().min(1).max(255).optional(),
|
|
3561
|
+
sourcePagePath: z10.string().trim().min(1).max(2048).optional()
|
|
3562
|
+
}).strict().superRefine((definition, context) => {
|
|
3563
|
+
const ajv = new Ajv({
|
|
3564
|
+
allErrors: true,
|
|
3565
|
+
removeAdditional: false,
|
|
3566
|
+
strict: true
|
|
3567
|
+
});
|
|
3568
|
+
addFormats.default(ajv);
|
|
3569
|
+
try {
|
|
3570
|
+
ajv.compile(definition.schemaJson);
|
|
3571
|
+
} catch {
|
|
3572
|
+
context.addIssue({
|
|
3573
|
+
code: z10.ZodIssueCode.custom,
|
|
3574
|
+
message: "schemaJson must be an executable JSON Schema supported by SiteOS Forms.",
|
|
3575
|
+
path: ["schemaJson"]
|
|
3576
|
+
});
|
|
3577
|
+
}
|
|
3429
3578
|
const fields = definition.normalizedFieldsJson;
|
|
3430
3579
|
const duplicateKeys = fields.map((field) => field.key).filter((key, index, keys) => keys.indexOf(key) !== index);
|
|
3431
3580
|
for (const key of new Set(duplicateKeys)) {
|
|
@@ -3506,6 +3655,11 @@ var FORMS_HELP = `Usage:
|
|
|
3506
3655
|
siteos forms environment create --slug <slug> --name <name> [--json]
|
|
3507
3656
|
siteos forms definition sync --environment <slug> --input <path> [--json]
|
|
3508
3657
|
siteos forms definition sync --environment <slug> --manifest <path> [--json]
|
|
3658
|
+
siteos forms definition list --environment <slug> [--status <active|inactive|all>] [--json]
|
|
3659
|
+
siteos forms definition read --environment <slug> --form <form-id> [--json]
|
|
3660
|
+
siteos forms definition archive --environment <slug> --form <form-id> --expected-revision <revision> [--json]
|
|
3661
|
+
siteos forms definition restore --environment <slug> --form <form-id> --expected-revision <revision> [--json]
|
|
3662
|
+
siteos forms definition delete --environment <slug> --form <form-id> [--apply --confirm <form-key> --expected-revision <revision> --expected-submissions <count>] [--json]
|
|
3509
3663
|
siteos forms definition check --input <path> [--json]
|
|
3510
3664
|
siteos forms definition check --manifest <path> [--json]
|
|
3511
3665
|
siteos forms credential list --environment <slug> [--json]
|
|
@@ -3513,6 +3667,9 @@ var FORMS_HELP = `Usage:
|
|
|
3513
3667
|
siteos forms credential rotate --environment <slug> --install [--name <name>] [--json]
|
|
3514
3668
|
siteos forms credential revoke --environment <slug> --credential <credential-id> [--json]
|
|
3515
3669
|
siteos forms credentials issue --environment <slug> [--name <name>] [--json]
|
|
3670
|
+
siteos forms submissions list --environment <slug> --form <form-id> [--query <text>] [--status <status>] [--from <ISO>] [--to <ISO>] [--limit <1-100>] [--cursor <cursor>] [--json]
|
|
3671
|
+
siteos forms submissions read --environment <slug> --form <form-id> --submission <id> [--json]
|
|
3672
|
+
siteos forms submissions status --environment <slug> --form <form-id> --submission <id> --status <new|read|archived|spam> --expected-status <status> [--json]
|
|
3516
3673
|
siteos forms submit --input <path> [--json]
|
|
3517
3674
|
|
|
3518
3675
|
Manage SiteOS Forms Environments, definitions, credentials, and submission smoke tests.`;
|
|
@@ -3534,6 +3691,8 @@ async function runFormsCommand(options) {
|
|
|
3534
3691
|
};
|
|
3535
3692
|
}
|
|
3536
3693
|
}
|
|
3694
|
+
if (args2[0] === "submissions")
|
|
3695
|
+
return runSubmissionsCommand({ ...options, args: args2.slice(1) });
|
|
3537
3696
|
if (args2[0] === "definition") {
|
|
3538
3697
|
return runDefinitionCommand({
|
|
3539
3698
|
...options,
|
|
@@ -3577,6 +3736,96 @@ async function runFormsCommand(options) {
|
|
|
3577
3736
|
}
|
|
3578
3737
|
return usageError2("Unknown SiteOS forms command.");
|
|
3579
3738
|
}
|
|
3739
|
+
async function runSubmissionsCommand(options) {
|
|
3740
|
+
const action = options.args[0];
|
|
3741
|
+
if (!action || !["list", "read", "status"].includes(action))
|
|
3742
|
+
return usageError2("Unknown Forms submissions command.");
|
|
3743
|
+
const parsed = parseFlags(options.args.slice(1), {
|
|
3744
|
+
allowed: /* @__PURE__ */ new Set([
|
|
3745
|
+
"--environment",
|
|
3746
|
+
"--form",
|
|
3747
|
+
"--json",
|
|
3748
|
+
...action === "list" ? ["--query", "--status", "--from", "--to", "--limit", "--cursor"] : [
|
|
3749
|
+
"--submission",
|
|
3750
|
+
...action === "status" ? ["--status", "--expected-status"] : []
|
|
3751
|
+
]
|
|
3752
|
+
]),
|
|
3753
|
+
boolean: /* @__PURE__ */ new Set(["--json"]),
|
|
3754
|
+
rejectDuplicates: true
|
|
3755
|
+
});
|
|
3756
|
+
if (!parsed.ok) return usageError2(parsed.error);
|
|
3757
|
+
if (parsed.positionals.length)
|
|
3758
|
+
return usageError2("Unexpected positional arguments.");
|
|
3759
|
+
const value = (key) => readStringFlag(parsed.flags, `--${key}`);
|
|
3760
|
+
const environmentSlug = value("environment");
|
|
3761
|
+
const formId = value("form");
|
|
3762
|
+
const submissionId = value("submission");
|
|
3763
|
+
if (!environmentSlug || !isValidEnvironmentSlug(environmentSlug) || !formId || formId.length > 255)
|
|
3764
|
+
return usageError2("Provide a valid --environment and --form.");
|
|
3765
|
+
if (action !== "list" && (!submissionId || submissionId.length > 255))
|
|
3766
|
+
return usageError2("Provide --submission.");
|
|
3767
|
+
const status = formsSubmissionStatusSchema.safeParse(value("status"));
|
|
3768
|
+
const expected = formsSubmissionStatusSchema.safeParse(
|
|
3769
|
+
value("expected-status")
|
|
3770
|
+
);
|
|
3771
|
+
if (value("status") && !status.success || action === "status" && (!status.success || !expected.success))
|
|
3772
|
+
return usageError2(
|
|
3773
|
+
"Use new, read, archived or spam for --status and --expected-status."
|
|
3774
|
+
);
|
|
3775
|
+
const filters = new URLSearchParams();
|
|
3776
|
+
for (const key of ["query", "status", "from", "to", "limit", "cursor"]) {
|
|
3777
|
+
const item = value(key);
|
|
3778
|
+
if (item) filters.set(key, item);
|
|
3779
|
+
}
|
|
3780
|
+
const limit = Number(value("limit") ?? 50);
|
|
3781
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 100)
|
|
3782
|
+
return usageError2("--limit must be between 1 and 100.");
|
|
3783
|
+
if ((value("query")?.length ?? 0) > 200 || (value("cursor")?.length ?? 0) > 2048)
|
|
3784
|
+
return usageError2("The query or cursor is too long.");
|
|
3785
|
+
for (const key of ["from", "to"])
|
|
3786
|
+
if (value(key) && !z10.string().datetime({ offset: true }).safeParse(value(key)).success)
|
|
3787
|
+
return usageError2(
|
|
3788
|
+
`--${key} must be an ISO timestamp including a timezone.`
|
|
3789
|
+
);
|
|
3790
|
+
if (value("from") && value("to") && Date.parse(value("from")) >= Date.parse(value("to")))
|
|
3791
|
+
return usageError2("--from must be earlier than --to (exclusive).");
|
|
3792
|
+
const context = await loadFormsManagementContext(
|
|
3793
|
+
options,
|
|
3794
|
+
"forms:workspace:write"
|
|
3795
|
+
);
|
|
3796
|
+
if (!context.ok) return context.error;
|
|
3797
|
+
try {
|
|
3798
|
+
const { environments } = await context.client.listEnvironments({
|
|
3799
|
+
grant: context.grant,
|
|
3800
|
+
projectId: context.project.id
|
|
3801
|
+
});
|
|
3802
|
+
requireEnvironmentProjectConvergence(environments, context.project.id);
|
|
3803
|
+
const environment = environments.find(
|
|
3804
|
+
(item) => item.slug === environmentSlug
|
|
3805
|
+
);
|
|
3806
|
+
if (!environment)
|
|
3807
|
+
return usageError2(
|
|
3808
|
+
"The selected Forms Environment does not exist. Run forms environment list."
|
|
3809
|
+
);
|
|
3810
|
+
const input = {
|
|
3811
|
+
grant: context.grant,
|
|
3812
|
+
environmentId: environment.id,
|
|
3813
|
+
formId
|
|
3814
|
+
};
|
|
3815
|
+
const response = action === "list" ? await context.client.listSubmissions({ ...input, filters }) : action === "read" ? await context.client.readSubmission({
|
|
3816
|
+
...input,
|
|
3817
|
+
submissionId
|
|
3818
|
+
}) : await context.client.updateSubmissionStatus({
|
|
3819
|
+
...input,
|
|
3820
|
+
submissionId,
|
|
3821
|
+
status: status.data,
|
|
3822
|
+
expectedStatus: expected.data
|
|
3823
|
+
});
|
|
3824
|
+
return { exitCode: 0, stdout: stringifySafeJson(response) };
|
|
3825
|
+
} catch (error) {
|
|
3826
|
+
return formatApiError(error);
|
|
3827
|
+
}
|
|
3828
|
+
}
|
|
3580
3829
|
async function runProjectCommand(options) {
|
|
3581
3830
|
const action = options.args[0];
|
|
3582
3831
|
if (!action || !["create", "list", "status", "use"].includes(action)) {
|
|
@@ -4017,6 +4266,10 @@ async function requireFormsProjectReference(cwd) {
|
|
|
4017
4266
|
return reference;
|
|
4018
4267
|
}
|
|
4019
4268
|
async function runDefinitionCommand(options) {
|
|
4269
|
+
if (["list", "read", "archive", "restore", "delete"].includes(
|
|
4270
|
+
options.args[0] ?? ""
|
|
4271
|
+
))
|
|
4272
|
+
return runDefinitionLifecycle(options);
|
|
4020
4273
|
if (options.args[0] === "sync") {
|
|
4021
4274
|
return runDefinitionSync({
|
|
4022
4275
|
...options,
|
|
@@ -4031,6 +4284,125 @@ async function runDefinitionCommand(options) {
|
|
|
4031
4284
|
}
|
|
4032
4285
|
return usageError2("Unknown SiteOS forms definition command.");
|
|
4033
4286
|
}
|
|
4287
|
+
async function runDefinitionLifecycle(options) {
|
|
4288
|
+
const action = options.args[0];
|
|
4289
|
+
const parsed = parseFlags(options.args.slice(1), {
|
|
4290
|
+
allowed: /* @__PURE__ */ new Set([
|
|
4291
|
+
"--environment",
|
|
4292
|
+
"--json",
|
|
4293
|
+
...action === "list" ? ["--status"] : ["--form"],
|
|
4294
|
+
...["archive", "restore", "delete"].includes(action) ? ["--expected-revision"] : [],
|
|
4295
|
+
...action === "delete" ? ["--apply", "--confirm", "--expected-submissions"] : []
|
|
4296
|
+
]),
|
|
4297
|
+
boolean: /* @__PURE__ */ new Set(["--json", "--apply"]),
|
|
4298
|
+
rejectDuplicates: true
|
|
4299
|
+
});
|
|
4300
|
+
if (!parsed.ok) return usageError2(parsed.error);
|
|
4301
|
+
if (parsed.positionals.length)
|
|
4302
|
+
return usageError2("Unexpected positional arguments.");
|
|
4303
|
+
const value = (key) => readStringFlag(parsed.flags, `--${key}`);
|
|
4304
|
+
const environmentSlug = value("environment");
|
|
4305
|
+
const formId = value("form");
|
|
4306
|
+
if (!environmentSlug || !isValidEnvironmentSlug(environmentSlug) || action !== "list" && (!formId || formId.length > 255))
|
|
4307
|
+
return usageError2("Provide a valid --environment and --form.");
|
|
4308
|
+
const status = value("status") ?? "active";
|
|
4309
|
+
if (!["active", "inactive", "all"].includes(status))
|
|
4310
|
+
return usageError2("Use active, inactive or all for --status.");
|
|
4311
|
+
const apply = parsed.flags.get("--apply") === true;
|
|
4312
|
+
const revision = Number(value("expected-revision"));
|
|
4313
|
+
const count = Number(value("expected-submissions"));
|
|
4314
|
+
const confirmKey = value("confirm");
|
|
4315
|
+
if ((action === "archive" || action === "restore" || apply) && (!Number.isSafeInteger(revision) || revision < 1))
|
|
4316
|
+
return usageError2(
|
|
4317
|
+
"Read the form first, then provide its --expected-revision."
|
|
4318
|
+
);
|
|
4319
|
+
if (apply && (!confirmKey || confirmKey.length > 255 || !Number.isSafeInteger(count) || count < 0))
|
|
4320
|
+
return usageError2(
|
|
4321
|
+
"Deletion requires --confirm with the exact form key and --expected-submissions from the preview."
|
|
4322
|
+
);
|
|
4323
|
+
if (action === "delete" && !apply && ["confirm", "expected-revision", "expected-submissions"].some(
|
|
4324
|
+
(key) => value(key) !== void 0
|
|
4325
|
+
))
|
|
4326
|
+
return usageError2(
|
|
4327
|
+
"Delete is a read-only preview. Add --apply with all confirmation values to delete."
|
|
4328
|
+
);
|
|
4329
|
+
const context = await loadFormsManagementContext(
|
|
4330
|
+
options,
|
|
4331
|
+
"forms:workspace:write"
|
|
4332
|
+
);
|
|
4333
|
+
if (!context.ok) return context.error;
|
|
4334
|
+
try {
|
|
4335
|
+
const { environments } = await context.client.listEnvironments({
|
|
4336
|
+
grant: context.grant,
|
|
4337
|
+
projectId: context.project.id
|
|
4338
|
+
});
|
|
4339
|
+
requireEnvironmentProjectConvergence(environments, context.project.id);
|
|
4340
|
+
const environment = environments.find(
|
|
4341
|
+
(item) => item.slug === environmentSlug
|
|
4342
|
+
);
|
|
4343
|
+
if (!environment)
|
|
4344
|
+
return usageError2(
|
|
4345
|
+
"The selected Forms Environment does not exist. Run forms environment list."
|
|
4346
|
+
);
|
|
4347
|
+
const input = {
|
|
4348
|
+
grant: context.grant,
|
|
4349
|
+
environmentId: environment.id,
|
|
4350
|
+
formId: formId ?? ""
|
|
4351
|
+
};
|
|
4352
|
+
if (action === "list")
|
|
4353
|
+
return {
|
|
4354
|
+
exitCode: 0,
|
|
4355
|
+
stdout: stringifySafeJson(
|
|
4356
|
+
await context.client.listDefinitions({
|
|
4357
|
+
...input,
|
|
4358
|
+
status
|
|
4359
|
+
})
|
|
4360
|
+
)
|
|
4361
|
+
};
|
|
4362
|
+
if (action === "read" || action === "delete" && !apply) {
|
|
4363
|
+
const preview = await context.client.readDefinition(input);
|
|
4364
|
+
if (preview.project.id !== context.project.id || preview.environment.id !== environment.id || preview.definition.id !== formId || preview.definition.environmentId !== environment.id)
|
|
4365
|
+
throw new SiteOSFormsApiError({
|
|
4366
|
+
code: "INVALID_RESPONSE",
|
|
4367
|
+
message: "The form does not match the selected Project and Environment."
|
|
4368
|
+
});
|
|
4369
|
+
return {
|
|
4370
|
+
exitCode: 0,
|
|
4371
|
+
stdout: stringifySafeJson(
|
|
4372
|
+
action === "read" ? preview : {
|
|
4373
|
+
...preview,
|
|
4374
|
+
action: "delete",
|
|
4375
|
+
applied: false,
|
|
4376
|
+
warning: "Permanently removes this form, all versions and all submitted answers. This cannot be undone. The form key remains reserved.",
|
|
4377
|
+
confirmation: {
|
|
4378
|
+
confirmKey: preview.definition.formKey,
|
|
4379
|
+
expectedRevision: preview.revision,
|
|
4380
|
+
expectedSubmissionCount: preview.definition.submissionCount
|
|
4381
|
+
}
|
|
4382
|
+
}
|
|
4383
|
+
)
|
|
4384
|
+
};
|
|
4385
|
+
}
|
|
4386
|
+
const response = await context.client.changeDefinition({
|
|
4387
|
+
...input,
|
|
4388
|
+
change: action === "delete" ? {
|
|
4389
|
+
action,
|
|
4390
|
+
confirmKey,
|
|
4391
|
+
expectedRevision: revision,
|
|
4392
|
+
expectedSubmissionCount: count
|
|
4393
|
+
} : {
|
|
4394
|
+
action,
|
|
4395
|
+
expectedRevision: revision
|
|
4396
|
+
}
|
|
4397
|
+
});
|
|
4398
|
+
return {
|
|
4399
|
+
exitCode: 0,
|
|
4400
|
+
stdout: stringifySafeJson({ ...response, action, applied: true })
|
|
4401
|
+
};
|
|
4402
|
+
} catch (error) {
|
|
4403
|
+
return formatApiError(error);
|
|
4404
|
+
}
|
|
4405
|
+
}
|
|
4034
4406
|
async function runDefinitionSync(options) {
|
|
4035
4407
|
const parsed = parseDefinitionSourceFlags(options.args, {
|
|
4036
4408
|
environmentRequired: true,
|
|
@@ -4713,8 +5085,8 @@ function formatApiError(error) {
|
|
|
4713
5085
|
error.status ? `Status: ${error.status}` : void 0,
|
|
4714
5086
|
`Code: ${error.code}`,
|
|
4715
5087
|
error.message,
|
|
4716
|
-
error.status === 404 && error.code === "NOT_FOUND" ? "
|
|
4717
|
-
error.status === 409 && error.code === "PROJECT_FORM_SUBMISSION_EXISTS" ? "The submission idempotency key was already used.
|
|
5088
|
+
error.status === 404 && error.code === "NOT_FOUND" ? "Verify the selected Project, Environment and form ID. The server must support this command." : void 0,
|
|
5089
|
+
error.status === 409 && error.code === "PROJECT_FORM_SUBMISSION_EXISTS" ? "The submission idempotency key was already used. Keep it for an unchanged retry and check the existing receipt before sending again." : void 0
|
|
4718
5090
|
].filter((line) => Boolean(line));
|
|
4719
5091
|
return {
|
|
4720
5092
|
exitCode: 1,
|