@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/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();