@claudetools/tools 0.9.0 → 0.9.2
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/cli.js +9 -1
- package/dist/codedna/__tests__/examples/mongoose-example.d.ts +6 -0
- package/dist/codedna/__tests__/examples/mongoose-example.js +163 -0
- package/dist/codedna/__tests__/fixtures/typeorm-production-test.d.ts +1 -0
- package/dist/codedna/__tests__/fixtures/typeorm-production-test.js +231 -0
- package/dist/codedna/__tests__/fixtures/typeorm-test.d.ts +1 -0
- package/dist/codedna/__tests__/fixtures/typeorm-test.js +124 -0
- package/dist/codedna/__tests__/laravel-output-review.d.ts +1 -0
- package/dist/codedna/__tests__/laravel-output-review.js +249 -0
- package/dist/codedna/__tests__/mongoose-output-test.d.ts +1 -0
- package/dist/codedna/__tests__/mongoose-output-test.js +178 -0
- package/dist/codedna/examples/radix-example.d.ts +2 -0
- package/dist/codedna/examples/radix-example.js +259 -0
- package/dist/codedna/index.d.ts +5 -3
- package/dist/codedna/index.js +6 -3
- package/dist/codedna/kappa-ast.d.ts +143 -5
- package/dist/codedna/kappa-drizzle-generator.js +8 -5
- package/dist/codedna/kappa-gofiber-generator.d.ts +65 -0
- package/dist/codedna/kappa-gofiber-generator.js +587 -0
- package/dist/codedna/kappa-laravel-generator.d.ts +68 -0
- package/dist/codedna/kappa-laravel-generator.js +741 -0
- package/dist/codedna/kappa-lexer.d.ts +44 -0
- package/dist/codedna/kappa-lexer.js +124 -0
- package/dist/codedna/kappa-mantine-generator.d.ts +65 -0
- package/dist/codedna/kappa-mantine-generator.js +518 -0
- package/dist/codedna/kappa-mongoose-generator.d.ts +44 -0
- package/dist/codedna/kappa-mongoose-generator.js +442 -0
- package/dist/codedna/kappa-parser.d.ts +43 -1
- package/dist/codedna/kappa-parser.js +601 -0
- package/dist/codedna/kappa-radix-generator.d.ts +61 -0
- package/dist/codedna/kappa-radix-generator.js +566 -0
- package/dist/codedna/kappa-typeorm-generator.d.ts +59 -0
- package/dist/codedna/kappa-typeorm-generator.js +723 -0
- package/dist/codedna/kappa-vitest-generator.d.ts +85 -0
- package/dist/codedna/kappa-vitest-generator.js +739 -0
- package/dist/codedna/parser.js +26 -1
- package/dist/codegen/cloud-client.d.ts +160 -0
- package/dist/codegen/cloud-client.js +195 -0
- package/dist/codegen/codegen-tool.d.ts +35 -0
- package/dist/codegen/codegen-tool.js +312 -0
- package/dist/codegen/field-inference.d.ts +24 -0
- package/dist/codegen/field-inference.js +101 -0
- package/dist/codegen/form-parser.d.ts +13 -0
- package/dist/codegen/form-parser.js +186 -0
- package/dist/codegen/index.d.ts +2 -0
- package/dist/codegen/index.js +4 -0
- package/dist/codegen/natural-parser.d.ts +50 -0
- package/dist/codegen/natural-parser.js +769 -0
- package/dist/handlers/codedna-handlers.d.ts +1 -1
- package/dist/handlers/codegen-handlers.d.ts +20 -0
- package/dist/handlers/codegen-handlers.js +60 -0
- package/dist/handlers/kappa-handlers.d.ts +97 -0
- package/dist/handlers/kappa-handlers.js +408 -0
- package/dist/handlers/tool-handlers.js +124 -221
- package/dist/helpers/api-client.js +48 -3
- package/dist/helpers/compact-formatter.d.ts +9 -2
- package/dist/helpers/compact-formatter.js +26 -2
- package/dist/helpers/config.d.ts +7 -2
- package/dist/helpers/config.js +25 -10
- package/dist/helpers/session-validation.d.ts +1 -1
- package/dist/helpers/session-validation.js +2 -4
- package/dist/helpers/tasks.d.ts +21 -0
- package/dist/helpers/tasks.js +52 -0
- package/dist/helpers/workers.d.ts +1 -1
- package/dist/helpers/workers.js +19 -19
- package/dist/setup.d.ts +1 -0
- package/dist/setup.js +228 -3
- package/dist/templates/claude-md.d.ts +1 -1
- package/dist/templates/claude-md.js +37 -152
- package/dist/templates/orchestrator-prompt.d.ts +2 -2
- package/dist/templates/orchestrator-prompt.js +31 -38
- package/dist/templates/self-critique.d.ts +50 -0
- package/dist/templates/self-critique.js +209 -0
- package/dist/templates/worker-prompt.d.ts +3 -3
- package/dist/templates/worker-prompt.js +18 -18
- package/dist/tools.js +77 -413
- package/docs/codedna/generator-testing-summary.md +205 -0
- package/docs/codedna/radix-ui-generator.md +478 -0
- package/docs/kappa-gofiber-generator.md +274 -0
- package/docs/kappa-laravel-fixes.md +172 -0
- package/docs/kappa-mongoose-generator.md +322 -0
- package/docs/kappa-vitest-generator.md +337 -0
- package/package.json +1 -1
- package/dist/context/deduplication.test.d.ts +0 -6
- package/dist/context/deduplication.test.js +0 -84
|
@@ -0,0 +1,518 @@
|
|
|
1
|
+
// =============================================================================
|
|
2
|
+
// Kappa v2.5 Mantine Generator
|
|
3
|
+
// =============================================================================
|
|
4
|
+
//
|
|
5
|
+
// Generates Mantine UI components and forms from Kappa ComponentBlock/FormBlock AST.
|
|
6
|
+
// Supports @mantine/core components, @mantine/form, and @mantine/hooks.
|
|
7
|
+
//
|
|
8
|
+
// =============================================================================
|
|
9
|
+
// Generator Class
|
|
10
|
+
// =============================================================================
|
|
11
|
+
export class KappaMantineGenerator {
|
|
12
|
+
provenance;
|
|
13
|
+
typescript;
|
|
14
|
+
zod;
|
|
15
|
+
forwardRef;
|
|
16
|
+
basePath;
|
|
17
|
+
constructor(options = {}) {
|
|
18
|
+
this.provenance = options.provenance ?? true;
|
|
19
|
+
this.typescript = options.typescript ?? true;
|
|
20
|
+
this.zod = options.zod ?? true;
|
|
21
|
+
this.forwardRef = options.forwardRef ?? false;
|
|
22
|
+
this.basePath = options.basePath ?? 'components';
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Generate components from ComponentBlock AST nodes
|
|
26
|
+
*/
|
|
27
|
+
generateComponents(components) {
|
|
28
|
+
const files = [];
|
|
29
|
+
for (const component of components) {
|
|
30
|
+
files.push(this.generateComponent(component));
|
|
31
|
+
}
|
|
32
|
+
return { files };
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Generate forms from FormBlock AST nodes
|
|
36
|
+
*/
|
|
37
|
+
generateForms(forms) {
|
|
38
|
+
const files = [];
|
|
39
|
+
for (const form of forms) {
|
|
40
|
+
files.push(this.generateForm(form));
|
|
41
|
+
}
|
|
42
|
+
return { files };
|
|
43
|
+
}
|
|
44
|
+
// ===========================================================================
|
|
45
|
+
// Component Generation
|
|
46
|
+
// ===========================================================================
|
|
47
|
+
generateComponent(component) {
|
|
48
|
+
const lines = [];
|
|
49
|
+
const ext = this.typescript ? 'tsx' : 'jsx';
|
|
50
|
+
// Header
|
|
51
|
+
if (this.provenance) {
|
|
52
|
+
lines.push('// Generated by Kappa v2.5 CodeDNA - Mantine');
|
|
53
|
+
lines.push(`// Component: ${component.name}`);
|
|
54
|
+
lines.push('');
|
|
55
|
+
}
|
|
56
|
+
// Imports
|
|
57
|
+
lines.push("import { Box, Stack, Group } from '@mantine/core';");
|
|
58
|
+
if (this.forwardRef) {
|
|
59
|
+
lines.push("import { forwardRef } from 'react';");
|
|
60
|
+
}
|
|
61
|
+
if (component.compound && component.compound.length > 0) {
|
|
62
|
+
lines.push("import { createContext, useContext } from 'react';");
|
|
63
|
+
}
|
|
64
|
+
lines.push('');
|
|
65
|
+
// Props interface
|
|
66
|
+
if (this.typescript) {
|
|
67
|
+
lines.push(this.generateComponentPropsInterface(component));
|
|
68
|
+
lines.push('');
|
|
69
|
+
}
|
|
70
|
+
// Component
|
|
71
|
+
if (component.compound && component.compound.length > 0) {
|
|
72
|
+
lines.push(this.generateCompoundComponent(component));
|
|
73
|
+
}
|
|
74
|
+
else if (this.forwardRef) {
|
|
75
|
+
lines.push(this.generateForwardRefComponent(component));
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
lines.push(this.generateFunctionComponent(component));
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
path: `${this.basePath}/${this.toKebabCase(component.name)}.${ext}`,
|
|
82
|
+
content: lines.join('\n'),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
generateComponentPropsInterface(component) {
|
|
86
|
+
const lines = [];
|
|
87
|
+
const propsName = `${component.name}Props`;
|
|
88
|
+
lines.push(`export interface ${propsName} {`);
|
|
89
|
+
for (const prop of component.props) {
|
|
90
|
+
const optional = prop.optional ? '?' : '';
|
|
91
|
+
lines.push(` ${prop.name}${optional}: ${this.mapPropType(prop.type)};`);
|
|
92
|
+
}
|
|
93
|
+
// Mantine style props
|
|
94
|
+
lines.push(' className?: string;');
|
|
95
|
+
lines.push(' style?: React.CSSProperties;');
|
|
96
|
+
lines.push(' children?: React.ReactNode;');
|
|
97
|
+
lines.push('}');
|
|
98
|
+
return lines.join('\n');
|
|
99
|
+
}
|
|
100
|
+
generateFunctionComponent(component) {
|
|
101
|
+
const lines = [];
|
|
102
|
+
const propsType = this.typescript ? `: ${component.name}Props` : '';
|
|
103
|
+
const destructuredProps = this.getDestructuredProps(component);
|
|
104
|
+
lines.push(`export function ${component.name}({ ${destructuredProps} }${propsType}) {`);
|
|
105
|
+
lines.push(' return (');
|
|
106
|
+
if (component.structure) {
|
|
107
|
+
lines.push(this.generateStructureJSX(component.structure, 4));
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
lines.push(` <Box className={className} style={style}>`);
|
|
111
|
+
lines.push(' {children}');
|
|
112
|
+
lines.push(' </Box>');
|
|
113
|
+
}
|
|
114
|
+
lines.push(' );');
|
|
115
|
+
lines.push('}');
|
|
116
|
+
return lines.join('\n');
|
|
117
|
+
}
|
|
118
|
+
generateForwardRefComponent(component) {
|
|
119
|
+
const lines = [];
|
|
120
|
+
const propsType = this.typescript ? `${component.name}Props` : '';
|
|
121
|
+
const refType = this.typescript ? 'HTMLDivElement' : '';
|
|
122
|
+
const destructuredProps = this.getDestructuredProps(component);
|
|
123
|
+
lines.push(`export const ${component.name} = forwardRef<${refType}${propsType ? `, ${propsType}` : ''}>(`);
|
|
124
|
+
lines.push(` function ${component.name}({ ${destructuredProps} }, ref) {`);
|
|
125
|
+
lines.push(' return (');
|
|
126
|
+
if (component.structure) {
|
|
127
|
+
lines.push(this.generateStructureJSX(component.structure, 6, 'ref={ref}'));
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
lines.push(` <Box ref={ref} className={className} style={style}>`);
|
|
131
|
+
lines.push(' {children}');
|
|
132
|
+
lines.push(' </Box>');
|
|
133
|
+
}
|
|
134
|
+
lines.push(' );');
|
|
135
|
+
lines.push(' }');
|
|
136
|
+
lines.push(');');
|
|
137
|
+
return lines.join('\n');
|
|
138
|
+
}
|
|
139
|
+
generateCompoundComponent(component) {
|
|
140
|
+
const lines = [];
|
|
141
|
+
// Context
|
|
142
|
+
if (this.typescript) {
|
|
143
|
+
lines.push(`interface ${component.name}ContextValue {`);
|
|
144
|
+
for (const prop of component.props) {
|
|
145
|
+
lines.push(` ${prop.name}${prop.optional ? '?' : ''}: ${this.mapPropType(prop.type)};`);
|
|
146
|
+
}
|
|
147
|
+
lines.push('}');
|
|
148
|
+
lines.push('');
|
|
149
|
+
}
|
|
150
|
+
const contextType = this.typescript ? `${component.name}ContextValue | null` : '';
|
|
151
|
+
lines.push(`const ${component.name}Context = createContext<${contextType}>(null);`);
|
|
152
|
+
lines.push('');
|
|
153
|
+
// Hook
|
|
154
|
+
lines.push(`function use${component.name}() {`);
|
|
155
|
+
lines.push(` const context = useContext(${component.name}Context);`);
|
|
156
|
+
lines.push(' if (!context) {');
|
|
157
|
+
lines.push(` throw new Error('${component.name} compound components must be used within ${component.name}');`);
|
|
158
|
+
lines.push(' }');
|
|
159
|
+
lines.push(' return context;');
|
|
160
|
+
lines.push('}');
|
|
161
|
+
lines.push('');
|
|
162
|
+
// Root component
|
|
163
|
+
const destructuredProps = this.getDestructuredProps(component);
|
|
164
|
+
const propsType = this.typescript ? `: ${component.name}Props` : '';
|
|
165
|
+
lines.push(`function ${component.name}Root({ ${destructuredProps} }${propsType}) {`);
|
|
166
|
+
const contextValue = component.props.map((p) => p.name).join(', ');
|
|
167
|
+
lines.push(` const value = { ${contextValue} };`);
|
|
168
|
+
lines.push('');
|
|
169
|
+
lines.push(' return (');
|
|
170
|
+
lines.push(` <${component.name}Context.Provider value={value}>`);
|
|
171
|
+
lines.push(` <Box className={className} style={style}>`);
|
|
172
|
+
lines.push(' {children}');
|
|
173
|
+
lines.push(' </Box>');
|
|
174
|
+
lines.push(` </${component.name}Context.Provider>`);
|
|
175
|
+
lines.push(' );');
|
|
176
|
+
lines.push('}');
|
|
177
|
+
lines.push('');
|
|
178
|
+
// Compound parts
|
|
179
|
+
for (const part of component.compound) {
|
|
180
|
+
lines.push(this.generateCompoundPart(component.name, part));
|
|
181
|
+
lines.push('');
|
|
182
|
+
}
|
|
183
|
+
// Export compound object
|
|
184
|
+
lines.push(`export const ${component.name} = Object.assign(${component.name}Root, {`);
|
|
185
|
+
for (const part of component.compound) {
|
|
186
|
+
lines.push(` ${part.name}: ${component.name}${part.name},`);
|
|
187
|
+
}
|
|
188
|
+
lines.push('});');
|
|
189
|
+
return lines.join('\n');
|
|
190
|
+
}
|
|
191
|
+
generateCompoundPart(parentName, part) {
|
|
192
|
+
const lines = [];
|
|
193
|
+
const partName = `${parentName}${part.name}`;
|
|
194
|
+
if (this.typescript) {
|
|
195
|
+
lines.push(`interface ${partName}Props {`);
|
|
196
|
+
lines.push(' className?: string;');
|
|
197
|
+
lines.push(' style?: React.CSSProperties;');
|
|
198
|
+
lines.push(' children?: React.ReactNode;');
|
|
199
|
+
if (part.props) {
|
|
200
|
+
for (const prop of part.props) {
|
|
201
|
+
lines.push(` ${prop}?: string;`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
lines.push('}');
|
|
205
|
+
lines.push('');
|
|
206
|
+
}
|
|
207
|
+
const propsType = this.typescript ? `: ${partName}Props` : '';
|
|
208
|
+
const propsStr = part.props
|
|
209
|
+
? `{ className, style, children, ${part.props.join(', ')} }`
|
|
210
|
+
: '{ className, style, children }';
|
|
211
|
+
lines.push(`function ${partName}(${propsStr}${propsType}) {`);
|
|
212
|
+
lines.push(` const context = use${parentName}();`);
|
|
213
|
+
lines.push('');
|
|
214
|
+
lines.push(' return (');
|
|
215
|
+
lines.push(` <Box component="${part.element}" className={className} style={style}>`);
|
|
216
|
+
lines.push(' {children}');
|
|
217
|
+
lines.push(' </Box>');
|
|
218
|
+
lines.push(' );');
|
|
219
|
+
lines.push('}');
|
|
220
|
+
return lines.join('\n');
|
|
221
|
+
}
|
|
222
|
+
generateStructureJSX(structure, indent, extraProps = '') {
|
|
223
|
+
const spaces = ' '.repeat(indent);
|
|
224
|
+
const lines = [];
|
|
225
|
+
const mantineComponent = this.mapToMantineComponent(structure.type);
|
|
226
|
+
const props = Object.entries(structure.props || {})
|
|
227
|
+
.map(([k, v]) => `${k}="${v}"`)
|
|
228
|
+
.join(' ');
|
|
229
|
+
const allProps = [extraProps, props, 'className={className}', 'style={style}'].filter(Boolean).join(' ');
|
|
230
|
+
if (structure.children && structure.children.length > 0) {
|
|
231
|
+
lines.push(`${spaces}<${mantineComponent} ${allProps}>`);
|
|
232
|
+
for (const child of structure.children) {
|
|
233
|
+
lines.push(this.generateStructureJSX(child, indent + 2));
|
|
234
|
+
}
|
|
235
|
+
lines.push(`${spaces}</${mantineComponent}>`);
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
lines.push(`${spaces}<${mantineComponent} ${allProps}>{children}</${mantineComponent}>`);
|
|
239
|
+
}
|
|
240
|
+
return lines.join('\n');
|
|
241
|
+
}
|
|
242
|
+
getDestructuredProps(component) {
|
|
243
|
+
const propNames = component.props.map((p) => p.name);
|
|
244
|
+
propNames.push('className', 'style', 'children');
|
|
245
|
+
return propNames.join(', ');
|
|
246
|
+
}
|
|
247
|
+
// ===========================================================================
|
|
248
|
+
// Form Generation
|
|
249
|
+
// ===========================================================================
|
|
250
|
+
generateForm(form) {
|
|
251
|
+
const lines = [];
|
|
252
|
+
const ext = this.typescript ? 'tsx' : 'jsx';
|
|
253
|
+
const formName = form.name;
|
|
254
|
+
const componentName = `${formName}Form`;
|
|
255
|
+
// Header
|
|
256
|
+
if (this.provenance) {
|
|
257
|
+
lines.push('// Generated by Kappa v2.5 CodeDNA - Mantine');
|
|
258
|
+
lines.push(`// Form: ${formName}`);
|
|
259
|
+
lines.push('');
|
|
260
|
+
}
|
|
261
|
+
lines.push("'use client';");
|
|
262
|
+
lines.push('');
|
|
263
|
+
// Imports
|
|
264
|
+
lines.push("import { useForm } from '@mantine/form';");
|
|
265
|
+
if (this.zod) {
|
|
266
|
+
lines.push("import { zodResolver } from 'mantine-form-zod-resolver';");
|
|
267
|
+
lines.push("import { z } from 'zod';");
|
|
268
|
+
}
|
|
269
|
+
lines.push("import { useState } from 'react';");
|
|
270
|
+
lines.push("import { TextInput, Textarea, NumberInput, Select, Checkbox, Button, Stack, Group } from '@mantine/core';");
|
|
271
|
+
lines.push('');
|
|
272
|
+
// Generate Zod schema
|
|
273
|
+
if (this.zod) {
|
|
274
|
+
lines.push(this.generateZodSchema(form));
|
|
275
|
+
lines.push('');
|
|
276
|
+
if (this.typescript) {
|
|
277
|
+
lines.push(`type ${formName}FormData = z.infer<typeof ${this.toCamelCase(formName)}Schema>;`);
|
|
278
|
+
lines.push('');
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// Component props type
|
|
282
|
+
if (this.typescript) {
|
|
283
|
+
lines.push(`interface ${componentName}Props {`);
|
|
284
|
+
lines.push(` onSubmit: (data: ${this.zod ? `${formName}FormData` : 'Record<string, unknown>'}) => void | Promise<void>;`);
|
|
285
|
+
lines.push(` initialValues?: Partial<${this.zod ? `${formName}FormData` : 'Record<string, unknown>'}>;`);
|
|
286
|
+
lines.push(` isLoading?: boolean;`);
|
|
287
|
+
lines.push('}');
|
|
288
|
+
lines.push('');
|
|
289
|
+
}
|
|
290
|
+
// Component
|
|
291
|
+
const propsType = this.typescript ? `: ${componentName}Props` : '';
|
|
292
|
+
lines.push(`export function ${componentName}({ onSubmit, initialValues, isLoading }${propsType}) {`);
|
|
293
|
+
lines.push(` const [submitting, setSubmitting] = useState(false);`);
|
|
294
|
+
lines.push('');
|
|
295
|
+
// Mantine form hook
|
|
296
|
+
lines.push(` const form = useForm${this.typescript && this.zod ? `<${formName}FormData>` : ''}({`);
|
|
297
|
+
if (this.zod) {
|
|
298
|
+
lines.push(` validate: zodResolver(${this.toCamelCase(formName)}Schema),`);
|
|
299
|
+
}
|
|
300
|
+
lines.push(` initialValues: initialValues ?? ${this.getInitialValues(form)},`);
|
|
301
|
+
lines.push(` });`);
|
|
302
|
+
lines.push('');
|
|
303
|
+
// Submit handler
|
|
304
|
+
lines.push(` async function handleSubmit(data${this.typescript && this.zod ? `: ${formName}FormData` : ''}) {`);
|
|
305
|
+
lines.push(` setSubmitting(true);`);
|
|
306
|
+
lines.push(` try {`);
|
|
307
|
+
lines.push(` await onSubmit(data);`);
|
|
308
|
+
if (form.submit.onSuccess) {
|
|
309
|
+
lines.push(` // onSuccess: ${form.submit.onSuccess}`);
|
|
310
|
+
}
|
|
311
|
+
lines.push(` } catch (error) {`);
|
|
312
|
+
if (form.submit.onError) {
|
|
313
|
+
lines.push(` // onError: ${form.submit.onError}`);
|
|
314
|
+
}
|
|
315
|
+
lines.push(` console.error('Form submission error:', error);`);
|
|
316
|
+
lines.push(` } finally {`);
|
|
317
|
+
lines.push(` setSubmitting(false);`);
|
|
318
|
+
lines.push(` }`);
|
|
319
|
+
lines.push(` }`);
|
|
320
|
+
lines.push('');
|
|
321
|
+
// JSX
|
|
322
|
+
lines.push(' return (');
|
|
323
|
+
lines.push(' <form onSubmit={form.onSubmit(handleSubmit)}>');
|
|
324
|
+
lines.push(' <Stack gap="md">');
|
|
325
|
+
// Render fields using layout or sequentially
|
|
326
|
+
if (form.layout.length > 0) {
|
|
327
|
+
for (const row of form.layout) {
|
|
328
|
+
lines.push(this.generateLayoutRow(row, form.fields));
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
for (const field of form.fields) {
|
|
333
|
+
lines.push(this.generateMantineField(field, 8));
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
// Submit button
|
|
337
|
+
lines.push(' <Group justify="flex-end" mt="md">');
|
|
338
|
+
lines.push(' <Button type="submit" loading={submitting || isLoading}>');
|
|
339
|
+
lines.push(` {submitting ? '${form.submit.loading || 'Submitting...'}' : '${form.submit.button}'}`);
|
|
340
|
+
lines.push(' </Button>');
|
|
341
|
+
lines.push(' </Group>');
|
|
342
|
+
lines.push(' </Stack>');
|
|
343
|
+
lines.push(' </form>');
|
|
344
|
+
lines.push(' );');
|
|
345
|
+
lines.push('}');
|
|
346
|
+
return {
|
|
347
|
+
path: `${this.basePath}/forms/${this.toKebabCase(formName)}-form.${ext}`,
|
|
348
|
+
content: lines.join('\n'),
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
generateZodSchema(form) {
|
|
352
|
+
const schemaName = `${this.toCamelCase(form.name)}Schema`;
|
|
353
|
+
const lines = [];
|
|
354
|
+
lines.push(`const ${schemaName} = z.object({`);
|
|
355
|
+
for (const field of form.fields) {
|
|
356
|
+
const zodType = this.fieldTypeToZod(field);
|
|
357
|
+
lines.push(` ${field.name}: ${zodType},`);
|
|
358
|
+
}
|
|
359
|
+
lines.push('});');
|
|
360
|
+
return lines.join('\n');
|
|
361
|
+
}
|
|
362
|
+
fieldTypeToZod(field) {
|
|
363
|
+
let zodType;
|
|
364
|
+
switch (field.type) {
|
|
365
|
+
case 'email':
|
|
366
|
+
zodType = "z.string().email('Invalid email address')";
|
|
367
|
+
break;
|
|
368
|
+
case 'number':
|
|
369
|
+
zodType = 'z.number()';
|
|
370
|
+
break;
|
|
371
|
+
case 'checkbox':
|
|
372
|
+
zodType = 'z.boolean()';
|
|
373
|
+
break;
|
|
374
|
+
case 'date':
|
|
375
|
+
case 'time':
|
|
376
|
+
case 'datetime':
|
|
377
|
+
zodType = 'z.string()';
|
|
378
|
+
break;
|
|
379
|
+
case 'multiselect':
|
|
380
|
+
zodType = 'z.array(z.string())';
|
|
381
|
+
break;
|
|
382
|
+
case 'file':
|
|
383
|
+
zodType = 'z.any()';
|
|
384
|
+
break;
|
|
385
|
+
default:
|
|
386
|
+
zodType = 'z.string()';
|
|
387
|
+
}
|
|
388
|
+
if (field.required) {
|
|
389
|
+
// Add min(1) for string types that need non-empty validation
|
|
390
|
+
if (field.type === 'text' ||
|
|
391
|
+
field.type === 'textarea' ||
|
|
392
|
+
field.type === 'password' ||
|
|
393
|
+
field.type === 'email' ||
|
|
394
|
+
field.type === 'select') {
|
|
395
|
+
zodType += ".min(1, 'Required')";
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
else {
|
|
399
|
+
zodType += '.optional()';
|
|
400
|
+
}
|
|
401
|
+
return zodType;
|
|
402
|
+
}
|
|
403
|
+
getInitialValues(form) {
|
|
404
|
+
const values = {};
|
|
405
|
+
for (const field of form.fields) {
|
|
406
|
+
if (field.defaultValue !== undefined) {
|
|
407
|
+
// Use the default value directly (already properly typed)
|
|
408
|
+
values[field.name] = field.defaultValue;
|
|
409
|
+
}
|
|
410
|
+
else if (field.type === 'checkbox') {
|
|
411
|
+
values[field.name] = false;
|
|
412
|
+
}
|
|
413
|
+
else if (field.type === 'number') {
|
|
414
|
+
values[field.name] = 0;
|
|
415
|
+
}
|
|
416
|
+
else if (field.type === 'multiselect') {
|
|
417
|
+
values[field.name] = [];
|
|
418
|
+
}
|
|
419
|
+
else {
|
|
420
|
+
values[field.name] = '';
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return JSON.stringify(values, null, 2);
|
|
424
|
+
}
|
|
425
|
+
generateLayoutRow(row, fields) {
|
|
426
|
+
const lines = [];
|
|
427
|
+
const columns = row.columns ?? row.fields.length;
|
|
428
|
+
lines.push(` <Group grow align="flex-start">`);
|
|
429
|
+
for (const fieldName of row.fields) {
|
|
430
|
+
const field = fields.find((f) => f.name === fieldName);
|
|
431
|
+
if (field) {
|
|
432
|
+
lines.push(this.generateMantineField(field, 10));
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
lines.push(' </Group>');
|
|
436
|
+
return lines.join('\n');
|
|
437
|
+
}
|
|
438
|
+
generateMantineField(field, indent) {
|
|
439
|
+
const spaces = ' '.repeat(indent);
|
|
440
|
+
switch (field.type) {
|
|
441
|
+
case 'textarea':
|
|
442
|
+
return `${spaces}<Textarea label="${field.label}" placeholder="${field.placeholder || ''}" ${field.required ? 'required' : ''} {...form.getInputProps('${field.name}')} />`;
|
|
443
|
+
case 'select':
|
|
444
|
+
const options = (field.options || []).map((opt) => `{ value: '${opt.value}', label: '${opt.label}' }`).join(', ');
|
|
445
|
+
return `${spaces}<Select label="${field.label}" placeholder="${field.placeholder || 'Select...'}" ${field.required ? 'required' : ''} data={[${options}]} {...form.getInputProps('${field.name}')} />`;
|
|
446
|
+
case 'checkbox':
|
|
447
|
+
return `${spaces}<Checkbox label="${field.label}" {...form.getInputProps('${field.name}', { type: 'checkbox' })} />`;
|
|
448
|
+
case 'number':
|
|
449
|
+
return `${spaces}<NumberInput label="${field.label}" placeholder="${field.placeholder || ''}" ${field.required ? 'required' : ''} {...form.getInputProps('${field.name}')} />`;
|
|
450
|
+
case 'email':
|
|
451
|
+
return `${spaces}<TextInput type="email" label="${field.label}" placeholder="${field.placeholder || ''}" ${field.required ? 'required' : ''} {...form.getInputProps('${field.name}')} />`;
|
|
452
|
+
case 'password':
|
|
453
|
+
return `${spaces}<TextInput type="password" label="${field.label}" placeholder="${field.placeholder || ''}" ${field.required ? 'required' : ''} {...form.getInputProps('${field.name}')} />`;
|
|
454
|
+
case 'date':
|
|
455
|
+
return `${spaces}<TextInput type="date" label="${field.label}" ${field.required ? 'required' : ''} {...form.getInputProps('${field.name}')} />`;
|
|
456
|
+
case 'time':
|
|
457
|
+
return `${spaces}<TextInput type="time" label="${field.label}" ${field.required ? 'required' : ''} {...form.getInputProps('${field.name}')} />`;
|
|
458
|
+
case 'datetime':
|
|
459
|
+
return `${spaces}<TextInput type="datetime-local" label="${field.label}" ${field.required ? 'required' : ''} {...form.getInputProps('${field.name}')} />`;
|
|
460
|
+
case 'file':
|
|
461
|
+
return `${spaces}<TextInput type="file" label="${field.label}" {...form.getInputProps('${field.name}')} />`;
|
|
462
|
+
default:
|
|
463
|
+
return `${spaces}<TextInput label="${field.label}" placeholder="${field.placeholder || ''}" ${field.required ? 'required' : ''} {...form.getInputProps('${field.name}')} />`;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
// ===========================================================================
|
|
467
|
+
// Helpers
|
|
468
|
+
// ===========================================================================
|
|
469
|
+
mapPropType(type) {
|
|
470
|
+
const typeMap = {
|
|
471
|
+
string: 'string',
|
|
472
|
+
number: 'number',
|
|
473
|
+
boolean: 'boolean',
|
|
474
|
+
node: 'React.ReactNode',
|
|
475
|
+
element: 'React.ReactElement',
|
|
476
|
+
func: '() => void',
|
|
477
|
+
array: 'unknown[]',
|
|
478
|
+
object: 'Record<string, unknown>',
|
|
479
|
+
};
|
|
480
|
+
return typeMap[type] || type;
|
|
481
|
+
}
|
|
482
|
+
mapToMantineComponent(type) {
|
|
483
|
+
const componentMap = {
|
|
484
|
+
div: 'Box',
|
|
485
|
+
section: 'Box',
|
|
486
|
+
header: 'Box',
|
|
487
|
+
footer: 'Box',
|
|
488
|
+
article: 'Box',
|
|
489
|
+
aside: 'Box',
|
|
490
|
+
main: 'Box',
|
|
491
|
+
nav: 'Box',
|
|
492
|
+
};
|
|
493
|
+
return componentMap[type] || 'Box';
|
|
494
|
+
}
|
|
495
|
+
toCamelCase(str) {
|
|
496
|
+
return str.charAt(0).toLowerCase() + str.slice(1);
|
|
497
|
+
}
|
|
498
|
+
toKebabCase(str) {
|
|
499
|
+
return str.replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
// =============================================================================
|
|
503
|
+
// Convenience Functions
|
|
504
|
+
// =============================================================================
|
|
505
|
+
/**
|
|
506
|
+
* Generate Mantine components from Kappa ComponentBlock nodes
|
|
507
|
+
*/
|
|
508
|
+
export function generateMantineComponents(components, options = {}) {
|
|
509
|
+
const generator = new KappaMantineGenerator(options);
|
|
510
|
+
return generator.generateComponents(components);
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Generate Mantine forms from Kappa FormBlock nodes
|
|
514
|
+
*/
|
|
515
|
+
export function generateMantineForms(forms, options = {}) {
|
|
516
|
+
const generator = new KappaMantineGenerator(options);
|
|
517
|
+
return generator.generateForms(forms);
|
|
518
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { EntityBlock } from './kappa-ast.js';
|
|
2
|
+
export interface MongooseGeneratorOptions {
|
|
3
|
+
/** Generate TypeScript interfaces (default: true) */
|
|
4
|
+
typescript?: boolean;
|
|
5
|
+
/** Add provenance comments (default: true) */
|
|
6
|
+
provenance?: boolean;
|
|
7
|
+
/** Generate separate types file (default: true) */
|
|
8
|
+
separateTypes?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface GeneratedMongooseSchema {
|
|
11
|
+
/** Main schema file content */
|
|
12
|
+
schema: string;
|
|
13
|
+
/** TypeScript types file content (if separateTypes is true) */
|
|
14
|
+
types?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare class KappaMongooseGenerator {
|
|
17
|
+
private typescript;
|
|
18
|
+
private provenance;
|
|
19
|
+
private separateTypes;
|
|
20
|
+
constructor(options?: MongooseGeneratorOptions);
|
|
21
|
+
/**
|
|
22
|
+
* Generate Mongoose schema from entity blocks
|
|
23
|
+
*/
|
|
24
|
+
generate(entities: EntityBlock[]): GeneratedMongooseSchema;
|
|
25
|
+
private generateSchemaFile;
|
|
26
|
+
private generateSchemaImports;
|
|
27
|
+
private fieldUsesSchemaTypes;
|
|
28
|
+
private generateSchema;
|
|
29
|
+
private generateField;
|
|
30
|
+
private generateRelationshipField;
|
|
31
|
+
private generateValidators;
|
|
32
|
+
private getMongooseType;
|
|
33
|
+
private generateIndexes;
|
|
34
|
+
private generateVirtualRelationships;
|
|
35
|
+
private generateInterface;
|
|
36
|
+
private getTypeScriptType;
|
|
37
|
+
private generateTypesFile;
|
|
38
|
+
private toSnakeCase;
|
|
39
|
+
private toCamelCase;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Generate Mongoose schema from Kappa entities
|
|
43
|
+
*/
|
|
44
|
+
export declare function generateMongooseSchema(entities: EntityBlock[], options?: MongooseGeneratorOptions): GeneratedMongooseSchema;
|