@developer_tribe/react-builder 0.1.4 → 0.1.8

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 (110) hide show
  1. package/dist/build-components/Button/Button.d.ts +1 -5
  2. package/dist/build-components/Button/ButtonProps.generated.d.ts +4 -0
  3. package/dist/build-components/Carousel/Carousel.d.ts +1 -5
  4. package/dist/build-components/Carousel/CarouselProps.generated.d.ts +4 -0
  5. package/dist/build-components/CarouselButtons/CarouselButtons.d.ts +1 -5
  6. package/dist/build-components/CarouselButtons/CarouselButtonsProps.generated.d.ts +4 -0
  7. package/dist/build-components/CarouselDots/CarouselDots.d.ts +1 -5
  8. package/dist/build-components/CarouselDots/CarouselDotsProps.generated.d.ts +4 -0
  9. package/dist/build-components/CarouselItem/CarouselItem.d.ts +1 -5
  10. package/dist/build-components/CarouselItem/CarouselItemProps.generated.d.ts +4 -0
  11. package/dist/build-components/CarouselProvider/CarouselProvider.d.ts +1 -5
  12. package/dist/build-components/CarouselProvider/CarouselProviderProps.generated.d.ts +4 -0
  13. package/dist/build-components/Image/Image.d.ts +1 -5
  14. package/dist/build-components/Image/ImageProps.generated.d.ts +4 -0
  15. package/dist/build-components/Onboard/Onboard.d.ts +1 -5
  16. package/dist/build-components/Onboard/OnboardProps.generated.d.ts +4 -0
  17. package/dist/build-components/OnboardBoardTitle/OnboardBoardTitle.d.ts +1 -5
  18. package/dist/build-components/OnboardBoardTitle/OnboardBoardTitleProps.generated.d.ts +4 -0
  19. package/dist/build-components/OnboardButton/OnboardButton.d.ts +1 -5
  20. package/dist/build-components/OnboardButton/OnboardButtonProps.generated.d.ts +4 -0
  21. package/dist/build-components/OnboardButtons/OnboardButtons.d.ts +1 -5
  22. package/dist/build-components/OnboardButtons/OnboardButtonsProps.generated.d.ts +4 -0
  23. package/dist/build-components/OnboardDot/OnboardDot.d.ts +5 -0
  24. package/dist/build-components/OnboardDot/OnboardDotProps.generated.d.ts +10 -0
  25. package/dist/build-components/OnboardDot/OnboardExpandingDotProps.generated.d.ts +10 -0
  26. package/dist/build-components/OnboardFooter/OnboardFooter.d.ts +1 -5
  27. package/dist/build-components/OnboardFooter/OnboardFooterProps.generated.d.ts +4 -0
  28. package/dist/build-components/OnboardImage/OnboardImage.d.ts +1 -5
  29. package/dist/build-components/OnboardImage/OnboardImageProps.generated.d.ts +4 -0
  30. package/dist/build-components/OnboardItem/OnboardItem.d.ts +1 -5
  31. package/dist/build-components/OnboardItem/OnboardItemProps.generated.d.ts +4 -0
  32. package/dist/build-components/OnboardProvider/OnboardProvider.d.ts +1 -5
  33. package/dist/build-components/OnboardProvider/OnboardProviderProps.generated.d.ts +4 -0
  34. package/dist/build-components/OnboardSubtitle/OnboardSubtitle.d.ts +1 -5
  35. package/dist/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.d.ts +4 -0
  36. package/dist/build-components/Text/Text.d.ts +1 -5
  37. package/dist/build-components/Text/TextProps.generated.d.ts +4 -0
  38. package/dist/build-components/View/View.d.ts +1 -5
  39. package/dist/build-components/View/ViewProps.generated.d.ts +4 -0
  40. package/dist/build-components/index.d.ts +19 -0
  41. package/dist/index.cjs.js +1 -1
  42. package/dist/index.d.ts +1 -0
  43. package/dist/index.esm.js +1 -1
  44. package/package.json +4 -3
  45. package/scripts/prebuild/build-components.js +20 -502
  46. package/scripts/prebuild/utils/createBuildComponentsIndex.js +24 -0
  47. package/scripts/prebuild/utils/createComponentTsx.js +21 -0
  48. package/scripts/prebuild/utils/createGeneratedProps.js +65 -0
  49. package/scripts/prebuild/utils/createRenderNodeGenerated.js +69 -0
  50. package/scripts/prebuild/utils/ensureDir.js +5 -0
  51. package/scripts/prebuild/utils/ensurePropsTs.js +15 -0
  52. package/scripts/prebuild/utils/fail.js +7 -0
  53. package/scripts/prebuild/utils/formatAllSourceFiles.js +39 -0
  54. package/scripts/prebuild/utils/formatWithPrettier.js +10 -0
  55. package/scripts/prebuild/utils/index.js +15 -0
  56. package/scripts/prebuild/utils/lintNonGeneratedOrThrow.js +27 -0
  57. package/scripts/prebuild/utils/validateAllComponentsOrThrow.js +156 -0
  58. package/scripts/prebuild/utils/validateExistingComponentTsx.js +40 -0
  59. package/scripts/prebuild/utils/validatePatternJson.js +77 -0
  60. package/scripts/public/bin.js +1 -6
  61. package/scripts/public/scripts/build/index.js +15 -4
  62. package/scripts/public/scripts/build/info.json +6 -0
  63. package/src/build-components/Button/Button.tsx +1 -6
  64. package/src/build-components/Button/ButtonProps.generated.ts +6 -0
  65. package/src/build-components/Carousel/Carousel.tsx +1 -6
  66. package/src/build-components/Carousel/CarouselProps.generated.ts +6 -0
  67. package/src/build-components/CarouselButtons/CarouselButtons.tsx +1 -6
  68. package/src/build-components/CarouselButtons/CarouselButtonsProps.generated.ts +6 -0
  69. package/src/build-components/CarouselDots/CarouselDots.tsx +1 -6
  70. package/src/build-components/CarouselDots/CarouselDotsProps.generated.ts +6 -0
  71. package/src/build-components/CarouselItem/CarouselItem.tsx +1 -6
  72. package/src/build-components/CarouselItem/CarouselItemProps.generated.ts +6 -0
  73. package/src/build-components/CarouselProvider/CarouselProvider.tsx +1 -5
  74. package/src/build-components/CarouselProvider/CarouselProviderProps.generated.ts +6 -0
  75. package/src/build-components/Image/Image.tsx +1 -6
  76. package/src/build-components/Image/ImageProps.generated.ts +6 -0
  77. package/src/build-components/Onboard/Onboard.tsx +1 -6
  78. package/src/build-components/Onboard/OnboardProps.generated.ts +6 -0
  79. package/src/build-components/OnboardBoardTitle/OnboardBoardTitle.tsx +1 -6
  80. package/src/build-components/OnboardBoardTitle/OnboardBoardTitleProps.generated.ts +6 -0
  81. package/src/build-components/OnboardButton/OnboardButton.tsx +1 -6
  82. package/src/build-components/OnboardButton/OnboardButtonProps.generated.ts +6 -0
  83. package/src/build-components/OnboardButtons/OnboardButtons.tsx +2 -6
  84. package/src/build-components/OnboardButtons/OnboardButtonsProps.generated.ts +6 -0
  85. package/src/build-components/OnboardDot/OnboardDot.tsx +9 -0
  86. package/src/build-components/OnboardDot/OnboardDotProps.generated.ts +20 -0
  87. package/src/build-components/{OnboardExpandingDot → OnboardDot}/OnboardExpandingDotProps.generated.ts +7 -1
  88. package/src/build-components/{OnboardExpandingDot → OnboardDot}/pattern.json +1 -1
  89. package/src/build-components/OnboardFooter/OnboardFooter.tsx +1 -6
  90. package/src/build-components/OnboardFooter/OnboardFooterProps.generated.ts +6 -0
  91. package/src/build-components/OnboardImage/OnboardImage.tsx +1 -6
  92. package/src/build-components/OnboardImage/OnboardImageProps.generated.ts +6 -0
  93. package/src/build-components/OnboardItem/OnboardItem.tsx +1 -5
  94. package/src/build-components/OnboardItem/OnboardItemProps.generated.ts +6 -0
  95. package/src/build-components/OnboardProvider/OnboardProvider.tsx +1 -5
  96. package/src/build-components/OnboardProvider/OnboardProviderProps.generated.ts +6 -0
  97. package/src/build-components/OnboardSubtitle/OnboardSubtitle.tsx +1 -6
  98. package/src/build-components/OnboardSubtitle/OnboardSubtitleProps.generated.ts +6 -0
  99. package/src/build-components/RenderNode.generated.tsx +3 -3
  100. package/src/build-components/Text/Text.tsx +1 -6
  101. package/src/build-components/Text/TextProps.generated.ts +6 -0
  102. package/src/build-components/View/View.tsx +5 -6
  103. package/src/build-components/View/ViewProps.generated.ts +6 -0
  104. package/src/build-components/index.ts +78 -0
  105. package/src/index.ts +1 -0
  106. package/src/utils/novaToJson.ts +1 -1
  107. package/src/utils/patterns.ts +1 -1
  108. package/dist/build-components/OnboardExpandingDot/OnboardExpandingDot.d.ts +0 -9
  109. package/dist/build-components/OnboardExpandingDot/OnboardExpandingDotProps.generated.d.ts +0 -6
  110. package/src/build-components/OnboardExpandingDot/OnboardExpandingDot.tsx +0 -14
@@ -0,0 +1,69 @@
1
+ import path from 'path';
2
+ import { promises as fs } from 'fs';
3
+ import { formatWithPrettier } from './formatWithPrettier.js';
4
+
5
+ export async function createRenderNodeGenerated(validated, paths) {
6
+ const { COMPONENTS_ROOT } = paths;
7
+ const targetPath = path.join(COMPONENTS_ROOT, 'RenderNode.generated.tsx');
8
+
9
+ // Build imports for all components discovered
10
+ const componentImports = validated
11
+ .map(
12
+ ({ componentName }) =>
13
+ `import ${componentName} from './${componentName}/${componentName}';`
14
+ )
15
+ .join('\n');
16
+
17
+ // Build switch cases from each component's pattern.type
18
+ const cases = validated
19
+ .map(({ componentName, patternJson }) => {
20
+ const type = patternJson?.pattern?.type;
21
+ return typeof type === 'string'
22
+ ? ` case ${JSON.stringify(type)}:\n return <${componentName} node={simpleNode} />;`
23
+ : null;
24
+ })
25
+ .filter(Boolean)
26
+ .join('\n');
27
+
28
+ const fileContent =
29
+ `/* AUTO-GENERATED FILE - DO NOT EDIT */\n` +
30
+ `import React from 'react';\n\n` +
31
+ `import {\n` +
32
+ ` Node,\n` +
33
+ ` NodeData,\n` +
34
+ ` isNodeArray,\n` +
35
+ ` isNodeNullOrUndefined,\n` +
36
+ ` isNodeString,\n` +
37
+ `} from '../index';\n\n` +
38
+ `import { other } from './other';\n\n` +
39
+ `// Builder components\n` +
40
+ componentImports +
41
+ `\n\n` +
42
+ `function RenderNode({ node }: { node: Node }) {\n` +
43
+ ` if (isNodeNullOrUndefined(node)) {\n` +
44
+ ` return null;\n` +
45
+ ` }\n` +
46
+ ` if (isNodeString(node)) {\n` +
47
+ ` return <Text node={{ children: node as string, type: 'text' }} />;\n` +
48
+ ` }\n` +
49
+ ` if (isNodeArray(node)) {\n` +
50
+ ` return (\n` +
51
+ ` <>\n` +
52
+ ` {(node as Node[]).map((item: Node, index) => (\n` +
53
+ ` <RenderNode key={index} node={item} />\n` +
54
+ ` ))}\n` +
55
+ ` </>\n` +
56
+ ` );\n` +
57
+ ` }\n\n` +
58
+ ` const simpleNode = node as NodeData;\n` +
59
+ ` switch (simpleNode?.type) {\n` +
60
+ cases +
61
+ `\n default:\n` +
62
+ ` return other(simpleNode?.type, node);\n` +
63
+ ` }\n` +
64
+ `}\n\n` +
65
+ `export default React.memo(RenderNode);\n`;
66
+
67
+ const formatted = await formatWithPrettier(fileContent);
68
+ await fs.writeFile(targetPath, formatted, 'utf8');
69
+ }
@@ -0,0 +1,5 @@
1
+ import { promises as fs } from 'fs';
2
+
3
+ export async function ensureDir(dirPath) {
4
+ await fs.mkdir(dirPath, { recursive: true });
5
+ }
@@ -0,0 +1,15 @@
1
+ import path from 'path';
2
+ import { promises as fs } from 'fs';
3
+ import { ensureDir } from './ensureDir.js';
4
+
5
+ export async function ensurePropsTs(componentDir, componentName) {
6
+ await ensureDir(componentDir);
7
+ const fileName = `${componentName}Props.ts`;
8
+ const filePath = path.join(componentDir, fileName);
9
+ const exists = await fs
10
+ .stat(filePath)
11
+ .then(() => true)
12
+ .catch(() => false);
13
+ if (!exists) return;
14
+ await fs.unlink(filePath);
15
+ }
@@ -0,0 +1,7 @@
1
+ export function fail(message) {
2
+ console.error(message);
3
+ const err = new Error(message);
4
+ // eslint-disable-next-line no-underscore-dangle
5
+ err._alreadyLogged = true;
6
+ throw err;
7
+ }
@@ -0,0 +1,39 @@
1
+ import path from 'path';
2
+ import { promises as fs } from 'fs';
3
+ import { formatWithPrettier } from './formatWithPrettier.js';
4
+
5
+ export async function formatAllSourceFiles(paths) {
6
+ const { SRC_ROOT } = paths;
7
+ async function* walk(dir) {
8
+ const dirents = (await fs.readFile)
9
+ ? await fs.readdir(dir, { withFileTypes: true })
10
+ : [];
11
+ for (const dirent of dirents) {
12
+ const full = path.join(dir, dirent.name);
13
+ if (dirent.isDirectory()) {
14
+ // Skip common non-source directories
15
+ if (dirent.name === 'node_modules' || dirent.name === 'dist') continue;
16
+ yield* walk(full);
17
+ } else {
18
+ yield full;
19
+ }
20
+ }
21
+ }
22
+
23
+ const targets = [];
24
+ for await (const filePath of walk(SRC_ROOT)) {
25
+ if (filePath.endsWith('.ts') || filePath.endsWith('.tsx')) {
26
+ targets.push(filePath);
27
+ }
28
+ }
29
+
30
+ await Promise.all(
31
+ targets.map(async filePath => {
32
+ const original = await fs.readFile(filePath, 'utf8');
33
+ const formatted = await formatWithPrettier(original);
34
+ if (formatted !== original) {
35
+ await fs.writeFile(filePath, formatted, 'utf8');
36
+ }
37
+ })
38
+ );
39
+ }
@@ -0,0 +1,10 @@
1
+ import prettier from 'prettier';
2
+
3
+ export async function formatWithPrettier(tempFileText) {
4
+ return prettier.format(tempFileText, {
5
+ parser: 'typescript',
6
+ semi: true,
7
+ singleQuote: true,
8
+ tabWidth: 2,
9
+ });
10
+ }
@@ -0,0 +1,15 @@
1
+ export { fail } from './fail.js';
2
+ export { ensureDir } from './ensureDir.js';
3
+ export { formatWithPrettier } from './formatWithPrettier.js';
4
+
5
+ export { lintNonGeneratedOrThrow } from './lintNonGeneratedOrThrow.js';
6
+
7
+ export { validateAllComponentsOrThrow } from './validateAllComponentsOrThrow.js';
8
+
9
+ export { createGeneratedProps } from './createGeneratedProps.js';
10
+ export { ensurePropsTs } from './ensurePropsTs.js';
11
+ export { createComponentTsx } from './createComponentTsx.js';
12
+ export { createRenderNodeGenerated } from './createRenderNodeGenerated.js';
13
+ export { createBuildComponentsIndex } from './createBuildComponentsIndex.js';
14
+ export { formatAllSourceFiles } from './formatAllSourceFiles.js';
15
+ export { validateExistingComponentTsx } from './validateExistingComponentTsx.js';
@@ -0,0 +1,27 @@
1
+ import path from 'path';
2
+ import { ESLint } from 'eslint';
3
+ import { fail } from './fail.js';
4
+
5
+ export async function lintNonGeneratedOrThrow(paths) {
6
+ const { PROJECT_ROOT } = paths;
7
+ const eslint = new ESLint({ cwd: PROJECT_ROOT });
8
+ const results = await eslint.lintFiles(['src/**/*.ts', 'src/**/*.tsx']);
9
+
10
+ const isGenerated = filePath =>
11
+ filePath.endsWith('.generated.ts') ||
12
+ filePath.endsWith('RenderNode.generated.tsx');
13
+
14
+ const errorResults = results.filter(
15
+ r => !isGenerated(r.filePath) && r.errorCount > 0
16
+ );
17
+
18
+ if (errorResults.length > 0) {
19
+ const summary = errorResults
20
+ .map(
21
+ r =>
22
+ `${path.relative(PROJECT_ROOT, r.filePath)}: ${r.errorCount} error(s)`
23
+ )
24
+ .join('\n');
25
+ return fail(`ESLint failed on non-generated files:\n${summary}`);
26
+ }
27
+ }
@@ -0,0 +1,156 @@
1
+ import path from 'path';
2
+ import { promises as fs } from 'fs';
3
+ import { formatWithPrettier } from './formatWithPrettier.js';
4
+ import { fail } from './fail.js';
5
+ import { validateExistingComponentTsx } from './validateExistingComponentTsx.js';
6
+
7
+ // single-use: get all entries with filtering
8
+ async function getAllEntriesInComponentsRoot(paths) {
9
+ const { COMPONENTS_ROOT } = paths;
10
+ const exists = await fs
11
+ .stat(COMPONENTS_ROOT)
12
+ .then(() => true)
13
+ .catch(() => false);
14
+ if (!exists) {
15
+ return fail(`Components root not found at ${COMPONENTS_ROOT}`);
16
+ }
17
+ return await fs
18
+ .readdir(COMPONENTS_ROOT, { withFileTypes: true })
19
+ .then(dirents => {
20
+ return dirents.filter(
21
+ d =>
22
+ d.name !== 'RenderNode.generated.tsx' &&
23
+ d.name !== 'other.ts' &&
24
+ d.name !== 'index.ts'
25
+ );
26
+ });
27
+ }
28
+
29
+ // single-use: ensure all entries are directories
30
+ function ensureAllEntriesAreFolders(dirents, paths) {
31
+ const { COMPONENTS_ROOT } = paths;
32
+ const notDirs = dirents.filter(d => !d.isDirectory()).map(d => d.name);
33
+ if (notDirs.length > 0) {
34
+ return fail(
35
+ `All entries in ${COMPONENTS_ROOT} must be directories. Non-directories found: ${notDirs.join(', ')}`
36
+ );
37
+ }
38
+ }
39
+
40
+ // single-use: ensure fallback other.ts exists
41
+ async function ensureOtherTsExists(paths) {
42
+ const { COMPONENTS_ROOT } = paths;
43
+ const otherPath = path.join(COMPONENTS_ROOT, 'other.ts');
44
+ const exists = await fs
45
+ .stat(otherPath)
46
+ .then(() => true)
47
+ .catch(() => false);
48
+ if (exists) return;
49
+
50
+ const content =
51
+ `import React from 'react';\n` +
52
+ `import type { Node } from '../types/Node';\n\n` +
53
+ `export function other(type: string, node: Node): React.ReactNode {\n` +
54
+ ` return null;\n` +
55
+ `}\n`;
56
+ const formatted = await formatWithPrettier(content);
57
+ await fs.writeFile(otherPath, formatted, 'utf8');
58
+ }
59
+
60
+ // single-use: validate pattern.json
61
+ async function validatePatternJson(componentDir, componentName) {
62
+ const patternPath = path.join(componentDir, 'pattern.json');
63
+ const exists = await fs
64
+ .stat(patternPath)
65
+ .then(() => true)
66
+ .catch(() => false);
67
+ if (!exists) {
68
+ return fail(
69
+ `Missing pattern.json for component ${componentName} at ${patternPath}`
70
+ );
71
+ }
72
+ const content = await fs.readFile(patternPath, 'utf8');
73
+ let data;
74
+ try {
75
+ data = JSON.parse(content);
76
+ } catch (error) {
77
+ const reason =
78
+ error instanceof SyntaxError
79
+ ? 'Invalid JSON'
80
+ : error.code || error.message;
81
+ return fail(`Failed to read JSON at ${patternPath}: ${reason}`);
82
+ }
83
+
84
+ if (typeof data.schemaVersion !== 'number') {
85
+ return fail(
86
+ `[${componentName}] pattern.json -> 'schemaVersion' must be a number`
87
+ );
88
+ }
89
+ if (typeof data.allowUnknownAttributes !== 'boolean') {
90
+ return fail(
91
+ `[${componentName}] pattern.json -> 'allowUnknownAttributes' must be a boolean`
92
+ );
93
+ }
94
+ if (typeof data.pattern !== 'object' || data.pattern == null) {
95
+ return fail(
96
+ `[${componentName}] pattern.json -> 'pattern' must be an object`
97
+ );
98
+ }
99
+ const { pattern } = data;
100
+ if (typeof pattern.type !== 'string') {
101
+ return fail(
102
+ `[${componentName}] pattern.json -> 'pattern.type' must be a string`
103
+ );
104
+ }
105
+ if (
106
+ typeof pattern.children !== 'string' &&
107
+ !Array.isArray(pattern.children)
108
+ ) {
109
+ return fail(
110
+ `[${componentName}] pattern.json -> 'pattern.children' must be a string or an array`
111
+ );
112
+ }
113
+ if (typeof pattern.attributes !== 'object' || pattern.attributes == null) {
114
+ return fail(
115
+ `[${componentName}] pattern.json -> 'pattern.attributes' must be an object`
116
+ );
117
+ }
118
+
119
+ for (const [attrName, attrType] of Object.entries(pattern.attributes)) {
120
+ const isValidType =
121
+ typeof attrType === 'string' &&
122
+ (attrType === 'string' || attrType === 'number' || attrType === 'boolean')
123
+ ? true
124
+ : Array.isArray(attrType) && attrType.every(v => typeof v === 'string');
125
+ if (!isValidType) {
126
+ return fail(
127
+ `[${componentName}] pattern.json -> 'pattern.attributes.${attrName}' must be 'string' | 'number' | 'boolean' | string[]`
128
+ );
129
+ }
130
+ }
131
+
132
+ return data;
133
+ }
134
+
135
+ // single-use: component-level validation aggregator
136
+ async function validateComponent(componentDir, componentName) {
137
+ const patternJson = await validatePatternJson(componentDir, componentName);
138
+ await validateExistingComponentTsx(componentDir, componentName);
139
+ return { componentDir, componentName, patternJson };
140
+ }
141
+
142
+ export async function validateAllComponentsOrThrow(paths) {
143
+ const dirents = await getAllEntriesInComponentsRoot(paths);
144
+ ensureAllEntriesAreFolders(dirents, paths);
145
+
146
+ const validated = [];
147
+ for (const dirent of dirents) {
148
+ const componentName = dirent.name;
149
+ const componentDir = path.join(paths.COMPONENTS_ROOT, componentName);
150
+ const result = await validateComponent(componentDir, componentName);
151
+ validated.push(result);
152
+ }
153
+ // Ensure fallback renderer exists for unknown component types
154
+ await ensureOtherTsExists(paths);
155
+ return validated;
156
+ }
@@ -0,0 +1,40 @@
1
+ import path from 'path';
2
+ import { promises as fs } from 'fs';
3
+ import { fail } from './fail.js';
4
+
5
+ export async function validateExistingComponentTsx(
6
+ componentDir,
7
+ componentName
8
+ ) {
9
+ const filePath = path.join(componentDir, `${componentName}.tsx`);
10
+ const exists = await fs
11
+ .stat(filePath)
12
+ .then(() => true)
13
+ .catch(() => false);
14
+ if (!exists) return; // Created later if missing
15
+
16
+ const content = await fs.readFile(filePath, 'utf8');
17
+ const hasConstDeclaration = new RegExp(`const\\s+${componentName}\\s*:`).test(
18
+ content
19
+ );
20
+ const hasFunctionDeclaration = new RegExp(
21
+ `function\\s+${componentName}\\s*\\(`
22
+ ).test(content);
23
+ const hasDeclaration = hasConstDeclaration || hasFunctionDeclaration;
24
+ const defaultExportMemo = new RegExp(
25
+ `export\\s+default\\s+React\\.memo\\(\\s*${componentName}\\s*\\)\\s*;?`
26
+ ).test(content);
27
+ const usesMemo = /React\\.memo\(/.test(content) || defaultExportMemo;
28
+
29
+ if (!hasDeclaration) {
30
+ return fail(
31
+ `${filePath} must declare component as 'const ${componentName}: React.FC = (...)' or 'function ${componentName}()'`
32
+ );
33
+ }
34
+ if (!usesMemo) {
35
+ return fail(`${filePath} must use React.memo`);
36
+ }
37
+ if (!defaultExportMemo) {
38
+ return fail(`${filePath} must default export React.memo(${componentName})`);
39
+ }
40
+ }
@@ -0,0 +1,77 @@
1
+ import path from 'path';
2
+ import { promises as fs } from 'fs';
3
+ import { fail } from './fail.js';
4
+
5
+ export async function validatePatternJson(componentDir, componentName) {
6
+ const patternPath = path.join(componentDir, 'pattern.json');
7
+ const exists = await fs
8
+ .stat(patternPath)
9
+ .then(() => true)
10
+ .catch(() => false);
11
+ if (!exists) {
12
+ return fail(
13
+ `Missing pattern.json for component ${componentName} at ${patternPath}`
14
+ );
15
+ }
16
+ const content = await fs.readFile(patternPath, 'utf8');
17
+ let data;
18
+ try {
19
+ data = JSON.parse(content);
20
+ } catch (error) {
21
+ const reason =
22
+ error instanceof SyntaxError
23
+ ? 'Invalid JSON'
24
+ : error.code || error.message;
25
+ return fail(`Failed to read JSON at ${patternPath}: ${reason}`);
26
+ }
27
+
28
+ if (typeof data.schemaVersion !== 'number') {
29
+ return fail(
30
+ `[${componentName}] pattern.json -> 'schemaVersion' must be a number`
31
+ );
32
+ }
33
+ if (typeof data.allowUnknownAttributes !== 'boolean') {
34
+ return fail(
35
+ `[${componentName}] pattern.json -> 'allowUnknownAttributes' must be a boolean`
36
+ );
37
+ }
38
+ if (typeof data.pattern !== 'object' || data.pattern == null) {
39
+ return fail(
40
+ `[${componentName}] pattern.json -> 'pattern' must be an object`
41
+ );
42
+ }
43
+ const { pattern } = data;
44
+ if (typeof pattern.type !== 'string') {
45
+ return fail(
46
+ `[${componentName}] pattern.json -> 'pattern.type' must be a string`
47
+ );
48
+ }
49
+ if (
50
+ typeof pattern.children !== 'string' &&
51
+ !Array.isArray(pattern.children)
52
+ ) {
53
+ return fail(
54
+ `[${componentName}] pattern.json -> 'pattern.children' must be a string or an array`
55
+ );
56
+ }
57
+ if (typeof pattern.attributes !== 'object' || pattern.attributes == null) {
58
+ return fail(
59
+ `[${componentName}] pattern.json -> 'pattern.attributes' must be an object`
60
+ );
61
+ }
62
+
63
+ for (const [attrName, attrType] of Object.entries(pattern.attributes)) {
64
+ const isValidType =
65
+ typeof attrType === 'string' &&
66
+ (attrType === 'string' || attrType === 'number' || attrType === 'boolean')
67
+ ? true
68
+ : Array.isArray(attrType) && attrType.every(v => typeof v === 'string');
69
+ if (!isValidType) {
70
+ return fail(
71
+ `[${componentName}] pattern.json -> 'pattern.attributes.${attrName}' must be 'string' | 'number' | 'boolean' | string[]`
72
+ );
73
+ }
74
+ }
75
+
76
+ return data;
77
+ }
@@ -5,7 +5,6 @@ import yargs from 'yargs/yargs';
5
5
  import { hideBin } from 'yargs/helpers';
6
6
  import Path from 'path';
7
7
  import { fileURLToPath } from 'url';
8
-
9
8
  const cli = yargs(hideBin(process.argv));
10
9
 
11
10
  const __dirname = Path.dirname(fileURLToPath(import.meta.url));
@@ -40,11 +39,7 @@ folderNames.forEach(folderName => {
40
39
  });
41
40
  },
42
41
  args => {
43
- const keys = Object.keys(json.options ?? {});
44
- const dynamicArgs = Object.entries(args ?? {})
45
- .filter(([key]) => keys.includes(key)) // Only accept specified keys
46
- .map(([key, value]) => `--${key} "${value}"`)
47
- .join(' ');
42
+ const dynamicArgs = process.argv.splice(2).join(' ');
48
43
  execSync(
49
44
  `node ${Path.join(directoryPath, folderName, 'index.js')} ${dynamicArgs}`,
50
45
  {
@@ -12,8 +12,9 @@ import { createRenderNodeGenerated } from './utils/createRenderNodeGenerated.js'
12
12
 
13
13
  const argv = yargs(hideBin(process.argv)).argv;
14
14
  export const args = argv;
15
- const builderPath = args.path || '/node_modules/@developer_tribe/react-builder';
15
+ const builderPath = args.path || 'node_modules/@developer_tribe/react-builder';
16
16
  const targetPath = process.cwd();
17
+ const selectedComponents = args.components || [];
17
18
 
18
19
  function run() {
19
20
  const builderComponentsPath = Path.join(
@@ -23,9 +24,19 @@ function run() {
23
24
  );
24
25
  checkPathExists(builderComponentsPath);
25
26
  const components = getAllComponents(builderComponentsPath);
26
- checkFolderAndFilesValid(targetPath, components);
27
- createMissingFoldersAndFiles(targetPath, components);
28
- createRenderNodeGenerated(builderComponentsPath, targetPath, components);
27
+ for (const component of selectedComponents) {
28
+ if (!components.includes(component)) {
29
+ console.error(`Component ${component} not found`);
30
+ process.exit(1);
31
+ }
32
+ }
33
+ checkFolderAndFilesValid(targetPath, selectedComponents);
34
+ createMissingFoldersAndFiles(targetPath, selectedComponents);
35
+ createRenderNodeGenerated(
36
+ builderComponentsPath,
37
+ targetPath,
38
+ selectedComponents
39
+ );
29
40
  }
30
41
 
31
42
  run();
@@ -6,6 +6,12 @@
6
6
  "type": "string",
7
7
  "describe": "path of project",
8
8
  "demandOption": false
9
+ },
10
+ "components": {
11
+ "alias": "c",
12
+ "type": "array",
13
+ "describe": "component names",
14
+ "demandOption": false
9
15
  }
10
16
  }
11
17
  }
@@ -1,10 +1,5 @@
1
1
  import React from 'react';
2
- import type { NodeData } from '../../types/Node';
3
- import type { ButtonPropsGenerated } from './ButtonProps.generated';
4
-
5
- type ButtonComponentProps = {
6
- node: NodeData<ButtonPropsGenerated['attributes']>;
7
- };
2
+ import type { ButtonComponentProps } from './ButtonProps.generated';
8
3
 
9
4
  function Button({ node }: ButtonComponentProps) {
10
5
  return String(node?.type ?? 'button');
@@ -1,5 +1,7 @@
1
1
  /* AUTO-GENERATED FILE - DO NOT EDIT */
2
2
 
3
+ import type { NodeData } from '../../types/Node';
4
+
3
5
  export interface ButtonPropsGenerated {
4
6
  child: string;
5
7
  attributes: {
@@ -19,3 +21,7 @@ export interface ButtonPropsGenerated {
19
21
  | '900';
20
22
  };
21
23
  }
24
+
25
+ export interface ButtonComponentProps {
26
+ node: NodeData<ButtonPropsGenerated['attributes']>;
27
+ }
@@ -1,13 +1,8 @@
1
1
  import React from 'react';
2
- import type { NodeData } from '../../types/Node';
3
- import type { CarouselPropsGenerated } from './CarouselProps.generated';
2
+ import type { CarouselComponentProps } from './CarouselProps.generated';
4
3
  import RenderNode from '../RenderNode.generated';
5
4
  import { isCarouselItem } from '../../utils/isCarousel';
6
5
 
7
- type CarouselComponentProps = {
8
- node: NodeData<CarouselPropsGenerated['attributes']>;
9
- };
10
-
11
6
  function Carousel({ node }: CarouselComponentProps) {
12
7
  // Ensure children are carouselItems
13
8
  const renderChildren = () => {
@@ -1,6 +1,12 @@
1
1
  /* AUTO-GENERATED FILE - DO NOT EDIT */
2
2
 
3
+ import type { NodeData } from '../../types/Node';
4
+
3
5
  export interface CarouselPropsGenerated {
4
6
  child: string;
5
7
  attributes: {};
6
8
  }
9
+
10
+ export interface CarouselComponentProps {
11
+ node: NodeData<CarouselPropsGenerated['attributes']>;
12
+ }
@@ -1,12 +1,7 @@
1
1
  import React, { useContext } from 'react';
2
- import type { NodeData } from '../../types/Node';
3
- import type { CarouselButtonsPropsGenerated } from './CarouselButtonsProps.generated';
2
+ import type { CarouselButtonsComponentProps } from './CarouselButtonsProps.generated';
4
3
  import { carouselContext } from '../CarouselProvider/CarouselProvider';
5
4
 
6
- type CarouselButtonsComponentProps = {
7
- node: NodeData<CarouselButtonsPropsGenerated['attributes']>;
8
- };
9
-
10
5
  function CarouselButtons({ node }: CarouselButtonsComponentProps) {
11
6
  const emblaApi = useContext(carouselContext);
12
7
  const buttonTypes = node.attributes?.buttonType || [
@@ -1,5 +1,7 @@
1
1
  /* AUTO-GENERATED FILE - DO NOT EDIT */
2
2
 
3
+ import type { NodeData } from '../../types/Node';
4
+
3
5
  export interface CarouselButtonsPropsGenerated {
4
6
  child: string;
5
7
  attributes: {
@@ -7,3 +9,7 @@ export interface CarouselButtonsPropsGenerated {
7
9
  skipNumber?: number;
8
10
  };
9
11
  }
12
+
13
+ export interface CarouselButtonsComponentProps {
14
+ node: NodeData<CarouselButtonsPropsGenerated['attributes']>;
15
+ }
@@ -1,12 +1,7 @@
1
1
  import React, { useContext, useEffect, useState } from 'react';
2
- import type { NodeData } from '../../types/Node';
3
- import type { CarouselDotsPropsGenerated } from './CarouselDotsProps.generated';
2
+ import type { CarouselDotsComponentProps } from './CarouselDotsProps.generated';
4
3
  import { carouselContext } from '../CarouselProvider/CarouselProvider';
5
4
 
6
- type CarouselDotsComponentProps = {
7
- node: NodeData<CarouselDotsPropsGenerated['attributes']>;
8
- };
9
-
10
5
  function CarouselDots({ node }: CarouselDotsComponentProps) {
11
6
  const dotType = node.attributes?.dotType || 'normal_dot';
12
7
  const emblaApi = useContext(carouselContext);
@@ -1,5 +1,7 @@
1
1
  /* AUTO-GENERATED FILE - DO NOT EDIT */
2
2
 
3
+ import type { NodeData } from '../../types/Node';
4
+
3
5
  export interface CarouselDotsPropsGenerated {
4
6
  child: string;
5
7
  attributes: {
@@ -12,3 +14,7 @@ export interface CarouselDotsPropsGenerated {
12
14
  | 'liquid_like';
13
15
  };
14
16
  }
17
+
18
+ export interface CarouselDotsComponentProps {
19
+ node: NodeData<CarouselDotsPropsGenerated['attributes']>;
20
+ }