@saastro/forms 0.1.3
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/README.md +347 -0
- package/cli.js +732 -0
- package/dist/DateRenderers-3JUQNLKJ.js +139 -0
- package/dist/DateRenderers-3JUQNLKJ.js.map +1 -0
- package/dist/chunk-GHDCNAWC.js +247 -0
- package/dist/chunk-GHDCNAWC.js.map +1 -0
- package/dist/chunk-YXKDN3PU.js +687 -0
- package/dist/chunk-YXKDN3PU.js.map +1 -0
- package/dist/index.d.ts +3754 -0
- package/dist/index.js +7115 -0
- package/dist/index.js.map +1 -0
- package/dist/submitOrchestrator-RF76MOWG.js +13 -0
- package/dist/submitOrchestrator-RF76MOWG.js.map +1 -0
- package/package.json +85 -0
package/cli.js
ADDED
|
@@ -0,0 +1,732 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* CLI para instalar componentes UI de @saastro/forms
|
|
5
|
+
* Uso: npx @saastro/forms add <component>
|
|
6
|
+
*
|
|
7
|
+
* CLI para instalar componentes shadcn requeridos por @saastro/forms
|
|
8
|
+
*
|
|
9
|
+
* Los componentes shadcn se instalan automáticamente desde shadcn/ui
|
|
10
|
+
* cuando se ejecuta `npm install` en el proyecto consumidor.
|
|
11
|
+
*
|
|
12
|
+
* Componentes shadcn requeridos:
|
|
13
|
+
* - input, button, label, textarea, select, checkbox, radio-group
|
|
14
|
+
* - popover, calendar, tooltip, slider, switch, separator, dialog, command
|
|
15
|
+
* - field, form
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { execSync } from 'child_process';
|
|
19
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
20
|
+
import { dirname, join } from 'path';
|
|
21
|
+
import { fileURLToPath } from 'url';
|
|
22
|
+
|
|
23
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
24
|
+
const __dirname = dirname(__filename);
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Obtiene la ruta base del paquete (funciona en desarrollo y producción)
|
|
28
|
+
*/
|
|
29
|
+
function getPackageRoot() {
|
|
30
|
+
// En desarrollo: cli.js está en packages/saastro-forms/
|
|
31
|
+
// En producción: cli.js está en node_modules/@saastro/forms/
|
|
32
|
+
const srcPath = join(__dirname, 'src');
|
|
33
|
+
if (existsSync(srcPath)) {
|
|
34
|
+
return __dirname; // Desarrollo: usar src/
|
|
35
|
+
}
|
|
36
|
+
// En producción, intentar encontrar src/ relativo a node_modules
|
|
37
|
+
return __dirname;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Componentes shadcn requeridos por @saastro/forms
|
|
41
|
+
const SHADCN_COMPONENTS = [
|
|
42
|
+
// Core
|
|
43
|
+
'input',
|
|
44
|
+
'button',
|
|
45
|
+
'label',
|
|
46
|
+
'textarea',
|
|
47
|
+
'select',
|
|
48
|
+
'checkbox',
|
|
49
|
+
'radio-group',
|
|
50
|
+
// Extras
|
|
51
|
+
'popover',
|
|
52
|
+
'calendar',
|
|
53
|
+
'tooltip',
|
|
54
|
+
'slider',
|
|
55
|
+
'switch',
|
|
56
|
+
'separator',
|
|
57
|
+
'dialog',
|
|
58
|
+
'command',
|
|
59
|
+
'input-otp',
|
|
60
|
+
'native-select',
|
|
61
|
+
'accordion', // Para StepsAccordion
|
|
62
|
+
// Nota: combobox no está disponible en shadcn, se construye con Popover + Command
|
|
63
|
+
// Nota: field y form son componentes shadcn que se instalan desde shadcn
|
|
64
|
+
'field',
|
|
65
|
+
'form',
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
// Componentes core disponibles (legacy, para compatibilidad)
|
|
69
|
+
// Componentes custom del paquete @saastro/forms (NO están en shadcn)
|
|
70
|
+
// Estos se copian desde el paquete al proyecto consumidor
|
|
71
|
+
const CORE_COMPONENTS = [
|
|
72
|
+
// Solo componentes custom que NO están en shadcn
|
|
73
|
+
// Los componentes shadcn (input, button, label, textarea, select, checkbox, radio-group, field, form)
|
|
74
|
+
// se instalan automáticamente desde shadcn mediante checkAndInstallComponents()
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
// Mapeo de nombres de componentes custom a archivos
|
|
78
|
+
// Solo componentes que NO están en shadcn y que viven en el paquete
|
|
79
|
+
// Los componentes shadcn se instalan automáticamente desde shadcn
|
|
80
|
+
const COMPONENT_FILES = {
|
|
81
|
+
// Ejemplo: si hubiera componentes custom aquí
|
|
82
|
+
// "button-pro": "button-pro.tsx",
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Lee components.json del proyecto consumidor
|
|
87
|
+
*/
|
|
88
|
+
function readComponentsConfig() {
|
|
89
|
+
const configPath = join(process.cwd(), 'components.json');
|
|
90
|
+
|
|
91
|
+
if (!existsSync(configPath)) {
|
|
92
|
+
console.error('❌ No se encontró components.json en el directorio actual.');
|
|
93
|
+
console.error(
|
|
94
|
+
' Asegúrate de estar en la raíz del proyecto y de tener components.json configurado.',
|
|
95
|
+
);
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const configContent = readFileSync(configPath, 'utf-8');
|
|
101
|
+
return JSON.parse(configContent);
|
|
102
|
+
} catch (error) {
|
|
103
|
+
console.error('❌ Error al leer components.json:', error.message);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Resuelve el path de un alias según components.json
|
|
110
|
+
*/
|
|
111
|
+
function resolveAlias(alias, config) {
|
|
112
|
+
const aliases = config.aliases || {};
|
|
113
|
+
return aliases[alias] || `src/${alias}`;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Transforma imports relativos a imports con alias
|
|
118
|
+
*/
|
|
119
|
+
function transformImports(content, config) {
|
|
120
|
+
const aliases = config.aliases || {};
|
|
121
|
+
const utilsPath = aliases.utils || 'src/lib/utils';
|
|
122
|
+
|
|
123
|
+
// Convertir imports relativos de lib/utils a alias
|
|
124
|
+
// Ejemplo: import { cn } from "../../lib/utils" -> import { cn } from "@/lib/utils"
|
|
125
|
+
const utilsAlias = utilsPath.replace(/^src\//, '@/');
|
|
126
|
+
|
|
127
|
+
// Reemplazar imports relativos de lib/utils
|
|
128
|
+
content = content.replace(/from\s+["']\.\.\/\.\.\/lib\/utils["']/g, `from "${utilsAlias}"`);
|
|
129
|
+
content = content.replace(/from\s+["']\.\.\/\.\.\/\.\.\/lib\/utils["']/g, `from "${utilsAlias}"`);
|
|
130
|
+
|
|
131
|
+
// Reemplazar imports absolutos de src/lib/utils a alias
|
|
132
|
+
content = content.replace(/from\s+["']src\/lib\/utils["']/g, `from "${utilsAlias}"`);
|
|
133
|
+
|
|
134
|
+
// Convertir imports relativos de otros componentes UI
|
|
135
|
+
// Ejemplo: import { Label } from "./label" -> import { Label } from "@/components/ui/label"
|
|
136
|
+
const uiPath = aliases.ui || 'src/components/ui';
|
|
137
|
+
const uiAlias = uiPath.replace(/^src\//, '@/');
|
|
138
|
+
|
|
139
|
+
content = content.replace(/from\s+["']\.\/([a-z-]+)["']/g, (match, component) => {
|
|
140
|
+
// Solo transformar si es un componente UI conocido
|
|
141
|
+
if (CORE_COMPONENTS.includes(component) || COMPONENT_FILES[component]) {
|
|
142
|
+
return `from "${uiAlias}/${component}"`;
|
|
143
|
+
}
|
|
144
|
+
return match;
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// Reemplazar imports absolutos de src/components/ui a alias
|
|
148
|
+
content = content.replace(
|
|
149
|
+
/from\s+["']src\/components\/ui\/([a-z-]+)["']/g,
|
|
150
|
+
(match, component) => {
|
|
151
|
+
return `from "${uiAlias}/${component}"`;
|
|
152
|
+
},
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
return content;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Verifica si un componente shadcn está instalado
|
|
160
|
+
*/
|
|
161
|
+
function isComponentInstalled(componentName, config) {
|
|
162
|
+
const uiPath = resolveAlias('ui', config);
|
|
163
|
+
const componentPath = join(process.cwd(), uiPath.replace(/^@\//, 'src/'), `${componentName}.tsx`);
|
|
164
|
+
return existsSync(componentPath);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Instala un componente shadcn usando el CLI oficial
|
|
169
|
+
*/
|
|
170
|
+
function installShadcnComponent(componentName, config, current, total) {
|
|
171
|
+
try {
|
|
172
|
+
const progress = total > 1 ? `[${current}/${total}] ` : '';
|
|
173
|
+
console.log(`\n${progress}📦 Instalando ${componentName}...`);
|
|
174
|
+
const startTime = Date.now();
|
|
175
|
+
// Usar --overwrite para evitar preguntas interactivas
|
|
176
|
+
execSync(`npx shadcn@latest add ${componentName} --yes --overwrite`, {
|
|
177
|
+
stdio: 'inherit',
|
|
178
|
+
cwd: process.cwd(),
|
|
179
|
+
});
|
|
180
|
+
const duration = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
181
|
+
console.log(`✓ ${componentName} instalado correctamente (${duration}s)`);
|
|
182
|
+
return true;
|
|
183
|
+
} catch (error) {
|
|
184
|
+
console.error(`\n❌ Error al instalar ${componentName}:`, error.message);
|
|
185
|
+
return false;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Verifica e instala todos los componentes shadcn requeridos
|
|
191
|
+
*/
|
|
192
|
+
function checkAndInstallComponents(config) {
|
|
193
|
+
console.log('🔍 Verificando componentes shadcn requeridos...\n');
|
|
194
|
+
|
|
195
|
+
const missingComponents = [];
|
|
196
|
+
const installedComponents = [];
|
|
197
|
+
|
|
198
|
+
for (const component of SHADCN_COMPONENTS) {
|
|
199
|
+
if (isComponentInstalled(component, config)) {
|
|
200
|
+
installedComponents.push(component);
|
|
201
|
+
console.log(`✓ ${component} ya está instalado`);
|
|
202
|
+
} else {
|
|
203
|
+
missingComponents.push(component);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (missingComponents.length === 0) {
|
|
208
|
+
console.log('\n✅ Todos los componentes requeridos están instalados.');
|
|
209
|
+
return true;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
console.log(`\n📦 Instalando ${missingComponents.length} componente(s) faltante(s)...\n`);
|
|
213
|
+
console.log(`Componentes a instalar: ${missingComponents.join(', ')}\n`);
|
|
214
|
+
|
|
215
|
+
// Intentar instalar todos de una vez (más rápido)
|
|
216
|
+
if (missingComponents.length > 1) {
|
|
217
|
+
console.log('⚡ Intentando instalar todos los componentes en un solo comando...\n');
|
|
218
|
+
try {
|
|
219
|
+
const componentsList = missingComponents.join(' ');
|
|
220
|
+
const startTime = Date.now();
|
|
221
|
+
// Usar --overwrite para evitar preguntas interactivas
|
|
222
|
+
execSync(`npx shadcn@latest add ${componentsList} --yes --overwrite`, {
|
|
223
|
+
stdio: 'inherit',
|
|
224
|
+
cwd: process.cwd(),
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// Transformar imports en los componentes instalados
|
|
228
|
+
console.log('\n🔄 Transformando imports a alias...');
|
|
229
|
+
for (const component of missingComponents) {
|
|
230
|
+
const uiPath = resolveAlias('ui', config);
|
|
231
|
+
const componentPath = join(
|
|
232
|
+
process.cwd(),
|
|
233
|
+
uiPath.replace(/^@\//, 'src/'),
|
|
234
|
+
`${component}.tsx`,
|
|
235
|
+
);
|
|
236
|
+
if (existsSync(componentPath)) {
|
|
237
|
+
let content = readFileSync(componentPath, 'utf-8');
|
|
238
|
+
content = transformImports(content, config);
|
|
239
|
+
writeFileSync(componentPath, content, 'utf-8');
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const duration = ((Date.now() - startTime) / 1000).toFixed(1);
|
|
244
|
+
console.log(`\n✅ Todos los componentes se instalaron correctamente (${duration}s).`);
|
|
245
|
+
return true;
|
|
246
|
+
} catch (error) {
|
|
247
|
+
console.error('\n⚠️ Error al instalar en lote, intentando uno por uno...\n');
|
|
248
|
+
console.error(`Error: ${error.message}\n`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Fallback: instalar uno por uno con progreso visible
|
|
253
|
+
console.log('📋 Instalando componentes uno por uno...\n');
|
|
254
|
+
let allInstalled = true;
|
|
255
|
+
const total = missingComponents.length;
|
|
256
|
+
|
|
257
|
+
for (let i = 0; i < missingComponents.length; i++) {
|
|
258
|
+
const component = missingComponents[i];
|
|
259
|
+
if (!installShadcnComponent(component, config, i + 1, total)) {
|
|
260
|
+
allInstalled = false;
|
|
261
|
+
} else {
|
|
262
|
+
// Transformar imports después de instalar
|
|
263
|
+
const uiPath = resolveAlias('ui', config);
|
|
264
|
+
const componentPath = join(process.cwd(), uiPath.replace(/^@\//, 'src/'), `${component}.tsx`);
|
|
265
|
+
if (existsSync(componentPath)) {
|
|
266
|
+
let content = readFileSync(componentPath, 'utf-8');
|
|
267
|
+
content = transformImports(content, config);
|
|
268
|
+
writeFileSync(componentPath, content, 'utf-8');
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (allInstalled) {
|
|
274
|
+
console.log('\n✅ Todos los componentes se instalaron correctamente.');
|
|
275
|
+
} else {
|
|
276
|
+
console.log('\n⚠️ Algunos componentes no se pudieron instalar. Revisa los errores arriba.');
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return allInstalled;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Copia lib/utils.ts si no existe
|
|
284
|
+
*/
|
|
285
|
+
function ensureUtils(config) {
|
|
286
|
+
const utilsPath = resolveAlias('utils', config);
|
|
287
|
+
const fullUtilsPath = join(process.cwd(), utilsPath.replace(/^@\//, 'src/') + '.ts');
|
|
288
|
+
|
|
289
|
+
if (existsSync(fullUtilsPath)) {
|
|
290
|
+
return; // Ya existe
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Crear directorio si no existe
|
|
294
|
+
const utilsDir = dirname(fullUtilsPath);
|
|
295
|
+
mkdirSync(utilsDir, { recursive: true });
|
|
296
|
+
|
|
297
|
+
// Copiar utils.ts desde el paquete
|
|
298
|
+
const packageRoot = getPackageRoot();
|
|
299
|
+
const sourceUtils = join(packageRoot, 'src/lib/utils.ts');
|
|
300
|
+
|
|
301
|
+
if (!existsSync(sourceUtils)) {
|
|
302
|
+
console.warn(
|
|
303
|
+
`⚠ No se encontró ${sourceUtils}. Asegúrate de que el paquete está correctamente instalado.`,
|
|
304
|
+
);
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const utilsContent = readFileSync(sourceUtils, 'utf-8');
|
|
309
|
+
|
|
310
|
+
writeFileSync(fullUtilsPath, utilsContent, 'utf-8');
|
|
311
|
+
console.log(`✓ Copiado ${utilsPath}.ts`);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Instala un componente custom del paquete (legacy - ya no se usa)
|
|
316
|
+
* Los componentes shadcn se instalan automáticamente desde shadcn
|
|
317
|
+
*/
|
|
318
|
+
function installComponent(componentName, config) {
|
|
319
|
+
if (!CORE_COMPONENTS.includes(componentName)) {
|
|
320
|
+
console.error(`❌ Componente "${componentName}" no está disponible.`);
|
|
321
|
+
console.error(` Los componentes shadcn se instalan automáticamente desde shadcn/ui.`);
|
|
322
|
+
console.error(` Componentes custom disponibles: ${CORE_COMPONENTS.join(', ') || 'ninguno'}`);
|
|
323
|
+
process.exit(1);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
const fileName = COMPONENT_FILES[componentName];
|
|
327
|
+
if (!fileName) {
|
|
328
|
+
console.error(`❌ Componente "${componentName}" no tiene archivo asociado.`);
|
|
329
|
+
process.exit(1);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const packageRoot = getPackageRoot();
|
|
333
|
+
const sourcePath = join(packageRoot, 'src/components/ui', fileName);
|
|
334
|
+
|
|
335
|
+
if (!existsSync(sourcePath)) {
|
|
336
|
+
console.error(`❌ No se encontró el archivo del componente: ${sourcePath}`);
|
|
337
|
+
console.error(` Asegúrate de que el paquete @saastro/forms está correctamente instalado.`);
|
|
338
|
+
process.exit(1);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Leer el contenido del componente
|
|
342
|
+
let content = readFileSync(sourcePath, 'utf-8');
|
|
343
|
+
|
|
344
|
+
// Transformar imports
|
|
345
|
+
content = transformImports(content, config);
|
|
346
|
+
|
|
347
|
+
// Determinar ruta de destino
|
|
348
|
+
const uiPath = resolveAlias('ui', config);
|
|
349
|
+
const destPath = join(process.cwd(), uiPath.replace(/^@\//, 'src/'), fileName);
|
|
350
|
+
|
|
351
|
+
// Crear directorio si no existe
|
|
352
|
+
mkdirSync(dirname(destPath), { recursive: true });
|
|
353
|
+
|
|
354
|
+
// Escribir componente
|
|
355
|
+
writeFileSync(destPath, content, 'utf-8');
|
|
356
|
+
|
|
357
|
+
console.log(`✓ Instalado ${componentName} en ${uiPath}/${fileName}`);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ═══════════════════════════════════════════════════════════
|
|
361
|
+
// INIT command — scaffolds zero-config example form
|
|
362
|
+
// ═══════════════════════════════════════════════════════════
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Zero-config example form template
|
|
366
|
+
* Uses Vite's import.meta.glob for auto-discovery of shadcn components
|
|
367
|
+
*/
|
|
368
|
+
const EXAMPLE_TEMPLATE = `import { Form, FormBuilder } from '@saastro/forms';
|
|
369
|
+
|
|
370
|
+
// Auto-discover all shadcn components at build time
|
|
371
|
+
// This eliminates the need for FormProvider or manual component registration
|
|
372
|
+
const uiComponents = import.meta.glob('@/components/ui/*.tsx', { eager: true });
|
|
373
|
+
|
|
374
|
+
const formConfig = FormBuilder.create('contact-form')
|
|
375
|
+
.layout('auto')
|
|
376
|
+
.columns(1)
|
|
377
|
+
.gap(4)
|
|
378
|
+
.addField('name', (f) =>
|
|
379
|
+
f.type('text').label('Name').placeholder('John Doe').required().minLength(2),
|
|
380
|
+
)
|
|
381
|
+
.addField('email', (f) =>
|
|
382
|
+
f.type('email').label('Email').placeholder('john@example.com').required().email(),
|
|
383
|
+
)
|
|
384
|
+
.addField('message', (f) =>
|
|
385
|
+
f.type('textarea').label('Message').placeholder('How can we help?').required().minLength(10),
|
|
386
|
+
)
|
|
387
|
+
.addStep('main', ['name', 'email', 'message'])
|
|
388
|
+
.initialStep('main')
|
|
389
|
+
.buttons({
|
|
390
|
+
submit: { type: 'submit', label: 'Send', variant: 'default' },
|
|
391
|
+
align: 'end',
|
|
392
|
+
})
|
|
393
|
+
.onSuccess((values) => {
|
|
394
|
+
console.log('Form submitted:', values);
|
|
395
|
+
})
|
|
396
|
+
.build();
|
|
397
|
+
|
|
398
|
+
export function ExampleForm() {
|
|
399
|
+
return (
|
|
400
|
+
<div className="mx-auto max-w-lg">
|
|
401
|
+
<Form config={formConfig} components={uiComponents} />
|
|
402
|
+
</div>
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
`;
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Alternative example using JSON config instead of FormBuilder
|
|
409
|
+
*/
|
|
410
|
+
const EXAMPLE_JSON_TEMPLATE = `import type { FormConfig } from '@saastro/forms';
|
|
411
|
+
import { Form } from '@saastro/forms';
|
|
412
|
+
|
|
413
|
+
// Auto-discover all shadcn components at build time
|
|
414
|
+
const uiComponents = import.meta.glob('@/components/ui/*.tsx', { eager: true });
|
|
415
|
+
|
|
416
|
+
// Pure JSON config — no Zod import needed.
|
|
417
|
+
// ValidationRules are compiled to Zod schemas internally.
|
|
418
|
+
const formConfig: FormConfig = {
|
|
419
|
+
formId: 'contact-form-json',
|
|
420
|
+
layout: {
|
|
421
|
+
mode: 'auto',
|
|
422
|
+
columns: 1,
|
|
423
|
+
gap: 4,
|
|
424
|
+
},
|
|
425
|
+
fields: {
|
|
426
|
+
name: {
|
|
427
|
+
type: 'text',
|
|
428
|
+
label: 'Name',
|
|
429
|
+
placeholder: 'John Doe',
|
|
430
|
+
schema: {
|
|
431
|
+
required: true,
|
|
432
|
+
minLength: 2,
|
|
433
|
+
minLengthMessage: 'Name must be at least 2 characters',
|
|
434
|
+
},
|
|
435
|
+
},
|
|
436
|
+
email: {
|
|
437
|
+
type: 'email',
|
|
438
|
+
label: 'Email',
|
|
439
|
+
placeholder: 'john@example.com',
|
|
440
|
+
schema: { required: true, format: 'email', formatMessage: 'Please enter a valid email' },
|
|
441
|
+
},
|
|
442
|
+
message: {
|
|
443
|
+
type: 'textarea',
|
|
444
|
+
label: 'Message',
|
|
445
|
+
placeholder: 'How can we help?',
|
|
446
|
+
schema: {
|
|
447
|
+
required: true,
|
|
448
|
+
minLength: 10,
|
|
449
|
+
minLengthMessage: 'Message must be at least 10 characters',
|
|
450
|
+
},
|
|
451
|
+
},
|
|
452
|
+
},
|
|
453
|
+
steps: {
|
|
454
|
+
main: { fields: ['name', 'email', 'message'] },
|
|
455
|
+
},
|
|
456
|
+
initialStep: 'main',
|
|
457
|
+
buttons: {
|
|
458
|
+
submit: { type: 'submit', label: 'Send Message', variant: 'default' },
|
|
459
|
+
align: 'end',
|
|
460
|
+
},
|
|
461
|
+
onSuccess: (values) => {
|
|
462
|
+
console.log('Form submitted (JSON):', values);
|
|
463
|
+
},
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
export function ExampleFormJson() {
|
|
467
|
+
return (
|
|
468
|
+
<div className="mx-auto max-w-lg">
|
|
469
|
+
<Form config={formConfig} components={uiComponents} />
|
|
470
|
+
</div>
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
`;
|
|
474
|
+
|
|
475
|
+
// Legacy templates kept for backwards compatibility
|
|
476
|
+
const BARREL_TEMPLATE = `/**
|
|
477
|
+
* @deprecated Use zero-config mode with import.meta.glob instead.
|
|
478
|
+
*
|
|
479
|
+
* Zero-config example:
|
|
480
|
+
* const uiComponents = import.meta.glob('@/components/ui/*.tsx', { eager: true });
|
|
481
|
+
* <Form config={config} components={uiComponents} />
|
|
482
|
+
*
|
|
483
|
+
* This file is kept for backwards compatibility with existing projects.
|
|
484
|
+
*/
|
|
485
|
+
|
|
486
|
+
// Inputs
|
|
487
|
+
export { Input } from '@/components/ui/input';
|
|
488
|
+
export { Textarea } from '@/components/ui/textarea';
|
|
489
|
+
export { Button } from '@/components/ui/button';
|
|
490
|
+
export { Label } from '@/components/ui/label';
|
|
491
|
+
|
|
492
|
+
// Checkbox & Switch
|
|
493
|
+
export { Checkbox } from '@/components/ui/checkbox';
|
|
494
|
+
export { Switch } from '@/components/ui/switch';
|
|
495
|
+
|
|
496
|
+
// Radio Group
|
|
497
|
+
export { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
|
498
|
+
|
|
499
|
+
// Select
|
|
500
|
+
export {
|
|
501
|
+
Select,
|
|
502
|
+
SelectTrigger,
|
|
503
|
+
SelectContent,
|
|
504
|
+
SelectItem,
|
|
505
|
+
SelectValue,
|
|
506
|
+
} from '@/components/ui/select';
|
|
507
|
+
|
|
508
|
+
// Native Select
|
|
509
|
+
export { NativeSelect } from '@/components/ui/native-select';
|
|
510
|
+
|
|
511
|
+
// Slider
|
|
512
|
+
export { Slider } from '@/components/ui/slider';
|
|
513
|
+
|
|
514
|
+
// Popover
|
|
515
|
+
export { Popover, PopoverTrigger, PopoverContent } from '@/components/ui/popover';
|
|
516
|
+
|
|
517
|
+
// Tooltip
|
|
518
|
+
export {
|
|
519
|
+
Tooltip,
|
|
520
|
+
TooltipTrigger,
|
|
521
|
+
TooltipContent,
|
|
522
|
+
TooltipProvider,
|
|
523
|
+
} from '@/components/ui/tooltip';
|
|
524
|
+
|
|
525
|
+
// Separator
|
|
526
|
+
export { Separator } from '@/components/ui/separator';
|
|
527
|
+
|
|
528
|
+
// Dialog
|
|
529
|
+
export {
|
|
530
|
+
Dialog,
|
|
531
|
+
DialogTrigger,
|
|
532
|
+
DialogContent,
|
|
533
|
+
DialogHeader,
|
|
534
|
+
DialogTitle,
|
|
535
|
+
DialogDescription,
|
|
536
|
+
} from '@/components/ui/dialog';
|
|
537
|
+
|
|
538
|
+
// Command
|
|
539
|
+
export {
|
|
540
|
+
Command,
|
|
541
|
+
CommandInput,
|
|
542
|
+
CommandList,
|
|
543
|
+
CommandEmpty,
|
|
544
|
+
CommandGroup,
|
|
545
|
+
CommandItem,
|
|
546
|
+
} from '@/components/ui/command';
|
|
547
|
+
|
|
548
|
+
// Input OTP
|
|
549
|
+
export { InputOTP, InputOTPGroup, InputOTPSlot } from '@/components/ui/input-otp';
|
|
550
|
+
|
|
551
|
+
// Accordion
|
|
552
|
+
export {
|
|
553
|
+
Accordion,
|
|
554
|
+
AccordionItem,
|
|
555
|
+
AccordionTrigger,
|
|
556
|
+
AccordionContent,
|
|
557
|
+
} from '@/components/ui/accordion';
|
|
558
|
+
|
|
559
|
+
// Calendar
|
|
560
|
+
export { Calendar } from '@/components/ui/calendar';
|
|
561
|
+
|
|
562
|
+
// Form (react-hook-form integration)
|
|
563
|
+
export { FormField, FormControl } from '@/components/ui/form';
|
|
564
|
+
|
|
565
|
+
// Field (composable field system)
|
|
566
|
+
export { Field, FieldLabel, FieldDescription, FieldError } from '@/components/ui/field';
|
|
567
|
+
`;
|
|
568
|
+
|
|
569
|
+
// Legacy provider template kept for backwards compatibility
|
|
570
|
+
const PROVIDER_TEMPLATE = `/**
|
|
571
|
+
* @deprecated Use zero-config mode instead.
|
|
572
|
+
*
|
|
573
|
+
* Zero-config example:
|
|
574
|
+
* const uiComponents = import.meta.glob('@/components/ui/*.tsx', { eager: true });
|
|
575
|
+
* <Form config={config} components={uiComponents} />
|
|
576
|
+
*/
|
|
577
|
+
import { ComponentProvider, createComponentRegistry } from '@saastro/forms';
|
|
578
|
+
import * as shadcn from '@/lib/form-components';
|
|
579
|
+
|
|
580
|
+
const componentRegistry = createComponentRegistry(shadcn);
|
|
581
|
+
|
|
582
|
+
export function FormProvider({ children }: { children: React.ReactNode }) {
|
|
583
|
+
return <ComponentProvider components={componentRegistry}>{children}</ComponentProvider>;
|
|
584
|
+
}
|
|
585
|
+
`;
|
|
586
|
+
|
|
587
|
+
function initProject() {
|
|
588
|
+
console.log('');
|
|
589
|
+
console.log('@saastro/forms init');
|
|
590
|
+
console.log('===================');
|
|
591
|
+
console.log('');
|
|
592
|
+
|
|
593
|
+
const config = readComponentsConfig();
|
|
594
|
+
const componentsDir = join(process.cwd(), 'src/components');
|
|
595
|
+
|
|
596
|
+
// 1. Create example form with FormBuilder
|
|
597
|
+
const examplePath = join(componentsDir, 'ExampleForm.tsx');
|
|
598
|
+
if (existsSync(examplePath)) {
|
|
599
|
+
console.log(' exists src/components/ExampleForm.tsx');
|
|
600
|
+
} else {
|
|
601
|
+
mkdirSync(dirname(examplePath), { recursive: true });
|
|
602
|
+
writeFileSync(examplePath, EXAMPLE_TEMPLATE, 'utf-8');
|
|
603
|
+
console.log(' create src/components/ExampleForm.tsx');
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// 2. Create example form with JSON config
|
|
607
|
+
const exampleJsonPath = join(componentsDir, 'ExampleFormJson.tsx');
|
|
608
|
+
if (existsSync(exampleJsonPath)) {
|
|
609
|
+
console.log(' exists src/components/ExampleFormJson.tsx');
|
|
610
|
+
} else {
|
|
611
|
+
writeFileSync(exampleJsonPath, EXAMPLE_JSON_TEMPLATE, 'utf-8');
|
|
612
|
+
console.log(' create src/components/ExampleFormJson.tsx');
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
console.log('');
|
|
616
|
+
console.log('Done! Next steps:');
|
|
617
|
+
console.log('');
|
|
618
|
+
console.log(' 1. Install required shadcn components:');
|
|
619
|
+
console.log(' npx @saastro/forms install-deps');
|
|
620
|
+
console.log('');
|
|
621
|
+
console.log(' 2. Use the form in your page:');
|
|
622
|
+
console.log('');
|
|
623
|
+
console.log(' ---');
|
|
624
|
+
console.log(' import { ExampleForm } from "@/components/ExampleForm";');
|
|
625
|
+
console.log(' ---');
|
|
626
|
+
console.log(' <ExampleForm client:idle />');
|
|
627
|
+
console.log('');
|
|
628
|
+
console.log(' Zero-config mode: Components are auto-discovered via import.meta.glob.');
|
|
629
|
+
console.log(' No FormProvider or form-components.ts needed!');
|
|
630
|
+
console.log('');
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
/**
|
|
634
|
+
* Funcion principal
|
|
635
|
+
*/
|
|
636
|
+
function main() {
|
|
637
|
+
const args = process.argv.slice(2);
|
|
638
|
+
|
|
639
|
+
// Modo postinstall: instalar dependencias shadcn automáticamente
|
|
640
|
+
if (args.length > 0 && args[0] === 'install-deps') {
|
|
641
|
+
// Verificar que estamos en un proyecto consumidor, no en el paquete mismo
|
|
642
|
+
const packageJsonPath = join(process.cwd(), 'package.json');
|
|
643
|
+
if (existsSync(packageJsonPath)) {
|
|
644
|
+
try {
|
|
645
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
646
|
+
if (packageJson.name === '@saastro/forms') {
|
|
647
|
+
console.log('ℹ️ Detectado que estás en el paquete @saastro/forms.');
|
|
648
|
+
console.log(
|
|
649
|
+
' Este comando debe ejecutarse desde el proyecto consumidor, no desde el paquete.',
|
|
650
|
+
);
|
|
651
|
+
console.log(' Ve a tu proyecto (ej: apps/astro-test) y ejecuta: npm install');
|
|
652
|
+
process.exit(0);
|
|
653
|
+
}
|
|
654
|
+
} catch (e) {
|
|
655
|
+
// Continuar si no se puede leer package.json
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
let config;
|
|
660
|
+
try {
|
|
661
|
+
config = readComponentsConfig();
|
|
662
|
+
} catch (error) {
|
|
663
|
+
// Si no hay components.json, no hacer nada (puede ser instalación en monorepo)
|
|
664
|
+
console.log('ℹ️ No se encontró components.json. Saltando instalación automática.');
|
|
665
|
+
console.log(
|
|
666
|
+
' Si necesitas instalar componentes shadcn, ejecuta: npx @saastro/forms install-deps',
|
|
667
|
+
);
|
|
668
|
+
process.exit(0);
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// Asegurar que utils.ts existe
|
|
672
|
+
ensureUtils(config);
|
|
673
|
+
|
|
674
|
+
// Verificar e instalar componentes shadcn
|
|
675
|
+
checkAndInstallComponents(config);
|
|
676
|
+
return;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// Modo init: genera barrel + provider + ejemplo
|
|
680
|
+
if (args.length > 0 && args[0] === 'init') {
|
|
681
|
+
initProject();
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// Modo legacy: add component (mantener para compatibilidad)
|
|
686
|
+
if (args.length > 0 && args[0] === 'add') {
|
|
687
|
+
if (args.length < 2) {
|
|
688
|
+
console.error('❌ Debes especificar un componente.');
|
|
689
|
+
console.error(' Ejemplo: npx @saastro/forms add input');
|
|
690
|
+
process.exit(1);
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
const componentName = args[1];
|
|
694
|
+
const config = readComponentsConfig();
|
|
695
|
+
|
|
696
|
+
// Asegurar que utils.ts existe
|
|
697
|
+
ensureUtils(config);
|
|
698
|
+
|
|
699
|
+
// Instalar componente
|
|
700
|
+
installComponent(componentName, config);
|
|
701
|
+
|
|
702
|
+
console.log('');
|
|
703
|
+
console.log('✓ Componente instalado correctamente.');
|
|
704
|
+
console.log(
|
|
705
|
+
' Recuerda configurar @source en tu CSS para que Tailwind escanee los componentes.',
|
|
706
|
+
);
|
|
707
|
+
return;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
// Mostrar ayuda
|
|
711
|
+
console.log('Usage: npx @saastro/forms <command>');
|
|
712
|
+
console.log('');
|
|
713
|
+
console.log('Commands:');
|
|
714
|
+
console.log(' init Generate zero-config example forms (FormBuilder + JSON)');
|
|
715
|
+
console.log(' install-deps Install all required shadcn/ui components');
|
|
716
|
+
console.log(' add <name> Install a specific component (legacy)');
|
|
717
|
+
console.log('');
|
|
718
|
+
console.log('Quick start:');
|
|
719
|
+
console.log(' npx @saastro/forms init');
|
|
720
|
+
console.log(' npx @saastro/forms install-deps');
|
|
721
|
+
console.log('');
|
|
722
|
+
console.log('Zero-config mode:');
|
|
723
|
+
console.log(" Components are auto-discovered via Vite's import.meta.glob.");
|
|
724
|
+
console.log(' No FormProvider or form-components.ts needed!');
|
|
725
|
+
console.log('');
|
|
726
|
+
console.log('Example usage:');
|
|
727
|
+
console.log(' const ui = import.meta.glob("@/components/ui/*.tsx", { eager: true });');
|
|
728
|
+
console.log(' <Form config={config} components={ui} />');
|
|
729
|
+
process.exit(0);
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
main();
|