@lesliechan721/aw-plugin-core 0.1.0-beta.38 → 0.1.0-beta.39
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/dist/aw-capabilities.d.ts +1 -1
- package/dist/aw-capabilities.js +4 -0
- package/dist/aw-capabilities.js.map +1 -1
- package/dist/generated/plugin-manifest-v3.d.ts +458 -5
- package/dist/manifest-contract.js +19 -1
- package/dist/manifest-contract.js.map +1 -1
- package/dist/manifest.d.ts +2 -1
- package/dist/manifest.js +7 -1
- package/dist/manifest.js.map +1 -1
- package/dist/plugin-authoring-capabilities.js +18 -7
- package/dist/plugin-authoring-capabilities.js.map +1 -1
- package/dist/plugin-config.d.ts +15 -1
- package/dist/plugin-config.js +422 -42
- package/dist/plugin-config.js.map +1 -1
- package/dist/plugin-requirements.js +34 -2
- package/dist/plugin-requirements.js.map +1 -1
- package/dist/plugin-script-contract.d.ts +1 -0
- package/dist/plugin-script-contract.js +8 -0
- package/dist/plugin-script-contract.js.map +1 -1
- package/dist/runtime-config.d.ts +38 -0
- package/dist/runtime-config.js +31 -0
- package/dist/runtime-config.js.map +1 -0
- package/dist/types.d.ts +72 -4
- package/package.json +1 -1
- package/schemas/plugin-manifest-v3.json +1261 -4
package/dist/plugin-config.js
CHANGED
|
@@ -154,7 +154,8 @@ function withArrayPath(issue, index) {
|
|
|
154
154
|
...issue,
|
|
155
155
|
path: {
|
|
156
156
|
...issue.path,
|
|
157
|
-
segments: [issue.path.field, index, ...(issue.path.
|
|
157
|
+
segments: [issue.path.field, index, ...(issue.path.segments?.slice(1)
|
|
158
|
+
?? (issue.path.property === undefined ? [] : [issue.path.property]))],
|
|
158
159
|
},
|
|
159
160
|
};
|
|
160
161
|
}
|
|
@@ -249,15 +250,53 @@ function validateScalarField(fieldDefinition, value, field, property, checkChoic
|
|
|
249
250
|
&& matchPluginConfigPreset(fieldDefinition, value) === undefined) {
|
|
250
251
|
issues.push(createIssue('choice', field, 'Expected a declared preset value.', property));
|
|
251
252
|
}
|
|
253
|
+
if (issues.length === 0 && fieldDefinition.const !== undefined && value !== fieldDefinition.const) {
|
|
254
|
+
issues.push(createIssue('const', field, 'Expected the declared constant value.', property));
|
|
255
|
+
}
|
|
252
256
|
return issues;
|
|
253
257
|
}
|
|
258
|
+
/** Resolve visible and allowed own properties without changing the supplied value. */
|
|
259
|
+
export function resolvePluginConfigObjectProperties(definition, value, field = 'value') {
|
|
260
|
+
const properties = { ...definition.properties };
|
|
261
|
+
if (definition.properties === undefined || definition.variants === undefined)
|
|
262
|
+
return { properties, issues: [] };
|
|
263
|
+
const variants = definition.variants;
|
|
264
|
+
const input = isSafeJsonRecord(value) ? value : undefined;
|
|
265
|
+
const missingSelector = input === undefined || !Object.hasOwn(input, variants.discriminator);
|
|
266
|
+
const selector = ownProperty(input, variants.discriminator);
|
|
267
|
+
const selectorField = definition.properties[variants.discriminator];
|
|
268
|
+
const issues = missingSelector ? [] : validateScalarField(selectorField, selector, field, variants.discriminator);
|
|
269
|
+
if (issues.length > 0)
|
|
270
|
+
return { properties, issues };
|
|
271
|
+
const caseName = missingSelector ? variants.missing : selector;
|
|
272
|
+
const selected = caseName === undefined ? undefined : ownProperty(variants.cases, caseName);
|
|
273
|
+
if (selected !== undefined) {
|
|
274
|
+
return { properties: { ...properties, ...selected.properties }, variant: { kind: 'case', case: caseName }, issues };
|
|
275
|
+
}
|
|
276
|
+
if (!missingSelector && variants.otherwise !== undefined) {
|
|
277
|
+
return { properties: { ...properties, ...variants.otherwise.properties }, variant: { kind: 'otherwise' }, issues };
|
|
278
|
+
}
|
|
279
|
+
return { properties, issues: [createIssue('variant', field, 'No object variant matches the discriminator.', variants.discriminator)] };
|
|
280
|
+
}
|
|
281
|
+
function withPropertyPath(issue, property) {
|
|
282
|
+
return {
|
|
283
|
+
...issue,
|
|
284
|
+
path: {
|
|
285
|
+
field: issue.path.field,
|
|
286
|
+
property,
|
|
287
|
+
segments: [issue.path.field, property, ...(issue.path.segments?.slice(1)
|
|
288
|
+
?? (issue.path.property === undefined ? [] : [issue.path.property]))],
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
}
|
|
254
292
|
function validateObjectField(fieldDefinition, value, field, checkChoices = true) {
|
|
255
293
|
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
|
|
256
294
|
return [createIssue('type', field, 'Expected an object.')];
|
|
257
295
|
}
|
|
258
296
|
const input = value;
|
|
259
297
|
const keys = Object.keys(input);
|
|
260
|
-
const
|
|
298
|
+
const resolved = resolvePluginConfigObjectProperties(fieldDefinition, value, field);
|
|
299
|
+
const issues = [...resolved.issues];
|
|
261
300
|
if (keys.length > MAX_OBJECT_PROPERTIES) {
|
|
262
301
|
issues.push(createIssue('maxProperties', field, `Expected at most ${MAX_OBJECT_PROPERTIES} properties.`));
|
|
263
302
|
}
|
|
@@ -269,7 +308,7 @@ function validateObjectField(fieldDefinition, value, field, checkChoices = true)
|
|
|
269
308
|
&& keys.length > fieldDefinition.validation.maxProperties) {
|
|
270
309
|
issues.push(createIssue('maxProperties', field, `Expected at most ${fieldDefinition.validation.maxProperties} properties.`));
|
|
271
310
|
}
|
|
272
|
-
for (const [property, child] of Object.entries(
|
|
311
|
+
for (const [property, child] of Object.entries(resolved.properties)) {
|
|
273
312
|
if (child.required === true && !Object.hasOwn(input, property)) {
|
|
274
313
|
issues.push(createIssue('required', field, 'Required property is missing.', property));
|
|
275
314
|
}
|
|
@@ -279,9 +318,21 @@ function validateObjectField(fieldDefinition, value, field, checkChoices = true)
|
|
|
279
318
|
issues.push(createIssue('dangerousKey', field, 'Dangerous config key is not allowed.', property));
|
|
280
319
|
continue;
|
|
281
320
|
}
|
|
282
|
-
const child = ownProperty(
|
|
321
|
+
const child = ownProperty(resolved.properties, property);
|
|
283
322
|
if (child !== undefined) {
|
|
284
|
-
issues.
|
|
323
|
+
if (fieldDefinition.variants?.discriminator === property && resolved.issues.length > 0)
|
|
324
|
+
continue;
|
|
325
|
+
if (child.type === 'array' || child.type === 'object') {
|
|
326
|
+
if (child.type === 'object' && !isSafeJsonRecord(childValue)) {
|
|
327
|
+
issues.push(withPropertyPath(createIssue('type', field, 'Expected a JSON object.'), property));
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
issues.push(...validatePluginConfigValue(child, childValue, field)
|
|
331
|
+
.map((issue) => withPropertyPath(issue, property)));
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
else
|
|
335
|
+
issues.push(...validateScalarField(child, childValue, field, property));
|
|
285
336
|
continue;
|
|
286
337
|
}
|
|
287
338
|
const dynamicChild = fieldDefinition.additionalProperties;
|
|
@@ -322,6 +373,7 @@ function validateArrayField(fieldDefinition, value, field, checkChoices = true)
|
|
|
322
373
|
if (count > max)
|
|
323
374
|
issues.push(createIssue('maxItems', field, `Expected at most ${max} items.`));
|
|
324
375
|
const seen = new Set();
|
|
376
|
+
const seenProperties = new Set();
|
|
325
377
|
for (const [index, item] of value.entries()) {
|
|
326
378
|
const itemIssues = fieldDefinition.items.type === 'object'
|
|
327
379
|
? isSafeJsonRecord(item)
|
|
@@ -329,6 +381,19 @@ function validateArrayField(fieldDefinition, value, field, checkChoices = true)
|
|
|
329
381
|
: [createIssue('type', field, 'Expected a JSON object.')]
|
|
330
382
|
: validateScalarField(fieldDefinition.items, item, field);
|
|
331
383
|
issues.push(...itemIssues.map((issue) => withArrayPath(issue, index)));
|
|
384
|
+
const uniqueBy = fieldDefinition.validation?.uniqueBy;
|
|
385
|
+
if (uniqueBy !== undefined && itemIssues.length === 0 && isSafeJsonRecord(item)) {
|
|
386
|
+
if (!Object.hasOwn(item, uniqueBy)) {
|
|
387
|
+
issues.push(withArrayPath(createIssue('uniqueBy', field, 'Unique property is missing.', uniqueBy), index));
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
const key = item[uniqueBy];
|
|
391
|
+
if (seenProperties.has(key)) {
|
|
392
|
+
issues.push(withArrayPath(createIssue('uniqueBy', field, 'Expected a unique property value.', uniqueBy), index));
|
|
393
|
+
}
|
|
394
|
+
seenProperties.add(key);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
332
397
|
if (fieldDefinition.validation?.uniqueItems === true && itemIssues.length === 0) {
|
|
333
398
|
const key = stablePluginConfigStringify(item);
|
|
334
399
|
if (seen.has(key)) {
|
|
@@ -397,14 +462,164 @@ export function validatePluginConfigDocument(definition, value) {
|
|
|
397
462
|
issues.push(createIssue('unknownField', field, 'Unknown config field.'));
|
|
398
463
|
}
|
|
399
464
|
}
|
|
465
|
+
return [...issues, ...validatePluginConfigReferences(definition, input, Object.keys(definition.fields))];
|
|
466
|
+
}
|
|
467
|
+
function scalarSchemas(definition) {
|
|
468
|
+
if (definition.type === 'array')
|
|
469
|
+
return scalarSchemas(definition.items);
|
|
470
|
+
if (definition.type !== 'object')
|
|
471
|
+
return [definition];
|
|
472
|
+
return definition.properties === undefined
|
|
473
|
+
? [definition.additionalProperties]
|
|
474
|
+
: [definition.properties, ...Object.values(definition.variants?.cases ?? {}).map((variant) => variant.properties),
|
|
475
|
+
...(definition.variants?.otherwise === undefined ? [] : [definition.variants.otherwise.properties])]
|
|
476
|
+
.flatMap((properties) => Object.values(properties).flatMap((child) => scalarSchemas(child)));
|
|
477
|
+
}
|
|
478
|
+
/** Returns validation dependencies only; callers still project their original allowed keys. */
|
|
479
|
+
export function getPluginConfigDependencyKeys(definition, keys) {
|
|
480
|
+
const result = new Set();
|
|
481
|
+
const visit = (key) => {
|
|
482
|
+
if (result.has(key))
|
|
483
|
+
return;
|
|
484
|
+
const field = ownProperty(definition.fields, key);
|
|
485
|
+
if (field === undefined)
|
|
486
|
+
throw new Error(`Unknown config key "${key}".`);
|
|
487
|
+
result.add(key);
|
|
488
|
+
for (const scalar of scalarSchemas(field)) {
|
|
489
|
+
if ('valueRef' in scalar && scalar.valueRef !== undefined)
|
|
490
|
+
visit(scalar.valueRef.field);
|
|
491
|
+
}
|
|
492
|
+
};
|
|
493
|
+
keys.forEach(visit);
|
|
494
|
+
return [...result];
|
|
495
|
+
}
|
|
496
|
+
function validatePluginConfigReferences(definition, input, keys) {
|
|
497
|
+
const issues = [];
|
|
498
|
+
const reservedPaths = new Set();
|
|
499
|
+
const sources = new Map();
|
|
500
|
+
const allowedValues = new Map();
|
|
501
|
+
const check = (scalar, value, field, segments = []) => {
|
|
502
|
+
if (!('valueRef' in scalar) || scalar.valueRef === undefined)
|
|
503
|
+
return;
|
|
504
|
+
const ref = scalar.valueRef;
|
|
505
|
+
let source = sources.get(ref.field);
|
|
506
|
+
if (source === undefined) {
|
|
507
|
+
const sourceSchema = definition.fields[ref.field];
|
|
508
|
+
const sourceValue = ownProperty(input, ref.field);
|
|
509
|
+
const valid = sourceValue === undefined
|
|
510
|
+
? sourceSchema.required !== true
|
|
511
|
+
: validatePluginConfigValue(sourceSchema, sourceValue, ref.field).length === 0;
|
|
512
|
+
source = { valid, entries: valid ? (sourceValue ?? []) : [] };
|
|
513
|
+
sources.set(ref.field, source);
|
|
514
|
+
}
|
|
515
|
+
const add = (code, message) => {
|
|
516
|
+
const property = segments.find((segment) => typeof segment === 'string');
|
|
517
|
+
const issue = createIssue(code, field, message, property);
|
|
518
|
+
issues.push(segments.length < 2 && typeof segments[0] !== 'number'
|
|
519
|
+
? issue : { ...issue, path: { ...issue.path, segments: [field, ...segments] } });
|
|
520
|
+
};
|
|
521
|
+
if (!source.valid) {
|
|
522
|
+
add('valueRefSource', `Referenced config "${ref.field}" is invalid; fix its complete array value.`);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
let allowed = allowedValues.get(ref);
|
|
526
|
+
if (allowed === undefined) {
|
|
527
|
+
allowed = new Set(ref.include);
|
|
528
|
+
for (const [sourceIndex, entry] of source.entries.entries()) {
|
|
529
|
+
if (ref.include?.includes(entry[ref.key]) === true) {
|
|
530
|
+
const pathKey = `${ref.field}\0${sourceIndex}\0${ref.key}`;
|
|
531
|
+
if (!reservedPaths.has(pathKey)) {
|
|
532
|
+
reservedPaths.add(pathKey);
|
|
533
|
+
issues.push(withArrayPath(createIssue('valueRefReserved', ref.field, 'Referenced property collides with a reserved included value.', ref.key), sourceIndex));
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
allowed.add(entry[ref.key]);
|
|
537
|
+
}
|
|
538
|
+
allowedValues.set(ref, allowed);
|
|
539
|
+
}
|
|
540
|
+
if (value === undefined || validateScalarField(scalar, value, field).length > 0)
|
|
541
|
+
return;
|
|
542
|
+
if (!allowed.has(value)) {
|
|
543
|
+
add('valueRef', `Expected an included value or a key from config "${ref.field}". Add that key first, update dependent fields, then remove the old key.`);
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
const walk = (schema, value, field, segments = []) => {
|
|
547
|
+
if (schema.type === 'array') {
|
|
548
|
+
// Reserved source values must remain invalid when a dependent array is absent.
|
|
549
|
+
for (const scalar of scalarSchemas(schema))
|
|
550
|
+
check(scalar, undefined, field, segments);
|
|
551
|
+
if (Array.isArray(value) && validatePluginConfigValue(schema, value, field).length === 0) {
|
|
552
|
+
value.forEach((item, index) => walk(schema.items, item, field, [...segments, index]));
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
else if (schema.type === 'object') {
|
|
556
|
+
if (schema.properties !== undefined) {
|
|
557
|
+
const resolved = resolvePluginConfigObjectProperties(schema, value, field);
|
|
558
|
+
for (const [property, child] of Object.entries(resolved.properties)) {
|
|
559
|
+
walk(child, isSafeJsonRecord(value) ? ownProperty(value, property) : undefined, field, [...segments, property]);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
else {
|
|
563
|
+
check(schema.additionalProperties, undefined, field, segments);
|
|
564
|
+
if (isSafeJsonRecord(value)) {
|
|
565
|
+
for (const [property, child] of Object.entries(value)) {
|
|
566
|
+
walk(schema.additionalProperties, child, field, [...segments, property]);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
else
|
|
572
|
+
check(schema, value, field, segments);
|
|
573
|
+
};
|
|
574
|
+
for (const field of keys) {
|
|
575
|
+
const schema = definition.fields[field];
|
|
576
|
+
walk(schema, ownProperty(input, field), field);
|
|
577
|
+
}
|
|
400
578
|
return issues;
|
|
401
579
|
}
|
|
580
|
+
/** Validate one effective candidate, including complete sources required by selected fields. */
|
|
581
|
+
export function validatePluginConfigCandidate(definition, values, keys) {
|
|
582
|
+
if (!isSafeJsonRecord(values))
|
|
583
|
+
return [createIssue('type', '$document', 'Expected a config JSON object.')];
|
|
584
|
+
if (keys === undefined)
|
|
585
|
+
return validatePluginConfigDocument(definition, values);
|
|
586
|
+
const closure = getPluginConfigDependencyKeys(definition, keys);
|
|
587
|
+
const projected = Object.fromEntries(closure.flatMap((key) => Object.hasOwn(values, key) ? [[key, values[key]]] : []));
|
|
588
|
+
return validatePluginConfigDocument({ ...definition, fields: Object.fromEntries(closure.map((key) => [key, definition.fields[key]])) }, projected);
|
|
589
|
+
}
|
|
590
|
+
function assertValueReferenceSchemas(fields, definitions, path, allowReferences = true) {
|
|
591
|
+
for (const definition of definitions) {
|
|
592
|
+
for (const scalar of scalarSchemas(definition)) {
|
|
593
|
+
if (!('valueRef' in scalar) || scalar.valueRef === undefined)
|
|
594
|
+
continue;
|
|
595
|
+
if (!allowReferences)
|
|
596
|
+
throw new Error(`Invalid ${path}: valueRef requires Config Definition v3.`);
|
|
597
|
+
const ref = scalar.valueRef;
|
|
598
|
+
const source = ownProperty(fields, ref.field);
|
|
599
|
+
if (source?.type !== 'array' || source.items.type !== 'object' || source.items.properties === undefined) {
|
|
600
|
+
throw new Error(`Invalid ${path}.valueRef.field: expected a declared fixed-object array.`);
|
|
601
|
+
}
|
|
602
|
+
const key = ownProperty(source.items.properties, ref.key);
|
|
603
|
+
if (key === undefined || key.type !== scalar.type || source.validation?.uniqueBy !== ref.key) {
|
|
604
|
+
throw new Error(`Invalid ${path}.valueRef.key: expected a scalar of the same type with matching uniqueBy.`);
|
|
605
|
+
}
|
|
606
|
+
if (ref.label !== undefined && ownProperty(source.items.properties, ref.label) === undefined) {
|
|
607
|
+
throw new Error(`Invalid ${path}.valueRef.label: expected a declared scalar property.`);
|
|
608
|
+
}
|
|
609
|
+
const label = ref.label === undefined ? undefined : ownProperty(source.items.properties, ref.label);
|
|
610
|
+
if (label?.type === 'array' || label?.type === 'object') {
|
|
611
|
+
throw new Error(`Invalid ${path}.valueRef.label: expected a declared scalar property.`);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
402
616
|
export function canonicalizePluginConfigValue(value) {
|
|
403
617
|
if (typeof value !== 'object')
|
|
404
618
|
return value;
|
|
405
619
|
if (Array.isArray(value))
|
|
406
620
|
return value.map((item) => canonicalizePluginConfigValue(item));
|
|
407
|
-
return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)
|
|
621
|
+
return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)
|
|
622
|
+
.map(([key, child]) => [key, canonicalizePluginConfigValue(child)]));
|
|
408
623
|
}
|
|
409
624
|
export function canonicalizePluginConfigValues(values) {
|
|
410
625
|
return Object.fromEntries(Object.entries(values)
|
|
@@ -481,7 +696,9 @@ function normalizeScalarField(value, path, options) {
|
|
|
481
696
|
allowed.push('required');
|
|
482
697
|
if (options.allowSensitive && type === 'string')
|
|
483
698
|
allowed.push('sensitive');
|
|
484
|
-
allowed.push('choices');
|
|
699
|
+
allowed.push('choices', 'valueRef');
|
|
700
|
+
if (options.allowConst)
|
|
701
|
+
allowed.push('const');
|
|
485
702
|
if (type !== 'boolean')
|
|
486
703
|
allowed.push('validation');
|
|
487
704
|
assertAllowedKeys(input, path, allowed);
|
|
@@ -494,10 +711,51 @@ function normalizeScalarField(value, path, options) {
|
|
|
494
711
|
if (sensitive === true && input.choices !== undefined) {
|
|
495
712
|
throw new Error(`Invalid ${path}.choices: sensitive fields cannot declare choices.`);
|
|
496
713
|
}
|
|
714
|
+
if (sensitive === true && input.valueRef !== undefined) {
|
|
715
|
+
throw new Error(`Invalid ${path}.valueRef: sensitive fields cannot declare value references.`);
|
|
716
|
+
}
|
|
717
|
+
if (sensitive === true && Object.hasOwn(input, 'const')) {
|
|
718
|
+
throw new Error(`Invalid ${path}.const: sensitive fields cannot declare a constant.`);
|
|
719
|
+
}
|
|
720
|
+
if (Object.hasOwn(input, 'const') && (typeof input.const !== type
|
|
721
|
+
|| (type === 'number' && !Number.isFinite(input.const)))) {
|
|
722
|
+
throw new Error(`Invalid ${path}.const: expected a finite scalar of type ${type}.`);
|
|
723
|
+
}
|
|
724
|
+
let valueRef;
|
|
725
|
+
if (input.valueRef !== undefined) {
|
|
726
|
+
const rawRef = asObject(input.valueRef, `${path}.valueRef`);
|
|
727
|
+
assertAllowedKeys(rawRef, `${path}.valueRef`, ['field', 'key', 'label', 'include']);
|
|
728
|
+
let include;
|
|
729
|
+
if (rawRef.include !== undefined) {
|
|
730
|
+
if (!Array.isArray(rawRef.include) || !isDenseDataArray(rawRef.include)
|
|
731
|
+
|| rawRef.include.length > PLUGIN_CONFIG_ARRAY_MAX_ITEMS) {
|
|
732
|
+
throw new Error(`Invalid ${path}.valueRef.include: expected at most ${PLUGIN_CONFIG_ARRAY_MAX_ITEMS} scalar values.`);
|
|
733
|
+
}
|
|
734
|
+
include = rawRef.include.map((entry) => {
|
|
735
|
+
if (typeof entry !== type || (type === 'number' && !Number.isFinite(entry))) {
|
|
736
|
+
throw new Error(`Invalid ${path}.valueRef.include: expected values of type ${type}.`);
|
|
737
|
+
}
|
|
738
|
+
return entry;
|
|
739
|
+
});
|
|
740
|
+
if (new Set(include).size !== include.length) {
|
|
741
|
+
throw new Error(`Invalid ${path}.valueRef.include: expected unique scalar values.`);
|
|
742
|
+
}
|
|
743
|
+
if (Buffer.byteLength(JSON.stringify(include), 'utf8') > PLUGIN_CONFIG_ARRAY_MAX_BYTES) {
|
|
744
|
+
throw new Error(`Invalid ${path}.valueRef.include: expected at most ${PLUGIN_CONFIG_ARRAY_MAX_BYTES} UTF-8 JSON bytes.`);
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
valueRef = {
|
|
748
|
+
field: nonEmptyString(rawRef.field, `${path}.valueRef.field`),
|
|
749
|
+
key: nonEmptyString(rawRef.key, `${path}.valueRef.key`),
|
|
750
|
+
label: optionalText(rawRef.label, `${path}.valueRef.label`),
|
|
751
|
+
include,
|
|
752
|
+
};
|
|
753
|
+
}
|
|
497
754
|
const common = {
|
|
498
755
|
title: optionalText(input.title, `${path}.title`),
|
|
499
756
|
description: optionalText(input.description, `${path}.description`),
|
|
500
757
|
...(options.allowRequired ? { required: optionalBoolean(input.required, `${path}.required`) } : {}),
|
|
758
|
+
...(valueRef === undefined ? {} : { valueRef }),
|
|
501
759
|
};
|
|
502
760
|
const defaultValue = options.allowDefault && input.default !== undefined
|
|
503
761
|
? input.default
|
|
@@ -516,6 +774,7 @@ function normalizeScalarField(value, path, options) {
|
|
|
516
774
|
return {
|
|
517
775
|
type,
|
|
518
776
|
...common,
|
|
777
|
+
...(Object.hasOwn(input, 'const') ? { const: input.const } : {}),
|
|
519
778
|
...(options.allowSensitive ? { sensitive } : {}),
|
|
520
779
|
...(options.allowDefault ? { default: defaultValue } : {}),
|
|
521
780
|
choices: input.choices === undefined
|
|
@@ -530,6 +789,7 @@ function normalizeScalarField(value, path, options) {
|
|
|
530
789
|
return {
|
|
531
790
|
type,
|
|
532
791
|
...common,
|
|
792
|
+
...(Object.hasOwn(input, 'const') ? { const: input.const } : {}),
|
|
533
793
|
...(options.allowDefault ? { default: defaultValue } : {}),
|
|
534
794
|
choices: input.choices === undefined
|
|
535
795
|
? undefined
|
|
@@ -542,6 +802,7 @@ function normalizeScalarField(value, path, options) {
|
|
|
542
802
|
return {
|
|
543
803
|
type,
|
|
544
804
|
...common,
|
|
805
|
+
...(Object.hasOwn(input, 'const') ? { const: input.const } : {}),
|
|
545
806
|
...(options.allowDefault ? { default: defaultValue } : {}),
|
|
546
807
|
choices: input.choices === undefined
|
|
547
808
|
? undefined
|
|
@@ -578,7 +839,79 @@ function assertChoices(definition, path) {
|
|
|
578
839
|
throw new Error(`Invalid ${path}.choices: expected at most one recommended preset.`);
|
|
579
840
|
}
|
|
580
841
|
}
|
|
581
|
-
function
|
|
842
|
+
function assertFieldConstraints(definition, path) {
|
|
843
|
+
assertChoices(definition, path);
|
|
844
|
+
if (definition.type !== 'object' && definition.type !== 'array' && definition.const !== undefined) {
|
|
845
|
+
const issues = validateScalarField(definition, definition.const, path);
|
|
846
|
+
if (issues.length > 0)
|
|
847
|
+
throw new Error(`Invalid ${path}.const: ${formatPluginConfigIssue(issues[0])}`);
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
function normalizeObjectProperty(value, path, v4) {
|
|
851
|
+
const input = asObject(value, path);
|
|
852
|
+
let definition;
|
|
853
|
+
if (v4 && (input.type === 'array' || input.type === 'object')) {
|
|
854
|
+
if (Object.hasOwn(input, 'default') || Object.hasOwn(input, 'sensitive')) {
|
|
855
|
+
throw new Error(`Invalid ${path}: object properties cannot declare default or sensitive.`);
|
|
856
|
+
}
|
|
857
|
+
if (input.type === 'object' && (Object.hasOwn(input, 'properties') || Object.hasOwn(input, 'variants'))) {
|
|
858
|
+
throw new Error(`Invalid ${path}: nested objects must be scalar key-value objects.`);
|
|
859
|
+
}
|
|
860
|
+
definition = (input.type === 'array'
|
|
861
|
+
? normalizeArrayField(input, path, true, true)
|
|
862
|
+
: normalizeObjectField(input, path, true));
|
|
863
|
+
delete definition.default;
|
|
864
|
+
}
|
|
865
|
+
else {
|
|
866
|
+
definition = normalizeScalarField(input, path, {
|
|
867
|
+
allowDefault: false, allowRequired: true, allowSensitive: false, allowConst: v4,
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
assertFieldConstraints(definition, path);
|
|
871
|
+
return definition;
|
|
872
|
+
}
|
|
873
|
+
function normalizeObjectVariants(value, properties, path) {
|
|
874
|
+
const input = asObject(value, path);
|
|
875
|
+
assertAllowedKeys(input, path, ['discriminator', 'cases', 'missing', 'otherwise']);
|
|
876
|
+
const discriminator = nonEmptyString(input.discriminator, `${path}.discriminator`);
|
|
877
|
+
if (ownProperty(properties, discriminator)?.type !== 'string') {
|
|
878
|
+
throw new Error(`Invalid ${path}.discriminator: expected a declared own string property.`);
|
|
879
|
+
}
|
|
880
|
+
const casesInput = asObject(input.cases, `${path}.cases`);
|
|
881
|
+
if (Object.keys(casesInput).length < 1 || Object.keys(casesInput).length > MAX_OBJECT_PROPERTIES) {
|
|
882
|
+
throw new Error(`Invalid ${path}.cases: expected 1..${MAX_OBJECT_PROPERTIES} cases.`);
|
|
883
|
+
}
|
|
884
|
+
const normalizeVariant = (value, variantPath) => {
|
|
885
|
+
const variant = asObject(value, variantPath);
|
|
886
|
+
assertAllowedKeys(variant, variantPath, ['properties']);
|
|
887
|
+
const children = asObject(variant.properties, `${variantPath}.properties`);
|
|
888
|
+
if (Object.keys(children).length + Object.keys(properties).length > MAX_OBJECT_PROPERTIES) {
|
|
889
|
+
throw new Error(`Invalid ${variantPath}.properties: common and case properties must total at most ${MAX_OBJECT_PROPERTIES}.`);
|
|
890
|
+
}
|
|
891
|
+
const result = createPropertyMap();
|
|
892
|
+
for (const [key, child] of Object.entries(children)) {
|
|
893
|
+
if (isDangerousConfigKey(key))
|
|
894
|
+
throw new Error(`Invalid ${variantPath}.properties: dangerous config key.`);
|
|
895
|
+
if (Object.hasOwn(properties, key))
|
|
896
|
+
throw new Error(`Invalid ${variantPath}.properties: case cannot override common property "${key}".`);
|
|
897
|
+
result[key] = normalizeObjectProperty(child, `${variantPath}.properties.${key}`, true);
|
|
898
|
+
}
|
|
899
|
+
return { properties: result };
|
|
900
|
+
};
|
|
901
|
+
const cases = createPropertyMap();
|
|
902
|
+
for (const [name, variant] of Object.entries(casesInput)) {
|
|
903
|
+
if (isDangerousConfigKey(name))
|
|
904
|
+
throw new Error(`Invalid ${path}.cases: dangerous case key.`);
|
|
905
|
+
cases[name] = normalizeVariant(variant, `${path}.cases.${name}`);
|
|
906
|
+
}
|
|
907
|
+
const missing = optionalText(input.missing, `${path}.missing`);
|
|
908
|
+
if (missing !== undefined && !Object.hasOwn(cases, missing)) {
|
|
909
|
+
throw new Error(`Invalid ${path}.missing: expected a declared case.`);
|
|
910
|
+
}
|
|
911
|
+
return { discriminator, cases, ...(missing === undefined ? {} : { missing }),
|
|
912
|
+
...(input.otherwise === undefined ? {} : { otherwise: normalizeVariant(input.otherwise, `${path}.otherwise`) }) };
|
|
913
|
+
}
|
|
914
|
+
function normalizeObjectField(value, path, v4 = false) {
|
|
582
915
|
const input = asObject(value, path);
|
|
583
916
|
assertAllowedKeys(input, path, [
|
|
584
917
|
'type',
|
|
@@ -590,6 +923,7 @@ function normalizeObjectField(value, path) {
|
|
|
590
923
|
'properties',
|
|
591
924
|
'additionalProperties',
|
|
592
925
|
'validation',
|
|
926
|
+
...(v4 ? ['variants'] : []),
|
|
593
927
|
]);
|
|
594
928
|
if (input.properties === undefined && (input.additionalProperties === undefined || input.additionalProperties === false)) {
|
|
595
929
|
throw new Error(`Invalid ${path}: key-value object config requires a scalar additionalProperties field.`);
|
|
@@ -608,12 +942,7 @@ function normalizeObjectField(value, path) {
|
|
|
608
942
|
if (isDangerousConfigKey(property)) {
|
|
609
943
|
throw new Error(`Invalid ${path}.properties: dangerous config key "${property}" is not allowed.`);
|
|
610
944
|
}
|
|
611
|
-
properties[property] =
|
|
612
|
-
allowDefault: false,
|
|
613
|
-
allowRequired: true,
|
|
614
|
-
allowSensitive: false,
|
|
615
|
-
});
|
|
616
|
-
assertChoices(properties[property], `${path}.properties.${property}`);
|
|
945
|
+
properties[property] = normalizeObjectProperty(child, `${path}.properties.${property}`, v4);
|
|
617
946
|
}
|
|
618
947
|
}
|
|
619
948
|
let additionalProperties;
|
|
@@ -621,8 +950,8 @@ function normalizeObjectField(value, path) {
|
|
|
621
950
|
additionalProperties = false;
|
|
622
951
|
}
|
|
623
952
|
else {
|
|
624
|
-
additionalProperties = normalizeScalarField(input.additionalProperties, `${path}.additionalProperties`, { allowDefault: false, allowRequired: false, allowSensitive: false });
|
|
625
|
-
|
|
953
|
+
additionalProperties = normalizeScalarField(input.additionalProperties, `${path}.additionalProperties`, { allowDefault: false, allowRequired: false, allowSensitive: false, allowConst: v4 });
|
|
954
|
+
assertFieldConstraints(additionalProperties, `${path}.additionalProperties`);
|
|
626
955
|
}
|
|
627
956
|
let validation;
|
|
628
957
|
if (input.validation !== undefined) {
|
|
@@ -651,6 +980,10 @@ function normalizeObjectField(value, path) {
|
|
|
651
980
|
: normalizeStringValidation(validationInput.key, `${path}.validation.key`),
|
|
652
981
|
};
|
|
653
982
|
}
|
|
983
|
+
if (input.variants !== undefined && properties === undefined) {
|
|
984
|
+
throw new Error(`Invalid ${path}.variants: variants require a fixed object.`);
|
|
985
|
+
}
|
|
986
|
+
const variants = input.variants === undefined ? undefined : normalizeObjectVariants(input.variants, properties, `${path}.variants`);
|
|
654
987
|
return {
|
|
655
988
|
type: 'object',
|
|
656
989
|
title: optionalText(input.title, `${path}.title`),
|
|
@@ -662,11 +995,11 @@ function normalizeObjectField(value, path) {
|
|
|
662
995
|
: normalizeChoices(input.choices, `${path}.choices`),
|
|
663
996
|
...(properties === undefined
|
|
664
997
|
? { additionalProperties: additionalProperties }
|
|
665
|
-
: { properties, additionalProperties: false }),
|
|
998
|
+
: { properties, additionalProperties: false, ...(variants === undefined ? {} : { variants }) }),
|
|
666
999
|
validation,
|
|
667
1000
|
};
|
|
668
1001
|
}
|
|
669
|
-
function normalizeArrayField(value, path) {
|
|
1002
|
+
function normalizeArrayField(value, path, v4 = false, scalarItemsOnly = false) {
|
|
670
1003
|
const input = asObject(value, path);
|
|
671
1004
|
assertAllowedKeys(input, path, ['type', 'title', 'description', 'default', 'required', 'items', 'choices', 'validation']);
|
|
672
1005
|
const itemPath = `${path}.items`;
|
|
@@ -677,20 +1010,20 @@ function normalizeArrayField(value, path) {
|
|
|
677
1010
|
}
|
|
678
1011
|
}
|
|
679
1012
|
let items;
|
|
680
|
-
if (item.type === 'object') {
|
|
681
|
-
const { default: _default, required: _required, ...objectItem } = normalizeObjectField(item, itemPath);
|
|
1013
|
+
if (item.type === 'object' && !scalarItemsOnly) {
|
|
1014
|
+
const { default: _default, required: _required, ...objectItem } = normalizeObjectField(item, itemPath, v4);
|
|
682
1015
|
items = objectItem;
|
|
683
1016
|
}
|
|
684
1017
|
else {
|
|
685
1018
|
items = normalizeScalarField(item, itemPath, {
|
|
686
|
-
allowDefault: false, allowRequired: false, allowSensitive: false,
|
|
1019
|
+
allowDefault: false, allowRequired: false, allowSensitive: false, allowConst: v4,
|
|
687
1020
|
});
|
|
688
1021
|
}
|
|
689
|
-
|
|
1022
|
+
assertFieldConstraints(items, itemPath);
|
|
690
1023
|
let validation;
|
|
691
1024
|
if (input.validation !== undefined) {
|
|
692
1025
|
const rawValidation = asObject(input.validation, `${path}.validation`);
|
|
693
|
-
assertAllowedKeys(rawValidation, `${path}.validation`, ['minItems', 'maxItems', 'uniqueItems']);
|
|
1026
|
+
assertAllowedKeys(rawValidation, `${path}.validation`, ['minItems', 'maxItems', 'uniqueItems', 'uniqueBy']);
|
|
694
1027
|
const minItems = rawValidation.minItems === undefined
|
|
695
1028
|
? undefined
|
|
696
1029
|
: nonNegativeInteger(rawValidation.minItems, `${path}.validation.minItems`);
|
|
@@ -703,10 +1036,17 @@ function normalizeArrayField(value, path) {
|
|
|
703
1036
|
if (minItems !== undefined && minItems > (maxItems ?? PLUGIN_CONFIG_ARRAY_MAX_ITEMS)) {
|
|
704
1037
|
throw new Error(`Invalid ${path}.validation.minItems: must not exceed effective maxItems.`);
|
|
705
1038
|
}
|
|
1039
|
+
const uniqueBy = optionalText(rawValidation.uniqueBy, `${path}.validation.uniqueBy`);
|
|
1040
|
+
const uniqueProperty = items.type === 'object' && uniqueBy !== undefined ? ownProperty(items.properties, uniqueBy) : undefined;
|
|
1041
|
+
if (uniqueBy !== undefined && (uniqueProperty === undefined
|
|
1042
|
+
|| uniqueProperty.type === 'array' || uniqueProperty.type === 'object')) {
|
|
1043
|
+
throw new Error(`Invalid ${path}.validation.uniqueBy: expected a declared fixed scalar property.`);
|
|
1044
|
+
}
|
|
706
1045
|
validation = {
|
|
707
1046
|
minItems,
|
|
708
1047
|
maxItems,
|
|
709
1048
|
uniqueItems: optionalBoolean(rawValidation.uniqueItems, `${path}.validation.uniqueItems`),
|
|
1049
|
+
...(uniqueBy === undefined ? {} : { uniqueBy }),
|
|
710
1050
|
};
|
|
711
1051
|
}
|
|
712
1052
|
return {
|
|
@@ -732,7 +1072,7 @@ export function decodePluginConfigSummaryPath(value) {
|
|
|
732
1072
|
throw new Error('Invalid summary path: dangerous config key.');
|
|
733
1073
|
return property;
|
|
734
1074
|
}
|
|
735
|
-
function expandConfigReferences(input, path) {
|
|
1075
|
+
function expandConfigReferences(input, path, v4 = false) {
|
|
736
1076
|
const definitions = input.definitions === undefined ? {} : asObject(input.definitions, `${path}.definitions`);
|
|
737
1077
|
if (Object.keys(definitions).length > PLUGIN_CONFIG_V3_MAX_DEFINITIONS) {
|
|
738
1078
|
throw new Error(`Invalid ${path}.definitions: expected at most ${PLUGIN_CONFIG_V3_MAX_DEFINITIONS} definitions.`);
|
|
@@ -742,7 +1082,7 @@ function expandConfigReferences(input, path) {
|
|
|
742
1082
|
throw new Error(`Invalid ${path}.definitions: invalid definition name "${name}".`);
|
|
743
1083
|
}
|
|
744
1084
|
}
|
|
745
|
-
const expand = (value, fieldPath, chain,
|
|
1085
|
+
const expand = (value, fieldPath, chain, context = 'field') => {
|
|
746
1086
|
const field = asObject(value, fieldPath);
|
|
747
1087
|
if (Object.hasOwn(field, '$ref')) {
|
|
748
1088
|
assertAllowedKeys(field, fieldPath, ['$ref', 'title', 'description', 'default', 'required']);
|
|
@@ -757,7 +1097,7 @@ function expandConfigReferences(input, path) {
|
|
|
757
1097
|
if (chain.length >= PLUGIN_CONFIG_V3_MAX_REF_DEPTH) {
|
|
758
1098
|
throw new Error(`Invalid ${fieldPath}.$ref: expected at most ${PLUGIN_CONFIG_V3_MAX_REF_DEPTH} reference edges.`);
|
|
759
1099
|
}
|
|
760
|
-
const result = expand(definitions[name], `${path}.definitions.${name}`, [...chain, name],
|
|
1100
|
+
const result = expand(definitions[name], `${path}.definitions.${name}`, [...chain, name], context);
|
|
761
1101
|
for (const key of ['title', 'description', 'default', 'required']) {
|
|
762
1102
|
if (Object.hasOwn(field, key))
|
|
763
1103
|
result[key] = field[key];
|
|
@@ -765,43 +1105,67 @@ function expandConfigReferences(input, path) {
|
|
|
765
1105
|
return result;
|
|
766
1106
|
}
|
|
767
1107
|
const result = { ...field };
|
|
768
|
-
if (
|
|
1108
|
+
if (context === 'scalar' || (!v4 && context === 'property'))
|
|
769
1109
|
return result;
|
|
1110
|
+
if (context === 'property' && field.type === 'object' && (field.properties !== undefined || field.variants !== undefined)) {
|
|
1111
|
+
throw new Error(`Invalid ${fieldPath}: nested objects must be scalar key-value objects.`);
|
|
1112
|
+
}
|
|
770
1113
|
if (field.type === 'array' && field.items !== undefined) {
|
|
771
|
-
result.items = expand(field.items, `${fieldPath}.items`, chain);
|
|
1114
|
+
result.items = expand(field.items, `${fieldPath}.items`, chain, context === 'property' ? 'scalar' : 'item');
|
|
772
1115
|
}
|
|
773
1116
|
if (field.properties !== undefined) {
|
|
774
1117
|
const properties = asObject(field.properties, `${fieldPath}.properties`);
|
|
775
1118
|
if (Object.keys(properties).length > MAX_OBJECT_PROPERTIES) {
|
|
776
1119
|
throw new Error(`Invalid ${fieldPath}.properties: expected at most ${MAX_OBJECT_PROPERTIES} properties.`);
|
|
777
1120
|
}
|
|
778
|
-
result.properties = Object.fromEntries(Object.entries(properties).map(([key, child]) => [key, expand(child, `${fieldPath}.properties.${key}`, chain,
|
|
1121
|
+
result.properties = Object.fromEntries(Object.entries(properties).map(([key, child]) => [key, expand(child, `${fieldPath}.properties.${key}`, chain, 'property')]));
|
|
779
1122
|
}
|
|
780
1123
|
if (field.additionalProperties !== undefined && field.additionalProperties !== false) {
|
|
781
|
-
result.additionalProperties = expand(field.additionalProperties, `${fieldPath}.additionalProperties`, chain,
|
|
1124
|
+
result.additionalProperties = expand(field.additionalProperties, `${fieldPath}.additionalProperties`, chain, 'scalar');
|
|
1125
|
+
}
|
|
1126
|
+
if (v4 && field.variants !== undefined) {
|
|
1127
|
+
const variants = asObject(field.variants, `${fieldPath}.variants`);
|
|
1128
|
+
const cases = asObject(variants.cases, `${fieldPath}.variants.cases`);
|
|
1129
|
+
if (Object.keys(cases).length > MAX_OBJECT_PROPERTIES)
|
|
1130
|
+
throw new Error(`Invalid ${fieldPath}.variants.cases: expected at most ${MAX_OBJECT_PROPERTIES} cases.`);
|
|
1131
|
+
const expandVariant = (value, variantPath) => {
|
|
1132
|
+
const variant = asObject(value, variantPath);
|
|
1133
|
+
const properties = asObject(variant.properties, `${variantPath}.properties`);
|
|
1134
|
+
if (Object.keys(properties).length > MAX_OBJECT_PROPERTIES)
|
|
1135
|
+
throw new Error(`Invalid ${variantPath}.properties: expected at most ${MAX_OBJECT_PROPERTIES} properties.`);
|
|
1136
|
+
return { ...variant, properties: Object.fromEntries(Object.entries(properties).map(([key, child]) => [key, expand(child, `${variantPath}.properties.${key}`, chain, 'property')])) };
|
|
1137
|
+
};
|
|
1138
|
+
result.variants = { ...variants, cases: Object.fromEntries(Object.entries(cases).map(([name, variant]) => [name, expandVariant(variant, `${fieldPath}.variants.cases.${name}`)])),
|
|
1139
|
+
...(variants.otherwise === undefined ? {} : { otherwise: expandVariant(variants.otherwise, `${fieldPath}.variants.otherwise`) }) };
|
|
782
1140
|
}
|
|
783
1141
|
return result;
|
|
784
1142
|
};
|
|
785
1143
|
// Validate unused definitions too, through the same field/default/choice validator.
|
|
1144
|
+
const normalizedDefinitions = [];
|
|
786
1145
|
for (const [name, value] of Object.entries(definitions)) {
|
|
787
|
-
normalizeConfigField(expand(value, `${path}.definitions.${name}`, []), `${path}.definitions.${name}`, true);
|
|
1146
|
+
normalizedDefinitions.push(normalizeConfigField(expand(value, `${path}.definitions.${name}`, []), `${path}.definitions.${name}`, true, v4));
|
|
788
1147
|
}
|
|
789
1148
|
const fields = asObject(input.fields, `${path}.fields`);
|
|
790
1149
|
if (Object.keys(fields).length > PLUGIN_CONFIG_V3_MAX_FIELDS) {
|
|
791
1150
|
throw new Error(`Invalid ${path}.fields: expected at most ${PLUGIN_CONFIG_V3_MAX_FIELDS} fields.`);
|
|
792
1151
|
}
|
|
793
|
-
|
|
1152
|
+
const expanded = Object.fromEntries(Object.entries(fields).map(([key, value]) => [key, expand(value, `${path}.fields.${key}`, [])]));
|
|
1153
|
+
if (normalizedDefinitions.some((definition) => scalarSchemas(definition).some((scalar) => 'valueRef' in scalar && scalar.valueRef !== undefined))) {
|
|
1154
|
+
const normalizedFields = Object.fromEntries(Object.entries(expanded).map(([key, value]) => [key, normalizeConfigField(value, `${path}.fields.${key}`, true, v4)]));
|
|
1155
|
+
assertValueReferenceSchemas(normalizedFields, normalizedDefinitions, `${path}.definitions`);
|
|
1156
|
+
}
|
|
1157
|
+
return expanded;
|
|
794
1158
|
}
|
|
795
|
-
function normalizeConfigField(rawDefinition, path, allowArray) {
|
|
1159
|
+
function normalizeConfigField(rawDefinition, path, allowArray, v4 = false) {
|
|
796
1160
|
const rawField = asObject(rawDefinition, path);
|
|
797
1161
|
const definition = rawField.type === 'object'
|
|
798
|
-
? normalizeObjectField(rawField, path)
|
|
1162
|
+
? normalizeObjectField(rawField, path, v4)
|
|
799
1163
|
: rawField.type === 'array' && allowArray
|
|
800
|
-
? normalizeArrayField(rawField, path)
|
|
1164
|
+
? normalizeArrayField(rawField, path, v4)
|
|
801
1165
|
: normalizeScalarField(rawField, path, {
|
|
802
|
-
allowDefault: true, allowRequired: true, allowSensitive: true,
|
|
1166
|
+
allowDefault: true, allowRequired: true, allowSensitive: true, allowConst: v4,
|
|
803
1167
|
});
|
|
804
|
-
|
|
1168
|
+
assertFieldConstraints(definition, path);
|
|
805
1169
|
if (definition.default !== undefined) {
|
|
806
1170
|
const issues = validatePluginConfigValue(definition, definition.default, path);
|
|
807
1171
|
if (issues.length > 0)
|
|
@@ -868,7 +1232,15 @@ function normalizeConfigSummaries(input, fields, path) {
|
|
|
868
1232
|
if (!summary)
|
|
869
1233
|
throw new Error(`Invalid ${viewPath}.${itemSummary ? 'itemSummary' : 'summary'}: unknown summary.`);
|
|
870
1234
|
for (const part of summary.parts) {
|
|
871
|
-
|
|
1235
|
+
const propertyName = decodePluginConfigSummaryPath(part.path);
|
|
1236
|
+
const declarations = [objectField.properties,
|
|
1237
|
+
...Object.values(objectField.variants?.cases ?? {}).map((variant) => variant.properties),
|
|
1238
|
+
...(objectField.variants?.otherwise === undefined ? [] : [objectField.variants.otherwise.properties])]
|
|
1239
|
+
.flatMap((properties) => {
|
|
1240
|
+
const property = ownProperty(properties, propertyName);
|
|
1241
|
+
return property === undefined ? [] : [property];
|
|
1242
|
+
});
|
|
1243
|
+
if (declarations.length === 0 || declarations.some((property) => property.type === 'array' || property.type === 'object')) {
|
|
872
1244
|
throw new Error(`Invalid ${viewPath}: summary path "${part.path}" must name a declared scalar property.`);
|
|
873
1245
|
}
|
|
874
1246
|
}
|
|
@@ -881,15 +1253,23 @@ function normalizeConfigSummaries(input, fields, path) {
|
|
|
881
1253
|
}
|
|
882
1254
|
export function normalizePluginConfigDefinition(value, path = 'config') {
|
|
883
1255
|
const input = asObject(value, path);
|
|
884
|
-
const
|
|
1256
|
+
const v4 = input.schemaVersion === 4;
|
|
1257
|
+
const v3 = input.schemaVersion === 3 || v4;
|
|
885
1258
|
assertAllowedKeys(input, path, v3 ? ['schemaVersion', 'definitions', 'fields', 'presentation'] : ['fields', 'presentation']);
|
|
886
|
-
const fieldsInput = v3 ? expandConfigReferences(input, path) : asObject(input.fields, `${path}.fields`);
|
|
1259
|
+
const fieldsInput = v3 ? expandConfigReferences(input, path, v4) : asObject(input.fields, `${path}.fields`);
|
|
887
1260
|
const fields = createPropertyMap();
|
|
888
1261
|
for (const [field, rawDefinition] of Object.entries(fieldsInput)) {
|
|
889
1262
|
if (!CONFIG_KEY_PATTERN.test(field) || isDangerousConfigKey(field)) {
|
|
890
1263
|
throw new Error(`Invalid ${path}.fields: config key "${field}" is not valid.`);
|
|
891
1264
|
}
|
|
892
|
-
fields[field] = normalizeConfigField(rawDefinition, `${path}.fields.${field}`, v3);
|
|
1265
|
+
fields[field] = normalizeConfigField(rawDefinition, `${path}.fields.${field}`, v3, v4);
|
|
1266
|
+
}
|
|
1267
|
+
assertValueReferenceSchemas(fields, Object.values(fields), path, v3);
|
|
1268
|
+
const defaultValues = Object.fromEntries(Object.entries(fields).flatMap(([field, definition]) => definition.default === undefined ? [] : [[field, definition.default]]));
|
|
1269
|
+
const defaultIssues = validatePluginConfigReferences({ schemaVersion: 4, fields }, defaultValues, Object.keys(fields))
|
|
1270
|
+
.filter((issue) => Object.hasOwn(defaultValues, issue.path.field));
|
|
1271
|
+
if (defaultIssues.length > 0) {
|
|
1272
|
+
throw new Error(`Invalid ${path}.default: ${formatPluginConfigIssue(defaultIssues[0])}`);
|
|
893
1273
|
}
|
|
894
1274
|
let presentation;
|
|
895
1275
|
if (input.presentation !== undefined) {
|
|
@@ -935,6 +1315,6 @@ export function normalizePluginConfigDefinition(value, path = 'config') {
|
|
|
935
1315
|
}),
|
|
936
1316
|
};
|
|
937
1317
|
}
|
|
938
|
-
return { ...(v3 ? { schemaVersion: 3 } : {}), fields, presentation };
|
|
1318
|
+
return { ...(v3 ? { schemaVersion: v4 ? 4 : 3 } : {}), fields, presentation };
|
|
939
1319
|
}
|
|
940
1320
|
//# sourceMappingURL=plugin-config.js.map
|