create-visualbuild-app 0.1.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +919 -58
  2. package/docs/user-guide.md +759 -0
  3. package/package.json +92 -85
  4. package/scripts/create-builder-app.mjs +513 -501
  5. package/scripts/vb-dev.mjs +41 -32
  6. package/scripts/vb-generate.mjs +332 -224
  7. package/templates/default/app/.vercelignore +6 -0
  8. package/templates/default/app/README.md +56 -21
  9. package/templates/default/app/visualbuild/components.json +3 -0
  10. package/templates/default/app/visualbuild/config.d.ts +48 -0
  11. package/templates/default/app/visualbuild.config.ts +38 -0
  12. package/templates/default/builder/README.md +37 -21
  13. package/visual-app-builder/server/parseReactPage.ts +776 -571
  14. package/visual-app-builder/shared/createReactComponentSource.d.mts +2 -0
  15. package/visual-app-builder/shared/createReactComponentSource.mjs +45 -0
  16. package/visual-app-builder/shared/generateReactSource.d.mts +57 -37
  17. package/visual-app-builder/shared/generateReactSource.mjs +608 -443
  18. package/visual-app-builder/shared/npmBuildCommand.d.mts +17 -0
  19. package/visual-app-builder/shared/npmBuildCommand.mjs +26 -0
  20. package/visual-app-builder/shared/syncCustomComponentSource.d.mts +13 -0
  21. package/visual-app-builder/shared/syncCustomComponentSource.mjs +345 -0
  22. package/visual-app-builder/shared/vercelDeployment.d.mts +31 -0
  23. package/visual-app-builder/shared/vercelDeployment.mjs +266 -0
  24. package/visual-app-builder/shared/visualbuildConfig.d.mts +144 -0
  25. package/visual-app-builder/shared/visualbuildConfig.mjs +578 -0
  26. package/visual-app-builder/src/App.tsx +1090 -874
  27. package/visual-app-builder/src/components/Canvas.tsx +1116 -1059
  28. package/visual-app-builder/src/components/CodePanel.tsx +1147 -812
  29. package/visual-app-builder/src/components/ComponentSidebar.tsx +365 -302
  30. package/visual-app-builder/src/components/DeploymentModal.tsx +295 -0
  31. package/visual-app-builder/src/components/PageSwitcher.tsx +133 -133
  32. package/visual-app-builder/src/components/ProjectExplorer.tsx +1054 -1054
  33. package/visual-app-builder/src/components/PropertiesPanel.tsx +792 -692
  34. package/visual-app-builder/src/components/Topbar.tsx +257 -128
  35. package/visual-app-builder/src/data/componentRegistry.tsx +613 -292
  36. package/visual-app-builder/src/data/tailwindClassCatalog.ts +95 -0
  37. package/visual-app-builder/src/index.css +383 -111
  38. package/visual-app-builder/src/stores/useAppStore.ts +2385 -1265
  39. package/visual-app-builder/src/theme.ts +71 -0
  40. package/visual-app-builder/src/types/index.ts +350 -261
  41. package/visual-app-builder/src/utils/codegen.ts +90 -66
  42. package/visual-app-builder/src/utils/deployment.ts +54 -0
  43. package/visual-app-builder/src/utils/projectPersistence.ts +218 -177
  44. package/visual-app-builder/vite.config.ts +1946 -1479
@@ -1,66 +1,90 @@
1
- import {
2
- createPageSourcePath as createAstPageSourcePath,
3
- generateAppSource,
4
- generateLayoutSource,
5
- generateNodeSource,
6
- generatePageSource,
7
- getPageSourcePath as getAstPageSourcePath,
8
- toPageComponentName as toAstPageComponentName,
9
- } from '../../shared/generateReactSource.mjs'
10
- import type { AppShell, ComponentType, Page, VisualNode } from '../types'
11
-
12
- export function generatePageCode(page: Page): string {
13
- return generatePageSource(page).trimEnd()
14
- }
15
-
16
- export function generateAppCode(pages: Page[], appShell: AppShell): string {
17
- return generateAppSource(pages, appShell).trimEnd()
18
- }
19
-
20
- export function generateLayoutCode(appShell: AppShell): string {
21
- return generateLayoutSource(appShell).trimEnd()
22
- }
23
-
24
- export function generateProjectSchema(
25
- pages: Page[],
26
- appShell: AppShell,
27
- projectName = 'my-blog'
28
- ): string {
29
- return JSON.stringify(
30
- {
31
- meta: {
32
- projectName,
33
- framework: 'react',
34
- styling: 'tailwind',
35
- },
36
- appShell,
37
- pages,
38
- },
39
- null,
40
- 2
41
- )
42
- }
43
-
44
- export function generateComponentSource(type: ComponentType): string {
45
- return `export function ${type}() {
46
- return null
47
- }`
48
- }
49
-
50
- export function toPageComponentName(name: string): string {
51
- return toAstPageComponentName(name)
52
- }
53
-
54
- export function getPageSourcePath(
55
- page: Pick<Page, 'name' | 'sourcePath'>
56
- ): string {
57
- return getAstPageSourcePath(page)
58
- }
59
-
60
- export function createPageSourcePath(name: string): string {
61
- return createAstPageSourcePath(name)
62
- }
63
-
64
- export function generateNodeCode(node: VisualNode, indent: string): string {
65
- return generateNodeSource(node, indent)
66
- }
1
+ import {
2
+ createPageSourcePath as createAstPageSourcePath,
3
+ generateAppSource,
4
+ generateLayoutSource,
5
+ generateNodeSource,
6
+ generatePageSource,
7
+ getPageSourcePath as getAstPageSourcePath,
8
+ toPageComponentName as toAstPageComponentName,
9
+ } from '../../shared/generateReactSource.mjs'
10
+ import type {
11
+ AppShell,
12
+ ComponentType,
13
+ CustomComponentDefinition,
14
+ Page,
15
+ VisualNode,
16
+ } from '../types'
17
+
18
+ export function generatePageCode(
19
+ page: Page,
20
+ customComponents: CustomComponentDefinition[] = []
21
+ ): string {
22
+ return generatePageSource(page, customComponents).trimEnd()
23
+ }
24
+
25
+ export function generateAppCode(
26
+ pages: Page[],
27
+ appShell: AppShell,
28
+ options?: {
29
+ appFile?: string
30
+ layoutFile?: string
31
+ emitLayout?: boolean
32
+ }
33
+ ): string {
34
+ return generateAppSource(pages, appShell, options).trimEnd()
35
+ }
36
+
37
+ export function generateLayoutCode(
38
+ appShell: AppShell,
39
+ customComponents: CustomComponentDefinition[] = [],
40
+ options?: { layoutFile?: string }
41
+ ): string {
42
+ return generateLayoutSource(appShell, customComponents, options).trimEnd()
43
+ }
44
+
45
+ export function generateProjectSchema(
46
+ pages: Page[],
47
+ appShell: AppShell,
48
+ projectName = 'my-app'
49
+ ): string {
50
+ return JSON.stringify(
51
+ {
52
+ meta: {
53
+ projectName,
54
+ framework: 'react',
55
+ styling: 'tailwind',
56
+ },
57
+ appShell,
58
+ pages,
59
+ },
60
+ null,
61
+ 2
62
+ )
63
+ }
64
+
65
+ export function generateComponentSource(type: ComponentType): string {
66
+ return `export function ${type}() {
67
+ return null
68
+ }`
69
+ }
70
+
71
+ export function toPageComponentName(name: string): string {
72
+ return toAstPageComponentName(name)
73
+ }
74
+
75
+ export function getPageSourcePath(
76
+ page: Pick<Page, 'name' | 'sourcePath'>
77
+ ): string {
78
+ return getAstPageSourcePath(page)
79
+ }
80
+
81
+ export function createPageSourcePath(
82
+ name: string,
83
+ pagesDir?: string
84
+ ): string {
85
+ return createAstPageSourcePath(name, pagesDir)
86
+ }
87
+
88
+ export function generateNodeCode(node: VisualNode, indent: string): string {
89
+ return generateNodeSource(node, indent)
90
+ }
@@ -0,0 +1,54 @@
1
+ export interface DeploymentConfiguration {
2
+ provider: 'vercel'
3
+ tokenConfigured: boolean
4
+ projectName: string
5
+ teamId: string
6
+ outputDirectory: string
7
+ sourcePolicy: 'static-build-output-only'
8
+ }
9
+
10
+ export interface DeploymentResult {
11
+ id: string
12
+ url: string
13
+ inspectorUrl?: string
14
+ readyState: 'READY'
15
+ fileCount: number
16
+ totalBytes: number
17
+ }
18
+
19
+ export async function loadDeploymentConfiguration() {
20
+ const response = await fetch('/__visualbuild/deploy')
21
+
22
+ if (!response.ok) {
23
+ throw new Error(await readDeploymentError(response))
24
+ }
25
+
26
+ return (await response.json()) as DeploymentConfiguration
27
+ }
28
+
29
+ export async function deployProjectToVercel(
30
+ projectName: string,
31
+ teamId: string
32
+ ) {
33
+ const response = await fetch('/__visualbuild/deploy', {
34
+ method: 'POST',
35
+ headers: {
36
+ 'Content-Type': 'application/json',
37
+ },
38
+ body: JSON.stringify({ projectName, teamId }),
39
+ })
40
+
41
+ if (!response.ok) {
42
+ throw new Error(await readDeploymentError(response))
43
+ }
44
+
45
+ return (await response.json()) as DeploymentResult
46
+ }
47
+
48
+ async function readDeploymentError(response: Response) {
49
+ const payload = (await response.json().catch(() => null)) as {
50
+ error?: string
51
+ } | null
52
+
53
+ return payload?.error ?? `Deployment failed (HTTP ${response.status})`
54
+ }
@@ -1,177 +1,218 @@
1
- import type { AppShell, Page } from '../types'
2
-
3
- export interface ProjectSchema {
4
- meta: {
5
- projectName: string
6
- framework: string
7
- styling: string
8
- }
9
- appShell: AppShell
10
- pages: Page[]
11
- runtimeProjectId?: string
12
- }
13
-
14
- export type ImportedProjectPage = Page
15
-
16
- export interface GeneratorDiagnostic {
17
- severity: 'error'
18
- phase: 'generate'
19
- code: string
20
- title: string
21
- message: string
22
- path: string
23
- suggestion?: string
24
- }
25
-
26
- class ProjectSaveError extends Error {
27
- diagnostic?: GeneratorDiagnostic
28
-
29
- constructor(message: string, diagnostic?: GeneratorDiagnostic) {
30
- super(message)
31
- this.name = 'ProjectSaveError'
32
- this.diagnostic = diagnostic
33
- }
34
- }
35
-
36
- export function getGeneratorDiagnostic(error: unknown) {
37
- return error instanceof ProjectSaveError ? error.diagnostic : undefined
38
- }
39
-
40
- export function createProjectSchema(
41
- pages: Page[],
42
- appShell: AppShell,
43
- projectName = 'my-blog'
44
- ): ProjectSchema {
45
- return {
46
- meta: {
47
- projectName,
48
- framework: 'react',
49
- styling: 'tailwind',
50
- },
51
- appShell,
52
- pages,
53
- }
54
- }
55
-
56
- export async function loadProjectSchema(): Promise<ProjectSchema | null> {
57
- const response = await fetch('/__visualbuild/pages')
58
-
59
- if (!response.ok) {
60
- return null
61
- }
62
-
63
- const schema = (await response.json()) as ProjectSchema
64
-
65
- return {
66
- ...schema,
67
- runtimeProjectId:
68
- response.headers.get('X-VisualBuild-Project-Id') ?? undefined,
69
- }
70
- }
71
-
72
- export async function saveProjectSchema(
73
- pages: Page[],
74
- appShell: AppShell,
75
- projectName: string
76
- ) {
77
- const response = await fetch('/__visualbuild/pages', {
78
- method: 'POST',
79
- headers: {
80
- 'Content-Type': 'application/json',
81
- },
82
- body: JSON.stringify(createProjectSchema(pages, appShell, projectName), null, 2),
83
- })
84
-
85
- if (!response.ok) {
86
- const responseBody = await response.text()
87
- let payload: {
88
- error?: string
89
- diagnostic?: GeneratorDiagnostic
90
- } | null = null
91
-
92
- try {
93
- payload = JSON.parse(responseBody) as {
94
- error?: string
95
- diagnostic?: GeneratorDiagnostic
96
- }
97
- } catch {
98
- // Older servers may still return a plain error string.
99
- }
100
-
101
- throw new ProjectSaveError(
102
- payload?.error ||
103
- responseBody ||
104
- 'Failed to save VisualBuild project',
105
- payload?.diagnostic
106
- )
107
- }
108
-
109
- notifyProjectFilesChanged()
110
- }
111
-
112
- export async function importProjectPage(
113
- path: string,
114
- name: string,
115
- route: string
116
- ) {
117
- const response = await fetch('/__visualbuild/import-page', {
118
- method: 'POST',
119
- headers: {
120
- 'Content-Type': 'application/json',
121
- },
122
- body: JSON.stringify({ path, name, route }),
123
- })
124
-
125
- if (!response.ok) {
126
- const payload = (await response.json().catch(() => null)) as {
127
- error?: string
128
- } | null
129
- throw new Error(payload?.error ?? 'Failed to import page source')
130
- }
131
-
132
- return (await response.json()) as ImportedProjectPage
133
- }
134
-
135
- export async function syncTailwindClasses(pages: Page[], appShell: AppShell) {
136
- const classNames = collectProjectClassNames(pages, appShell)
137
- const response = await fetch('/__visualbuild/tailwind', {
138
- method: 'POST',
139
- headers: {
140
- 'Content-Type': 'text/plain',
141
- },
142
- body: classNames.join('\n'),
143
- })
144
-
145
- if (!response.ok) {
146
- throw new Error(
147
- (await response.text()) || 'Failed to compile Tailwind classes'
148
- )
149
- }
150
- }
151
-
152
- function collectProjectClassNames(pages: Page[], appShell: AppShell) {
153
- const classNames = new Set<string>()
154
- const visitNode = (node: Page['root']) => {
155
- const value = String(node.props.className ?? '').trim()
156
-
157
- if (value) {
158
- classNames.add(value)
159
- }
160
-
161
- node.children.forEach(visitNode)
162
- }
163
-
164
- if (appShell.header) visitNode(appShell.header)
165
- if (appShell.footer) visitNode(appShell.footer)
166
-
167
- pages.forEach((page) => {
168
- visitNode(page.root)
169
- page.components.forEach(visitNode)
170
- })
171
-
172
- return [...classNames]
173
- }
174
-
175
- function notifyProjectFilesChanged() {
176
- window.dispatchEvent(new Event('visualbuild:filesystem-changed'))
177
- }
1
+ import type {
2
+ AppShell,
3
+ CustomComponentDefinition,
4
+ Page,
5
+ VisualBuildRuntimeConfig,
6
+ } from '../types'
7
+
8
+ export interface ProjectSchema {
9
+ meta: {
10
+ projectName: string
11
+ framework: string
12
+ styling: string
13
+ }
14
+ appShell: AppShell
15
+ pages: Page[]
16
+ runtimeProjectId?: string
17
+ }
18
+
19
+ export type ImportedProjectPage = Page
20
+
21
+ export interface GeneratorDiagnostic {
22
+ severity: 'error'
23
+ phase: 'generate'
24
+ code: string
25
+ title: string
26
+ message: string
27
+ path: string
28
+ suggestion?: string
29
+ }
30
+
31
+ class ProjectSaveError extends Error {
32
+ diagnostic?: GeneratorDiagnostic
33
+
34
+ constructor(message: string, diagnostic?: GeneratorDiagnostic) {
35
+ super(message)
36
+ this.name = 'ProjectSaveError'
37
+ this.diagnostic = diagnostic
38
+ }
39
+ }
40
+
41
+ export function getGeneratorDiagnostic(error: unknown) {
42
+ return error instanceof ProjectSaveError ? error.diagnostic : undefined
43
+ }
44
+
45
+ export function createProjectSchema(
46
+ pages: Page[],
47
+ appShell: AppShell,
48
+ projectName = 'my-app'
49
+ ): ProjectSchema {
50
+ return {
51
+ meta: {
52
+ projectName,
53
+ framework: 'react',
54
+ styling: 'tailwind',
55
+ },
56
+ appShell,
57
+ pages,
58
+ }
59
+ }
60
+
61
+ export async function loadProjectSchema(): Promise<ProjectSchema | null> {
62
+ const response = await fetch('/__visualbuild/pages')
63
+
64
+ if (!response.ok) {
65
+ return null
66
+ }
67
+
68
+ const schema = (await response.json()) as ProjectSchema
69
+
70
+ return {
71
+ ...schema,
72
+ runtimeProjectId:
73
+ response.headers.get('X-VisualBuild-Project-Id') ?? undefined,
74
+ }
75
+ }
76
+
77
+ export async function loadProjectConfig(): Promise<{
78
+ customComponents: CustomComponentDefinition[]
79
+ } & VisualBuildRuntimeConfig> {
80
+ const response = await fetch('/__visualbuild/config')
81
+
82
+ if (!response.ok) {
83
+ const payload = (await response.json().catch(() => null)) as {
84
+ error?: string
85
+ } | null
86
+ throw new Error(payload?.error ?? 'Failed to load visualbuild.config.ts')
87
+ }
88
+
89
+ return (await response.json()) as {
90
+ customComponents: CustomComponentDefinition[]
91
+ } & VisualBuildRuntimeConfig
92
+ }
93
+
94
+ export async function saveProjectSchema(
95
+ pages: Page[],
96
+ appShell: AppShell,
97
+ projectName: string
98
+ ) {
99
+ const response = await fetch('/__visualbuild/pages', {
100
+ method: 'POST',
101
+ headers: {
102
+ 'Content-Type': 'application/json',
103
+ },
104
+ body: JSON.stringify(createProjectSchema(pages, appShell, projectName), null, 2),
105
+ })
106
+
107
+ if (!response.ok) {
108
+ const responseBody = await response.text()
109
+ let payload: {
110
+ error?: string
111
+ diagnostic?: GeneratorDiagnostic
112
+ } | null = null
113
+
114
+ try {
115
+ payload = JSON.parse(responseBody) as {
116
+ error?: string
117
+ diagnostic?: GeneratorDiagnostic
118
+ }
119
+ } catch {
120
+ // Older servers may still return a plain error string.
121
+ }
122
+
123
+ throw new ProjectSaveError(
124
+ payload?.error ||
125
+ responseBody ||
126
+ 'Failed to save VisualBuild project',
127
+ payload?.diagnostic
128
+ )
129
+ }
130
+
131
+ notifyProjectFilesChanged()
132
+ }
133
+
134
+ export async function importProjectPage(
135
+ path: string,
136
+ name: string,
137
+ route: string
138
+ ) {
139
+ const response = await fetch('/__visualbuild/import-page', {
140
+ method: 'POST',
141
+ headers: {
142
+ 'Content-Type': 'application/json',
143
+ },
144
+ body: JSON.stringify({ path, name, route }),
145
+ })
146
+
147
+ if (!response.ok) {
148
+ const payload = (await response.json().catch(() => null)) as {
149
+ error?: string
150
+ } | null
151
+ throw new Error(payload?.error ?? 'Failed to import page source')
152
+ }
153
+
154
+ return (await response.json()) as ImportedProjectPage
155
+ }
156
+
157
+ export async function registerProjectComponent(path: string) {
158
+ const response = await fetch('/__visualbuild/register-component', {
159
+ method: 'POST',
160
+ headers: {
161
+ 'Content-Type': 'application/json',
162
+ },
163
+ body: JSON.stringify({ path }),
164
+ })
165
+
166
+ if (!response.ok) {
167
+ const payload = (await response.json().catch(() => null)) as {
168
+ error?: string
169
+ } | null
170
+ throw new Error(payload?.error ?? 'Failed to register component source')
171
+ }
172
+
173
+ return (await response.json()) as CustomComponentDefinition
174
+ }
175
+
176
+ export async function syncTailwindClasses(pages: Page[], appShell: AppShell) {
177
+ const classNames = collectProjectClassNames(pages, appShell)
178
+ const response = await fetch('/__visualbuild/tailwind', {
179
+ method: 'POST',
180
+ headers: {
181
+ 'Content-Type': 'text/plain',
182
+ },
183
+ body: classNames.join('\n'),
184
+ })
185
+
186
+ if (!response.ok) {
187
+ throw new Error(
188
+ (await response.text()) || 'Failed to compile Tailwind classes'
189
+ )
190
+ }
191
+ }
192
+
193
+ function collectProjectClassNames(pages: Page[], appShell: AppShell) {
194
+ const classNames = new Set<string>()
195
+ const visitNode = (node: Page['root']) => {
196
+ const value = String(node.props.className ?? '').trim()
197
+
198
+ if (value) {
199
+ classNames.add(value)
200
+ }
201
+
202
+ node.children.forEach(visitNode)
203
+ }
204
+
205
+ if (appShell.header) visitNode(appShell.header)
206
+ if (appShell.footer) visitNode(appShell.footer)
207
+
208
+ pages.forEach((page) => {
209
+ visitNode(page.root)
210
+ page.components.forEach(visitNode)
211
+ })
212
+
213
+ return [...classNames]
214
+ }
215
+
216
+ function notifyProjectFilesChanged() {
217
+ window.dispatchEvent(new Event('visualbuild:filesystem-changed'))
218
+ }