Setting the file. One moment.
Component Capabilities · Wix Headless Replatform · wix/skills · Skills Docs
ContentsBack to the top of the page 28.10
Workflow
Next
Script Component Registry
scripts/lib/ component-capabilities.mjs
JavaScript · 121 lines · 8 KB
,
6 ]);
7 const SLOT_KINDS = new Set ([ "text" , "icon" , "url" , "action" , "media" , "content" , "boolean" , "number" ]);
8
9 export function validateComponentCapabilities ( manifest , expected = {}) {
10 const errors = [];
11 if ( ! isObject (manifest)) return { ok: false , errors: [ "capability manifest must be an object" ] };
12 for ( const field of Object. keys (manifest)) if ( ! TOP_LEVEL_FIELDS . has (field)) errors. push ( `unknown top-level field: ${ field }` );
13 if (manifest.schemaVersion !== COMPONENT_CAPABILITIES_SCHEMA_VERSION ) errors. push ( `schemaVersion must be ${ COMPONENT_CAPABILITIES_SCHEMA_VERSION }` );
14 if ( ! isObject (manifest.component) || ! manifest.component.name || ! manifest.component.revision) errors. push ( "component name and revision are required" );
15 if ( ! isObject (manifest.contract) || ! manifest.contract.name || ! manifest.contract.version) errors. push ( "contract name and version are required" );
16 rejectUnknown (manifest.component, new Set ([ "name" , "revision" ]), "component" , errors);
17 rejectUnknown (manifest.contract, new Set ([ "name" , "version" ]), "contract" , errors);
18 if (expected.name && manifest.component?.name !== expected.name) errors. push ( `component name must be ${ expected . name }` );
19 if (expected.revision && manifest.component?.revision !== expected.revision) errors. push ( `component revision must be ${ expected . revision }` );
20 if (expected.contract && manifest.contract?.name !== expected.contract) errors. push ( `contract name must be ${ expected . contract }` );
21
22 const axes = uniqueNamed (manifest.axes, "axis" , errors);
23 for ( const axis of axes) {
24 if ( ! Array. isArray (axis.values) || ! axis.values. length || axis.values. some (( value ) => typeof value !== "string" )) errors. push ( `axis ${ axis . name } requires string values` );
25 if ( new Set (axis.values || []).size !== (axis.values || []). length ) errors. push ( `axis ${ axis . name } values must be unique` );
26 if ( ! axis.values?. includes (axis.default)) errors. push ( `axis ${ axis . name } default must be one of its values` );
27 rejectUnknown (axis, new Set ([ "name" , "values" , "default" , "description" ]), `axis ${ axis . name }` , errors);
28 }
29 const slots = uniqueNamed (manifest.slots, "slot" , errors);
30 for ( const slot of slots) {
31 if ( ! SLOT_KINDS . has (slot.kind)) errors. push ( `slot ${ slot . name } has unsupported kind ${ slot . kind }` );
32 rejectUnknown (slot, new Set ([ "name" , "kind" , "description" ]), `slot ${ slot . name }` , errors);
33 }
34 uniqueStrings (manifest.states, "states" , errors);
35 const compositions = uniqueNamed (manifest.compositions, "composition" , errors);
36 const compositionNames = new Set (compositions. map (( entry ) => entry.name));
37 const slotNames = new Set (slots. map (( entry ) => entry.name));
38 if ( ! compositionNames. has (manifest.defaultComposition)) errors. push ( "defaultComposition must name a supported composition" );
39 for ( const composition of compositions) {
40 uniqueStrings (composition.requiredSlots, `composition ${ composition . name } requiredSlots` , errors);
41 for ( const slot of composition.requiredSlots || []) if ( ! slotNames. has (slot)) errors. push ( `composition ${ composition . name } references unknown slot ${ slot }` );
42 rejectUnknown (composition, new Set ([ "name" , "requiredSlots" , "description" ]), `composition ${ composition . name }` , errors);
43 }
44 const unsupported = uniqueNamed (manifest.unsupportedCompositions, "unsupported composition" , errors);
45 for ( const entry of unsupported) {
46 if ( ! entry.reason) errors. push ( `unsupported composition ${ entry . name } requires a reason` );
47 if (compositionNames. has (entry.name)) errors. push ( `composition ${ entry . name } cannot be both supported and unsupported` );
48 rejectUnknown (entry, new Set ([ "name" , "reason" ]), `unsupported composition ${ entry . name }` , errors);
49 }
50 const constraints = uniqueNamed (manifest.constraints, "constraint" , errors, "id" );
51 for ( const constraint of constraints) {
52 if ( ! constraint.description) errors. push ( `constraint ${ constraint . id } requires a description` );
53 uniqueStrings (constraint.appliesTo, `constraint ${ constraint . id } appliesTo` , errors);
54 uniqueStrings (constraint.requires, `constraint ${ constraint . id } requires` , errors);
55 rejectUnknown (constraint, new Set ([ "id" , "description" , "appliesTo" , "requires" ]), `constraint ${ constraint . id }` , errors);
56 }
57 uniqueStrings (manifest.evidenceRefs, "evidenceRefs" , errors, { minimum: 1 });
58 return { ok: errors. length === 0 , errors };
59 }
60
61 export function resolveCapabilityBinding ( manifest , requirements = {}) {
62 const validation = validateComponentCapabilities (manifest);
63 if ( ! validation.ok) return { ok: false , reasons: validation.errors };
64 const reasons = [];
65 const axes = Object. fromEntries (manifest.axes. map (( axis ) => [axis.name, requirements.axes?.[axis.name] ?? axis.default]));
66 for ( const [ name , value ] of Object. entries (requirements.axes || {})) {
67 const axis = manifest.axes. find (( entry ) => entry.name === name);
68 if ( ! axis) reasons. push ( `unsupported-axis:${ name }` );
69 else if ( ! axis.values. includes (value)) reasons. push ( `unsupported-axis-value:${ name }=${ value }` );
70 }
71 const compositionName = requirements.composition || manifest.defaultComposition;
72 const composition = manifest.compositions. find (( entry ) => entry.name === compositionName);
73 if ( ! composition) reasons. push ( `unsupported-composition:${ compositionName }` );
74 const availableSlots = new Set (requirements.availableSlots || []);
75 for ( const slot of requirements.requiredSlots || []) {
76 if ( ! manifest.slots. some (( entry ) => entry.name === slot)) reasons. push ( `unsupported-slot:${ slot }` );
77 }
78 for ( const slot of composition?.requiredSlots || []) if ( ! availableSlots. has (slot)) reasons. push ( `missing-slot:${ slot }` );
79 for ( const state of requirements.requiredStates || []) if ( ! manifest.states. includes (state)) reasons. push ( `unsupported-state:${ state }` );
80
81 const selectedTokens = new Set ([
82 `composition:${ compositionName }` ,
83 ... Object. entries (axes). map (([ name , value ]) => `axis:${ name }=${ value }` ),
84 ... [ ... availableSlots]. map (( slot ) => String (slot). startsWith ( "slot:" ) ? slot : `slot:${ slot }` ),
85 ]);
86 for ( const constraint of manifest.constraints) {
87 if ((constraint.appliesTo || []). every (( token ) => selectedTokens. has (token))) {
88 for ( const token of constraint.requires || []) if ( ! selectedTokens. has (token)) reasons. push ( `constraint:${ constraint . id }:${ token }` );
89 }
90 }
91 return {
92 ok: reasons. length === 0 ,
93 reasons: [ ...new Set (reasons)],
94 binding: { axes, composition: compositionName, availableSlots: [ ... availableSlots]. sort () },
95 };
96 }
97
98 function uniqueNamed ( value , label , errors , key = "name" ) {
99 if ( ! Array. isArray (value)) {
100 errors. push ( `${ label }s must be an array` );
101 return [];
102 }
103 const entries = value. filter (( entry ) => isObject (entry));
104 if (entries. length !== value. length ) errors. push ( `${ label }s must contain objects` );
105 const names = entries. map (( entry ) => entry[key]);
106 if (names. some (( name ) => typeof name !== "string" || ! name)) errors. push ( `${ label }s require ${ key }` );
107 if ( new Set (names).size !== names. length ) errors. push ( `${ label } ${ key }s must be unique` );
108 return entries;
109 }
110
111 function uniqueStrings ( value , label , errors , { minimum = 0 } = {}) {
112 if ( ! Array. isArray (value) || value. some (( entry ) => typeof entry !== "string" || ! entry)) errors. push ( `${ label } must contain strings` );
113 else if ( new Set (value).size !== value. length ) errors. push ( `${ label } must be unique` );
114 if ((value?. length || 0 ) < minimum) errors. push ( `${ label } must contain at least ${ minimum } item(s)` );
115 }
116
117 function rejectUnknown ( value , allowed , label , errors ) {
118 for ( const field of Object. keys (value || {})) if ( ! allowed. has (field)) errors. push ( `${ label } has unknown field ${ field }` );
119 }
120
121 function isObject ( value ) { return Boolean (value) && typeof value === "object" && ! Array. isArray (value); }