@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 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
- }).passthrough();
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
- }).passthrough().superRefine((definition, context) => {
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" ? "The form was not found. Run definition sync before testing submissions." : void 0,
4717
- error.status === 409 && error.code === "PROJECT_FORM_SUBMISSION_EXISTS" ? "The submission idempotency key was already used. Retry with a new idempotency key." : void 0
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,