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.
- package/CHANGELOG.md +206 -0
- package/LICENSE +21 -0
- package/README.md +341 -0
- package/dist/bin/index.js +1077 -0
- package/dist/bin/registry.js +105 -0
- package/dist/components/FormikSchemaForm.d.ts +11 -0
- package/dist/components/SchemaForm.d.ts +26 -0
- package/dist/components/ThemeSwitcher.d.ts +1 -0
- package/dist/components/builder/FormBuilder.d.ts +1 -0
- package/dist/components/demo/Documentation.d.ts +1 -0
- package/dist/components/demo/StatesGallery.d.ts +1 -0
- package/dist/components/form/FormRenderer.d.ts +9 -0
- package/dist/components/layout/GridContainer.d.ts +8 -0
- package/dist/components/layout/GridItem.d.ts +8 -0
- package/dist/components/pages/ChangelogPage.d.ts +1 -0
- package/dist/components/pages/DemoPage.d.ts +6 -0
- package/dist/components/pages/GuidePage.d.ts +1 -0
- package/dist/components/pages/Home.d.ts +1 -0
- package/dist/components/pages/ReadmePage.d.ts +1 -0
- package/dist/components/pages/TasksPage.d.ts +1 -0
- package/dist/components/pages/ThemeOverridesPage.d.ts +1 -0
- package/dist/components/playground/ComponentPlayground.d.ts +8 -0
- package/dist/components/playground/PlaygroundPage.d.ts +1 -0
- package/dist/components/playground/components-config.d.ts +10 -0
- package/dist/components/playground/types.d.ts +14 -0
- package/dist/components/theme/ThemeContext.d.ts +16 -0
- package/dist/components/theme/defaultTheme.d.ts +2 -0
- package/dist/components/theme/types.d.ts +38 -0
- package/dist/components/ui/Checkbox.d.ts +6 -0
- package/dist/components/ui/FileInput.d.ts +12 -0
- package/dist/components/ui/Grid.d.ts +18 -0
- package/dist/components/ui/RadioGroup.d.ts +16 -0
- package/dist/components/ui/Select.d.ts +9 -0
- package/dist/components/ui/Switch.d.ts +6 -0
- package/dist/components/ui/TextInput.d.ts +6 -0
- package/dist/components/ui/Textarea.d.ts +10 -0
- package/dist/components/ui/date/Calendar.d.ts +16 -0
- package/dist/components/ui/date/DatePicker.d.ts +12 -0
- package/dist/components/ui/date/DateRangePicker.d.ts +18 -0
- package/dist/components/ui/date/DateTimePicker.d.ts +12 -0
- package/dist/components/ui/date/TimePicker.d.ts +12 -0
- package/dist/components/ui/date/TimeView.d.ts +7 -0
- package/dist/components/ui/date/index.d.ts +4 -0
- package/dist/components/ui/field/BaseField.d.ts +16 -0
- package/dist/components/ui/field/ErrorTooltip.d.ts +6 -0
- package/dist/components/ui/field/LabelWrapper.d.ts +14 -0
- package/dist/components/ui/field/types.d.ts +15 -0
- package/dist/components/ui/select/Autocomplete.d.ts +15 -0
- package/dist/components/ui/select/Chip.d.ts +7 -0
- package/dist/components/ui/select/types.d.ts +5 -0
- package/dist/context/ThemeContext.d.ts +14 -0
- package/dist/hooks/useDebounce.d.ts +1 -0
- package/dist/hooks/useForm.d.ts +28 -0
- package/dist/index.js +11801 -0
- package/dist/lib/demo-schemas.d.ts +2 -0
- package/dist/lib/index.d.ts +17 -0
- package/dist/lib/schema-helpers.d.ts +29 -0
- package/dist/types/schema.d.ts +136 -0
- package/dist/utils/date-utils.d.ts +10 -0
- package/dist/utils/dependencies.d.ts +2 -0
- package/dist/utils/validation.d.ts +7 -0
- package/dist/utils/yup-validation.d.ts +7 -0
- package/dist/vite.svg +1 -0
- 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
|
+
}
|