@crediblemark/build 0.25.4 → 0.25.5

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 (41) hide show
  1. package/README.md +85 -0
  2. package/bin/credbuild.js +524 -0
  3. package/dist/ArrayField-JYCS3HWS.mjs +19 -0
  4. package/dist/{ObjectField-JYRSRCWB.css → ArrayField-PAJW74AC.css} +325 -324
  5. package/dist/{Editor-CVT4RKSU.css → Editor-Y342RWPN.css} +7 -7
  6. package/dist/{Editor-IPG4RO2L.mjs → Editor-YAHPKMZZ.mjs} +8 -8
  7. package/dist/ObjectField-IH6PVWJ4.mjs +19 -0
  8. package/dist/{ArrayField-JRN7ZFM7.css → ObjectField-WACQHQ4N.css} +320 -319
  9. package/dist/{Render-I6PZ5MEA.mjs → Render-5WYR4VZX.mjs} +1 -1
  10. package/dist/{Render-DOE2LXKX.css → Render-HVM2KISU.css} +1 -1
  11. package/dist/{chunk-3Q7JODIK.mjs → chunk-2BGYCLSQ.mjs} +12 -12
  12. package/dist/{chunk-PWRZZUWM.mjs → chunk-3TEHZVFI.mjs} +1 -1
  13. package/dist/{chunk-FQKSRGRK.mjs → chunk-BZP3I754.mjs} +69 -39
  14. package/dist/{chunk-ISXFLTBM.mjs → chunk-H6RPI3T6.mjs} +1 -1
  15. package/dist/{chunk-NYIULQV4.mjs → chunk-HDKBRPN3.mjs} +12 -12
  16. package/dist/{chunk-2RLA42JA.mjs → chunk-HIE6KXP6.mjs} +2 -2
  17. package/dist/{chunk-QMMMJ4RO.mjs → chunk-KRSRYBNG.mjs} +5 -5
  18. package/dist/{chunk-F2M5GHDX.mjs → chunk-N2OOMTBI.mjs} +1 -1
  19. package/dist/{chunk-MZIV4ZKR.mjs → chunk-QX5Z2UCM.mjs} +2 -2
  20. package/dist/{chunk-RBWVOMMW.mjs → chunk-WJLEG7PD.mjs} +4 -4
  21. package/dist/{chunk-NYBTD3FJ.mjs → chunk-XHLOMZCG.mjs} +2 -2
  22. package/dist/{chunk-UNMCNIJ3.mjs → chunk-XLLRNZSM.mjs} +1 -1
  23. package/dist/{chunk-W57TTDB2.mjs → chunk-YNBP3U53.mjs} +1 -1
  24. package/dist/{full-C7RVGAUA.css → full-PLJRDIMB.css} +6 -6
  25. package/dist/{full-A2JX3OI2.mjs → full-ZDZMHNGB.mjs} +5 -5
  26. package/dist/index.css +210 -276
  27. package/dist/index.js +124 -93
  28. package/dist/index.mjs +13 -13
  29. package/dist/{loaded-6L45JULO.mjs → loaded-D6R36LKI.mjs} +2 -2
  30. package/dist/{loaded-4FAPBCAU.css → loaded-FY2TCG5Y.css} +1 -1
  31. package/dist/{loaded-2FUW3WCF.mjs → loaded-RSMV7TSD.mjs} +2 -2
  32. package/dist/{loaded-Y7SRBM7R.mjs → loaded-SZ4RADOC.mjs} +2 -2
  33. package/dist/no-external.css +115 -110
  34. package/dist/no-external.js +124 -93
  35. package/dist/no-external.mjs +13 -13
  36. package/dist/rsc.css +1 -1
  37. package/dist/rsc.js +4 -4
  38. package/dist/rsc.mjs +2 -2
  39. package/package.json +5 -1
  40. package/dist/ArrayField-HI4LZRUN.mjs +0 -19
  41. package/dist/ObjectField-CHMT4KPZ.mjs +0 -19
package/README.md CHANGED
@@ -14,16 +14,101 @@ The open-source visual editor for React. Build complex, high-performance page bu
14
14
  - **Magic Edit Mode**: Integrated pattern for `/edit` URL rewrites.
15
15
  - **React Server Components (RSC) Support**: Optimized for modern Next.js applications.
16
16
 
17
+ ## 🔌 Companion Block Library: `@crediblemark/build-ui`
18
+
19
+ Accelerate your visual editor setup by pairing it with the official companion block library: **`@crediblemark/build-ui`**. It provides **45+ highly polished thematic blocks** (Hero sections, Navigation, Features, Grid lists, Accordions, and Footers) designed with clean Vanilla CSS that plug straight into your editor out of the box.
20
+
21
+ Integrating the library is completely automated via the CLI:
22
+ ```bash
23
+ npx credbuild add build-ui
24
+ ```
25
+
26
+ ---
27
+
17
28
  ## 🚀 Installation
18
29
 
19
30
  ```bash
20
31
  bun add @crediblemark/build
21
32
  ```
22
33
 
34
+ ### ⚡ Zero-Config Companion Scaffolding
35
+
36
+ To instantly add and auto-configure companion block libraries (such as `@crediblemark/build-ui`) in your project, run our intelligent, zero-config CLI:
37
+
38
+ ```bash
39
+ npx credbuild add build-ui
40
+ ```
41
+
42
+ The CLI tool will dynamically:
43
+ - **Detect your package manager** (`bun`, `pnpm`, `yarn`, or `npm`) by scanning local lockfiles and run the correct install command automatically.
44
+ - **Auto-inject companion styles** by prepending `@import "@crediblemark/build-ui/sidebar-neat.css";` to your global CSS stylesheet.
45
+ - **Generate configuration files** by autogenerating `credbuild.config.tsx` configured with `buildUiPreset` exports if no config is found.
46
+
47
+ ### 🔍 Automatic Component Registry Scaffolding (`credbuild scan`)
48
+
49
+ To automatically scan your React component directories and generate visual editor schemas, run our intelligent **Static Analysis CLI**:
50
+
51
+ ```bash
52
+ npx credbuild scan [directory]
53
+ ```
54
+
55
+ #### What it does:
56
+ - **TypeScript AST-Driven & Fallback Regex Parser**: Dynamically detects your local project's `typescript` package to run a highly accurate AST analysis on your `.tsx` source files. If TypeScript is not installed, it automatically falls back to a robust and resilient regex-based engine.
57
+ - **Smart Prop Field Mappings**:
58
+ - `string` ➔ `text` (and intelligently maps properties named `description`, `content`, `text`, or `body` to `textarea`).
59
+ - `boolean` ➔ `boolean`.
60
+ - `number` ➔ `number`.
61
+ - Union String Literals (e.g. `'primary' | 'secondary' | 'danger'`) ➔ `select` with populated options.
62
+ - **Export Type Resolution**: Distinguishes default vs named exports (e.g. `export default function Card` ➔ `import Card from "..."` vs `export const Card` ➔ `import { Card } from "..."`).
63
+ - **Configuration Scaffolding**: Generates a unified `credbuild.components.tsx` file in your root folder ready to be imported and merged directly in your main `credbuild.config.tsx`:
64
+
65
+ ```tsx
66
+ import { buildUiPreset } from "@crediblemark/build-ui";
67
+ import { customComponents } from "./credbuild.components";
68
+
69
+ const config = {
70
+ ...buildUiPreset,
71
+ components: {
72
+ ...buildUiPreset.components,
73
+ ...customComponents
74
+ }
75
+ };
76
+
77
+ export default config;
78
+ ```
79
+
23
80
  ## 🏗️ Next.js Implementation Pattern
24
81
 
25
82
  To replicate the professional CMS setup used in `NEXT_CMS`, follow this architecture:
26
83
 
84
+ ### 🚨 Next.js 14+ (App Router) Required Configurations
85
+
86
+ When using CredBuild in a modern Next.js environment, you must apply the following configurations to bypass Webpack restrictions and development iframe rendering issues:
87
+
88
+ **1. Webpack & Image Optimization (`next.config.ts`)**
89
+ Bypass ESM errors for server-side DOM parsers (JSDOM, Parse5) and disable image optimization to allow arbitrary external images in the builder canvas.
90
+
91
+ ```typescript
92
+ const nextConfig = {
93
+ serverExternalPackages: ['isomorphic-dompurify', 'jsdom', 'parse5'],
94
+ images: { unoptimized: true },
95
+ webpack: (config, { isServer, webpack }) => {
96
+ if (!isServer) {
97
+ config.plugins.push(new webpack.IgnorePlugin({ resourceRegExp: /^(parse5|jsdom|isomorphic-dompurify)$/ }));
98
+ }
99
+ return config;
100
+ }
101
+ };
102
+ export default nextConfig;
103
+ ```
104
+
105
+ **2. Disable Editor Iframe in Development**
106
+ Next.js injects CSS asynchronously in development mode. If the editor is rendered inside an iframe, the CSS will fail to load, resulting in 0px DropZones (blocks bouncing back). Pass `iframe={{ enabled: false }}` to the client component:
107
+
108
+ ```tsx
109
+ <CredBuild config={config} data={data} iframe={{ enabled: false }} />
110
+ ```
111
+
27
112
  ### 1. Folder Structure
28
113
  Organize your project to keep the editor logic separate from your public site logic:
29
114
  ```
@@ -0,0 +1,524 @@
1
+ #!/usr/bin/env node
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+ const { execSync } = require('child_process');
6
+
7
+ // Color helpers
8
+ const colors = {
9
+ reset: "\x1b[0m",
10
+ green: "\x1b[32m",
11
+ cyan: "\x1b[36m",
12
+ yellow: "\x1b[33m",
13
+ red: "\x1b[31m",
14
+ bold: "\x1b[1m"
15
+ };
16
+
17
+ console.log(`${colors.cyan}${colors.bold}⚡ Crediblemark CLI v0.25.4${colors.reset}\n`);
18
+
19
+ const args = process.argv.slice(2);
20
+ const command = args[0];
21
+
22
+ if (!command || (command !== 'add' && command !== 'scan')) {
23
+ console.log(`${colors.yellow}Usage:${colors.reset}`);
24
+ console.log(" npx credbuild add [package-name]");
25
+ console.log(" npx credbuild scan [dir]");
26
+ console.log("\nExamples:");
27
+ console.log(" npx credbuild add build-ui");
28
+ console.log(" npx credbuild scan src/components");
29
+ process.exit(1);
30
+ }
31
+
32
+ const cwd = process.cwd();
33
+
34
+ if (command === 'add') {
35
+ const targetPackage = args[1];
36
+ if (!targetPackage) {
37
+ console.log(`${colors.yellow}Usage:${colors.reset}`);
38
+ console.log(" npx credbuild add [package-name]");
39
+ process.exit(1);
40
+ }
41
+
42
+ // Map short name to full package name
43
+ let packageName = targetPackage;
44
+ if (targetPackage === 'build-ui') {
45
+ packageName = '@crediblemark/build-ui';
46
+ }
47
+
48
+ console.log(`📦 Preparing to add ${colors.green}${packageName}${colors.reset}...`);
49
+
50
+ // Step 1: Detect package manager by scanning lockfiles
51
+ let packageManager = 'npm';
52
+ let installCmd = 'install';
53
+
54
+ if (fs.existsSync(path.join(cwd, 'bun.lock')) || fs.existsSync(path.join(cwd, 'bun.lockb'))) {
55
+ packageManager = 'bun';
56
+ installCmd = 'add';
57
+ } else if (fs.existsSync(path.join(cwd, 'pnpm-lock.yaml'))) {
58
+ packageManager = 'pnpm';
59
+ installCmd = 'add';
60
+ } else if (fs.existsSync(path.join(cwd, 'yarn.lock'))) {
61
+ packageManager = 'yarn';
62
+ installCmd = 'add';
63
+ }
64
+
65
+ console.log(`🔍 Detected package manager: ${colors.cyan}${packageManager}${colors.reset}`);
66
+
67
+ // Step 2: Install the companion package
68
+ console.log(`\n🚀 Installing ${colors.green}${packageName}${colors.reset} using ${packageManager}...`);
69
+ try {
70
+ const fullCmd = `${packageManager} ${installCmd} ${packageName}`;
71
+ console.log(`${colors.yellow}> ${fullCmd}${colors.reset}`);
72
+ execSync(fullCmd, { stdio: 'inherit' });
73
+ console.log(`\n✅ ${colors.green}Successfully installed${colors.reset} ${packageName}!`);
74
+ } catch (error) {
75
+ console.error(`\n❌ ${colors.red}Failed to install${colors.reset} ${packageName}. Error:`, error.message);
76
+ process.exit(1);
77
+ }
78
+
79
+ // Step 3: Handle CSS Stylesheets for @crediblemark/build-ui
80
+ if (packageName === '@crediblemark/build-ui') {
81
+ const cssPaths = [
82
+ 'app/globals.css',
83
+ 'src/app/globals.css',
84
+ 'src/index.css',
85
+ 'src/globals.css',
86
+ 'styles/globals.css',
87
+ 'styles.css',
88
+ 'index.css',
89
+ 'credbuild.css'
90
+ ];
91
+
92
+ let cssFileFound = null;
93
+ for (const relPath of cssPaths) {
94
+ const fullPath = path.join(cwd, relPath);
95
+ if (fs.existsSync(fullPath)) {
96
+ cssFileFound = fullPath;
97
+ break;
98
+ }
99
+ }
100
+
101
+ const importStatement = `@import "@crediblemark/build-ui/sidebar-neat.css";\n`;
102
+
103
+ if (cssFileFound) {
104
+ const content = fs.readFileSync(cssFileFound, 'utf8');
105
+ if (!content.includes('sidebar-neat.css')) {
106
+ // Prepend import statement
107
+ fs.writeFileSync(cssFileFound, importStatement + content, 'utf8');
108
+ console.log(`✨ ${colors.cyan}Added stylesheet import${colors.reset} to: ${colors.bold}${path.relative(cwd, cssFileFound)}${colors.reset}`);
109
+ } else {
110
+ console.log(`ℹ️ Stylesheet import already exists in: ${colors.bold}${path.relative(cwd, cssFileFound)}${colors.reset}`);
111
+ }
112
+ } else {
113
+ // If no css file found, create a simple style.css or let the user know
114
+ const defaultCssPath = path.join(cwd, 'credbuild.css');
115
+ fs.writeFileSync(defaultCssPath, importStatement, 'utf8');
116
+ console.log(`✨ ${colors.cyan}Created stylesheet${colors.reset} at: ${colors.bold}credbuild.css${colors.reset}`);
117
+ }
118
+
119
+ // Step 4: Handle Config File (credbuild.config.tsx)
120
+ const configPaths = [
121
+ 'credbuild.config.tsx',
122
+ 'credbuild.config.ts',
123
+ 'credbuild.config.js',
124
+ 'src/credbuild.config.tsx',
125
+ 'src/credbuild.config.ts'
126
+ ];
127
+
128
+ let configFound = null;
129
+ for (const relPath of configPaths) {
130
+ const fullPath = path.join(cwd, relPath);
131
+ if (fs.existsSync(fullPath)) {
132
+ configFound = fullPath;
133
+ break;
134
+ }
135
+ }
136
+
137
+ if (!configFound) {
138
+ const defaultConfigContent = `import { buildUiPreset } from "@crediblemark/build-ui";
139
+
140
+ // Crediblemark Visual Editor Configuration
141
+ // Automatically configured for @crediblemark/build-ui
142
+ const config = buildUiPreset;
143
+
144
+ export default config;
145
+ `;
146
+ const targetConfigPath = path.join(cwd, 'credbuild.config.tsx');
147
+ fs.writeFileSync(targetConfigPath, defaultConfigContent, 'utf8');
148
+ console.log(`✨ ${colors.cyan}Generated default configuration${colors.reset} at: ${colors.bold}credbuild.config.tsx${colors.reset}`);
149
+ } else {
150
+ console.log(`\n⚠️ ${colors.yellow}Configuration file already exists${colors.reset} at: ${colors.bold}${path.relative(cwd, configFound)}${colors.reset}`);
151
+ console.log(`💡 To integrate the companion blocks, import ${colors.green}buildUiPreset${colors.reset} from ${colors.green}'@crediblemark/build-ui'${colors.reset} and register it in your config.`);
152
+ }
153
+
154
+ console.log(`\n🎉 ${colors.green}${colors.bold}All set!${colors.reset} Your visual editor companion blocks are now fully configured and ready to roll! 🚀`);
155
+ }
156
+ } else if (command === 'scan') {
157
+ // Credbuild Scan command logic
158
+ let scanDir = args[1];
159
+
160
+ if (!scanDir) {
161
+ const commonDirs = ['src/components', 'components', 'app/components', 'src/app/components'];
162
+ for (const dir of commonDirs) {
163
+ if (fs.existsSync(path.join(cwd, dir))) {
164
+ scanDir = dir;
165
+ break;
166
+ }
167
+ }
168
+ }
169
+
170
+ const scanPath = scanDir ? path.resolve(cwd, scanDir) : cwd;
171
+ console.log(`🔍 Scanning directory: ${colors.cyan}${path.relative(cwd, scanPath) || '.'}${colors.reset}`);
172
+
173
+ if (!fs.existsSync(scanPath)) {
174
+ console.error(`❌ ${colors.red}Directory does not exist:${colors.reset} ${scanPath}`);
175
+ process.exit(1);
176
+ }
177
+
178
+ // 1. Recursive file scanner
179
+ function scanDirectory(dirPath, files = []) {
180
+ const entries = fs.readdirSync(dirPath, { withFileTypes: true });
181
+ for (const entry of entries) {
182
+ const fullPath = path.join(dirPath, entry.name);
183
+ if (entry.isDirectory()) {
184
+ if (['node_modules', '.git', '.next', 'dist', 'build', 'out', '.gemini', '.system_generated'].includes(entry.name)) {
185
+ continue;
186
+ }
187
+ scanDirectory(fullPath, files);
188
+ } else if (entry.isFile() && entry.name.endsWith('.tsx')) {
189
+ files.push(fullPath);
190
+ }
191
+ }
192
+ return files;
193
+ }
194
+
195
+ const componentFiles = scanDirectory(scanPath);
196
+ if (componentFiles.length === 0) {
197
+ console.log(`ℹ️ No component files (*.tsx) found in the scanned directory.`);
198
+ process.exit(0);
199
+ }
200
+
201
+ console.log(`Found ${colors.green}${componentFiles.length}${colors.reset} potential React component file(s).`);
202
+
203
+ // Try loading TypeScript compiler dynamically
204
+ let ts = null;
205
+ try {
206
+ const tsPath = require.resolve('typescript', { paths: [cwd] });
207
+ ts = require(tsPath);
208
+ console.log(`✨ Using project's local ${colors.bold}typescript${colors.reset} package for AST analysis.`);
209
+ } catch (err) {
210
+ console.log(`💡 ${colors.yellow}TypeScript not found in project's node_modules. Falling back to regex parser.${colors.reset}`);
211
+ }
212
+
213
+ // Type Mapping Logic
214
+ function mapTypeToField(propName, propInfo) {
215
+ if (!propInfo) return null;
216
+ if (propInfo.type === 'boolean') return { type: 'boolean' };
217
+ if (propInfo.type === 'number') return { type: 'number' };
218
+ if (propInfo.type === 'union') {
219
+ return { type: 'select', options: propInfo.options };
220
+ }
221
+ if (propInfo.type === 'string') {
222
+ const name = propName.toLowerCase();
223
+ if (name === 'description' || name === 'content' || name === 'text' || name === 'body') {
224
+ return { type: 'textarea' };
225
+ }
226
+ return { type: 'text' };
227
+ }
228
+ return null;
229
+ }
230
+
231
+ // AST Parsing Logic
232
+ function parseWithTS(filePath) {
233
+ const fileContent = fs.readFileSync(filePath, 'utf8');
234
+ const sourceFile = ts.createSourceFile(filePath, fileContent, ts.ScriptTarget.Latest, true);
235
+
236
+ const types = {};
237
+ const components = [];
238
+
239
+ function parseTSType(typeNode) {
240
+ if (!typeNode) return null;
241
+ const kind = typeNode.kind;
242
+
243
+ if (kind === ts.SyntaxKind.StringKeyword) return { type: 'string' };
244
+ if (kind === ts.SyntaxKind.BooleanKeyword) return { type: 'boolean' };
245
+ if (kind === ts.SyntaxKind.NumberKeyword) return { type: 'number' };
246
+ if (ts.isUnionTypeNode(typeNode)) {
247
+ const isLiteralUnion = typeNode.types.every(t => ts.isLiteralTypeNode(t) && ts.isStringLiteral(t.literal));
248
+ if (isLiteralUnion) {
249
+ const options = typeNode.types.map(t => t.literal.text);
250
+ return { type: 'union', options };
251
+ }
252
+ }
253
+ return null;
254
+ }
255
+
256
+ function extractPropsFromMembers(members) {
257
+ const props = {};
258
+ members.forEach(member => {
259
+ if (ts.isPropertySignature(member) && member.name) {
260
+ const propName = member.name.getText(sourceFile);
261
+ const propType = parseTSType(member.type);
262
+ if (propType) {
263
+ props[propName] = propType;
264
+ }
265
+ }
266
+ });
267
+ return props;
268
+ }
269
+
270
+ function visit(node) {
271
+ if (ts.isInterfaceDeclaration(node)) {
272
+ const typeName = node.name.text;
273
+ types[typeName] = extractPropsFromMembers(node.members);
274
+ } else if (ts.isTypeAliasDeclaration(node)) {
275
+ const typeName = node.name.text;
276
+ if (ts.isTypeLiteralNode(node.type)) {
277
+ types[typeName] = extractPropsFromMembers(node.type.members);
278
+ }
279
+ }
280
+
281
+ if (ts.isFunctionDeclaration(node) && node.name) {
282
+ const isExported = node.modifiers && node.modifiers.some(m => m.kind === ts.SyntaxKind.ExportKeyword);
283
+ const isDefault = node.modifiers && node.modifiers.some(m => m.kind === ts.SyntaxKind.DefaultKeyword);
284
+ const name = node.name.text;
285
+ if (isExported && /^[A-Z]/.test(name)) {
286
+ let propTypeName = null;
287
+ if (node.parameters && node.parameters.length > 0) {
288
+ const firstParam = node.parameters[0];
289
+ if (firstParam.type && ts.isTypeReferenceNode(firstParam.type)) {
290
+ propTypeName = firstParam.type.typeName.getText(sourceFile);
291
+ }
292
+ }
293
+ components.push({ name, propTypeName, isDefault });
294
+ }
295
+ }
296
+
297
+ if (ts.isVariableStatement(node)) {
298
+ const isExported = node.modifiers && node.modifiers.some(m => m.kind === ts.SyntaxKind.ExportKeyword);
299
+ if (isExported) {
300
+ node.declarationList.declarations.forEach(decl => {
301
+ if (decl.name && ts.isIdentifier(decl.name)) {
302
+ const name = decl.name.text;
303
+ if (/^[A-Z]/.test(name)) {
304
+ let propTypeName = null;
305
+ const init = decl.initializer;
306
+ if (init && (ts.isArrowFunction(init) || ts.isFunctionExpression(init))) {
307
+ if (init.parameters && init.parameters.length > 0) {
308
+ const firstParam = init.parameters[0];
309
+ if (firstParam.type && ts.isTypeReferenceNode(firstParam.type)) {
310
+ propTypeName = firstParam.type.typeName.getText(sourceFile);
311
+ }
312
+ }
313
+ components.push({ name, propTypeName, isDefault: false });
314
+ }
315
+ }
316
+ }
317
+ });
318
+ }
319
+ }
320
+
321
+ if (ts.isExportAssignment(node) && !node.isExportEquals) {
322
+ const expr = node.expression;
323
+ if (ts.isIdentifier(expr)) {
324
+ const name = expr.text;
325
+ if (/^[A-Z]/.test(name)) {
326
+ const existing = components.find(c => c.name === name);
327
+ if (existing) {
328
+ existing.isDefault = true;
329
+ } else {
330
+ components.push({ name, propTypeName: null, isDefault: true });
331
+ }
332
+ }
333
+ }
334
+ }
335
+
336
+ ts.forEachChild(node, visit);
337
+ }
338
+
339
+ visit(sourceFile);
340
+
341
+ const scanned = [];
342
+ components.forEach(comp => {
343
+ let fields = null;
344
+ const lookupNames = [comp.propTypeName, comp.name + 'Props', 'Props', 'Props' + comp.name].filter(Boolean);
345
+ for (const lookup of lookupNames) {
346
+ if (types[lookup]) {
347
+ fields = types[lookup];
348
+ break;
349
+ }
350
+ }
351
+ scanned.push({
352
+ name: comp.name,
353
+ isDefault: comp.isDefault,
354
+ fields: fields || {}
355
+ });
356
+ });
357
+
358
+ return scanned;
359
+ }
360
+
361
+ // Regex Parsing Logic
362
+ function parseWithRegex(filePath) {
363
+ const fileContent = fs.readFileSync(filePath, 'utf8');
364
+ const componentsMap = new Map();
365
+
366
+ const funcRegex = /export\s+(default\s+)?function\s+([A-Z]\w*)\s*\(/g;
367
+ let match;
368
+ while ((match = funcRegex.exec(fileContent)) !== null) {
369
+ const isDefault = !!match[1];
370
+ const name = match[2];
371
+ componentsMap.set(name, { name, isDefault, propTypeName: null });
372
+ }
373
+
374
+ const constRegex = /export\s+const\s+([A-Z]\w*)\s*=\s*(?:\([^)]*\)|[^=]+)\s*=>/g;
375
+ while ((match = constRegex.exec(fileContent)) !== null) {
376
+ const name = match[1];
377
+ componentsMap.set(name, { name, isDefault: false, propTypeName: null });
378
+ }
379
+
380
+ const defExportRegex = /export\s+default\s+([A-Z]\w*)\s*;/g;
381
+ while ((match = defExportRegex.exec(fileContent)) !== null) {
382
+ const name = match[1];
383
+ if (componentsMap.has(name)) {
384
+ componentsMap.get(name).isDefault = true;
385
+ } else {
386
+ componentsMap.set(name, { name, isDefault: true, propTypeName: null });
387
+ }
388
+ }
389
+
390
+ const types = {};
391
+ const typeRegex = /(?:interface|type)\s+([a-zA-Z0-9_]+)\s*(?:=)?\s*\{([\s\S]*?)\}/g;
392
+ while ((match = typeRegex.exec(fileContent)) !== null) {
393
+ const typeName = match[1];
394
+ const fieldsBody = match[2];
395
+ const fields = {};
396
+ const fieldRegex = /([a-zA-Z0-9_]+)\s*\??\s*:\s*([^;,\n]+)/g;
397
+ let fieldMatch;
398
+ while ((fieldMatch = fieldRegex.exec(fieldsBody)) !== null) {
399
+ const fieldName = fieldMatch[1].trim();
400
+ const rawType = fieldMatch[2].trim();
401
+
402
+ if (rawType === 'string') {
403
+ fields[fieldName] = { type: 'string' };
404
+ } else if (rawType === 'boolean') {
405
+ fields[fieldName] = { type: 'boolean' };
406
+ } else if (rawType === 'number') {
407
+ fields[fieldName] = { type: 'number' };
408
+ } else if (/^(?:'[^']+'|"[^"]+")(?:\s*\|\s*(?:'[^']+'|"[^"]+"))+$/.test(rawType)) {
409
+ const options = rawType.split('|').map(opt => opt.trim().replace(/^['"]|['"]$/g, ''));
410
+ fields[fieldName] = { type: 'union', options };
411
+ }
412
+ }
413
+ types[typeName] = fields;
414
+ }
415
+
416
+ const scanned = [];
417
+ for (const comp of componentsMap.values()) {
418
+ let fields = null;
419
+ const lookupNames = [comp.name + 'Props', 'Props', 'Props' + comp.name];
420
+ for (const lookup of lookupNames) {
421
+ if (types[lookup]) {
422
+ fields = types[lookup];
423
+ break;
424
+ }
425
+ }
426
+ scanned.push({
427
+ name: comp.name,
428
+ isDefault: comp.isDefault,
429
+ fields: fields || {}
430
+ });
431
+ }
432
+
433
+ return scanned;
434
+ }
435
+
436
+ // Scan all component files
437
+ const scannedComponents = [];
438
+ for (const filePath of componentFiles) {
439
+ try {
440
+ const results = ts ? parseWithTS(filePath) : parseWithRegex(filePath);
441
+ for (const comp of results) {
442
+ scannedComponents.push({
443
+ ...comp,
444
+ filePath
445
+ });
446
+ }
447
+ } catch (e) {
448
+ console.warn(`⚠️ Warning: Failed to parse ${colors.yellow}${path.relative(cwd, filePath)}${colors.reset}: ${e.message}`);
449
+ }
450
+ }
451
+
452
+ if (scannedComponents.length === 0) {
453
+ console.log(`ℹ️ Found *.tsx files but couldn't detect any exported React components inside them.`);
454
+ process.exit(0);
455
+ }
456
+
457
+ console.log(`\n✨ Successfully analyzed components! Ready to output.`);
458
+
459
+ // Generate credbuild.components.tsx
460
+ let imports = `import React from 'react';\n`;
461
+ let customComponentsContent = `export const customComponents: Record<string, any> = {\n`;
462
+
463
+ scannedComponents.forEach(comp => {
464
+ // Relative path normalized for imports
465
+ let relPath = path.relative(cwd, comp.filePath);
466
+ relPath = relPath.replace(/\.tsx$/, '').replace(/\\/g, '/');
467
+ if (!relPath.startsWith('.')) relPath = './' + relPath;
468
+
469
+ // Generate Import
470
+ if (comp.isDefault) {
471
+ imports += `import ${comp.name} from "${relPath}";\n`;
472
+ } else {
473
+ imports += `import { ${comp.name} } from "${relPath}";\n`;
474
+ }
475
+
476
+ // Generate Config fields
477
+ const fieldsConfig = {};
478
+ for (const [name, info] of Object.entries(comp.fields)) {
479
+ const mapped = mapTypeToField(name, info);
480
+ if (mapped) fieldsConfig[name] = mapped;
481
+ }
482
+
483
+ // Format fields mapping as TS string
484
+ const fieldsStr = JSON.stringify(fieldsConfig, null, 6)
485
+ .split('\n')
486
+ .map((line, idx) => idx === 0 ? line : ' ' + line)
487
+ .join('\n');
488
+
489
+ customComponentsContent += ` ${comp.name}: {\n`;
490
+ customComponentsContent += ` component: ${comp.name},\n`;
491
+ customComponentsContent += ` fields: ${fieldsStr}\n`;
492
+ customComponentsContent += ` },\n`;
493
+ });
494
+
495
+ customComponentsContent += `};\n`;
496
+
497
+ const outputContent = `// Crediblemark Generated Component Visual Editor Schema
498
+ // Generated automatically via 'credbuild scan'
499
+ // You can merge these into your credbuild.config.tsx
500
+
501
+ ${imports}
502
+ ${customComponentsContent}
503
+ `;
504
+
505
+ const outputPath = path.join(cwd, 'credbuild.components.tsx');
506
+ fs.writeFileSync(outputPath, outputContent, 'utf8');
507
+
508
+ console.log(`\n🎉 ${colors.green}${colors.bold}Success!${colors.reset} Visual editor schemas generated at: ${colors.bold}credbuild.components.tsx${colors.reset}`);
509
+ console.log(`\nTo integrate these components in your ${colors.bold}credbuild.config.tsx${colors.reset}:`);
510
+ console.log(`${colors.cyan}-------------------------------------------------------`);
511
+ console.log(`import { buildUiPreset } from "@crediblemark/build-ui";`);
512
+ console.log(`import { customComponents } from "./credbuild.components";`);
513
+ console.log(``);
514
+ console.log(`const config = {`);
515
+ console.log(` ...buildUiPreset,`);
516
+ console.log(` components: {`);
517
+ console.log(` ...buildUiPreset.components,`);
518
+ console.log(` ...customComponents`);
519
+ console.log(` }`);
520
+ console.log(`};`);
521
+ console.log(``);
522
+ console.log(`export default config;`);
523
+ console.log(`-------------------------------------------------------${colors.reset}`);
524
+ }
@@ -0,0 +1,19 @@
1
+ import {
2
+ ArrayField
3
+ } from "./chunk-KRSRYBNG.mjs";
4
+ import "./chunk-HDKBRPN3.mjs";
5
+ import "./chunk-QX5Z2UCM.mjs";
6
+ import "./chunk-H6RPI3T6.mjs";
7
+ import "./chunk-2BGYCLSQ.mjs";
8
+ import "./chunk-YNBP3U53.mjs";
9
+ import "./chunk-XLLRNZSM.mjs";
10
+ import "./chunk-3TEHZVFI.mjs";
11
+ import "./chunk-N2OOMTBI.mjs";
12
+ import "./chunk-64A37UUC.mjs";
13
+ import "./chunk-SMAWAXVX.mjs";
14
+ import "./chunk-BFHV72KK.mjs";
15
+ import "./chunk-7LXZOPYT.mjs";
16
+ import "./chunk-AFVEAZTD.mjs";
17
+ export {
18
+ ArrayField
19
+ };