formstack-ui 0.0.1

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.
Files changed (64) hide show
  1. package/CHANGELOG.md +206 -0
  2. package/LICENSE +21 -0
  3. package/README.md +341 -0
  4. package/dist/bin/index.js +1077 -0
  5. package/dist/bin/registry.js +105 -0
  6. package/dist/components/FormikSchemaForm.d.ts +11 -0
  7. package/dist/components/SchemaForm.d.ts +26 -0
  8. package/dist/components/ThemeSwitcher.d.ts +1 -0
  9. package/dist/components/builder/FormBuilder.d.ts +1 -0
  10. package/dist/components/demo/Documentation.d.ts +1 -0
  11. package/dist/components/demo/StatesGallery.d.ts +1 -0
  12. package/dist/components/form/FormRenderer.d.ts +9 -0
  13. package/dist/components/layout/GridContainer.d.ts +8 -0
  14. package/dist/components/layout/GridItem.d.ts +8 -0
  15. package/dist/components/pages/ChangelogPage.d.ts +1 -0
  16. package/dist/components/pages/DemoPage.d.ts +6 -0
  17. package/dist/components/pages/GuidePage.d.ts +1 -0
  18. package/dist/components/pages/Home.d.ts +1 -0
  19. package/dist/components/pages/ReadmePage.d.ts +1 -0
  20. package/dist/components/pages/TasksPage.d.ts +1 -0
  21. package/dist/components/pages/ThemeOverridesPage.d.ts +1 -0
  22. package/dist/components/playground/ComponentPlayground.d.ts +8 -0
  23. package/dist/components/playground/PlaygroundPage.d.ts +1 -0
  24. package/dist/components/playground/components-config.d.ts +10 -0
  25. package/dist/components/playground/types.d.ts +14 -0
  26. package/dist/components/theme/ThemeContext.d.ts +16 -0
  27. package/dist/components/theme/defaultTheme.d.ts +2 -0
  28. package/dist/components/theme/types.d.ts +38 -0
  29. package/dist/components/ui/Checkbox.d.ts +6 -0
  30. package/dist/components/ui/FileInput.d.ts +12 -0
  31. package/dist/components/ui/Grid.d.ts +18 -0
  32. package/dist/components/ui/RadioGroup.d.ts +16 -0
  33. package/dist/components/ui/Select.d.ts +9 -0
  34. package/dist/components/ui/Switch.d.ts +6 -0
  35. package/dist/components/ui/TextInput.d.ts +6 -0
  36. package/dist/components/ui/Textarea.d.ts +10 -0
  37. package/dist/components/ui/date/Calendar.d.ts +16 -0
  38. package/dist/components/ui/date/DatePicker.d.ts +12 -0
  39. package/dist/components/ui/date/DateRangePicker.d.ts +18 -0
  40. package/dist/components/ui/date/DateTimePicker.d.ts +12 -0
  41. package/dist/components/ui/date/TimePicker.d.ts +12 -0
  42. package/dist/components/ui/date/TimeView.d.ts +7 -0
  43. package/dist/components/ui/date/index.d.ts +4 -0
  44. package/dist/components/ui/field/BaseField.d.ts +16 -0
  45. package/dist/components/ui/field/ErrorTooltip.d.ts +6 -0
  46. package/dist/components/ui/field/LabelWrapper.d.ts +14 -0
  47. package/dist/components/ui/field/types.d.ts +15 -0
  48. package/dist/components/ui/select/Autocomplete.d.ts +15 -0
  49. package/dist/components/ui/select/Chip.d.ts +7 -0
  50. package/dist/components/ui/select/types.d.ts +5 -0
  51. package/dist/context/ThemeContext.d.ts +14 -0
  52. package/dist/hooks/useDebounce.d.ts +1 -0
  53. package/dist/hooks/useForm.d.ts +28 -0
  54. package/dist/index.js +11801 -0
  55. package/dist/lib/demo-schemas.d.ts +2 -0
  56. package/dist/lib/index.d.ts +17 -0
  57. package/dist/lib/schema-helpers.d.ts +29 -0
  58. package/dist/types/schema.d.ts +136 -0
  59. package/dist/utils/date-utils.d.ts +10 -0
  60. package/dist/utils/dependencies.d.ts +2 -0
  61. package/dist/utils/validation.d.ts +7 -0
  62. package/dist/utils/yup-validation.d.ts +7 -0
  63. package/dist/vite.svg +1 -0
  64. package/package.json +130 -0
@@ -0,0 +1,1077 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander";
3
+ import prompts from "prompts";
4
+ import chalk from "chalk";
5
+ import { execa } from "execa";
6
+ import fs from "fs-extra";
7
+ import path from "path";
8
+ import ora from "ora";
9
+ import { fileURLToPath } from "url";
10
+ import { registry, getComponent } from "./registry.js";
11
+ // Tailwind v4 CSS Template
12
+ const TAILWIND_V4_CSS = `@import "tailwindcss";
13
+ @source "../node_modules/r-form-engine/dist";
14
+
15
+ @variant dark (&:where(.dark, .dark *));
16
+
17
+ /* =========================================
18
+ BASE PALETTE (STATIC)
19
+ ========================================= */
20
+ :root {
21
+ --color-primary:#003c71;
22
+ --color-secondary:#ff681d;
23
+ --brand-secondary-orange: #ff681d; /* New explicit brand variable */
24
+ --zinc-0: #ffffff;
25
+ --zinc-50: oklch(0.985 0.01 240);
26
+ --zinc-100: oklch(0.965 0.01 240);
27
+ --zinc-200: oklch(0.920 0.02 240);
28
+ --zinc-300: oklch(0.870 0.03 240);
29
+ --zinc-400: oklch(0.700 0.04 240);
30
+ --zinc-500: oklch(0.550 0.05 240);
31
+ --zinc-600: oklch(0.440 0.05 240);
32
+ --zinc-700: oklch(0.350 0.05 240);
33
+ --zinc-800: oklch(0.270 0.05 240);
34
+ --zinc-900: oklch(0.200 0.04 240);
35
+ --zinc-950: oklch(0.130 0.03 240);
36
+
37
+ --primary-400: oklch(0.65 0.18 260);
38
+ --primary-500: oklch(0.55 0.22 260);
39
+ --primary-600: oklch(0.45 0.22 260);
40
+
41
+ --font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
42
+ }
43
+
44
+ /* =========================================
45
+ LIGHT THEME (VISIBLY LIGHT)
46
+ ========================================= */
47
+ :root {
48
+
49
+ --color-background: var(--zinc-50);
50
+ --color-foreground: var(--zinc-900);
51
+
52
+ --color-card: var(--zinc-0);
53
+ --color-card-foreground: var(--zinc-900);
54
+
55
+ --color-popover: var(--zinc-0);
56
+ --color-popover-foreground: var(--zinc-900);
57
+
58
+ --color-primary: var(--primary-500);
59
+ --color-primary-foreground: #ffffff;
60
+
61
+ --color-secondary: var(--zinc-100);
62
+ --color-secondary-foreground: var(--zinc-800);
63
+
64
+ --color-muted: var(--zinc-100);
65
+ --color-muted-foreground: var(--zinc-500);
66
+
67
+ --color-accent: var(--zinc-100);
68
+ --color-accent-foreground: var(--zinc-800);
69
+
70
+ --color-destructive: oklch(0.63 0.26 29);
71
+ --color-destructive-foreground: #ffffff;
72
+
73
+ --color-border: var(--zinc-300);
74
+ --color-input: var(--zinc-0);
75
+ --color-ring: var(--primary-500);
76
+
77
+ --color-control-checked: var(--color-primary);
78
+
79
+ --color-tooltip-bg: var(--zinc-900);
80
+ --color-tooltip-text: #ffffff;
81
+
82
+ --color-surface-50: var(--zinc-50);
83
+ --color-surface-100: var(--zinc-100);
84
+ --color-surface-200: var(--zinc-200);
85
+ --color-surface-300: var(--zinc-300);
86
+ --color-surface-400: var(--zinc-400);
87
+ }
88
+
89
+
90
+
91
+ /* =========================================
92
+ MANUAL DARK MODE
93
+ ========================================= */
94
+ html.dark, .dark {
95
+ color-scheme: dark;
96
+
97
+ --color-background: var(--zinc-950);
98
+ --color-foreground: var(--zinc-50);
99
+
100
+ --color-card: var(--zinc-900);
101
+ --color-card-foreground: var(--zinc-50);
102
+
103
+ --color-popover: var(--zinc-900);
104
+ --color-popover-foreground: var(--zinc-50);
105
+
106
+ --color-primary: var(--primary-400);
107
+ --color-primary-foreground: #ffffff;
108
+
109
+ --color-secondary: var(--zinc-800);
110
+ --color-secondary-foreground: var(--zinc-50);
111
+
112
+ --color-muted: var(--zinc-800);
113
+ --color-muted-foreground: var(--zinc-400);
114
+
115
+ --color-accent: var(--zinc-800);
116
+ --color-accent-foreground: var(--zinc-50);
117
+
118
+ --color-destructive: oklch(0.4 0.2 29);
119
+
120
+ --color-border: var(--zinc-800);
121
+ --color-input: var(--zinc-900);
122
+ --color-ring: var(--primary-400);
123
+
124
+ --color-control-checked: var(--brand-secondary-orange);
125
+
126
+ --color-tooltip-bg: var(--zinc-800);
127
+ --color-tooltip-text: var(--zinc-50);
128
+
129
+ --color-surface-50: var(--zinc-950);
130
+ --color-surface-100: var(--zinc-900);
131
+ --color-surface-200: var(--zinc-800);
132
+ --color-surface-300: var(--zinc-700);
133
+ --color-surface-400: var(--zinc-600);
134
+ }
135
+
136
+ /* =========================================
137
+ TAILWIND v4 THEME MAP
138
+ ========================================= */
139
+ @theme {
140
+ --color-background: var(--color-background);
141
+ --color-foreground: var(--color-foreground);
142
+ --color-card: var(--color-card);
143
+ --color-card-foreground: var(--color-card-foreground);
144
+ --color-popover: var(--color-popover);
145
+ --color-popover-foreground: var(--color-popover-foreground);
146
+ --color-primary: var(--color-primary);
147
+ --color-primary-foreground: var(--color-primary-foreground);
148
+ --color-secondary: var(--color-secondary);
149
+ --color-secondary-foreground: var(--color-secondary-foreground);
150
+ --color-muted: var(--color-muted);
151
+ --color-muted-foreground: var(--color-muted-foreground);
152
+ --color-accent: var(--color-accent);
153
+ --color-accent-foreground: var(--color-accent-foreground);
154
+ --color-destructive: var(--color-destructive);
155
+ --color-destructive-foreground: var(--color-destructive-foreground);
156
+ --color-border: var(--color-border);
157
+ --color-input: var(--color-input);
158
+ --color-ring: var(--color-ring);
159
+ --color-control-checked: var(--color-control-checked);
160
+ --color-tooltip-bg: var(--color-tooltip-bg);
161
+ --color-tooltip-text: var(--color-tooltip-text);
162
+ --color-surface-50: var(--color-surface-50);
163
+ --color-surface-100: var(--color-surface-100);
164
+ --color-surface-200: var(--color-surface-200);
165
+ --color-surface-300: var(--color-surface-300);
166
+ --color-surface-400: var(--color-surface-400);
167
+ --font-sans: var(--font-sans);
168
+ }
169
+
170
+ /* =========================================
171
+ GLOBALS
172
+ ========================================= */
173
+ body {
174
+ background-color: var(--color-background);
175
+ color: var(--color-foreground);
176
+ font-family: var(--font-sans);
177
+ -webkit-font-smoothing: antialiased;
178
+ -moz-osx-font-smoothing: grayscale;
179
+ }
180
+
181
+ @layer base {
182
+ input:focus,
183
+ textarea:focus,
184
+ select:focus,
185
+ .ring-2,
186
+ .focus\\:ring-2:focus,
187
+ .focus-within\\:ring-2:focus-within {
188
+ outline: none !important;
189
+ box-shadow: none !important;
190
+ }
191
+ }
192
+ `;
193
+ // Get package version
194
+ const __filename = fileURLToPath(import.meta.url);
195
+ const __dirname = path.dirname(__filename);
196
+ // Helper to read package.json
197
+ const getPackageParam = (param) => {
198
+ try {
199
+ const pkgPath = path.resolve(__dirname, "../../package.json");
200
+ if (fs.existsSync(pkgPath)) {
201
+ const pkg = fs.readJSONSync(pkgPath);
202
+ return pkg[param];
203
+ }
204
+ return "1.0.0";
205
+ }
206
+ catch (e) {
207
+ return "1.0.0";
208
+ }
209
+ };
210
+ // --- LANDING PAGE TEMPLATES ---
211
+ const generateLandingPageContent = (config, version) => {
212
+ const { framework, validationLib, formState } = config;
213
+ const isNext = framework === "next";
214
+ const componentToUse = formState === "formik" ? "FormikSchemaForm" : "SchemaForm";
215
+ // Note: We use Tailwind v4 classes assuming it's configured.
216
+ // We use standard React imports since the library handles the rest.
217
+ return `${isNext ? '"use client";\n' : ""}import { ${componentToUse}, ThemeProvider } from 'r-form-engine';
218
+ import type { FormSchema } from 'r-form-engine';
219
+ ${isNext ? "import './globals.css';" : "import 'r-form-engine/styles';"}
220
+
221
+ const schema: FormSchema = {
222
+ title: "Welcome to FormEngine",
223
+ description: "Your project is pre-configured with ${validationLib} and ${formState}.",
224
+ fields: [
225
+ {
226
+ id: "name",
227
+ name: "name",
228
+ label: "What's your name?",
229
+ type: "text",
230
+ placeholder: "Enter your name",
231
+ validation: [{ type: "required", message: "Name is required" }],
232
+ grid: { colSpan: 12 }
233
+ },
234
+ {
235
+ id: "framework",
236
+ name: "framework",
237
+ label: "Framework",
238
+ type: "text",
239
+ defaultValue: "${framework}",
240
+ readOnly: true,
241
+ grid: { colSpan: 4 }
242
+ },
243
+ {
244
+ id: "validation",
245
+ name: "validation",
246
+ label: "Validation",
247
+ type: "text",
248
+ defaultValue: "${validationLib}",
249
+ readOnly: true,
250
+ grid: { colSpan: 4 }
251
+ },
252
+ {
253
+ id: "formState",
254
+ name: "formState",
255
+ label: "State Manager",
256
+ type: "text",
257
+ defaultValue: "${formState}",
258
+ readOnly: true,
259
+ grid: { colSpan: 4 }
260
+ }
261
+ ]
262
+ };
263
+
264
+ export default function ${isNext ? "Page" : "App"}() {
265
+ return (
266
+ <ThemeProvider defaultTheme="system">
267
+ <div className="min-h-screen bg-background text-foreground p-8 flex flex-col items-center border-t-4 border-primary">
268
+ <header className="mb-16 text-center max-w-3xl">
269
+ <div className="inline-block px-4 py-1.5 mb-6 text-sm font-bold bg-primary/10 text-primary rounded-full border border-primary/20 uppercase tracking-widest">
270
+ v${version} Successfully Scaffolded
271
+ </div>
272
+ <h1 className="text-5xl md:text-7xl font-black tracking-tighter mb-6">
273
+ Build Forms <span className="text-primary italic">Faster.</span>
274
+ </h1>
275
+ <p className="text-muted-foreground text-xl leading-relaxed mx-auto max-w-xl">
276
+ Your <strong>${framework}</strong> project is ready with <strong>${validationLib}</strong> validation and <strong>${formState}</strong> state management.
277
+ </p>
278
+ <div className="mt-10 flex flex-wrap gap-4 justify-center">
279
+ <a
280
+ href="https://adarshatl03.github.io/form-engine/"
281
+ target="_blank"
282
+ className="px-10 py-4 bg-primary text-primary-foreground rounded-2xl font-black hover:scale-105 transition-all shadow-xl shadow-primary/25"
283
+ >
284
+ Documentation
285
+ </a>
286
+ <a
287
+ href="https://github.com/adarshatl03/form-engine"
288
+ target="_blank"
289
+ className="px-10 py-4 bg-card border-2 border-border rounded-2xl font-bold hover:bg-muted transition-colors"
290
+ >
291
+ GitHub
292
+ </a>
293
+ </div>
294
+ </header>
295
+
296
+ <main className="w-full max-w-2xl bg-card rounded-[2.5rem] shadow-2xl overflow-hidden border border-border">
297
+ <div className="p-12">
298
+ <h2 className="text-3xl font-black mb-10 tracking-tight">Quick Start</h2>
299
+ <${componentToUse}
300
+ schema={schema}
301
+ onSubmit={(v) => {
302
+ console.log("Form Values:", v);
303
+ alert("Submission successful! check console.");
304
+ }}
305
+ />
306
+ </div>
307
+ <div className="bg-muted p-8 flex flex-col sm:flex-row justify-between items-center gap-6 border-t border-border">
308
+ <div className="text-center sm:text-left">
309
+ <p className="text-xs uppercase tracking-widest font-black text-muted-foreground mb-1">Project Stack</p>
310
+ <p className="text-sm font-bold opacity-80">${framework} • ${validationLib} • ${formState}</p>
311
+ </div>
312
+ <button
313
+ onClick={() => window.open('https://adarshatl03.github.io/form-engine/playground', '_blank')}
314
+ className="px-6 py-2 bg-foreground text-background rounded-full font-bold text-sm hover:opacity-90 transition-opacity"
315
+ >
316
+ Open Form Builder →
317
+ </button>
318
+ </div>
319
+ </main>
320
+
321
+ <footer className="mt-20 text-muted-foreground font-medium text-sm">
322
+ Built with r-form-engine • The modern form stack
323
+ </footer>
324
+ </div>
325
+ </ThemeProvider>
326
+ );
327
+ }
328
+ `;
329
+ };
330
+ // Utility: Resolve package root
331
+ const getPackageRoot = () => {
332
+ // __dirname is dist/bin. Package root is ../..
333
+ return path.resolve(__dirname, "../..");
334
+ };
335
+ // Utility: Read file from package
336
+ const readPackageFile = (relativePath) => {
337
+ const pkgRoot = getPackageRoot();
338
+ const fullPath = path.join(pkgRoot, relativePath);
339
+ if (fs.existsSync(fullPath)) {
340
+ return fs.readFileSync(fullPath, "utf-8");
341
+ }
342
+ throw new Error(`File not found in package: ${relativePath}`);
343
+ };
344
+ // Utility: Check if config exists
345
+ const CONFIG_FILE = "r-form.json";
346
+ const hasConfig = () => fs.existsSync(path.resolve(process.cwd(), CONFIG_FILE));
347
+ const getConfig = () => fs.readJSONSync(path.resolve(process.cwd(), CONFIG_FILE));
348
+ const program = new Command();
349
+ program
350
+ .name("r-form-engine")
351
+ .description("CLI for r-form-engine")
352
+ .version(getPackageParam("version"));
353
+ program
354
+ .command("add")
355
+ .description("Add a component to your project")
356
+ .argument("[component]", "The component to add")
357
+ .action(async (componentName) => {
358
+ if (!hasConfig()) {
359
+ console.log(chalk.yellow("Configuration file not found. Please run 'init' first or proceed to initialize now."));
360
+ const confirm = await prompts({
361
+ type: "confirm",
362
+ name: "init",
363
+ message: "Run init now?",
364
+ initial: true,
365
+ });
366
+ if (confirm.init) {
367
+ console.log(chalk.blue("Please run 'npx r-form-engine init' first."));
368
+ return;
369
+ }
370
+ return;
371
+ }
372
+ const config = getConfig();
373
+ const componentsDir = config.paths?.components || "src/components/ui";
374
+ const coreDir = config.paths?.core || "src/components/r-form";
375
+ let componentsToAdd = componentName ? [componentName] : [];
376
+ if (!componentName) {
377
+ const res = await prompts({
378
+ type: "multiselect",
379
+ name: "components",
380
+ message: "Select components to add:",
381
+ choices: Object.keys(registry)
382
+ .filter((k) => k !== "theme-core")
383
+ .map((k) => ({ title: k, value: k })),
384
+ });
385
+ componentsToAdd = res.components;
386
+ }
387
+ if (componentsToAdd.length === 0)
388
+ return;
389
+ const spinner = ora("Adding components...").start();
390
+ // Resolve dependencies including internal ones recursively
391
+ const resolvedList = new Set();
392
+ const resolveDeps = (name) => {
393
+ if (resolvedList.has(name))
394
+ return;
395
+ const comp = getComponent(name);
396
+ if (!comp) {
397
+ spinner.fail(`Component '${name}' not found in registry.`);
398
+ return;
399
+ }
400
+ resolvedList.add(name);
401
+ comp.internalDependencies?.forEach((dep) => resolveDeps(dep));
402
+ };
403
+ try {
404
+ componentsToAdd.forEach((c) => resolveDeps(c));
405
+ // Check for existing files and prompt
406
+ const existingFiles = [];
407
+ for (const name of resolvedList) {
408
+ const comp = getComponent(name);
409
+ if (!comp)
410
+ continue;
411
+ const targetBase = comp.type === "core" ? coreDir : componentsDir;
412
+ for (const fileRel of comp.files) {
413
+ const fileName = path.basename(fileRel);
414
+ let targetPath;
415
+ if (comp.type === "core") {
416
+ targetPath = path.join(targetBase, fileName);
417
+ }
418
+ else {
419
+ const cleanRel = fileRel
420
+ .replace(/^src\/components\/ui\//, "")
421
+ .replace(/^src\/components\/theme\//, "");
422
+ targetPath = path.join(targetBase, cleanRel);
423
+ }
424
+ if (fs.existsSync(targetPath)) {
425
+ existingFiles.push(targetPath);
426
+ }
427
+ }
428
+ }
429
+ if (existingFiles.length > 0) {
430
+ spinner.stop();
431
+ const confirm = await prompts({
432
+ type: "confirm",
433
+ name: "overwrite",
434
+ message: `Some files already exist. Overwrite?`,
435
+ initial: false,
436
+ });
437
+ if (!confirm.overwrite) {
438
+ console.log(chalk.yellow("Installation cancelled."));
439
+ return;
440
+ }
441
+ spinner.start();
442
+ }
443
+ // Process list
444
+ for (const name of resolvedList) {
445
+ spinner.text = `Installing ${name}...`;
446
+ const comp = getComponent(name);
447
+ if (!comp)
448
+ continue;
449
+ // Determine target directory
450
+ const targetBase = comp.type === "core" ? coreDir : componentsDir;
451
+ // Copy Files
452
+ for (const fileRel of comp.files) {
453
+ const content = readPackageFile(fileRel);
454
+ const fileName = path.basename(fileRel);
455
+ // Simplified target path logic
456
+ let targetPath;
457
+ if (comp.type === "core") {
458
+ targetPath = path.join(targetBase, fileName);
459
+ }
460
+ else {
461
+ const cleanRel = fileRel
462
+ .replace(/^src\/components\/ui\//, "")
463
+ .replace(/^src\/components\/theme\//, "");
464
+ targetPath = path.join(targetBase, cleanRel);
465
+ }
466
+ fs.ensureDirSync(path.dirname(targetPath));
467
+ // FIX IMPORTS
468
+ let processedContent = content;
469
+ if (comp.type !== "core") {
470
+ const relativeToCore = path
471
+ .relative(path.dirname(targetPath), coreDir)
472
+ .replace(/\\/g, "/");
473
+ const coreImportPath = relativeToCore.startsWith(".")
474
+ ? relativeToCore
475
+ : `./${relativeToCore}`;
476
+ processedContent = processedContent.replace(/from ".*\/theme\/ThemeContext"/g, `from "${coreImportPath}/ThemeContext"`);
477
+ processedContent = processedContent.replace(/from ".*\/theme\/types"/g, `from "${coreImportPath}/types"`);
478
+ processedContent = processedContent.replace(/from ".*\/theme\/defaultTheme"/g, `from "${coreImportPath}/defaultTheme"`);
479
+ }
480
+ fs.writeFileSync(targetPath, processedContent);
481
+ }
482
+ // Install Deps
483
+ if (comp.dependencies && comp.dependencies.length > 0) {
484
+ spinner.text = `Installing dependencies for ${name}...`;
485
+ await execa("npm", ["install", ...comp.dependencies]);
486
+ }
487
+ }
488
+ // Save to config
489
+ const installed = new Set(config.components || []);
490
+ componentsToAdd.forEach((c) => installed.add(c));
491
+ config.components = Array.from(installed);
492
+ fs.writeJSONSync(path.resolve(process.cwd(), CONFIG_FILE), config, { spaces: 2 });
493
+ spinner.succeed("Components added successfully!");
494
+ }
495
+ catch (e) {
496
+ spinner.fail("Failed to add components.");
497
+ console.error(e);
498
+ }
499
+ });
500
+ program
501
+ .command("update")
502
+ .description("Update installed components")
503
+ .argument("[components...]", "Specific components to update")
504
+ .action(async (components) => {
505
+ if (!hasConfig()) {
506
+ console.log(chalk.red("No configuration found. Run init first."));
507
+ return;
508
+ }
509
+ const config = getConfig();
510
+ const installedComponents = config.components || [];
511
+ if (installedComponents.length === 0) {
512
+ console.log(chalk.yellow("No components installed to update."));
513
+ return;
514
+ }
515
+ const toUpdate = components.length > 0 ? components : installedComponents;
516
+ console.log(chalk.blue(`Updating ${toUpdate.length} components...\n`));
517
+ // We can just call the 'add' logic internally by triggering the action
518
+ // But to avoid recursive CLI calls, we'll just re-run the core logic
519
+ // or better yet, just tell them we are re-installing
520
+ for (const name of toUpdate) {
521
+ if (!installedComponents.includes(name)) {
522
+ console.log(chalk.red(`Component '${name}' is not installed.`));
523
+ continue;
524
+ }
525
+ // Re-run add logic (simplified for update)
526
+ // Actually, let's just use the add command logic
527
+ // to ensure all internal deps are also updated
528
+ }
529
+ // For simplicity, we'll just re-run 'add' for everything in a single go
530
+ const spinner = ora("Updating components...").start();
531
+ const componentsDir = config.paths?.components || "src/components/ui";
532
+ const coreDir = config.paths?.core || "src/components/r-form";
533
+ const resolvedList = new Set();
534
+ const resolveDeps = (name) => {
535
+ if (resolvedList.has(name))
536
+ return;
537
+ const comp = getComponent(name);
538
+ if (!comp)
539
+ return;
540
+ resolvedList.add(name);
541
+ comp.internalDependencies?.forEach((dep) => resolveDeps(dep));
542
+ };
543
+ try {
544
+ toUpdate.forEach((c) => resolveDeps(c));
545
+ for (const name of resolvedList) {
546
+ spinner.text = `Updating ${name}...`;
547
+ const comp = getComponent(name);
548
+ if (!comp)
549
+ continue;
550
+ const targetBase = comp.type === "core" ? coreDir : componentsDir;
551
+ for (const fileRel of comp.files) {
552
+ const content = readPackageFile(fileRel);
553
+ const fileName = path.basename(fileRel);
554
+ let targetPath;
555
+ if (comp.type === "core") {
556
+ targetPath = path.join(targetBase, fileName);
557
+ }
558
+ else {
559
+ const cleanRel = fileRel
560
+ .replace(/^src\/components\/ui\//, "")
561
+ .replace(/^src\/components\/theme\//, "");
562
+ targetPath = path.join(targetBase, cleanRel);
563
+ }
564
+ fs.ensureDirSync(path.dirname(targetPath));
565
+ let processedContent = content;
566
+ if (comp.type !== "core") {
567
+ const relativeToCore = path
568
+ .relative(path.dirname(targetPath), coreDir)
569
+ .replace(/\\/g, "/");
570
+ const coreImportPath = relativeToCore.startsWith(".")
571
+ ? relativeToCore
572
+ : `./${relativeToCore}`;
573
+ processedContent = processedContent.replace(/from ".*\/theme\/ThemeContext"/g, `from "${coreImportPath}/ThemeContext"`);
574
+ processedContent = processedContent.replace(/from ".*\/theme\/types"/g, `from "${coreImportPath}/types"`);
575
+ processedContent = processedContent.replace(/from ".*\/theme\/defaultTheme"/g, `from "${coreImportPath}/defaultTheme"`);
576
+ }
577
+ fs.writeFileSync(targetPath, processedContent);
578
+ }
579
+ if (comp.dependencies && comp.dependencies.length > 0) {
580
+ await execa("npm", ["install", ...comp.dependencies]);
581
+ }
582
+ }
583
+ spinner.succeed("Components updated successfully!");
584
+ }
585
+ catch (e) {
586
+ spinner.fail("Failed to update components.");
587
+ console.error(e);
588
+ }
589
+ });
590
+ program
591
+ .command("init")
592
+ .description("Initialize r-form-engine in your project")
593
+ .action(async () => {
594
+ console.log(chalk.blue.bold("\n🚀 Initializing r-form-engine...\n"));
595
+ const mode = await prompts({
596
+ type: "select",
597
+ name: "mode",
598
+ message: "How would you like to use r-form-engine?",
599
+ choices: [
600
+ {
601
+ title: "Library (npm install)",
602
+ value: "library",
603
+ description: "Standard usage, import components from package.",
604
+ },
605
+ {
606
+ title: "Copy Code (shadcn-like)",
607
+ value: "copy",
608
+ description: "Own the code, copy components to your project.",
609
+ },
610
+ ],
611
+ initial: 0,
612
+ });
613
+ if (mode.mode === "library") {
614
+ await runLibraryInit();
615
+ }
616
+ else {
617
+ await runCopyInit();
618
+ }
619
+ });
620
+ program
621
+ .command("create")
622
+ .description("Create a new r-form-engine project")
623
+ .argument("[name]", "Project name")
624
+ .action(async (projectName) => {
625
+ let targetDir = projectName;
626
+ if (!targetDir) {
627
+ const res = await prompts({
628
+ type: "text",
629
+ name: "name",
630
+ message: "What is your project name?",
631
+ initial: "my-form-app",
632
+ });
633
+ targetDir = res.name;
634
+ }
635
+ if (!targetDir)
636
+ return;
637
+ const frameworkConfig = await prompts([
638
+ {
639
+ type: "select",
640
+ name: "framework",
641
+ message: "Select framework",
642
+ choices: [
643
+ { title: "Vite (React)", value: "vite" },
644
+ { title: "Next.js", value: "next" },
645
+ ],
646
+ initial: 0,
647
+ },
648
+ {
649
+ type: "select",
650
+ name: "validationLib",
651
+ message: "Select validation library",
652
+ choices: [
653
+ { title: "Zod (Recommended)", value: "zod" },
654
+ { title: "Yup", value: "yup" },
655
+ ],
656
+ initial: 0,
657
+ },
658
+ {
659
+ type: "select",
660
+ name: "formState",
661
+ message: "Select form state manager",
662
+ choices: [
663
+ { title: "Standard (useForm Hook)", value: "standard" },
664
+ { title: "Formik", value: "formik" },
665
+ { title: "React Hook Form", value: "rhf" },
666
+ ],
667
+ initial: 0,
668
+ },
669
+ {
670
+ type: "confirm",
671
+ name: "tailwind",
672
+ message: "Add Tailwind CSS?",
673
+ initial: true,
674
+ },
675
+ {
676
+ type: (_, values) => (values.tailwind ? "select" : null),
677
+ name: "tailwindVersion",
678
+ message: "Which version of Tailwind CSS?",
679
+ choices: [
680
+ { title: "v4 (CSS-based configuration)", value: "v4" },
681
+ { title: "v3 (Config-based)", value: "v3" },
682
+ ],
683
+ initial: 0,
684
+ },
685
+ ]);
686
+ const projectPath = path.resolve(process.cwd(), targetDir);
687
+ console.log(chalk.blue(`\nCreating project in ${projectPath}...`));
688
+ try {
689
+ if (frameworkConfig.framework === "vite") {
690
+ await execa("npm", ["create", "vite@latest", targetDir, "--", "--template", "react-ts"], {
691
+ stdio: "inherit",
692
+ });
693
+ }
694
+ else {
695
+ const args = ["create-next-app@latest", targetDir, "--typescript", "--eslint"];
696
+ if (frameworkConfig.tailwind && frameworkConfig.tailwindVersion === "v3") {
697
+ args.push("--tailwind");
698
+ }
699
+ else {
700
+ args.push("--no-tailwind");
701
+ }
702
+ args.push("--no-src-dir", "--app", "--import-alias", "@/*");
703
+ await execa("npx", args, { stdio: "inherit" });
704
+ }
705
+ console.log(chalk.green("\nProject scaffolded. Initializing r-form-engine..."));
706
+ process.chdir(projectPath);
707
+ const spinner = ora("Installing r-form-engine dependencies...").start();
708
+ const deps = ["r-form-engine"];
709
+ if (frameworkConfig.validationLib === "zod")
710
+ deps.push("zod");
711
+ if (frameworkConfig.validationLib === "yup")
712
+ deps.push("yup");
713
+ if (frameworkConfig.formState === "rhf")
714
+ deps.push("react-hook-form", "@hookform/resolvers");
715
+ if (frameworkConfig.formState === "formik")
716
+ deps.push("formik");
717
+ if (frameworkConfig.tailwind) {
718
+ if (frameworkConfig.tailwindVersion === "v4") {
719
+ deps.push("tailwindcss");
720
+ if (frameworkConfig.framework === "vite") {
721
+ deps.push("@tailwindcss/vite");
722
+ }
723
+ else {
724
+ deps.push("@tailwindcss/postcss");
725
+ }
726
+ }
727
+ else {
728
+ deps.push("tailwindcss@3", "postcss", "autoprefixer", "clsx", "tailwind-merge");
729
+ }
730
+ }
731
+ await execa("npm", ["install", ...deps]);
732
+ if (frameworkConfig.tailwind) {
733
+ if (frameworkConfig.tailwindVersion === "v4" && frameworkConfig.framework === "vite") {
734
+ spinner.text = "Configuring Tailwind v4...";
735
+ const cssPath = "src/index.css";
736
+ fs.ensureDirSync(path.dirname(cssPath));
737
+ fs.writeFileSync(cssPath, TAILWIND_V4_CSS);
738
+ const viteConfigPath = "vite.config.ts";
739
+ if (fs.existsSync(viteConfigPath)) {
740
+ let viteConfig = fs.readFileSync(viteConfigPath, "utf-8");
741
+ if (!viteConfig.includes("@tailwindcss/vite")) {
742
+ viteConfig = `import tailwindcss from '@tailwindcss/vite';\n` + viteConfig;
743
+ viteConfig = viteConfig.replace("plugins: [", "plugins: [tailwindcss(),");
744
+ fs.writeFileSync(viteConfigPath, viteConfig);
745
+ }
746
+ }
747
+ }
748
+ else if (frameworkConfig.tailwindVersion === "v4" &&
749
+ frameworkConfig.framework === "next") {
750
+ spinner.text = "Configuring Tailwind v4...";
751
+ const cssPath = "app/globals.css";
752
+ fs.ensureDirSync(path.dirname(cssPath));
753
+ fs.writeFileSync(cssPath, TAILWIND_V4_CSS);
754
+ const postcssContent = `const config = {
755
+ plugins: {
756
+ '@tailwindcss/postcss': {},
757
+ },
758
+ };
759
+ export default config;
760
+ `;
761
+ fs.writeFileSync("postcss.config.mjs", postcssContent);
762
+ }
763
+ else {
764
+ await configureTailwindInternal(frameworkConfig.tailwindVersion);
765
+ }
766
+ }
767
+ // 4. Generate Landing Page
768
+ spinner.text = "Generating landing page...";
769
+ const landingPageContent = generateLandingPageContent(frameworkConfig, getPackageParam("version"));
770
+ if (frameworkConfig.framework === "vite") {
771
+ fs.writeFileSync("src/App.tsx", landingPageContent);
772
+ // Remove default boilerplate if exists
773
+ const defaultAppCss = "src/App.css";
774
+ if (fs.existsSync(defaultAppCss)) {
775
+ fs.writeFileSync(defaultAppCss, "/* Custom styles can go here */");
776
+ }
777
+ }
778
+ else {
779
+ const pagePath = "app/page.tsx";
780
+ fs.ensureDirSync(path.dirname(pagePath));
781
+ fs.writeFileSync(pagePath, landingPageContent);
782
+ }
783
+ spinner.succeed(chalk.green("Setup complete!"));
784
+ console.log(`\nTo get started:\n cd ${targetDir}\n npm run dev`);
785
+ }
786
+ catch (e) {
787
+ console.error(chalk.red("Error creating project:"), e);
788
+ }
789
+ });
790
+ program.parse();
791
+ // --- HELPER FUNCTIONS ---
792
+ async function runLibraryInit() {
793
+ const config = await prompts([
794
+ {
795
+ type: "select",
796
+ name: "framework",
797
+ message: "Which framework are you using?",
798
+ choices: [
799
+ { title: "Vite (React)", value: "vite" },
800
+ { title: "Next.js", value: "next" },
801
+ { title: "Other", value: "other" },
802
+ ],
803
+ initial: 0,
804
+ },
805
+ {
806
+ type: "select",
807
+ name: "language",
808
+ message: "Which language are you using?",
809
+ choices: [
810
+ { title: "TypeScript", value: "typescript" },
811
+ { title: "JavaScript", value: "javascript" },
812
+ ],
813
+ initial: 0,
814
+ },
815
+ {
816
+ type: "text",
817
+ name: "packageName",
818
+ message: "What is the package name you are using?",
819
+ initial: "r-form-engine",
820
+ },
821
+ {
822
+ type: "confirm",
823
+ name: "tailwind",
824
+ message: "Would you like to use Tailwind CSS for styling?",
825
+ initial: true,
826
+ },
827
+ {
828
+ type: (prev) => (prev ? "select" : null),
829
+ name: "tailwindVersion",
830
+ message: "Which version of Tailwind CSS?",
831
+ choices: [
832
+ { title: "v4 (CSS-based configuration)", value: "v4" },
833
+ { title: "v3 (Config-based)", value: "v3" },
834
+ ],
835
+ initial: 0,
836
+ },
837
+ {
838
+ type: (_, values) => (values.tailwind ? "confirm" : null),
839
+ name: "configureTailwind",
840
+ message: "Do you want us to install and configure Tailwind CSS automatically?",
841
+ initial: true,
842
+ },
843
+ {
844
+ type: "select",
845
+ name: "schemaResolver",
846
+ message: "Which schema validation library would you like to use?",
847
+ choices: [
848
+ { title: "Zod (Recommended)", value: "zod" },
849
+ { title: "Yup", value: "yup" },
850
+ { title: "None", value: "none" },
851
+ ],
852
+ initial: 0,
853
+ },
854
+ {
855
+ type: "select",
856
+ name: "formState",
857
+ message: "Which form state manager would you like to use?",
858
+ choices: [
859
+ { title: "Standard (r-form-engine)", value: "standard" },
860
+ { title: "Formik", value: "formik" },
861
+ { title: "React Hook Form", value: "rhf" },
862
+ ],
863
+ initial: 0,
864
+ },
865
+ {
866
+ type: "confirm",
867
+ name: "installDeps",
868
+ message: "Would you like to install dependencies now?",
869
+ initial: true,
870
+ },
871
+ ]);
872
+ if (!config.packageName) {
873
+ console.log("Cancelled");
874
+ return;
875
+ }
876
+ const spinner = ora("Setting up library...").start();
877
+ try {
878
+ // Write config
879
+ const configFile = {
880
+ mode: "library",
881
+ framework: config.framework,
882
+ language: config.language,
883
+ packageName: config.packageName,
884
+ tailwind: config.tailwind,
885
+ validation: config.schemaResolver,
886
+ formState: config.formState,
887
+ };
888
+ fs.writeJSONSync(path.resolve(process.cwd(), CONFIG_FILE), configFile, { spaces: 2 });
889
+ if (config.installDeps) {
890
+ const deps = [config.packageName];
891
+ if (config.schemaResolver === "zod")
892
+ deps.push("zod");
893
+ if (config.schemaResolver === "yup")
894
+ deps.push("yup");
895
+ if (config.formState === "formik")
896
+ deps.push("formik");
897
+ if (config.formState === "rhf")
898
+ deps.push("react-hook-form", "@hookform/resolvers");
899
+ if (config.tailwind && config.configureTailwind) {
900
+ if (config.tailwindVersion === "v4") {
901
+ deps.push("tailwindcss", "@tailwindcss/vite");
902
+ }
903
+ else {
904
+ deps.push("tailwindcss@3", "postcss", "autoprefixer", "clsx", "tailwind-merge");
905
+ }
906
+ }
907
+ await execa("npm", ["install", ...deps]);
908
+ }
909
+ if (config.tailwind && config.configureTailwind) {
910
+ await configureTailwindInternal(config.tailwindVersion);
911
+ }
912
+ spinner.succeed("Library initialized!");
913
+ }
914
+ catch (e) {
915
+ spinner.fail("Failed");
916
+ console.error(e);
917
+ }
918
+ }
919
+ async function runCopyInit() {
920
+ const config = await prompts([
921
+ {
922
+ type: "select",
923
+ name: "framework",
924
+ message: "Which framework are you using?",
925
+ choices: [
926
+ { title: "Vite (React)", value: "vite" },
927
+ { title: "Next.js", value: "next" },
928
+ { title: "Other", value: "other" },
929
+ ],
930
+ initial: 0,
931
+ },
932
+ {
933
+ type: "select",
934
+ name: "language",
935
+ message: "Which language are you using?",
936
+ choices: [
937
+ { title: "TypeScript", value: "typescript" },
938
+ { title: "JavaScript", value: "javascript" },
939
+ ],
940
+ initial: 0,
941
+ },
942
+ {
943
+ type: "text",
944
+ name: "packageName",
945
+ message: "What is the package name you are using?",
946
+ initial: "r-form-engine",
947
+ },
948
+ {
949
+ type: "text",
950
+ name: "componentsDir",
951
+ message: "Where should we place components?",
952
+ initial: (_, values) => values.framework === "next" ? "app/components/ui" : "src/components/ui",
953
+ },
954
+ {
955
+ type: "text",
956
+ name: "coreDir",
957
+ message: "Where should we place core files (theme, utils)?",
958
+ initial: (_, values) => values.framework === "next" ? "app/components/r-form" : "src/components/r-form",
959
+ },
960
+ {
961
+ type: "confirm",
962
+ name: "tailwind",
963
+ message: "Configure Tailwind CSS?",
964
+ initial: true,
965
+ },
966
+ {
967
+ type: (_, values) => (values.tailwind ? "select" : null),
968
+ name: "tailwindVersion",
969
+ message: "Tailwind Version?",
970
+ choices: [
971
+ { title: "v4", value: "v4" },
972
+ { title: "v3", value: "v3" },
973
+ ],
974
+ initial: 0,
975
+ },
976
+ ]);
977
+ const spinner = ora("Initializing project structure...").start();
978
+ try {
979
+ // 1. Write Config
980
+ const configFile = {
981
+ mode: "copy",
982
+ framework: config.framework,
983
+ language: config.language,
984
+ packageName: config.packageName,
985
+ style: "default",
986
+ paths: {
987
+ components: config.componentsDir,
988
+ core: config.coreDir,
989
+ },
990
+ tailwind: config.tailwind,
991
+ components: [],
992
+ };
993
+ fs.writeJSONSync(path.resolve(process.cwd(), CONFIG_FILE), configFile, { spaces: 2 });
994
+ // 2. Install Base Deps
995
+ const deps = [
996
+ "react-hook-form",
997
+ "zod",
998
+ "@hookform/resolvers",
999
+ "clsx",
1000
+ "tailwind-merge",
1001
+ "lucide-react",
1002
+ "date-fns",
1003
+ ];
1004
+ if (config.tailwind) {
1005
+ if (config.tailwindVersion === "v4") {
1006
+ deps.push("tailwindcss", "@tailwindcss/vite");
1007
+ }
1008
+ else {
1009
+ deps.push("tailwindcss@3", "postcss", "autoprefixer");
1010
+ }
1011
+ }
1012
+ await execa("npm", ["install", ...deps]);
1013
+ // 3. Configure Tailwind
1014
+ if (config.tailwind) {
1015
+ await configureTailwindInternal(config.tailwindVersion, true);
1016
+ }
1017
+ spinner.succeed("Configuration saved.");
1018
+ console.log(chalk.green("You are set up! Run this to install the core system:"));
1019
+ console.log(chalk.cyan("npx r-form-engine add theme-core"));
1020
+ }
1021
+ catch (e) {
1022
+ spinner.fail("Initialization failed");
1023
+ console.error(e);
1024
+ }
1025
+ }
1026
+ async function configureTailwindInternal(version, localMode = false) {
1027
+ const config = hasConfig() ? getConfig() : { packageName: "r-form-engine" };
1028
+ // Locate CSS file
1029
+ const cssFiles = ["src/index.css", "src/globals.css", "src/App.css", "styles/globals.css"];
1030
+ const foundCss = cssFiles.find((f) => fs.existsSync(path.resolve(process.cwd(), f)));
1031
+ const cssPath = foundCss
1032
+ ? path.resolve(process.cwd(), foundCss)
1033
+ : path.resolve(process.cwd(), "src/index.css");
1034
+ if (!foundCss) {
1035
+ fs.ensureDirSync(path.dirname(cssPath));
1036
+ fs.writeFileSync(cssPath, "");
1037
+ }
1038
+ if (version === "v4") {
1039
+ // v4
1040
+ const cssContent = fs.readFileSync(cssPath, "utf-8");
1041
+ if (!cssContent.includes('@import "tailwindcss"')) {
1042
+ const sourceLine = localMode ? "" : `@source "../node_modules/${config.packageName}/dist";\n`;
1043
+ const newContent = `@import "tailwindcss";\n${sourceLine}\n${cssContent}`;
1044
+ fs.writeFileSync(cssPath, newContent);
1045
+ }
1046
+ // If empty/new, use template (local mode removes @source)
1047
+ if (!foundCss) {
1048
+ let template = TAILWIND_V4_CSS;
1049
+ if (localMode) {
1050
+ template = template.replace(`@source "../node_modules/r-form-engine/dist";`, "");
1051
+ }
1052
+ else {
1053
+ template = template.replace(`@source "../node_modules/r-form-engine/dist";`, `@source "../node_modules/${config.packageName}/dist";`);
1054
+ }
1055
+ fs.writeFileSync(cssPath, template);
1056
+ }
1057
+ }
1058
+ else {
1059
+ // v3
1060
+ await execa("npx", ["tailwindcss", "init", "-p"]);
1061
+ const tailwindConfigPath = path.resolve(process.cwd(), "tailwind.config.js");
1062
+ if (fs.existsSync(tailwindConfigPath)) {
1063
+ let content = fs.readFileSync(tailwindConfigPath, "utf-8");
1064
+ if (!content.includes(config.packageName) && !localMode) {
1065
+ const search = "content: [";
1066
+ const replace = `content: [\n "./node_modules/${config.packageName}/dist/**/*.{js,ts,jsx,tsx}",`;
1067
+ content = content.replace(search, replace);
1068
+ fs.writeFileSync(tailwindConfigPath, content);
1069
+ }
1070
+ }
1071
+ const cssContent = fs.readFileSync(cssPath, "utf-8");
1072
+ if (!cssContent.includes("@tailwind base")) {
1073
+ const directives = `@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n`;
1074
+ fs.writeFileSync(cssPath, directives + cssContent);
1075
+ }
1076
+ }
1077
+ }