blockparty 0.1.0

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 (51) hide show
  1. package/README.md +102 -0
  2. package/dist/cli.d.ts +3 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +54 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/commands/build.d.ts +2 -0
  7. package/dist/commands/build.d.ts.map +1 -0
  8. package/dist/commands/build.js +66 -0
  9. package/dist/commands/build.js.map +1 -0
  10. package/dist/commands/storybook.d.ts +2 -0
  11. package/dist/commands/storybook.d.ts.map +1 -0
  12. package/dist/commands/storybook.js +95 -0
  13. package/dist/commands/storybook.js.map +1 -0
  14. package/dist/discoverBlocks.d.ts +9 -0
  15. package/dist/discoverBlocks.d.ts.map +1 -0
  16. package/dist/discoverBlocks.js +63 -0
  17. package/dist/discoverBlocks.js.map +1 -0
  18. package/dist/extractProps.d.ts +10 -0
  19. package/dist/extractProps.d.ts.map +1 -0
  20. package/dist/extractProps.js +251 -0
  21. package/dist/extractProps.js.map +1 -0
  22. package/dist/extractProps.test.d.ts +2 -0
  23. package/dist/extractProps.test.d.ts.map +1 -0
  24. package/dist/extractProps.test.js +305 -0
  25. package/dist/extractProps.test.js.map +1 -0
  26. package/dist/generateBlocksModule.d.ts +3 -0
  27. package/dist/generateBlocksModule.d.ts.map +1 -0
  28. package/dist/generateBlocksModule.js +21 -0
  29. package/dist/generateBlocksModule.js.map +1 -0
  30. package/dist/index.d.ts +4 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +5 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/parseReadme.d.ts +15 -0
  35. package/dist/parseReadme.d.ts.map +1 -0
  36. package/dist/parseReadme.js +84 -0
  37. package/dist/parseReadme.js.map +1 -0
  38. package/dist/parseReadme.test.d.ts +2 -0
  39. package/dist/parseReadme.test.d.ts.map +1 -0
  40. package/dist/parseReadme.test.js +142 -0
  41. package/dist/parseReadme.test.js.map +1 -0
  42. package/dist/templates/App.tsx +236 -0
  43. package/dist/templates/ErrorBoundary.tsx +53 -0
  44. package/dist/templates/PropsEditor.tsx +707 -0
  45. package/dist/templates/index.html +27 -0
  46. package/dist/templates/index.tsx +10 -0
  47. package/dist/viteConfig.d.ts +12 -0
  48. package/dist/viteConfig.d.ts.map +1 -0
  49. package/dist/viteConfig.js +22 -0
  50. package/dist/viteConfig.js.map +1 -0
  51. package/package.json +60 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseReadme.d.ts","sourceRoot":"","sources":["../src/parseReadme.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,WAAW,CAAC,EAAE,MAAM,CAAA;IAEpB,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACrC;AAGD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG;IAAE,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAyB1G;AAGD,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,GAAG,aAAa,CAiCtE;AAED,wBAAsB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAgCjF"}
@@ -0,0 +1,84 @@
1
+ import { readFile } from 'fs/promises';
2
+ import { existsSync } from 'fs';
3
+ import { join } from 'path';
4
+ // exported for tests
5
+ export function parseFrontmatter(content) {
6
+ const frontmatterRegex = /^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/;
7
+ const match = content.match(frontmatterRegex);
8
+ if (!match) {
9
+ return { frontmatter: {}, content };
10
+ }
11
+ const frontmatterText = match[1];
12
+ const remainingContent = match[2];
13
+ const frontmatter = {};
14
+ const lines = frontmatterText.split('\n');
15
+ for (const line of lines) {
16
+ const colonIndex = line.indexOf(':');
17
+ if (colonIndex > 0) {
18
+ const key = line.slice(0, colonIndex).trim();
19
+ const value = line.slice(colonIndex + 1).trim();
20
+ // Remove quotes if present
21
+ frontmatter[key] = value.replace(/^["']|["']$/g, '');
22
+ }
23
+ }
24
+ return { frontmatter, content: remainingContent };
25
+ }
26
+ // exported for tests
27
+ export function extractMarkdownMetadata(content) {
28
+ const lines = content.trim().split('\n');
29
+ let name;
30
+ let description;
31
+ // Look for first heading
32
+ for (let i = 0; i < lines.length; i++) {
33
+ const line = lines[i].trim();
34
+ if (line.startsWith('#')) {
35
+ // Found a heading - extract the name
36
+ name = line.replace(/^#+\s*/, '').trim();
37
+ // Look for the first non-empty paragraph after the heading
38
+ for (let j = i + 1; j < lines.length; j++) {
39
+ const nextLine = lines[j].trim();
40
+ // Skip empty lines
41
+ if (!nextLine)
42
+ continue;
43
+ // Skip if it's another heading
44
+ if (nextLine.startsWith('#'))
45
+ break;
46
+ // Found a paragraph - this is the description
47
+ description = nextLine;
48
+ break;
49
+ }
50
+ break;
51
+ }
52
+ }
53
+ return { name, description };
54
+ }
55
+ export async function parseReadmeMetadata(dirPath) {
56
+ const readmePath = join(dirPath, 'README.md');
57
+ if (!existsSync(readmePath)) {
58
+ return {};
59
+ }
60
+ try {
61
+ const content = await readFile(readmePath, 'utf-8');
62
+ const { frontmatter, content: remainingContent } = parseFrontmatter(content);
63
+ let name = frontmatter.name;
64
+ let description = frontmatter.description;
65
+ // If name or description is missing from frontmatter, extract from markdown
66
+ if (!name || !description) {
67
+ const markdownMetadata = extractMarkdownMetadata(remainingContent);
68
+ name ?? (name = markdownMetadata.name);
69
+ description ?? (description = markdownMetadata.description);
70
+ }
71
+ const result = {
72
+ name,
73
+ description,
74
+ readme: remainingContent,
75
+ frontmatter
76
+ };
77
+ return result;
78
+ }
79
+ catch (error) {
80
+ console.error(`Error parsing README.md at ${readmePath}:`, error);
81
+ return {};
82
+ }
83
+ }
84
+ //# sourceMappingURL=parseReadme.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseReadme.js","sourceRoot":"","sources":["../src/parseReadme.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=parseReadme.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseReadme.test.d.ts","sourceRoot":"","sources":["../src/parseReadme.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,142 @@
1
+ import { test, describe } from 'node:test';
2
+ import assert from 'node:assert';
3
+ import { parseFrontmatter, extractMarkdownMetadata } from './parseReadme.js';
4
+ describe('parseFrontmatter', () => {
5
+ test('extracts frontmatter and remaining content', () => {
6
+ const content = `---
7
+ name: Big Numbers
8
+ description: Display large numbers
9
+ ---
10
+
11
+ # Heading
12
+
13
+ Content here`;
14
+ const result = parseFrontmatter(content);
15
+ assert.strictEqual(result.frontmatter.name, 'Big Numbers');
16
+ assert.strictEqual(result.frontmatter.description, 'Display large numbers');
17
+ assert.strictEqual(result.content.trim(), '# Heading\n\nContent here');
18
+ });
19
+ test('handles frontmatter with quoted values', () => {
20
+ const content = `---
21
+ name: "My Component"
22
+ description: 'A cool component'
23
+ ---
24
+
25
+ Content`;
26
+ const result = parseFrontmatter(content);
27
+ assert.strictEqual(result.frontmatter.name, 'My Component');
28
+ assert.strictEqual(result.frontmatter.description, 'A cool component');
29
+ });
30
+ test('returns empty frontmatter when no frontmatter present', () => {
31
+ const content = `# Heading
32
+
33
+ Just some content without frontmatter`;
34
+ const result = parseFrontmatter(content);
35
+ assert.deepStrictEqual(result.frontmatter, {});
36
+ assert.strictEqual(result.content, content);
37
+ });
38
+ test('handles frontmatter with multiple properties', () => {
39
+ const content = `---
40
+ name: Component
41
+ description: A description
42
+ author: John Doe
43
+ version: 1.0.0
44
+ ---
45
+
46
+ Content`;
47
+ const result = parseFrontmatter(content);
48
+ assert.strictEqual(result.frontmatter.name, 'Component');
49
+ assert.strictEqual(result.frontmatter.description, 'A description');
50
+ assert.strictEqual(result.frontmatter.author, 'John Doe');
51
+ assert.strictEqual(result.frontmatter.version, '1.0.0');
52
+ });
53
+ test('handles empty frontmatter', () => {
54
+ const content = `---
55
+
56
+ ---
57
+
58
+ Content`;
59
+ const result = parseFrontmatter(content);
60
+ assert.deepStrictEqual(result.frontmatter, {});
61
+ assert.strictEqual(result.content, 'Content');
62
+ });
63
+ });
64
+ describe('extractMarkdownMetadata', () => {
65
+ test('extracts name from first heading and description from first paragraph', () => {
66
+ const content = `# Big Numbers
67
+
68
+ Display large numerical values with formatting
69
+
70
+ Additional content here`;
71
+ const result = extractMarkdownMetadata(content);
72
+ assert.strictEqual(result.name, 'Big Numbers');
73
+ assert.strictEqual(result.description, 'Display large numerical values with formatting');
74
+ });
75
+ test('handles h2 headings', () => {
76
+ const content = `## Component Name
77
+
78
+ This is the description`;
79
+ const result = extractMarkdownMetadata(content);
80
+ assert.strictEqual(result.name, 'Component Name');
81
+ assert.strictEqual(result.description, 'This is the description');
82
+ });
83
+ test('handles headings with multiple hash marks', () => {
84
+ const content = `#### Deep Heading
85
+
86
+ Description text`;
87
+ const result = extractMarkdownMetadata(content);
88
+ assert.strictEqual(result.name, 'Deep Heading');
89
+ assert.strictEqual(result.description, 'Description text');
90
+ });
91
+ test('skips empty lines between heading and description', () => {
92
+ const content = `# Component
93
+
94
+
95
+ Description after blank lines`;
96
+ const result = extractMarkdownMetadata(content);
97
+ assert.strictEqual(result.name, 'Component');
98
+ assert.strictEqual(result.description, 'Description after blank lines');
99
+ });
100
+ test('returns undefined when no heading present', () => {
101
+ const content = `Just some text without a heading`;
102
+ const result = extractMarkdownMetadata(content);
103
+ assert.strictEqual(result.name, undefined);
104
+ assert.strictEqual(result.description, undefined);
105
+ });
106
+ test('returns undefined description when no paragraph after heading', () => {
107
+ const content = `# Component Name
108
+
109
+ ## Another Heading`;
110
+ const result = extractMarkdownMetadata(content);
111
+ assert.strictEqual(result.name, 'Component Name');
112
+ assert.strictEqual(result.description, undefined);
113
+ });
114
+ test('handles heading-only content', () => {
115
+ const content = `# Lonely Heading`;
116
+ const result = extractMarkdownMetadata(content);
117
+ assert.strictEqual(result.name, 'Lonely Heading');
118
+ assert.strictEqual(result.description, undefined);
119
+ });
120
+ test('handles content with leading whitespace', () => {
121
+ const content = `
122
+
123
+ # Heading with Space
124
+
125
+ Description with space
126
+ `;
127
+ const result = extractMarkdownMetadata(content);
128
+ assert.strictEqual(result.name, 'Heading with Space');
129
+ assert.strictEqual(result.description, 'Description with space');
130
+ });
131
+ test('stops at next heading when looking for description', () => {
132
+ const content = `# First Heading
133
+
134
+ ## Second Heading
135
+
136
+ This should not be the description`;
137
+ const result = extractMarkdownMetadata(content);
138
+ assert.strictEqual(result.name, 'First Heading');
139
+ assert.strictEqual(result.description, undefined);
140
+ });
141
+ });
142
+ //# sourceMappingURL=parseReadme.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parseReadme.test.js","sourceRoot":"","sources":["../src/parseReadme.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,236 @@
1
+ import { useState, useEffect } from 'react'
2
+ import { ErrorBoundary } from './ErrorBoundary'
3
+ import { PropsEditor } from './PropsEditor'
4
+ import { blocks } from './blocks'
5
+
6
+ interface PropDefinition {
7
+ name: string
8
+ type: string
9
+ optional: boolean
10
+ }
11
+
12
+ const STORAGE_KEY = 'blockparty-state'
13
+
14
+ const isComplexType = (type: string) => {
15
+ return type.includes('[') || type.includes('{') || type.includes('|')
16
+ }
17
+
18
+ const getDefaultValue = (type: string, optional: boolean) => {
19
+ if (optional) return ''
20
+
21
+ if (type.includes('[]')) {
22
+ return '[]'
23
+ }
24
+
25
+ if (type.includes('{') || type.includes('object')) {
26
+ return '{}'
27
+ }
28
+
29
+ if (type === 'number') {
30
+ return '0'
31
+ }
32
+
33
+ if (type === 'boolean') {
34
+ return 'false'
35
+ }
36
+
37
+ return ''
38
+ }
39
+
40
+ const parseValue = (value: string, type: string, propDefs: PropDefinition[]) => {
41
+ if (!value) return value
42
+
43
+ // For React.ReactNode, parse and render the block(s) - always an array
44
+ if (type === 'React.ReactNode' || type === 'ReactNode') {
45
+ try {
46
+ const parsed = typeof value === 'string' ? JSON.parse(value) : value
47
+
48
+ if (Array.isArray(parsed)) {
49
+ return parsed.map((item, index) => {
50
+ if (item && typeof item === 'object' && '__block' in item) {
51
+ const blockName = item.__block
52
+ const blockProps = item.__props || {}
53
+ const block = blocks.find(b => b.name === blockName)
54
+
55
+ if (block) {
56
+ const parsedBlockProps = Object.fromEntries(
57
+ Object.entries(blockProps).map(([key, val]) => {
58
+ const propDef = block.propDefinitions.find((p: PropDefinition) => p.name === key)
59
+ return [key, propDef ? parseValue(val as string, propDef.type, block.propDefinitions) : val]
60
+ })
61
+ )
62
+ return <block.Component key={index} {...parsedBlockProps} />
63
+ }
64
+ }
65
+ return null
66
+ }).filter(Boolean)
67
+ }
68
+ } catch {
69
+ // Fall through
70
+ }
71
+ }
72
+
73
+ // For complex types, try to parse as JSON
74
+ if (isComplexType(type)) {
75
+ try {
76
+ return JSON.parse(value)
77
+ } catch {
78
+ return value
79
+ }
80
+ }
81
+
82
+ // For simple number type, parse as number
83
+ if (type === 'number') {
84
+ const num = Number(value)
85
+ return isNaN(num) ? value : num
86
+ }
87
+
88
+ // For boolean type
89
+ if (type === 'boolean') {
90
+ return value === 'true'
91
+ }
92
+
93
+ return value
94
+ }
95
+
96
+ const formatValue = (value: any, type: string) => {
97
+ if (value === undefined || value === null) return ''
98
+ if (isComplexType(type)) {
99
+ try {
100
+ return JSON.stringify(value, null, 2)
101
+ } catch {
102
+ return String(value)
103
+ }
104
+ }
105
+ return String(value)
106
+ }
107
+
108
+ export function App() {
109
+ const [selectedBlock, setSelectedBlock] = useState(() => {
110
+ try {
111
+ const saved = localStorage.getItem(STORAGE_KEY)
112
+ return saved ? JSON.parse(saved).selectedBlock ?? 0 : 0
113
+ } catch {
114
+ return 0
115
+ }
116
+ })
117
+
118
+ const [props, setProps] = useState<Record<string, string>>(() => {
119
+ try {
120
+ const saved = localStorage.getItem(STORAGE_KEY)
121
+ return saved ? JSON.parse(saved).props ?? {} : {}
122
+ } catch {
123
+ return {}
124
+ }
125
+ })
126
+
127
+ const currentBlock = blocks[selectedBlock]
128
+ const CurrentComponent = currentBlock.Component
129
+ const propDefinitions: PropDefinition[] = currentBlock.propDefinitions
130
+
131
+ useEffect(() => {
132
+ try {
133
+ localStorage.setItem(STORAGE_KEY, JSON.stringify({ selectedBlock, props }))
134
+ } catch (e) {
135
+ console.error('Failed to save state:', e)
136
+ }
137
+ }, [selectedBlock, props])
138
+
139
+ // Initialize props with defaults when block changes or prop definitions change
140
+ useEffect(() => {
141
+ const validPropNames = new Set(propDefinitions.map(p => p.name))
142
+
143
+ // Remove props that no longer exist in the prop definitions
144
+ const filteredProps = Object.fromEntries(
145
+ Object.entries(props).filter(([key]) => validPropNames.has(key))
146
+ )
147
+
148
+ // Add default values for required props that don't have values
149
+ const defaultProps = propDefinitions.reduce((acc: Record<string, string>, propDef) => {
150
+ if (!propDef.optional && !filteredProps[propDef.name]) {
151
+ acc[propDef.name] = getDefaultValue(propDef.type, propDef.optional)
152
+ }
153
+ return acc
154
+ }, {})
155
+
156
+ const newProps = { ...filteredProps, ...defaultProps }
157
+
158
+ // Only update if props actually changed
159
+ if (JSON.stringify(props) !== JSON.stringify(newProps)) {
160
+ setProps(newProps)
161
+ }
162
+ }, [selectedBlock, propDefinitions])
163
+
164
+ const parsedProps = Object.fromEntries(
165
+ Object.entries(props).map(([key, value]) => {
166
+ const propDef = propDefinitions.find((p: PropDefinition) => p.name === key)
167
+ return [key, propDef ? parseValue(value as string, propDef.type, propDefinitions) : value]
168
+ })
169
+ )
170
+
171
+ return (
172
+ <div style={{ display: 'flex', height: '100vh', fontFamily: 'system-ui, sans-serif' }}>
173
+ {/* Left sidebar - block list */}
174
+ <aside style={{ width: '200px', borderRight: '1px solid #ddd', padding: '20px', overflow: 'auto' }}>
175
+ <h2 style={{ marginTop: 0, fontSize: '18px' }}>🎉 Block Party</h2>
176
+
177
+ <h3 style={{ fontSize: '14px', textTransform: 'uppercase', color: '#666', marginTop: '24px' }}>Blocks</h3>
178
+ <ul style={{ listStyle: 'none', padding: 0 }}>
179
+ {blocks.map((block, idx) => (
180
+ <li key={idx}>
181
+ <button
182
+ onClick={() => {
183
+ setSelectedBlock(idx)
184
+ setProps({})
185
+ }}
186
+ style={{
187
+ width: '100%',
188
+ padding: '8px 12px',
189
+ textAlign: 'left',
190
+ border: 'none',
191
+ background: selectedBlock === idx ? '#0066ff' : 'transparent',
192
+ color: selectedBlock === idx ? 'white' : 'black',
193
+ cursor: 'pointer',
194
+ borderRadius: '4px',
195
+ marginBottom: '4px'
196
+ }}
197
+ >
198
+ {block.name}
199
+ </button>
200
+ </li>
201
+ ))}
202
+ </ul>
203
+ </aside>
204
+
205
+ {/* Center - component preview */}
206
+ <main style={{ flex: 1, padding: '40px', overflow: 'auto' }}>
207
+ <div style={{ maxWidth: '1200px', margin: '0 auto' }}>
208
+ <ErrorBoundary key={`${selectedBlock}-${JSON.stringify(props)}`}>
209
+ <CurrentComponent {...parsedProps} />
210
+ </ErrorBoundary>
211
+ </div>
212
+ </main>
213
+
214
+ {/* Right sidebar - description and props */}
215
+ <aside style={{ width: '320px', borderLeft: '1px solid #ddd', padding: '20px', overflow: 'auto', background: '#fafafa' }}>
216
+ <h3 style={{ marginTop: 0, fontSize: '16px' }}>{currentBlock.name}</h3>
217
+
218
+ {currentBlock.description && (
219
+ <div style={{ marginBottom: '24px', padding: '12px', background: '#fff', borderRadius: '4px', border: '1px solid #e0e0e0' }}>
220
+ <p style={{ fontSize: '12px', color: '#666', margin: 0, lineHeight: '1.5' }}>
221
+ {currentBlock.description}
222
+ </p>
223
+ </div>
224
+ )}
225
+
226
+ <h4 style={{ fontSize: '14px', textTransform: 'uppercase', color: '#666', marginBottom: '12px' }}>Props</h4>
227
+ <PropsEditor
228
+ propDefinitions={propDefinitions}
229
+ props={props}
230
+ onPropsChange={setProps}
231
+ availableBlocks={blocks}
232
+ />
233
+ </aside>
234
+ </div>
235
+ )
236
+ }
@@ -0,0 +1,53 @@
1
+ import { Component } from 'react'
2
+
3
+ interface ErrorBoundaryProps {
4
+ children: React.ReactNode
5
+ }
6
+
7
+ interface ErrorBoundaryState {
8
+ hasError: boolean
9
+ error: Error | null
10
+ }
11
+
12
+ export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
13
+ constructor(props: ErrorBoundaryProps) {
14
+ super(props)
15
+ this.state = { hasError: false, error: null }
16
+ }
17
+
18
+ static getDerivedStateFromError(error: Error) {
19
+ return { hasError: true, error }
20
+ }
21
+
22
+ componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
23
+ console.error('Component error:', error, errorInfo)
24
+ }
25
+
26
+ render() {
27
+ if (this.state.hasError) {
28
+ return (
29
+ <div style={{
30
+ padding: '20px',
31
+ background: '#fee',
32
+ border: '1px solid #fcc',
33
+ borderRadius: '4px',
34
+ color: '#c33',
35
+ fontFamily: 'monospace',
36
+ fontSize: '12px'
37
+ }}>
38
+ <strong style={{ display: 'block', marginBottom: '8px', fontSize: '14px' }}>
39
+ ⚠️ Component Error
40
+ </strong>
41
+ <pre style={{ margin: 0, whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}>
42
+ {this.state.error?.toString()}
43
+ </pre>
44
+ <p style={{ marginTop: '12px', fontSize: '11px', color: '#666' }}>
45
+ Check your props values. Complex types should be valid JSON.
46
+ </p>
47
+ </div>
48
+ )
49
+ }
50
+
51
+ return this.props.children
52
+ }
53
+ }