@africode/core 5.0.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/AFRICODE_FRAMEWORK_GUIDE.md +707 -0
- package/LICENSE +623 -0
- package/README.md +442 -0
- package/bin/africode.js +73 -0
- package/bin/africode.js.1758507140 +343 -0
- package/bin/cli.ts +83 -0
- package/bin/create-africode.js +158 -0
- package/bin/scaffold.ts +219 -0
- package/components/accordion.js +183 -0
- package/components/alert.js +131 -0
- package/components/auth.js +172 -0
- package/components/avatar.js +117 -0
- package/components/badge.js +104 -0
- package/components/base.d.ts +139 -0
- package/components/base.js +184 -0
- package/components/button.js +164 -0
- package/components/card.js +137 -0
- package/components/cultural-card.js +243 -0
- package/components/divider.js +83 -0
- package/components/dropdown.js +171 -0
- package/components/error-boundary.js +155 -0
- package/components/form.js +131 -0
- package/components/grid.js +273 -0
- package/components/hero.js +138 -0
- package/components/icon.js +36 -0
- package/components/index.js +57 -0
- package/components/input.js +256 -0
- package/components/kanga-card.js +185 -0
- package/components/language-switcher.js +108 -0
- package/components/loader.js +80 -0
- package/components/modal.js +262 -0
- package/components/motion.js +84 -0
- package/components/navbar.js +236 -0
- package/components/pattern-showcase.js +225 -0
- package/components/progress.js +134 -0
- package/components/react.js +111 -0
- package/components/section.js +54 -0
- package/components/select.js +322 -0
- package/components/sidebar.js +180 -0
- package/components/skeleton.js +85 -0
- package/components/table.js +181 -0
- package/components/tabs.js +202 -0
- package/components/theme-toggle.js +82 -0
- package/components/toast.js +139 -0
- package/components/tooltip.js +167 -0
- package/core/a2ui-schema-manager.js +344 -0
- package/core/a2ui.js +431 -0
- package/core/bun-runtime.js +799 -0
- package/core/cli/commands/add.js +23 -0
- package/core/cli/commands/audit.js +58 -0
- package/core/cli/commands/build.js +137 -0
- package/core/cli/commands/create-plugin.js +241 -0
- package/core/cli/commands/dev.js +228 -0
- package/core/cli/commands/lint.js +23 -0
- package/core/cli/commands/test.js +34 -0
- package/core/cli/migrator.js +71 -0
- package/core/cli/ui.js +46 -0
- package/core/compliance.js +628 -0
- package/core/config.js +263 -0
- package/core/db-advanced.js +481 -0
- package/core/db.js +284 -0
- package/core/enhanced-hmr.js +404 -0
- package/core/errors.js +222 -0
- package/core/file-router.js +290 -0
- package/core/heartbeat.js +64 -0
- package/core/hmr-client.js +204 -0
- package/core/hmr.js +196 -0
- package/core/html.d.ts +116 -0
- package/core/html.js +160 -0
- package/core/hydration.js +52 -0
- package/core/lipa-namba-journey.js +572 -0
- package/core/motion.js +106 -0
- package/core/nida-cig-middleware.js +455 -0
- package/core/patterns.d.ts +124 -0
- package/core/patterns.js +833 -0
- package/core/plugins/index.js +312 -0
- package/core/router.js +387 -0
- package/core/sdk-client.js +62 -0
- package/core/sdk.d.ts +133 -0
- package/core/sdk.js +123 -0
- package/core/seo.js +76 -0
- package/core/server/auth-endpoints.js +339 -0
- package/core/server/auth.js +180 -0
- package/core/server/csrf.js +206 -0
- package/core/server/db.js +39 -0
- package/core/server/middleware.js +324 -0
- package/core/server/rate-limit.js +238 -0
- package/core/server/render.js +69 -0
- package/core/server/router.js +120 -0
- package/core/shim.js +28 -0
- package/core/state.d.ts +86 -0
- package/core/state.js +242 -0
- package/core/store.d.ts +122 -0
- package/core/store.js +61 -0
- package/core/validation.d.ts +233 -0
- package/core/validation.js +590 -0
- package/core/websocket.js +639 -0
- package/dist/africode.js +2905 -0
- package/dist/africode.js.map +61 -0
- package/dist/build-info.json +23 -0
- package/dist/components.js +2888 -0
- package/dist/components.js.map +58 -0
- package/dist/styles/africanity.css +322 -0
- package/dist/styles/typography.css +141 -0
- package/docs/IDE-Guide.md +50 -0
- package/package.json +110 -0
- package/src/index.ts +196 -0
- package/styles/africanity.css +322 -0
- package/styles/typography.css +141 -0
- package/templates/starter/.env.example +15 -0
- package/templates/starter/africode.config.js +40 -0
- package/templates/starter/package.json +14 -0
- package/templates/starter/src/pages/index.html +46 -0
- package/templates/starter/src/pages/index.js +32 -0
- package/templates/starter/src/styles/main.css +4 -0
- package/templates/starter-3d/.env.example +7 -0
- package/templates/starter-3d/africode.config.js +29 -0
- package/templates/starter-3d/components/af-model-viewer.js +125 -0
- package/templates/starter-3d/package.json +15 -0
- package/templates/starter-3d/src/pages/index.html +46 -0
- package/templates/starter-3d/src/pages/index.js +50 -0
- package/templates/starter-3d/src/styles/main.css +4 -0
- package/templates/starter-react/.env.example +15 -0
- package/templates/starter-react/africode.config.js +40 -0
- package/templates/starter-react/package.json +16 -0
- package/templates/starter-react/src/pages/index.html +46 -0
- package/templates/starter-react/src/pages/index.js +68 -0
- package/templates/starter-react/src/styles/main.css +4 -0
- package/templates/starter-tailwind/.env.example +15 -0
- package/templates/starter-tailwind/africode.config.js +40 -0
- package/templates/starter-tailwind/package.json +20 -0
- package/templates/starter-tailwind/src/pages/index.html +46 -0
- package/templates/starter-tailwind/src/pages/index.js +37 -0
- package/templates/starter-tailwind/src/styles/main.css +4 -0
- package/templates/starter-tailwind/src/styles/tailwind.css +1 -0
- package/templates/starter-tailwind/src/tailwind-loader.js +30 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AfriCode Tooltip Component
|
|
3
|
+
*
|
|
4
|
+
* Hover tooltips with positioning options.
|
|
5
|
+
*
|
|
6
|
+
* @module components/tooltip
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { AfriCodeComponent, registerComponent } from './base.js';
|
|
10
|
+
|
|
11
|
+
export class AfriTooltip extends AfriCodeComponent {
|
|
12
|
+
static get observedAttributes() {
|
|
13
|
+
return ['text', 'position'];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
constructor() {
|
|
17
|
+
super();
|
|
18
|
+
this._tooltipId = `af-tooltip-${Math.random().toString(36).substr(2, 9)}`;
|
|
19
|
+
this.render();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
connectedCallback() {
|
|
23
|
+
this._setupTooltipListeners();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
attributeChangedCallback() {
|
|
27
|
+
this.render();
|
|
28
|
+
this._setupTooltipListeners();
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
_setupTooltipListeners() {
|
|
32
|
+
const trigger = this.shadowRoot.querySelector('.tooltip-trigger');
|
|
33
|
+
const content = this.shadowRoot.querySelector('.tooltip-content');
|
|
34
|
+
|
|
35
|
+
if (!trigger || !content) {return;}
|
|
36
|
+
|
|
37
|
+
trigger.addEventListener('focus', () => {
|
|
38
|
+
content.style.opacity = '1';
|
|
39
|
+
content.style.visibility = 'visible';
|
|
40
|
+
content.setAttribute('aria-hidden', 'false');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
trigger.addEventListener('blur', () => {
|
|
44
|
+
content.style.opacity = '0';
|
|
45
|
+
content.style.visibility = 'hidden';
|
|
46
|
+
content.setAttribute('aria-hidden', 'true');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
trigger.addEventListener('mouseenter', () => {
|
|
50
|
+
content.style.opacity = '1';
|
|
51
|
+
content.style.visibility = 'visible';
|
|
52
|
+
content.setAttribute('aria-hidden', 'false');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
trigger.addEventListener('mouseleave', () => {
|
|
56
|
+
content.style.opacity = '0';
|
|
57
|
+
content.style.visibility = 'hidden';
|
|
58
|
+
content.setAttribute('aria-hidden', 'true');
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
render() {
|
|
63
|
+
const text = this.getAttribute('text') || 'Tooltip';
|
|
64
|
+
const position = this.getAttribute('position') || 'top';
|
|
65
|
+
|
|
66
|
+
const positions = {
|
|
67
|
+
top: `
|
|
68
|
+
bottom: 100%;
|
|
69
|
+
left: 50%;
|
|
70
|
+
transform: translateX(-50%) translateY(-8px);
|
|
71
|
+
`,
|
|
72
|
+
bottom: `
|
|
73
|
+
top: 100%;
|
|
74
|
+
left: 50%;
|
|
75
|
+
transform: translateX(-50%) translateY(8px);
|
|
76
|
+
`,
|
|
77
|
+
left: `
|
|
78
|
+
right: 100%;
|
|
79
|
+
top: 50%;
|
|
80
|
+
transform: translateY(-50%) translateX(-8px);
|
|
81
|
+
`,
|
|
82
|
+
right: `
|
|
83
|
+
left: 100%;
|
|
84
|
+
top: 50%;
|
|
85
|
+
transform: translateY(-50%) translateX(8px);
|
|
86
|
+
`
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
this.shadowRoot.innerHTML = `
|
|
90
|
+
<style>
|
|
91
|
+
:host {
|
|
92
|
+
display: inline-block;
|
|
93
|
+
position: relative;
|
|
94
|
+
font-family: 'Inter', system-ui, sans-serif;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.tooltip-trigger {
|
|
98
|
+
display: inline-block;
|
|
99
|
+
cursor: help;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
.tooltip-content {
|
|
103
|
+
position: absolute;
|
|
104
|
+
${positions[position]}
|
|
105
|
+
padding: 8px 12px;
|
|
106
|
+
background: #1e1e1e;
|
|
107
|
+
border: 1px solid #2a2a2a;
|
|
108
|
+
border-radius: 6px;
|
|
109
|
+
color: #ffffff;
|
|
110
|
+
font-size: 0.85rem;
|
|
111
|
+
white-space: nowrap;
|
|
112
|
+
opacity: 0;
|
|
113
|
+
visibility: hidden;
|
|
114
|
+
transition: all 0.2s ease;
|
|
115
|
+
z-index: 1000;
|
|
116
|
+
box-shadow: 0 4px 20px rgba(0,0,0,0.4);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.tooltip-trigger:hover + .tooltip-content,
|
|
120
|
+
.tooltip-trigger:focus + .tooltip-content,
|
|
121
|
+
.tooltip-content:hover {
|
|
122
|
+
opacity: 1;
|
|
123
|
+
visibility: visible;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/* Arrow */
|
|
127
|
+
.tooltip-content::after {
|
|
128
|
+
content: '';
|
|
129
|
+
position: absolute;
|
|
130
|
+
border: 6px solid transparent;
|
|
131
|
+
${position === 'top' ? `
|
|
132
|
+
top: 100%;
|
|
133
|
+
left: 50%;
|
|
134
|
+
transform: translateX(-50%);
|
|
135
|
+
border-top-color: #1e1e1e;
|
|
136
|
+
` : ''}
|
|
137
|
+
${position === 'bottom' ? `
|
|
138
|
+
bottom: 100%;
|
|
139
|
+
left: 50%;
|
|
140
|
+
transform: translateX(-50%);
|
|
141
|
+
border-bottom-color: #1e1e1e;
|
|
142
|
+
` : ''}
|
|
143
|
+
${position === 'left' ? `
|
|
144
|
+
left: 100%;
|
|
145
|
+
top: 50%;
|
|
146
|
+
transform: translateY(-50%);
|
|
147
|
+
border-left-color: #1e1e1e;
|
|
148
|
+
` : ''}
|
|
149
|
+
${position === 'right' ? `
|
|
150
|
+
right: 100%;
|
|
151
|
+
top: 50%;
|
|
152
|
+
transform: translateY(-50%);
|
|
153
|
+
border-right-color: #1e1e1e;
|
|
154
|
+
` : ''}
|
|
155
|
+
}
|
|
156
|
+
</style>
|
|
157
|
+
|
|
158
|
+
<span class="tooltip-trigger" tabindex="0" aria-describedby="${this._tooltipId}">
|
|
159
|
+
<slot></slot>
|
|
160
|
+
</span>
|
|
161
|
+
<div class="tooltip-content" id="${this._tooltipId}" role="tooltip" aria-hidden="true">${text}</div>
|
|
162
|
+
`;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
registerComponent('af-tooltip', AfriTooltip);
|
|
167
|
+
export default AfriTooltip;
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A2UI Schema Manager
|
|
3
|
+
* Generates and validates component manifest for AI agent consumption
|
|
4
|
+
*
|
|
5
|
+
* This ensures AI agents only generate code using pre-approved, safe components
|
|
6
|
+
* Prevents hallucination of raw HTML/JavaScript
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
|
|
12
|
+
export class A2UISchemaManager {
|
|
13
|
+
constructor(componentDir = './components') {
|
|
14
|
+
this.componentDir = componentDir;
|
|
15
|
+
this.schema = {
|
|
16
|
+
version: '5.0.0',
|
|
17
|
+
timestamp: new Date().toISOString(),
|
|
18
|
+
components: [],
|
|
19
|
+
patterns: this.getEthnomathematicalPatterns(),
|
|
20
|
+
forbiddenPatterns: [
|
|
21
|
+
'eval()',
|
|
22
|
+
'innerHTML = userInput',
|
|
23
|
+
'fetch without CORS headers',
|
|
24
|
+
'localStorage for sensitive data',
|
|
25
|
+
'document.domain manipulation',
|
|
26
|
+
'postMessage without origin check'
|
|
27
|
+
],
|
|
28
|
+
constraintRules: [
|
|
29
|
+
'All dynamic content must use Web Components',
|
|
30
|
+
'No raw HTML generation outside of Shadow DOM',
|
|
31
|
+
'All user input must be validated with schemas',
|
|
32
|
+
'All network requests must include CORS headers',
|
|
33
|
+
'All fintech operations must call compliance middleware',
|
|
34
|
+
'All identity operations must use NIDA CIG endpoints',
|
|
35
|
+
'All payments must be processed through TIPS',
|
|
36
|
+
'All transactions must be logged for AML/FIU reporting'
|
|
37
|
+
]
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Ethnomathematical pattern generation rules
|
|
43
|
+
* Encodes African fractal geometry and cultural weaving algorithms
|
|
44
|
+
*/
|
|
45
|
+
getEthnomathematicalPatterns() {
|
|
46
|
+
return {
|
|
47
|
+
kente: {
|
|
48
|
+
name: 'Kente Weaving',
|
|
49
|
+
algorithm: 'glide_reflection',
|
|
50
|
+
parameters: {
|
|
51
|
+
baseColor: { type: 'string', pattern: '^#[0-9a-fA-F]{6}$' },
|
|
52
|
+
accentColor: { type: 'string', pattern: '^#[0-9a-fA-F]{6}$' },
|
|
53
|
+
width: { type: 'number', min: 100, max: 1000 },
|
|
54
|
+
height: { type: 'number', min: 100, max: 1000 },
|
|
55
|
+
complexity: { type: 'number', min: 1, max: 5 }
|
|
56
|
+
},
|
|
57
|
+
rules: [
|
|
58
|
+
'Use glide reflection symmetry (translation + reflection)',
|
|
59
|
+
'Maintain golden ratio proportions',
|
|
60
|
+
'Alternate colors in checkerboard pattern',
|
|
61
|
+
'Apply fractal scaling for complexity levels'
|
|
62
|
+
],
|
|
63
|
+
usage: 'Cultural textiles, ceremonial garments, digital art'
|
|
64
|
+
},
|
|
65
|
+
shuka: {
|
|
66
|
+
name: 'Maasai Shuka Tartan',
|
|
67
|
+
algorithm: 'strip_weaving',
|
|
68
|
+
parameters: {
|
|
69
|
+
primaryColor: { type: 'string', pattern: '^#[0-9a-fA-F]{6}$' },
|
|
70
|
+
secondaryColor: { type: 'string', pattern: '^#[0-9a-fA-F]{6}$' },
|
|
71
|
+
stripeWidth: { type: 'number', min: 5, max: 50 },
|
|
72
|
+
pattern: { type: 'string', enum: ['horizontal', 'vertical', 'diagonal', 'plaid'] },
|
|
73
|
+
borderWidth: { type: 'number', min: 1, max: 10 }
|
|
74
|
+
},
|
|
75
|
+
rules: [
|
|
76
|
+
'Use strip weaving technique with color alternation',
|
|
77
|
+
'Maintain consistent stripe proportions',
|
|
78
|
+
'Apply border framing for cultural authenticity',
|
|
79
|
+
'Use earthy color palettes (reds, blacks, whites)'
|
|
80
|
+
],
|
|
81
|
+
usage: 'Traditional garments, cultural identity, modern fashion'
|
|
82
|
+
},
|
|
83
|
+
ndebele: {
|
|
84
|
+
name: 'Ndebele Geometric Fractals',
|
|
85
|
+
algorithm: 'recursive_geometry',
|
|
86
|
+
parameters: {
|
|
87
|
+
baseColor: { type: 'string', pattern: '^#[0-9a-fA-F]{6}$' },
|
|
88
|
+
accentColors: { type: 'array', items: { type: 'string', pattern: '^#[0-9a-fA-F]{6}$' }, maxItems: 4 },
|
|
89
|
+
recursionDepth: { type: 'number', min: 1, max: 4 },
|
|
90
|
+
symmetry: { type: 'string', enum: ['rotational', 'reflection', 'translational'] },
|
|
91
|
+
scale: { type: 'number', min: 0.1, max: 2.0 }
|
|
92
|
+
},
|
|
93
|
+
rules: [
|
|
94
|
+
'Apply recursive geometric subdivision',
|
|
95
|
+
'Use rotational symmetry around center points',
|
|
96
|
+
'Maintain fractal self-similarity at all scales',
|
|
97
|
+
'Incorporate traditional Ndebele color symbolism'
|
|
98
|
+
],
|
|
99
|
+
usage: 'Wall art, beadwork, architectural decoration, digital design'
|
|
100
|
+
},
|
|
101
|
+
kitenge: {
|
|
102
|
+
name: 'Kitenge Floral Patterns',
|
|
103
|
+
algorithm: 'organic_growth',
|
|
104
|
+
parameters: {
|
|
105
|
+
backgroundColor: { type: 'string', pattern: '^#[0-9a-fA-F]{6}$' },
|
|
106
|
+
floralColors: { type: 'array', items: { type: 'string', pattern: '^#[0-9a-fA-F]{6}$' }, minItems: 2, maxItems: 6 },
|
|
107
|
+
density: { type: 'number', min: 0.1, max: 1.0 },
|
|
108
|
+
scale: { type: 'number', min: 0.5, max: 2.0 },
|
|
109
|
+
symmetry: { type: 'boolean', default: true }
|
|
110
|
+
},
|
|
111
|
+
rules: [
|
|
112
|
+
'Use organic growth algorithms for natural forms',
|
|
113
|
+
'Apply bilateral symmetry for traditional aesthetics',
|
|
114
|
+
'Vary density for different fabric weights',
|
|
115
|
+
'Maintain cultural color significance (greens, blues, earth tones)'
|
|
116
|
+
],
|
|
117
|
+
usage: 'Textile printing, fashion design, home decor, cultural products'
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Scan components directory and extract metadata
|
|
124
|
+
*/
|
|
125
|
+
async scanComponents() {
|
|
126
|
+
const files = fs.readdirSync(this.componentDir).filter(f =>
|
|
127
|
+
f.endsWith('.js') && !f.startsWith('.')
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
for (const file of files) {
|
|
131
|
+
const filePath = path.join(this.componentDir, file);
|
|
132
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
133
|
+
const metadata = this.extractComponentMetadata(file, content);
|
|
134
|
+
|
|
135
|
+
if (metadata) {
|
|
136
|
+
this.schema.components.push(metadata);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return this.schema;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Extract component metadata from source code
|
|
145
|
+
* Looks for JSDoc comments and Web Component definition
|
|
146
|
+
*/
|
|
147
|
+
extractComponentMetadata(filename, content) {
|
|
148
|
+
// Extract tagName
|
|
149
|
+
const tagMatch = content.match(/customElements\.define\(['"]([a-z0-9-]+)['"]/);
|
|
150
|
+
if (!tagMatch) return null;
|
|
151
|
+
|
|
152
|
+
const tagName = tagMatch[1];
|
|
153
|
+
|
|
154
|
+
// Extract slots
|
|
155
|
+
const slotMatches = [...content.matchAll(/<slot[^>]*name=['"]([^'"]+)['"]/g)];
|
|
156
|
+
const slots = slotMatches.map(m => m[1]);
|
|
157
|
+
|
|
158
|
+
// Extract attributes from constructor/connectedCallback
|
|
159
|
+
const attrMatches = [...content.matchAll(/this\.getAttribute\(['"]([^'"]+)['"]\)/g)];
|
|
160
|
+
const attributes = {};
|
|
161
|
+
attrMatches.forEach(m => {
|
|
162
|
+
attributes[m[1]] = { type: 'string', required: false };
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// Extract events from dispatchEvent calls
|
|
166
|
+
const eventMatches = [...content.matchAll(/dispatchEvent\(\s*new\s+CustomEvent\(['"]([^'"]+)['"]/g)];
|
|
167
|
+
const events = eventMatches.map(m => ({
|
|
168
|
+
name: m[1],
|
|
169
|
+
detail: { type: 'object' }
|
|
170
|
+
}));
|
|
171
|
+
|
|
172
|
+
// Extract CSS custom properties
|
|
173
|
+
const cssMatches = [...content.matchAll(/var\(['--([a-z0-9-]+)['"\)]/g)];
|
|
174
|
+
const cssCustomProperties = cssMatches.map(m => '--' + m[1]);
|
|
175
|
+
|
|
176
|
+
// Extract JSDoc comments
|
|
177
|
+
const jsdocMatch = content.match(/\/\*\*[\s\S]*?\*\//);
|
|
178
|
+
const description = jsdocMatch ? jsdocMatch[0] : '';
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
tagName,
|
|
182
|
+
filePath: `components/${filename}`,
|
|
183
|
+
description: description.substring(0, 200),
|
|
184
|
+
slots,
|
|
185
|
+
attributes,
|
|
186
|
+
events,
|
|
187
|
+
cssCustomProperties: [...new Set(cssCustomProperties)],
|
|
188
|
+
example: `<${tagName}>Content</${tagName}>`,
|
|
189
|
+
securityNotes: 'Sanitizes slot content'
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Validate proposed HTML against schema
|
|
195
|
+
* Ensures code only uses approved components
|
|
196
|
+
*/
|
|
197
|
+
validateHTML(html) {
|
|
198
|
+
const errors = [];
|
|
199
|
+
const warnings = [];
|
|
200
|
+
|
|
201
|
+
// Extract all custom elements used
|
|
202
|
+
const componentMatches = [...html.matchAll(/<(af-[a-z0-9-]+)/g)];
|
|
203
|
+
const usedComponents = new Set(componentMatches.map(m => m[1]));
|
|
204
|
+
|
|
205
|
+
// Check against approved components
|
|
206
|
+
const approvedTags = this.schema.components.map(c => c.tagName);
|
|
207
|
+
for (const tag of usedComponents) {
|
|
208
|
+
if (!approvedTags.includes(tag)) {
|
|
209
|
+
errors.push(`Unknown component: <${tag}> — not in COMPONENT_SCHEMA`);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Check for forbidden patterns
|
|
214
|
+
for (const pattern of this.schema.forbiddenPatterns) {
|
|
215
|
+
if (pattern === 'eval()') {
|
|
216
|
+
if (/eval\s*\(/i.test(html)) {
|
|
217
|
+
errors.push('Forbidden: eval() detected');
|
|
218
|
+
}
|
|
219
|
+
} else if (pattern === 'innerHTML = userInput') {
|
|
220
|
+
if (/innerHTML\s*=\s*(?!`[^`]*<[a-z]/i.test(html)) {
|
|
221
|
+
warnings.push('Warning: innerHTML assignment detected — ensure content is sanitized');
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return { valid: errors.length === 0, errors, warnings };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Generate and save the complete A2UI catalog
|
|
231
|
+
*/
|
|
232
|
+
async generateManifest(outputPath = './catalog.json') {
|
|
233
|
+
console.log('🤖 Generating A2UI component catalog...');
|
|
234
|
+
|
|
235
|
+
// Scan components
|
|
236
|
+
await this.scanComponents();
|
|
237
|
+
|
|
238
|
+
// Add pattern generation hints
|
|
239
|
+
this.schema.usageHints = {
|
|
240
|
+
components: {
|
|
241
|
+
'af-button': 'Use for primary/secondary actions, always include accessibility labels',
|
|
242
|
+
'af-card': 'Use for content containers, supports cultural card variants',
|
|
243
|
+
'af-form': 'Use for data collection, automatically validates with schemas',
|
|
244
|
+
'af-nida-verify': 'Use for identity verification, requires NIDA CIG compliance',
|
|
245
|
+
'af-payment-form': 'Use for payments, automatically handles TIPS processing',
|
|
246
|
+
'af-cultural-card': 'Use for cultural content, supports pattern overlays'
|
|
247
|
+
},
|
|
248
|
+
patterns: {
|
|
249
|
+
'kente': 'Use for formal/cultural contexts, high complexity for ceremonies',
|
|
250
|
+
'shuka': 'Use for traditional Maasai designs, earthy color palettes',
|
|
251
|
+
'ndebele': 'Use for geometric art, recursive depth for complexity',
|
|
252
|
+
'kitenge': 'Use for floral patterns, organic growth for natural feel'
|
|
253
|
+
},
|
|
254
|
+
constraints: [
|
|
255
|
+
'Always use af-* components instead of raw HTML',
|
|
256
|
+
'Validate all user input with schema rules',
|
|
257
|
+
'Include cultural context in component selection',
|
|
258
|
+
'Ensure fintech operations call compliance middleware',
|
|
259
|
+
'Use appropriate ethnomathematical patterns for cultural authenticity'
|
|
260
|
+
]
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// Save catalog
|
|
264
|
+
fs.writeFileSync(outputPath, JSON.stringify(this.schema, null, 2));
|
|
265
|
+
console.log(`✅ A2UI catalog generated: ${outputPath}`);
|
|
266
|
+
|
|
267
|
+
return this.schema;
|
|
268
|
+
}
|
|
269
|
+
const component = this.schema.components.find(c => c.tagName === tagName);
|
|
270
|
+
if (!component) {
|
|
271
|
+
return { valid: false, error: `Component ${tagName} not found in schema` };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const errors = [];
|
|
275
|
+
|
|
276
|
+
// Check required attributes
|
|
277
|
+
for (const [attr, config] of Object.entries(component.attributes)) {
|
|
278
|
+
if (config.required && !(attr in props)) {
|
|
279
|
+
errors.push(`Missing required attribute: ${attr}`);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Validate attribute value if needed
|
|
283
|
+
if (attr in props && config.pattern) {
|
|
284
|
+
const regex = new RegExp(config.pattern);
|
|
285
|
+
if (!regex.test(props[attr])) {
|
|
286
|
+
errors.push(`Attribute ${attr} does not match pattern: ${config.pattern}`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return { valid: errors.length === 0, errors };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Generate TypeScript type definitions for components
|
|
296
|
+
*/
|
|
297
|
+
generateTypeDefinitions() {
|
|
298
|
+
let defs = '// Auto-generated AfriCode Component Types\n\n';
|
|
299
|
+
|
|
300
|
+
for (const component of this.schema.components) {
|
|
301
|
+
defs += `export interface ${this.toCamelCase(component.tagName)}Props {\n`;
|
|
302
|
+
|
|
303
|
+
for (const [attr, config] of Object.entries(component.attributes)) {
|
|
304
|
+
const optional = !config.required ? '?' : '';
|
|
305
|
+
defs += ` ${attr}${optional}: ${config.type};\n`;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
defs += `}\n\n`;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return defs;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Export schema to JSON file
|
|
316
|
+
*/
|
|
317
|
+
async exportSchema(filePath = './COMPONENT_SCHEMA.json') {
|
|
318
|
+
fs.writeFileSync(filePath, JSON.stringify(this.schema, null, 2));
|
|
319
|
+
console.log(`✅ Component schema exported to ${filePath}`);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Utility: Convert kebab-case to camelCase
|
|
324
|
+
*/
|
|
325
|
+
toCamelCase(str) {
|
|
326
|
+
return str.replace(/-./g, x => x[1].toUpperCase());
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* CLI Usage
|
|
332
|
+
* node core/a2ui-schema-manager.js generate
|
|
333
|
+
*/
|
|
334
|
+
if (import.meta.main) {
|
|
335
|
+
const manager = new A2UISchemaManager('./components');
|
|
336
|
+
|
|
337
|
+
(async () => {
|
|
338
|
+
await manager.scanComponents();
|
|
339
|
+
await manager.exportSchema();
|
|
340
|
+
console.log(`📋 Schema contains ${manager.schema.components.length} approved components`);
|
|
341
|
+
})();
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export default A2UISchemaManager;
|