@lesliechan721/aw-plugin-core 0.1.0-beta.3 → 0.1.0-beta.30

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.
Files changed (43) hide show
  1. package/dist/aw-capabilities.d.ts +27 -0
  2. package/dist/aw-capabilities.js +108 -0
  3. package/dist/aw-capabilities.js.map +1 -0
  4. package/dist/catalog-workspace-resolution.d.ts +3 -0
  5. package/dist/catalog-workspace-resolution.js +34 -0
  6. package/dist/catalog-workspace-resolution.js.map +1 -0
  7. package/dist/generated/plugin-manifest-v3.d.ts +675 -0
  8. package/dist/generated/{plugin-manifest-v1.js → plugin-manifest-v3.js} +1 -1
  9. package/dist/generated/plugin-manifest-v3.js.map +1 -0
  10. package/dist/index.d.ts +2 -2
  11. package/dist/index.js +1 -1
  12. package/dist/index.js.map +1 -1
  13. package/dist/manifest-contract.d.ts +3 -6
  14. package/dist/manifest-contract.js +209 -406
  15. package/dist/manifest-contract.js.map +1 -1
  16. package/dist/manifest-type-contract.d.ts +2 -2
  17. package/dist/manifest.d.ts +10 -8
  18. package/dist/manifest.js +11 -7
  19. package/dist/manifest.js.map +1 -1
  20. package/dist/plugin-authoring-capabilities.d.ts +21 -0
  21. package/dist/plugin-authoring-capabilities.js +187 -0
  22. package/dist/plugin-authoring-capabilities.js.map +1 -0
  23. package/dist/plugin-config.d.ts +13 -0
  24. package/dist/plugin-config.js +645 -0
  25. package/dist/plugin-config.js.map +1 -0
  26. package/dist/plugin-requirements.d.ts +34 -0
  27. package/dist/plugin-requirements.js +250 -0
  28. package/dist/plugin-requirements.js.map +1 -0
  29. package/dist/plugin-script-contract.d.ts +10 -12
  30. package/dist/plugin-script-contract.js +34 -64
  31. package/dist/plugin-script-contract.js.map +1 -1
  32. package/dist/types.d.ts +142 -62
  33. package/dist/workspace.d.ts +54 -29
  34. package/dist/workspace.js +497 -269
  35. package/dist/workspace.js.map +1 -1
  36. package/package.json +8 -4
  37. package/schemas/plugin-manifest-v3.json +590 -0
  38. package/dist/generated/plugin-manifest-v1.d.ts +0 -368
  39. package/dist/generated/plugin-manifest-v1.js.map +0 -1
  40. package/dist/plugin-release.d.ts +0 -30
  41. package/dist/plugin-release.js +0 -187
  42. package/dist/plugin-release.js.map +0 -1
  43. package/schemas/plugin-manifest-v1.json +0 -329
@@ -0,0 +1,645 @@
1
+ import { RE2 } from 're2-wasm';
2
+ const CONFIG_KEY_PATTERN = /^[A-Za-z][A-Za-z0-9_]*$/u;
3
+ const IDENTIFIER_PATTERN = /^[A-Za-z][A-Za-z0-9_]*$/u;
4
+ const SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
5
+ const SEMVER_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|(?:[0-9A-Za-z-]*[A-Za-z-][0-9A-Za-z-]*))(?:\.(?:0|[1-9]\d*|(?:[0-9A-Za-z-]*[A-Za-z-][0-9A-Za-z-]*)))*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?$/u;
6
+ const HOSTNAME_PATTERN = /^(?=.{1,253}$)(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?)(?:\.(?:[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?))*$/u;
7
+ const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/u;
8
+ const STRING_FORMATS = new Set([
9
+ 'identifier',
10
+ 'slug',
11
+ 'semver',
12
+ 'url',
13
+ 'hostname',
14
+ 'email',
15
+ ]);
16
+ const MAX_OBJECT_PROPERTIES = 32;
17
+ const MAX_PATTERN_CODE_POINTS = 512;
18
+ const MAX_STRING_CODE_POINTS = 4096;
19
+ const DANGEROUS_CONFIG_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
20
+ function createPropertyMap() {
21
+ return Object.create(null);
22
+ }
23
+ function ownProperty(value, property) {
24
+ return value !== undefined && Object.hasOwn(value, property) ? value[property] : undefined;
25
+ }
26
+ function isDangerousConfigKey(value) {
27
+ return DANGEROUS_CONFIG_KEYS.has(value);
28
+ }
29
+ function asObject(value, path) {
30
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
31
+ throw new Error(`Invalid ${path}: expected an object.`);
32
+ }
33
+ return value;
34
+ }
35
+ function assertAllowedKeys(value, path, allowed) {
36
+ for (const key of Object.keys(value)) {
37
+ if (!allowed.includes(key)) {
38
+ throw new Error(`Invalid ${path}: unknown field "${key}".`);
39
+ }
40
+ }
41
+ }
42
+ function nonEmptyString(value, path) {
43
+ if (typeof value !== 'string' || value.trim() === '') {
44
+ throw new Error(`Invalid ${path}: expected a non-empty string.`);
45
+ }
46
+ return value;
47
+ }
48
+ function optionalText(value, path) {
49
+ return value === undefined ? undefined : nonEmptyString(value, path);
50
+ }
51
+ function optionalBoolean(value, path) {
52
+ if (value === undefined)
53
+ return undefined;
54
+ if (typeof value !== 'boolean') {
55
+ throw new Error(`Invalid ${path}: expected a boolean.`);
56
+ }
57
+ return value;
58
+ }
59
+ function finiteNumber(value, path) {
60
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
61
+ throw new Error(`Invalid ${path}: expected a finite number.`);
62
+ }
63
+ return value;
64
+ }
65
+ function nonNegativeInteger(value, path) {
66
+ const result = finiteNumber(value, path);
67
+ if (!Number.isInteger(result) || result < 0) {
68
+ throw new Error(`Invalid ${path}: expected a non-negative integer.`);
69
+ }
70
+ return result;
71
+ }
72
+ function normalizeStringValidation(value, path) {
73
+ const input = asObject(value, path);
74
+ assertAllowedKeys(input, path, ['minLength', 'maxLength', 'pattern', 'format']);
75
+ const minLength = input.minLength === undefined
76
+ ? undefined
77
+ : nonNegativeInteger(input.minLength, `${path}.minLength`);
78
+ const maxLength = input.maxLength === undefined
79
+ ? undefined
80
+ : nonNegativeInteger(input.maxLength, `${path}.maxLength`);
81
+ if (maxLength !== undefined && maxLength > MAX_STRING_CODE_POINTS) {
82
+ throw new Error(`Invalid ${path}.maxLength: expected at most ${MAX_STRING_CODE_POINTS}.`);
83
+ }
84
+ if (minLength !== undefined && maxLength !== undefined && minLength > maxLength) {
85
+ throw new Error(`Invalid ${path}: minLength must be less than or equal to maxLength.`);
86
+ }
87
+ if (input.pattern !== undefined && typeof input.pattern !== 'string') {
88
+ throw new Error(`Invalid ${path}.pattern: expected a string.`);
89
+ }
90
+ const pattern = input.pattern;
91
+ if (pattern !== undefined) {
92
+ if ([...pattern].length > MAX_PATTERN_CODE_POINTS) {
93
+ throw new Error(`Invalid ${path}.pattern: expected at most ${MAX_PATTERN_CODE_POINTS} Unicode code points.`);
94
+ }
95
+ if (maxLength === undefined) {
96
+ throw new Error(`Invalid ${path}: maxLength is required when pattern is declared.`);
97
+ }
98
+ try {
99
+ new RE2(pattern, 'u');
100
+ }
101
+ catch {
102
+ throw new Error(`Invalid ${path}.pattern: expected a valid RE2 regular expression.`);
103
+ }
104
+ }
105
+ const format = input.format === undefined
106
+ ? undefined
107
+ : nonEmptyString(input.format, `${path}.format`);
108
+ if (format !== undefined && !STRING_FORMATS.has(format)) {
109
+ throw new Error(`Invalid ${path}.format: unsupported string format "${format}".`);
110
+ }
111
+ return { minLength, maxLength, pattern, format };
112
+ }
113
+ function normalizeNumberValidation(value, path) {
114
+ const input = asObject(value, path);
115
+ assertAllowedKeys(input, path, ['integer', 'minimum', 'maximum', 'multipleOf']);
116
+ const integer = optionalBoolean(input.integer, `${path}.integer`);
117
+ const minimum = input.minimum === undefined
118
+ ? undefined
119
+ : finiteNumber(input.minimum, `${path}.minimum`);
120
+ const maximum = input.maximum === undefined
121
+ ? undefined
122
+ : finiteNumber(input.maximum, `${path}.maximum`);
123
+ const multipleOf = input.multipleOf === undefined
124
+ ? undefined
125
+ : finiteNumber(input.multipleOf, `${path}.multipleOf`);
126
+ if (minimum !== undefined && maximum !== undefined && minimum > maximum) {
127
+ throw new Error(`Invalid ${path}: minimum must be less than or equal to maximum.`);
128
+ }
129
+ if (multipleOf !== undefined && multipleOf <= 0) {
130
+ throw new Error(`Invalid ${path}.multipleOf: expected a number greater than zero.`);
131
+ }
132
+ return { integer, minimum, maximum, multipleOf };
133
+ }
134
+ function createIssue(code, field, message, property) {
135
+ return {
136
+ code,
137
+ path: property === undefined ? { field } : { field, property },
138
+ message,
139
+ recoverable: true,
140
+ };
141
+ }
142
+ function stringFormatMatches(value, format) {
143
+ if (format === 'identifier')
144
+ return IDENTIFIER_PATTERN.test(value);
145
+ if (format === 'slug')
146
+ return SLUG_PATTERN.test(value);
147
+ if (format === 'semver')
148
+ return SEMVER_PATTERN.test(value);
149
+ if (format === 'hostname')
150
+ return HOSTNAME_PATTERN.test(value);
151
+ if (format === 'email')
152
+ return EMAIL_PATTERN.test(value);
153
+ try {
154
+ const url = new URL(value);
155
+ return url.protocol.length > 1;
156
+ }
157
+ catch {
158
+ return false;
159
+ }
160
+ }
161
+ function validateString(value, validation, field, property) {
162
+ if (typeof value !== 'string') {
163
+ return [createIssue('type', field, 'Expected a string.', property)];
164
+ }
165
+ const issues = [];
166
+ if (validation?.minLength !== undefined && [...value].length < validation.minLength) {
167
+ issues.push(createIssue('minLength', field, `Expected at least ${validation.minLength} characters.`, property));
168
+ }
169
+ if (validation?.maxLength !== undefined && [...value].length > validation.maxLength) {
170
+ issues.push(createIssue('maxLength', field, `Expected at most ${validation.maxLength} characters.`, property));
171
+ }
172
+ if (validation?.pattern !== undefined && !new RE2(validation.pattern, 'u').test(value)) {
173
+ issues.push(createIssue('pattern', field, `Expected a value that matches ${validation.pattern}.`, property));
174
+ }
175
+ if (validation?.format !== undefined && !stringFormatMatches(value, validation.format)) {
176
+ issues.push(createIssue('format', field, `Expected ${validation.format} format.`, property));
177
+ }
178
+ return issues;
179
+ }
180
+ function validateNumber(value, validation, field, property) {
181
+ if (typeof value !== 'number' || !Number.isFinite(value)) {
182
+ return [createIssue('type', field, 'Expected a finite number.', property)];
183
+ }
184
+ const issues = [];
185
+ if (validation?.integer === true && !Number.isInteger(value)) {
186
+ issues.push(createIssue('integer', field, 'Expected an integer.', property));
187
+ }
188
+ if (validation?.minimum !== undefined && value < validation.minimum) {
189
+ issues.push(createIssue('minimum', field, `Expected a number greater than or equal to ${validation.minimum}.`, property));
190
+ }
191
+ if (validation?.maximum !== undefined && value > validation.maximum) {
192
+ issues.push(createIssue('maximum', field, `Expected a number less than or equal to ${validation.maximum}.`, property));
193
+ }
194
+ if (validation?.multipleOf !== undefined
195
+ && Math.abs(value / validation.multipleOf - Math.round(value / validation.multipleOf)) > 1e-9) {
196
+ issues.push(createIssue('multipleOf', field, `Expected a multiple of ${validation.multipleOf}.`, property));
197
+ }
198
+ return issues;
199
+ }
200
+ function validateScalarField(fieldDefinition, value, field, property, checkChoices = true) {
201
+ const issues = fieldDefinition.type === 'string'
202
+ ? validateString(value, fieldDefinition.validation, field, property)
203
+ : fieldDefinition.type === 'number'
204
+ ? validateNumber(value, fieldDefinition.validation, field, property)
205
+ : typeof value === 'boolean'
206
+ ? []
207
+ : [createIssue('type', field, 'Expected a boolean.', property)];
208
+ if (checkChoices
209
+ && issues.length === 0
210
+ && fieldDefinition.choices?.allowCustom === false
211
+ && matchPluginConfigPreset(fieldDefinition, value) === undefined) {
212
+ issues.push(createIssue('choice', field, 'Expected a declared preset value.', property));
213
+ }
214
+ return issues;
215
+ }
216
+ function validateObjectField(fieldDefinition, value, field, checkChoices = true) {
217
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
218
+ return [createIssue('type', field, 'Expected an object.')];
219
+ }
220
+ const input = value;
221
+ const keys = Object.keys(input);
222
+ const issues = [];
223
+ if (keys.length > MAX_OBJECT_PROPERTIES) {
224
+ issues.push(createIssue('maxProperties', field, `Expected at most ${MAX_OBJECT_PROPERTIES} properties.`));
225
+ }
226
+ if (fieldDefinition.validation?.minProperties !== undefined
227
+ && keys.length < fieldDefinition.validation.minProperties) {
228
+ issues.push(createIssue('minProperties', field, `Expected at least ${fieldDefinition.validation.minProperties} properties.`));
229
+ }
230
+ if (fieldDefinition.validation?.maxProperties !== undefined
231
+ && keys.length > fieldDefinition.validation.maxProperties) {
232
+ issues.push(createIssue('maxProperties', field, `Expected at most ${fieldDefinition.validation.maxProperties} properties.`));
233
+ }
234
+ for (const [property, child] of Object.entries(fieldDefinition.properties ?? {})) {
235
+ if (child.required === true && !Object.hasOwn(input, property)) {
236
+ issues.push(createIssue('required', field, 'Required property is missing.', property));
237
+ }
238
+ }
239
+ for (const [property, childValue] of Object.entries(input)) {
240
+ if (isDangerousConfigKey(property)) {
241
+ issues.push(createIssue('dangerousKey', field, 'Dangerous config key is not allowed.', property));
242
+ continue;
243
+ }
244
+ const child = ownProperty(fieldDefinition.properties, property);
245
+ if (child !== undefined) {
246
+ issues.push(...validateScalarField(child, childValue, field, property));
247
+ continue;
248
+ }
249
+ const dynamicChild = fieldDefinition.additionalProperties;
250
+ if (dynamicChild === undefined || dynamicChild === false) {
251
+ issues.push(createIssue('additionalProperty', field, 'Additional property is not allowed.', property));
252
+ continue;
253
+ }
254
+ if (fieldDefinition.validation?.key !== undefined) {
255
+ issues.push(...validateString(property, fieldDefinition.validation.key, field, property));
256
+ }
257
+ issues.push(...validateScalarField(dynamicChild, childValue, field, property));
258
+ }
259
+ if (checkChoices
260
+ && issues.length === 0
261
+ && fieldDefinition.choices?.allowCustom === false
262
+ && matchPluginConfigPreset(fieldDefinition, input) === undefined) {
263
+ issues.push(createIssue('choice', field, 'Expected a declared preset value.'));
264
+ }
265
+ return issues;
266
+ }
267
+ function resolveValueArguments(first, second, third) {
268
+ if (typeof first === 'string') {
269
+ return {
270
+ field: first,
271
+ definition: second,
272
+ value: third,
273
+ };
274
+ }
275
+ return {
276
+ definition: first,
277
+ value: second,
278
+ field: typeof third === 'string' ? third : 'value',
279
+ };
280
+ }
281
+ export function validatePluginConfigValue(first, second, third) {
282
+ const resolved = resolveValueArguments(first, second, third);
283
+ return resolved.definition.type === 'object'
284
+ ? validateObjectField(resolved.definition, resolved.value, resolved.field)
285
+ : validateScalarField(resolved.definition, resolved.value, resolved.field);
286
+ }
287
+ export function validatePluginConfigDocument(definition, value) {
288
+ if (typeof value !== 'object' || value === null || Array.isArray(value)) {
289
+ return [createIssue('type', '$document', 'Expected a config object.')];
290
+ }
291
+ const input = value;
292
+ const issues = [];
293
+ for (const [field, fieldDefinition] of Object.entries(definition.fields)) {
294
+ if (!Object.hasOwn(input, field)) {
295
+ if (fieldDefinition.required === true) {
296
+ issues.push(createIssue('required', field, 'Required field is missing.'));
297
+ }
298
+ continue;
299
+ }
300
+ issues.push(...validatePluginConfigValue(fieldDefinition, input[field], field));
301
+ }
302
+ for (const field of Object.keys(input)) {
303
+ if (isDangerousConfigKey(field)) {
304
+ issues.push(createIssue('dangerousKey', field, 'Dangerous config key is not allowed.'));
305
+ continue;
306
+ }
307
+ if (!Object.hasOwn(definition.fields, field)) {
308
+ issues.push(createIssue('unknownField', field, 'Unknown config field.'));
309
+ }
310
+ }
311
+ return issues;
312
+ }
313
+ export function canonicalizePluginConfigValue(value) {
314
+ if (typeof value !== 'object')
315
+ return value;
316
+ return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0));
317
+ }
318
+ export function canonicalizePluginConfigValues(values) {
319
+ return Object.fromEntries(Object.entries(values)
320
+ .sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)
321
+ .map(([key, value]) => [key, canonicalizePluginConfigValue(value)]));
322
+ }
323
+ export function stablePluginConfigStringify(value) {
324
+ return JSON.stringify(canonicalizePluginConfigValue(value));
325
+ }
326
+ export function matchPluginConfigPreset(definition, value) {
327
+ return definition.choices?.presets.find((preset) => stablePluginConfigStringify(preset.value) === stablePluginConfigStringify(value));
328
+ }
329
+ export function validatePluginConfigPreset(definition, preset, field = 'value') {
330
+ return definition.type === 'object'
331
+ ? validateObjectField(definition, preset.value, field, false)
332
+ : validateScalarField(definition, preset.value, field, undefined, false);
333
+ }
334
+ export function redactPluginConfigValue(definition, value) {
335
+ if (definition.type === 'string' && definition.sensitive === true)
336
+ return '[redacted]';
337
+ return value;
338
+ }
339
+ export function formatPluginConfigIssue(issue) {
340
+ const path = issue.path.property === undefined
341
+ ? issue.path.field
342
+ : `${issue.path.field}.${issue.path.property}`;
343
+ return `${path}: ${issue.message}`;
344
+ }
345
+ function normalizePreset(value, path) {
346
+ const input = asObject(value, path);
347
+ assertAllowedKeys(input, path, ['id', 'title', 'description', 'value', 'recommended']);
348
+ if (!Object.hasOwn(input, 'value')) {
349
+ throw new Error(`Invalid ${path}.value: value is required.`);
350
+ }
351
+ if (input.recommended !== undefined && input.recommended !== true) {
352
+ throw new Error(`Invalid ${path}.recommended: expected true.`);
353
+ }
354
+ return {
355
+ id: nonEmptyString(input.id, `${path}.id`),
356
+ title: nonEmptyString(input.title, `${path}.title`),
357
+ description: optionalText(input.description, `${path}.description`),
358
+ value: input.value,
359
+ recommended: input.recommended,
360
+ };
361
+ }
362
+ function normalizeChoices(value, path) {
363
+ const input = asObject(value, path);
364
+ assertAllowedKeys(input, path, ['allowCustom', 'presets']);
365
+ if (typeof input.allowCustom !== 'boolean') {
366
+ throw new Error(`Invalid ${path}.allowCustom: expected a boolean.`);
367
+ }
368
+ if (!Array.isArray(input.presets)) {
369
+ throw new Error(`Invalid ${path}.presets: expected an array.`);
370
+ }
371
+ return {
372
+ allowCustom: input.allowCustom,
373
+ presets: input.presets.map((preset, index) => normalizePreset(preset, `${path}.presets[${index}]`)),
374
+ };
375
+ }
376
+ function normalizeScalarField(value, path, options) {
377
+ const input = asObject(value, path);
378
+ const type = nonEmptyString(input.type, `${path}.type`);
379
+ if (type !== 'string' && type !== 'number' && type !== 'boolean') {
380
+ throw new Error(`Invalid ${path}.type: expected string, number, or boolean.`);
381
+ }
382
+ const allowed = ['type', 'title', 'description'];
383
+ if (options.allowDefault)
384
+ allowed.push('default');
385
+ if (options.allowRequired)
386
+ allowed.push('required');
387
+ if (options.allowSensitive && type === 'string')
388
+ allowed.push('sensitive');
389
+ allowed.push('choices');
390
+ if (type !== 'boolean')
391
+ allowed.push('validation');
392
+ assertAllowedKeys(input, path, allowed);
393
+ const sensitive = options.allowSensitive && type === 'string'
394
+ ? optionalBoolean(input.sensitive, `${path}.sensitive`)
395
+ : undefined;
396
+ if (sensitive === true && input.default !== undefined) {
397
+ throw new Error(`Invalid ${path}.default: sensitive fields cannot declare a default.`);
398
+ }
399
+ if (sensitive === true && input.choices !== undefined) {
400
+ throw new Error(`Invalid ${path}.choices: sensitive fields cannot declare choices.`);
401
+ }
402
+ const common = {
403
+ title: optionalText(input.title, `${path}.title`),
404
+ description: optionalText(input.description, `${path}.description`),
405
+ required: options.allowRequired ? optionalBoolean(input.required, `${path}.required`) : undefined,
406
+ };
407
+ const defaultValue = options.allowDefault && input.default !== undefined
408
+ ? input.default
409
+ : undefined;
410
+ if (type === 'string') {
411
+ if (sensitive === true) {
412
+ return {
413
+ type,
414
+ ...common,
415
+ sensitive: true,
416
+ validation: input.validation === undefined
417
+ ? undefined
418
+ : normalizeStringValidation(input.validation, `${path}.validation`),
419
+ };
420
+ }
421
+ return {
422
+ type,
423
+ ...common,
424
+ sensitive,
425
+ default: defaultValue,
426
+ choices: input.choices === undefined
427
+ ? undefined
428
+ : normalizeChoices(input.choices, `${path}.choices`),
429
+ validation: input.validation === undefined
430
+ ? undefined
431
+ : normalizeStringValidation(input.validation, `${path}.validation`),
432
+ };
433
+ }
434
+ if (type === 'number') {
435
+ return {
436
+ type,
437
+ ...common,
438
+ default: defaultValue,
439
+ choices: input.choices === undefined
440
+ ? undefined
441
+ : normalizeChoices(input.choices, `${path}.choices`),
442
+ validation: input.validation === undefined
443
+ ? undefined
444
+ : normalizeNumberValidation(input.validation, `${path}.validation`),
445
+ };
446
+ }
447
+ return {
448
+ type,
449
+ ...common,
450
+ default: defaultValue,
451
+ choices: input.choices === undefined
452
+ ? undefined
453
+ : normalizeChoices(input.choices, `${path}.choices`),
454
+ };
455
+ }
456
+ function assertChoices(definition, path) {
457
+ if (definition.choices === undefined)
458
+ return;
459
+ if (definition.choices.allowCustom === false && definition.choices.presets.length === 0) {
460
+ throw new Error(`Invalid ${path}.choices.presets: expected at least one preset when allowCustom is false.`);
461
+ }
462
+ const ids = new Set();
463
+ const values = new Set();
464
+ let recommendedCount = 0;
465
+ for (const [index, preset] of definition.choices.presets.entries()) {
466
+ if (ids.has(preset.id)) {
467
+ throw new Error(`Invalid ${path}.choices.presets[${index}].id: duplicate preset id "${preset.id}".`);
468
+ }
469
+ ids.add(preset.id);
470
+ const canonicalValue = stablePluginConfigStringify(preset.value);
471
+ if (values.has(canonicalValue)) {
472
+ throw new Error(`Invalid ${path}.choices.presets[${index}].value: duplicate preset value.`);
473
+ }
474
+ values.add(canonicalValue);
475
+ if (preset.recommended === true)
476
+ recommendedCount += 1;
477
+ const issues = validatePluginConfigPreset(definition, preset, path);
478
+ if (issues.length > 0) {
479
+ throw new Error(`Invalid ${path}.choices.presets[${index}].value: ${formatPluginConfigIssue(issues[0])}`);
480
+ }
481
+ }
482
+ if (recommendedCount > 1) {
483
+ throw new Error(`Invalid ${path}.choices: expected at most one recommended preset.`);
484
+ }
485
+ }
486
+ function normalizeObjectField(value, path) {
487
+ const input = asObject(value, path);
488
+ assertAllowedKeys(input, path, [
489
+ 'type',
490
+ 'title',
491
+ 'description',
492
+ 'default',
493
+ 'required',
494
+ 'choices',
495
+ 'properties',
496
+ 'additionalProperties',
497
+ 'validation',
498
+ ]);
499
+ if (input.properties === undefined && (input.additionalProperties === undefined || input.additionalProperties === false)) {
500
+ throw new Error(`Invalid ${path}: key-value object config requires a scalar additionalProperties field.`);
501
+ }
502
+ if (input.properties !== undefined && input.additionalProperties !== undefined && input.additionalProperties !== false) {
503
+ throw new Error(`Invalid ${path}: fixed and key-value object config modes cannot be mixed.`);
504
+ }
505
+ let properties;
506
+ if (input.properties !== undefined) {
507
+ const propertiesInput = asObject(input.properties, `${path}.properties`);
508
+ if (Object.keys(propertiesInput).length > MAX_OBJECT_PROPERTIES) {
509
+ throw new Error(`Invalid ${path}.properties: expected at most ${MAX_OBJECT_PROPERTIES} properties.`);
510
+ }
511
+ properties = createPropertyMap();
512
+ for (const [property, child] of Object.entries(propertiesInput)) {
513
+ if (isDangerousConfigKey(property)) {
514
+ throw new Error(`Invalid ${path}.properties: dangerous config key "${property}" is not allowed.`);
515
+ }
516
+ properties[property] = normalizeScalarField(child, `${path}.properties.${property}`, {
517
+ allowDefault: false,
518
+ allowRequired: true,
519
+ allowSensitive: false,
520
+ });
521
+ assertChoices(properties[property], `${path}.properties.${property}`);
522
+ }
523
+ }
524
+ let additionalProperties;
525
+ if (input.additionalProperties === undefined || input.additionalProperties === false) {
526
+ additionalProperties = false;
527
+ }
528
+ else {
529
+ additionalProperties = normalizeScalarField(input.additionalProperties, `${path}.additionalProperties`, { allowDefault: false, allowRequired: false, allowSensitive: false });
530
+ assertChoices(additionalProperties, `${path}.additionalProperties`);
531
+ }
532
+ let validation;
533
+ if (input.validation !== undefined) {
534
+ const validationInput = asObject(input.validation, `${path}.validation`);
535
+ assertAllowedKeys(validationInput, `${path}.validation`, ['minProperties', 'maxProperties', 'key']);
536
+ const minProperties = validationInput.minProperties === undefined
537
+ ? undefined
538
+ : nonNegativeInteger(validationInput.minProperties, `${path}.validation.minProperties`);
539
+ const maxProperties = validationInput.maxProperties === undefined
540
+ ? undefined
541
+ : nonNegativeInteger(validationInput.maxProperties, `${path}.validation.maxProperties`);
542
+ if (minProperties !== undefined && minProperties > MAX_OBJECT_PROPERTIES) {
543
+ throw new Error(`Invalid ${path}.validation.minProperties: expected at most ${MAX_OBJECT_PROPERTIES}.`);
544
+ }
545
+ if (maxProperties !== undefined && maxProperties > MAX_OBJECT_PROPERTIES) {
546
+ throw new Error(`Invalid ${path}.validation.maxProperties: expected at most ${MAX_OBJECT_PROPERTIES}.`);
547
+ }
548
+ if (minProperties !== undefined && maxProperties !== undefined && minProperties > maxProperties) {
549
+ throw new Error(`Invalid ${path}.validation: minProperties must be less than or equal to maxProperties.`);
550
+ }
551
+ validation = {
552
+ minProperties,
553
+ maxProperties,
554
+ key: validationInput.key === undefined
555
+ ? undefined
556
+ : normalizeStringValidation(validationInput.key, `${path}.validation.key`),
557
+ };
558
+ }
559
+ return {
560
+ type: 'object',
561
+ title: optionalText(input.title, `${path}.title`),
562
+ description: optionalText(input.description, `${path}.description`),
563
+ default: input.default,
564
+ required: optionalBoolean(input.required, `${path}.required`),
565
+ choices: input.choices === undefined
566
+ ? undefined
567
+ : normalizeChoices(input.choices, `${path}.choices`),
568
+ ...(properties === undefined
569
+ ? { additionalProperties: additionalProperties }
570
+ : { properties, additionalProperties: false }),
571
+ validation,
572
+ };
573
+ }
574
+ export function normalizePluginConfigDefinition(value, path = 'config') {
575
+ const input = asObject(value, path);
576
+ assertAllowedKeys(input, path, ['fields', 'presentation']);
577
+ const fieldsInput = asObject(input.fields, `${path}.fields`);
578
+ const fields = createPropertyMap();
579
+ for (const [field, rawDefinition] of Object.entries(fieldsInput)) {
580
+ if (!CONFIG_KEY_PATTERN.test(field) || isDangerousConfigKey(field)) {
581
+ throw new Error(`Invalid ${path}.fields: config key "${field}" is not valid.`);
582
+ }
583
+ const rawField = asObject(rawDefinition, `${path}.fields.${field}`);
584
+ const definition = rawField.type === 'object'
585
+ ? normalizeObjectField(rawField, `${path}.fields.${field}`)
586
+ : normalizeScalarField(rawField, `${path}.fields.${field}`, {
587
+ allowDefault: true,
588
+ allowRequired: true,
589
+ allowSensitive: true,
590
+ });
591
+ assertChoices(definition, `${path}.fields.${field}`);
592
+ if (definition.default !== undefined) {
593
+ const issues = validatePluginConfigValue(definition, definition.default, field);
594
+ if (issues.length > 0) {
595
+ throw new Error(`Invalid ${path}.fields.${field}.default: ${formatPluginConfigIssue(issues[0])}`);
596
+ }
597
+ }
598
+ fields[field] = definition;
599
+ }
600
+ let presentation;
601
+ if (input.presentation !== undefined) {
602
+ const presentationInput = asObject(input.presentation, `${path}.presentation`);
603
+ assertAllowedKeys(presentationInput, `${path}.presentation`, ['groups']);
604
+ if (presentationInput.groups !== undefined && !Array.isArray(presentationInput.groups)) {
605
+ throw new Error(`Invalid ${path}.presentation.groups: expected an array.`);
606
+ }
607
+ const groupIds = new Set();
608
+ const groupedFields = new Set();
609
+ presentation = {
610
+ groups: presentationInput.groups?.map((rawGroup, index) => {
611
+ const groupPath = `${path}.presentation.groups[${index}]`;
612
+ const group = asObject(rawGroup, groupPath);
613
+ assertAllowedKeys(group, groupPath, ['id', 'title', 'description', 'fields', 'advanced']);
614
+ const id = nonEmptyString(group.id, `${groupPath}.id`);
615
+ if (groupIds.has(id)) {
616
+ throw new Error(`Invalid ${groupPath}.id: duplicate group id "${id}".`);
617
+ }
618
+ groupIds.add(id);
619
+ if (!Array.isArray(group.fields)) {
620
+ throw new Error(`Invalid ${groupPath}.fields: expected an array.`);
621
+ }
622
+ const groupFields = group.fields.map((entry, fieldIndex) => {
623
+ const field = nonEmptyString(entry, `${groupPath}.fields[${fieldIndex}]`);
624
+ if (!Object.hasOwn(fields, field)) {
625
+ throw new Error(`Invalid ${groupPath}.fields[${fieldIndex}]: unknown config key "${field}".`);
626
+ }
627
+ if (groupedFields.has(field)) {
628
+ throw new Error(`Invalid ${groupPath}.fields[${fieldIndex}]: config key "${field}" is assigned to more than one group.`);
629
+ }
630
+ groupedFields.add(field);
631
+ return field;
632
+ });
633
+ return {
634
+ id,
635
+ title: nonEmptyString(group.title, `${groupPath}.title`),
636
+ description: optionalText(group.description, `${groupPath}.description`),
637
+ fields: groupFields,
638
+ advanced: optionalBoolean(group.advanced, `${groupPath}.advanced`),
639
+ };
640
+ }),
641
+ };
642
+ }
643
+ return { fields, presentation };
644
+ }
645
+ //# sourceMappingURL=plugin-config.js.map