meno-core 1.0.38 → 1.0.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/build-astro.ts +914 -0
- package/dist/build-static.js +2 -2
- package/dist/chunks/{chunk-UR7L5UZ3.js → chunk-HNAS6BSS.js} +2 -2
- package/dist/chunks/{chunk-EUCAKI5U.js → chunk-W6HDII4T.js} +8 -1
- package/dist/chunks/{chunk-EUCAKI5U.js.map → chunk-W6HDII4T.js.map} +2 -2
- package/dist/chunks/{chunk-JACS3C25.js → chunk-WK5XLASY.js} +2 -2
- package/dist/entries/server-router.js +2 -2
- package/dist/lib/client/index.js +5 -3
- package/dist/lib/client/index.js.map +2 -2
- package/dist/lib/server/index.js +1840 -5
- package/dist/lib/server/index.js.map +4 -4
- package/dist/lib/shared/index.js +5 -3
- package/dist/lib/shared/index.js.map +2 -2
- package/lib/client/theme.ts +4 -1
- package/lib/server/astro/componentEmitter.ts +208 -0
- package/lib/server/astro/cssCollector.ts +147 -0
- package/lib/server/astro/index.ts +5 -0
- package/lib/server/astro/nodeToAstro.ts +771 -0
- package/lib/server/astro/pageEmitter.ts +190 -0
- package/lib/server/astro/tailwindMapper.ts +547 -0
- package/lib/server/index.ts +3 -0
- package/lib/server/ssr/htmlGenerator.ts +3 -0
- package/lib/server/ssr/ssrRenderer.test.ts +8 -4
- package/lib/server/ssr/ssrRenderer.ts +1 -3
- package/lib/shared/themeDefaults.test.ts +1 -1
- package/lib/shared/themeDefaults.ts +4 -1
- package/package.json +1 -1
- /package/dist/chunks/{chunk-UR7L5UZ3.js.map → chunk-HNAS6BSS.js.map} +0 -0
- /package/dist/chunks/{chunk-JACS3C25.js.map → chunk-WK5XLASY.js.map} +0 -0
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Component File Generator
|
|
3
|
+
* Generates .astro files from StructuredComponentDefinitions
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
ComponentDefinition,
|
|
8
|
+
StructuredComponentDefinition,
|
|
9
|
+
PropDefinition,
|
|
10
|
+
} from '../../shared/types';
|
|
11
|
+
import type { BasePropDefinition, ListPropDefinition, LinkPropValue } from '../../shared/types/components';
|
|
12
|
+
import type { BreakpointConfig } from '../../shared/breakpoints';
|
|
13
|
+
import { DEFAULT_BREAKPOINTS } from '../../shared/breakpoints';
|
|
14
|
+
import { nodeToAstro, type AstroEmitContext } from './nodeToAstro';
|
|
15
|
+
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
// Helpers
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Convert a PropDefinition to a TypeScript type string
|
|
22
|
+
*/
|
|
23
|
+
function propDefToTSType(def: PropDefinition): string {
|
|
24
|
+
switch (def.type) {
|
|
25
|
+
case 'string':
|
|
26
|
+
case 'rich-text':
|
|
27
|
+
case 'file':
|
|
28
|
+
return 'string';
|
|
29
|
+
case 'number':
|
|
30
|
+
return 'number';
|
|
31
|
+
case 'boolean':
|
|
32
|
+
return 'boolean';
|
|
33
|
+
case 'select':
|
|
34
|
+
if ('options' in def && def.options && def.options.length > 0) {
|
|
35
|
+
return def.options.map((o) => `'${o}'`).join(' | ');
|
|
36
|
+
}
|
|
37
|
+
return 'string';
|
|
38
|
+
case 'link':
|
|
39
|
+
return '{ href: string; target?: string }';
|
|
40
|
+
case 'list':
|
|
41
|
+
return 'any[]';
|
|
42
|
+
default:
|
|
43
|
+
return 'any';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Format a default value for destructuring
|
|
49
|
+
*/
|
|
50
|
+
function formatDefault(def: PropDefinition): string | null {
|
|
51
|
+
if (!('default' in def) || def.default === undefined) return null;
|
|
52
|
+
const val = def.default;
|
|
53
|
+
|
|
54
|
+
if (typeof val === 'string') return JSON.stringify(val);
|
|
55
|
+
if (typeof val === 'number' || typeof val === 'boolean') return String(val);
|
|
56
|
+
|
|
57
|
+
// I18nValue
|
|
58
|
+
if (typeof val === 'object' && val !== null && '_i18n' in val) {
|
|
59
|
+
// Use the first available locale value
|
|
60
|
+
for (const [key, v] of Object.entries(val)) {
|
|
61
|
+
if (key !== '_i18n' && typeof v === 'string') return JSON.stringify(v);
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Link value
|
|
67
|
+
if (typeof val === 'object' && val !== null && 'href' in val) {
|
|
68
|
+
return JSON.stringify(val);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Arrays
|
|
72
|
+
if (Array.isArray(val)) {
|
|
73
|
+
return JSON.stringify(val);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return JSON.stringify(val);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
// Main emitter
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Generate a .astro file string from a component definition
|
|
85
|
+
*/
|
|
86
|
+
export function emitAstroComponent(
|
|
87
|
+
name: string,
|
|
88
|
+
def: ComponentDefinition,
|
|
89
|
+
allComponents: Record<string, ComponentDefinition>,
|
|
90
|
+
breakpoints: BreakpointConfig = DEFAULT_BREAKPOINTS
|
|
91
|
+
): string {
|
|
92
|
+
const comp = def.component;
|
|
93
|
+
const propDefs = comp.interface || {};
|
|
94
|
+
const structure = comp.structure;
|
|
95
|
+
|
|
96
|
+
if (!structure) {
|
|
97
|
+
// Component with no structure - just CSS/JS
|
|
98
|
+
return buildNoStructureComponent(name, comp);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Build the Astro context for template emission
|
|
102
|
+
const ctx: AstroEmitContext = {
|
|
103
|
+
imports: new Set<string>(),
|
|
104
|
+
isComponentDef: true,
|
|
105
|
+
componentProps: propDefs,
|
|
106
|
+
globalComponents: allComponents,
|
|
107
|
+
indent: 0,
|
|
108
|
+
ssrFallbacks: new Map(),
|
|
109
|
+
elementPath: [0],
|
|
110
|
+
fileType: 'component',
|
|
111
|
+
fileName: name,
|
|
112
|
+
breakpoints,
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// Emit the template body
|
|
116
|
+
const templateBody = nodeToAstro(structure, ctx);
|
|
117
|
+
|
|
118
|
+
// Build frontmatter
|
|
119
|
+
const frontmatter = buildFrontmatter(name, propDefs, ctx.imports, ctx.dynamicTags);
|
|
120
|
+
|
|
121
|
+
// Build style/script sections
|
|
122
|
+
const styleSection = comp.css ? `\n<style>\n${comp.css}\n</style>\n` : '';
|
|
123
|
+
const scriptSection = comp.javascript ? `\n<script>\n${comp.javascript}\n</script>\n` : '';
|
|
124
|
+
|
|
125
|
+
return `---\n${frontmatter}---\n${templateBody}${styleSection}${scriptSection}`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Build the frontmatter section (imports, Props interface, destructuring)
|
|
130
|
+
*/
|
|
131
|
+
function buildFrontmatter(
|
|
132
|
+
componentName: string,
|
|
133
|
+
propDefs: Record<string, PropDefinition>,
|
|
134
|
+
imports: Set<string>,
|
|
135
|
+
dynamicTags?: Map<string, string>
|
|
136
|
+
): string {
|
|
137
|
+
const lines: string[] = [];
|
|
138
|
+
|
|
139
|
+
// Component imports
|
|
140
|
+
for (const imp of Array.from(imports).sort()) {
|
|
141
|
+
lines.push(`import ${imp} from './${imp}.astro';`);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (lines.length > 0) lines.push('');
|
|
145
|
+
|
|
146
|
+
const propEntries = Object.entries(propDefs);
|
|
147
|
+
|
|
148
|
+
if (propEntries.length > 0) {
|
|
149
|
+
// Interface
|
|
150
|
+
lines.push('interface Props {');
|
|
151
|
+
for (const [propName, propDef] of propEntries) {
|
|
152
|
+
if (propName === 'children') continue;
|
|
153
|
+
const tsType = propDefToTSType(propDef);
|
|
154
|
+
const optional = 'default' in propDef && propDef.default !== undefined;
|
|
155
|
+
lines.push(` ${propName}${optional ? '?' : ''}: ${tsType};`);
|
|
156
|
+
}
|
|
157
|
+
lines.push('}');
|
|
158
|
+
lines.push('');
|
|
159
|
+
|
|
160
|
+
// Destructuring with defaults
|
|
161
|
+
const destructParts: string[] = [];
|
|
162
|
+
for (const [propName, propDef] of propEntries) {
|
|
163
|
+
if (propName === 'children') continue;
|
|
164
|
+
const defaultVal = formatDefault(propDef);
|
|
165
|
+
if (defaultVal !== null) {
|
|
166
|
+
destructParts.push(`${propName} = ${defaultVal}`);
|
|
167
|
+
} else {
|
|
168
|
+
destructParts.push(propName);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (destructParts.length > 0) {
|
|
173
|
+
if (destructParts.length <= 3 && destructParts.join(', ').length < 80) {
|
|
174
|
+
lines.push(`const { ${destructParts.join(', ')} } = Astro.props;`);
|
|
175
|
+
} else {
|
|
176
|
+
lines.push('const {');
|
|
177
|
+
for (const part of destructParts) {
|
|
178
|
+
lines.push(` ${part},`);
|
|
179
|
+
}
|
|
180
|
+
lines.push('} = Astro.props;');
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Dynamic tag definitions (e.g., const Tag_0_0 = `h${size}`)
|
|
186
|
+
if (dynamicTags && dynamicTags.size > 0) {
|
|
187
|
+
lines.push('');
|
|
188
|
+
for (const [varName, templateExpr] of dynamicTags) {
|
|
189
|
+
lines.push(`const ${varName} = \`${templateExpr}\`;`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (lines.length > 0) lines.push('');
|
|
194
|
+
return lines.join('\n');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Build a component with no structure (CSS/JS only)
|
|
199
|
+
*/
|
|
200
|
+
function buildNoStructureComponent(
|
|
201
|
+
name: string,
|
|
202
|
+
comp: StructuredComponentDefinition
|
|
203
|
+
): string {
|
|
204
|
+
let content = '---\n---\n<slot />\n';
|
|
205
|
+
if (comp.css) content += `\n<style>\n${comp.css}\n</style>\n`;
|
|
206
|
+
if (comp.javascript) content += `\n<script>\n${comp.javascript}\n</script>\n`;
|
|
207
|
+
return content;
|
|
208
|
+
}
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mapping Variant CSS Collector (Tailwind)
|
|
3
|
+
* Walks all component definitions to find StyleMapping objects,
|
|
4
|
+
* generates Tailwind classes for every possible value, ensuring
|
|
5
|
+
* the Tailwind safelist covers all prop variants.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ComponentDefinition, ComponentNode } from '../../shared/types';
|
|
9
|
+
import type {
|
|
10
|
+
StyleObject,
|
|
11
|
+
ResponsiveStyleObject,
|
|
12
|
+
StyleMapping,
|
|
13
|
+
} from '../../shared/types/styles';
|
|
14
|
+
import type { BreakpointConfig } from '../../shared/breakpoints';
|
|
15
|
+
import { DEFAULT_BREAKPOINTS } from '../../shared/breakpoints';
|
|
16
|
+
import { propertyToTailwind } from './tailwindMapper';
|
|
17
|
+
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
// Helpers
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
function isStyleMapping(value: unknown): value is StyleMapping {
|
|
23
|
+
return (
|
|
24
|
+
typeof value === 'object' &&
|
|
25
|
+
value !== null &&
|
|
26
|
+
'_mapping' in value &&
|
|
27
|
+
(value as StyleMapping)._mapping === true
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function isResponsiveStyle(
|
|
32
|
+
style: StyleObject | ResponsiveStyleObject
|
|
33
|
+
): style is ResponsiveStyleObject {
|
|
34
|
+
return 'base' in style || 'tablet' in style || 'mobile' in style;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Walk a style object and collect Tailwind classes for every possible mapping value
|
|
39
|
+
*/
|
|
40
|
+
function collectFromStyle(
|
|
41
|
+
style: StyleObject | ResponsiveStyleObject | undefined,
|
|
42
|
+
classes: Set<string>,
|
|
43
|
+
breakpoints: BreakpointConfig
|
|
44
|
+
): void {
|
|
45
|
+
if (!style) return;
|
|
46
|
+
|
|
47
|
+
if (isResponsiveStyle(style)) {
|
|
48
|
+
for (const [bp, bpStyle] of Object.entries(style)) {
|
|
49
|
+
if (!bpStyle) continue;
|
|
50
|
+
let prefix = '';
|
|
51
|
+
if (bp !== 'base') {
|
|
52
|
+
const bpValue = breakpoints[bp]?.breakpoint;
|
|
53
|
+
if (bpValue) {
|
|
54
|
+
prefix = `max-[${bpValue}px]:`;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
collectFromFlatStyle(bpStyle, prefix, classes);
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
collectFromFlatStyle(style, '', classes);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function collectFromFlatStyle(
|
|
65
|
+
style: StyleObject,
|
|
66
|
+
prefix: string,
|
|
67
|
+
classes: Set<string>
|
|
68
|
+
): void {
|
|
69
|
+
for (const [property, value] of Object.entries(style)) {
|
|
70
|
+
if (!isStyleMapping(value)) continue;
|
|
71
|
+
|
|
72
|
+
// Generate a Tailwind class for each possible value in the mapping
|
|
73
|
+
for (const [, cssValue] of Object.entries(value.values)) {
|
|
74
|
+
const twClass = propertyToTailwind(property, cssValue);
|
|
75
|
+
if (twClass) {
|
|
76
|
+
classes.add(prefix ? `${prefix}${twClass}` : twClass);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Recursively walk a component node tree to collect mapping classes
|
|
84
|
+
*/
|
|
85
|
+
function walkNode(
|
|
86
|
+
node: ComponentNode | ComponentNode[] | string | number | null | undefined,
|
|
87
|
+
classes: Set<string>,
|
|
88
|
+
breakpoints: BreakpointConfig
|
|
89
|
+
): void {
|
|
90
|
+
if (!node || typeof node === 'string' || typeof node === 'number') return;
|
|
91
|
+
|
|
92
|
+
if (Array.isArray(node)) {
|
|
93
|
+
for (const child of node) {
|
|
94
|
+
walkNode(child, classes, breakpoints);
|
|
95
|
+
}
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Collect from style
|
|
100
|
+
if ('style' in node && node.style) {
|
|
101
|
+
collectFromStyle(node.style as StyleObject | ResponsiveStyleObject, classes, breakpoints);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Collect from interactive styles
|
|
105
|
+
if ('interactiveStyles' in node && Array.isArray((node as any).interactiveStyles)) {
|
|
106
|
+
for (const rule of (node as any).interactiveStyles) {
|
|
107
|
+
if (rule.style) {
|
|
108
|
+
collectFromStyle(rule.style, classes, breakpoints);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Recurse into children
|
|
114
|
+
if ('children' in node && node.children) {
|
|
115
|
+
if (Array.isArray(node.children)) {
|
|
116
|
+
for (const child of node.children) {
|
|
117
|
+
walkNode(child as ComponentNode, classes, breakpoints);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
// Main export
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Walk all component definitions, find all StyleMapping objects,
|
|
129
|
+
* and generate Tailwind classes for every value.
|
|
130
|
+
*
|
|
131
|
+
* @returns Set of Tailwind class names for the safelist
|
|
132
|
+
*/
|
|
133
|
+
export function collectAllMappingClasses(
|
|
134
|
+
componentDefs: Record<string, ComponentDefinition>,
|
|
135
|
+
breakpoints: BreakpointConfig = DEFAULT_BREAKPOINTS
|
|
136
|
+
): Set<string> {
|
|
137
|
+
const classes = new Set<string>();
|
|
138
|
+
|
|
139
|
+
for (const def of Object.values(componentDefs)) {
|
|
140
|
+
const structure = def.component?.structure;
|
|
141
|
+
if (structure) {
|
|
142
|
+
walkNode(structure, classes, breakpoints);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return classes;
|
|
147
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { nodeToAstro, type AstroEmitContext } from './nodeToAstro';
|
|
2
|
+
export { emitAstroComponent } from './componentEmitter';
|
|
3
|
+
export { emitAstroPage, type PageEmitOptions } from './pageEmitter';
|
|
4
|
+
export { collectAllMappingClasses } from './cssCollector';
|
|
5
|
+
export { propertyToTailwind, stylesToTailwind, responsiveStylesToTailwind } from './tailwindMapper';
|