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.
- package/README.md +919 -58
- package/docs/user-guide.md +759 -0
- package/package.json +92 -85
- package/scripts/create-builder-app.mjs +513 -501
- package/scripts/vb-dev.mjs +41 -32
- package/scripts/vb-generate.mjs +332 -224
- package/templates/default/app/.vercelignore +6 -0
- package/templates/default/app/README.md +56 -21
- package/templates/default/app/visualbuild/components.json +3 -0
- package/templates/default/app/visualbuild/config.d.ts +48 -0
- package/templates/default/app/visualbuild.config.ts +38 -0
- package/templates/default/builder/README.md +37 -21
- package/visual-app-builder/server/parseReactPage.ts +776 -571
- package/visual-app-builder/shared/createReactComponentSource.d.mts +2 -0
- package/visual-app-builder/shared/createReactComponentSource.mjs +45 -0
- package/visual-app-builder/shared/generateReactSource.d.mts +57 -37
- package/visual-app-builder/shared/generateReactSource.mjs +608 -443
- package/visual-app-builder/shared/npmBuildCommand.d.mts +17 -0
- package/visual-app-builder/shared/npmBuildCommand.mjs +26 -0
- package/visual-app-builder/shared/syncCustomComponentSource.d.mts +13 -0
- package/visual-app-builder/shared/syncCustomComponentSource.mjs +345 -0
- package/visual-app-builder/shared/vercelDeployment.d.mts +31 -0
- package/visual-app-builder/shared/vercelDeployment.mjs +266 -0
- package/visual-app-builder/shared/visualbuildConfig.d.mts +144 -0
- package/visual-app-builder/shared/visualbuildConfig.mjs +578 -0
- package/visual-app-builder/src/App.tsx +1090 -874
- package/visual-app-builder/src/components/Canvas.tsx +1116 -1059
- package/visual-app-builder/src/components/CodePanel.tsx +1147 -812
- package/visual-app-builder/src/components/ComponentSidebar.tsx +365 -302
- package/visual-app-builder/src/components/DeploymentModal.tsx +295 -0
- package/visual-app-builder/src/components/PageSwitcher.tsx +133 -133
- package/visual-app-builder/src/components/ProjectExplorer.tsx +1054 -1054
- package/visual-app-builder/src/components/PropertiesPanel.tsx +792 -692
- package/visual-app-builder/src/components/Topbar.tsx +257 -128
- package/visual-app-builder/src/data/componentRegistry.tsx +613 -292
- package/visual-app-builder/src/data/tailwindClassCatalog.ts +95 -0
- package/visual-app-builder/src/index.css +383 -111
- package/visual-app-builder/src/stores/useAppStore.ts +2385 -1265
- package/visual-app-builder/src/theme.ts +71 -0
- package/visual-app-builder/src/types/index.ts +350 -261
- package/visual-app-builder/src/utils/codegen.ts +90 -66
- package/visual-app-builder/src/utils/deployment.ts +54 -0
- package/visual-app-builder/src/utils/projectPersistence.ts +218 -177
- package/visual-app-builder/vite.config.ts +1946 -1479
|
@@ -1,501 +1,513 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { execFile } from 'node:child_process'
|
|
4
|
-
import fs from 'node:fs/promises'
|
|
5
|
-
import path from 'node:path'
|
|
6
|
-
import { fileURLToPath } from 'node:url'
|
|
7
|
-
import { promisify } from 'node:util'
|
|
8
|
-
|
|
9
|
-
const execFileAsync = promisify(execFile)
|
|
10
|
-
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
11
|
-
const DEFAULT_PROJECT_NAME = 'my-app'
|
|
12
|
-
const BUILDER_FOLDER_NAME = 'visual-builder'
|
|
13
|
-
const templateRoot = path.join(repoRoot, 'templates', 'default')
|
|
14
|
-
const appTemplateDir = path.join(templateRoot, 'app')
|
|
15
|
-
const builderTemplateDir = path.join(templateRoot, 'builder')
|
|
16
|
-
const editorSourceDir = path.join(repoRoot, 'visual-app-builder')
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
'@
|
|
23
|
-
'@
|
|
24
|
-
'@types/
|
|
25
|
-
'@types/react
|
|
26
|
-
'@
|
|
27
|
-
'
|
|
28
|
-
'eslint
|
|
29
|
-
'eslint-plugin-react-
|
|
30
|
-
'
|
|
31
|
-
'
|
|
32
|
-
'
|
|
33
|
-
'typescript
|
|
34
|
-
'
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
'@babel/
|
|
40
|
-
'@babel/
|
|
41
|
-
'@
|
|
42
|
-
'@dnd-kit/
|
|
43
|
-
'@dnd-kit/
|
|
44
|
-
'@
|
|
45
|
-
'
|
|
46
|
-
'
|
|
47
|
-
'react
|
|
48
|
-
'
|
|
49
|
-
'
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
'@
|
|
55
|
-
'@types/
|
|
56
|
-
'@types/react
|
|
57
|
-
'@
|
|
58
|
-
'
|
|
59
|
-
'eslint
|
|
60
|
-
'eslint-plugin-react-
|
|
61
|
-
'
|
|
62
|
-
'
|
|
63
|
-
'
|
|
64
|
-
'typescript
|
|
65
|
-
'
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
.
|
|
76
|
-
.
|
|
77
|
-
.replace(/[
|
|
78
|
-
.replace(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
.
|
|
87
|
-
.
|
|
88
|
-
.
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
await assertTargetIsWritable(
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
await replaceInFile(path.join(
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
['Welcome to My
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
'
|
|
235
|
-
'
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
'
|
|
245
|
-
'
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
Install and run the
|
|
455
|
-
|
|
456
|
-
\`\`\`powershell
|
|
457
|
-
cd
|
|
458
|
-
npm.cmd install
|
|
459
|
-
npm.cmd run
|
|
460
|
-
\`\`\`
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
await
|
|
478
|
-
await
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
console.
|
|
500
|
-
|
|
501
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { execFile } from 'node:child_process'
|
|
4
|
+
import fs from 'node:fs/promises'
|
|
5
|
+
import path from 'node:path'
|
|
6
|
+
import { fileURLToPath } from 'node:url'
|
|
7
|
+
import { promisify } from 'node:util'
|
|
8
|
+
|
|
9
|
+
const execFileAsync = promisify(execFile)
|
|
10
|
+
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
11
|
+
const DEFAULT_PROJECT_NAME = 'my-app'
|
|
12
|
+
const BUILDER_FOLDER_NAME = 'visual-builder'
|
|
13
|
+
const templateRoot = path.join(repoRoot, 'templates', 'default')
|
|
14
|
+
const appTemplateDir = path.join(templateRoot, 'app')
|
|
15
|
+
const builderTemplateDir = path.join(templateRoot, 'builder')
|
|
16
|
+
const editorSourceDir = path.join(repoRoot, 'visual-app-builder')
|
|
17
|
+
const userGuidePath = path.join(repoRoot, 'docs', 'user-guide.md')
|
|
18
|
+
const scriptFilesToCopy = ['vb-dev.mjs', 'vb-generate.mjs']
|
|
19
|
+
|
|
20
|
+
const appPackageDependencies = ['react', 'react-dom']
|
|
21
|
+
const appPackageDevDependencies = [
|
|
22
|
+
'@eslint/js',
|
|
23
|
+
'@tailwindcss/vite',
|
|
24
|
+
'@types/node',
|
|
25
|
+
'@types/react',
|
|
26
|
+
'@types/react-dom',
|
|
27
|
+
'@vitejs/plugin-react',
|
|
28
|
+
'eslint',
|
|
29
|
+
'eslint-plugin-react-hooks',
|
|
30
|
+
'eslint-plugin-react-refresh',
|
|
31
|
+
'globals',
|
|
32
|
+
'tailwindcss',
|
|
33
|
+
'typescript',
|
|
34
|
+
'typescript-eslint',
|
|
35
|
+
'vite',
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
const builderPackageDependencies = [
|
|
39
|
+
'@babel/generator',
|
|
40
|
+
'@babel/parser',
|
|
41
|
+
'@babel/types',
|
|
42
|
+
'@dnd-kit/core',
|
|
43
|
+
'@dnd-kit/sortable',
|
|
44
|
+
'@dnd-kit/utilities',
|
|
45
|
+
'@tailwindcss/vite',
|
|
46
|
+
'chokidar',
|
|
47
|
+
'react',
|
|
48
|
+
'react-dom',
|
|
49
|
+
'uuid',
|
|
50
|
+
'zustand',
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
const builderPackageDevDependencies = [
|
|
54
|
+
'@eslint/js',
|
|
55
|
+
'@types/node',
|
|
56
|
+
'@types/react',
|
|
57
|
+
'@types/react-dom',
|
|
58
|
+
'@vitejs/plugin-react',
|
|
59
|
+
'eslint',
|
|
60
|
+
'eslint-plugin-react-hooks',
|
|
61
|
+
'eslint-plugin-react-refresh',
|
|
62
|
+
'globals',
|
|
63
|
+
'tailwindcss',
|
|
64
|
+
'typescript',
|
|
65
|
+
'typescript-eslint',
|
|
66
|
+
'vite',
|
|
67
|
+
]
|
|
68
|
+
|
|
69
|
+
function getTargetBaseDir() {
|
|
70
|
+
return path.resolve(process.env.INIT_CWD || process.cwd())
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function toProjectSlug(input) {
|
|
74
|
+
const slug = String(input || DEFAULT_PROJECT_NAME)
|
|
75
|
+
.trim()
|
|
76
|
+
.toLowerCase()
|
|
77
|
+
.replace(/['"]/g, '')
|
|
78
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
79
|
+
.replace(/^-+|-+$/g, '')
|
|
80
|
+
|
|
81
|
+
return slug || DEFAULT_PROJECT_NAME
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function toDisplayName(slug) {
|
|
85
|
+
return slug
|
|
86
|
+
.split(/[-_]+/)
|
|
87
|
+
.filter(Boolean)
|
|
88
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
|
|
89
|
+
.join(' ')
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function pathExists(filePath) {
|
|
93
|
+
try {
|
|
94
|
+
await fs.access(filePath)
|
|
95
|
+
return true
|
|
96
|
+
} catch {
|
|
97
|
+
return false
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function assertTargetIsWritable(targetDir) {
|
|
102
|
+
if (!(await pathExists(targetDir))) {
|
|
103
|
+
return
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const entries = await fs.readdir(targetDir)
|
|
107
|
+
|
|
108
|
+
if (entries.length > 0) {
|
|
109
|
+
throw new Error(
|
|
110
|
+
`Target folder "${targetDir}" already exists and is not empty.`
|
|
111
|
+
)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function assertScaffoldTargetsAreWritable(appDir, builderDir) {
|
|
116
|
+
await assertTargetIsWritable(appDir)
|
|
117
|
+
await assertTargetIsWritable(builderDir)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function assertScaffoldAssetsExist() {
|
|
121
|
+
const requiredAssets = [
|
|
122
|
+
appTemplateDir,
|
|
123
|
+
builderTemplateDir,
|
|
124
|
+
editorSourceDir,
|
|
125
|
+
userGuidePath,
|
|
126
|
+
...scriptFilesToCopy.map((file) => path.join(repoRoot, 'scripts', file)),
|
|
127
|
+
]
|
|
128
|
+
|
|
129
|
+
for (const assetPath of requiredAssets) {
|
|
130
|
+
if (!(await pathExists(assetPath))) {
|
|
131
|
+
throw new Error(
|
|
132
|
+
`Required scaffold asset is missing: ${path.relative(repoRoot, assetPath)}`
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function copyAppTemplate(appDir) {
|
|
139
|
+
await fs.cp(appTemplateDir, appDir, { recursive: true })
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async function copyBuilderTemplate(builderDir) {
|
|
143
|
+
await fs.cp(builderTemplateDir, builderDir, { recursive: true })
|
|
144
|
+
await fs.cp(
|
|
145
|
+
editorSourceDir,
|
|
146
|
+
path.join(builderDir, 'visual-app-builder'),
|
|
147
|
+
{ recursive: true }
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
await fs.mkdir(path.join(builderDir, 'scripts'), { recursive: true })
|
|
151
|
+
|
|
152
|
+
for (const file of scriptFilesToCopy) {
|
|
153
|
+
await fs.copyFile(
|
|
154
|
+
path.join(repoRoot, 'scripts', file),
|
|
155
|
+
path.join(builderDir, 'scripts', file)
|
|
156
|
+
)
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function rewriteJsonFile(filePath, update) {
|
|
161
|
+
const contents = await fs.readFile(filePath, 'utf8')
|
|
162
|
+
const json = JSON.parse(contents)
|
|
163
|
+
const nextJson = update(json)
|
|
164
|
+
await fs.writeFile(filePath, `${JSON.stringify(nextJson, null, 2)}\n`)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function replaceInFile(filePath, replacements) {
|
|
168
|
+
if (!(await pathExists(filePath))) {
|
|
169
|
+
return
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
let contents = await fs.readFile(filePath, 'utf8')
|
|
173
|
+
|
|
174
|
+
for (const [from, to] of replacements) {
|
|
175
|
+
contents = contents.split(from).join(to)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
await fs.writeFile(filePath, contents)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function seedProject(appDir, builderDir, projectName, displayName) {
|
|
182
|
+
const appPackageJson = await readRootPackageJson()
|
|
183
|
+
const builderPackageJson = await readRootPackageJson()
|
|
184
|
+
const relativeAppDir = toPosixPath(path.relative(builderDir, appDir))
|
|
185
|
+
|
|
186
|
+
await fs.writeFile(
|
|
187
|
+
path.join(appDir, 'package.json'),
|
|
188
|
+
`${JSON.stringify(
|
|
189
|
+
createAppPackageJson(appPackageJson, projectName),
|
|
190
|
+
null,
|
|
191
|
+
2
|
|
192
|
+
)}\n`
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
await fs.writeFile(
|
|
196
|
+
path.join(builderDir, 'package.json'),
|
|
197
|
+
`${JSON.stringify(
|
|
198
|
+
createBuilderPackageJson(
|
|
199
|
+
builderPackageJson,
|
|
200
|
+
relativeAppDir
|
|
201
|
+
),
|
|
202
|
+
null,
|
|
203
|
+
2
|
|
204
|
+
)}\n`
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
await rewriteJsonFile(
|
|
208
|
+
path.join(appDir, 'visualbuild', 'pages.json'),
|
|
209
|
+
(schema) => seedProjectSchema(schema, projectName, displayName)
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
const templateReplacements = [
|
|
213
|
+
['__DISPLAY_NAME__', displayName],
|
|
214
|
+
['__PROJECT_NAME__', projectName],
|
|
215
|
+
]
|
|
216
|
+
|
|
217
|
+
await replaceInFile(path.join(appDir, 'index.html'), templateReplacements)
|
|
218
|
+
await replaceInFile(path.join(appDir, 'README.md'), templateReplacements)
|
|
219
|
+
await replaceInFile(path.join(builderDir, 'README.md'), templateReplacements)
|
|
220
|
+
|
|
221
|
+
const sourceReplacements = [
|
|
222
|
+
['Welcome to My App', `Welcome to ${displayName}`],
|
|
223
|
+
['My App', displayName],
|
|
224
|
+
['my-app', projectName],
|
|
225
|
+
]
|
|
226
|
+
|
|
227
|
+
await replaceInFile(
|
|
228
|
+
path.join(builderDir, 'visual-app-builder', 'src', 'stores', 'useAppStore.ts'),
|
|
229
|
+
sourceReplacements
|
|
230
|
+
)
|
|
231
|
+
await replaceInFile(
|
|
232
|
+
path.join(
|
|
233
|
+
builderDir,
|
|
234
|
+
'visual-app-builder',
|
|
235
|
+
'src',
|
|
236
|
+
'utils',
|
|
237
|
+
'projectPersistence.ts'
|
|
238
|
+
),
|
|
239
|
+
sourceReplacements
|
|
240
|
+
)
|
|
241
|
+
await replaceInFile(
|
|
242
|
+
path.join(
|
|
243
|
+
builderDir,
|
|
244
|
+
'visual-app-builder',
|
|
245
|
+
'src',
|
|
246
|
+
'data',
|
|
247
|
+
'componentRegistry.tsx'
|
|
248
|
+
),
|
|
249
|
+
sourceReplacements
|
|
250
|
+
)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async function readRootPackageJson() {
|
|
254
|
+
return JSON.parse(await fs.readFile(path.join(repoRoot, 'package.json'), 'utf8'))
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function createAppPackageJson(packageJson, projectName) {
|
|
258
|
+
const allDependencies = mergeDependencies(packageJson)
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
name: projectName,
|
|
262
|
+
private: true,
|
|
263
|
+
version: packageJson.version,
|
|
264
|
+
type: 'module',
|
|
265
|
+
scripts: {
|
|
266
|
+
dev: 'vite',
|
|
267
|
+
build: 'tsc -b && vite build',
|
|
268
|
+
lint: 'eslint .',
|
|
269
|
+
preview: 'vite preview',
|
|
270
|
+
},
|
|
271
|
+
dependencies: pickDependencies(allDependencies, appPackageDependencies),
|
|
272
|
+
devDependencies: pickDependencies(
|
|
273
|
+
allDependencies,
|
|
274
|
+
appPackageDevDependencies
|
|
275
|
+
),
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function createBuilderPackageJson(packageJson, relativeAppDir) {
|
|
280
|
+
const allDependencies = mergeDependencies(packageJson)
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
name: BUILDER_FOLDER_NAME,
|
|
284
|
+
private: true,
|
|
285
|
+
version: packageJson.version,
|
|
286
|
+
type: 'module',
|
|
287
|
+
scripts: {
|
|
288
|
+
'visual-builder': `node scripts/vb-dev.mjs ${JSON.stringify(relativeAppDir)}`,
|
|
289
|
+
'vb:generate': `node scripts/vb-generate.mjs ${JSON.stringify(relativeAppDir)}`,
|
|
290
|
+
build: 'vite build --config visual-app-builder/vite.config.ts',
|
|
291
|
+
lint: 'eslint .',
|
|
292
|
+
},
|
|
293
|
+
dependencies: pickDependencies(
|
|
294
|
+
allDependencies,
|
|
295
|
+
builderPackageDependencies
|
|
296
|
+
),
|
|
297
|
+
devDependencies: pickDependencies(
|
|
298
|
+
allDependencies,
|
|
299
|
+
builderPackageDevDependencies
|
|
300
|
+
),
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function mergeDependencies(packageJson) {
|
|
305
|
+
return {
|
|
306
|
+
...(packageJson.dependencies ?? {}),
|
|
307
|
+
...(packageJson.devDependencies ?? {}),
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function pickDependencies(source, names) {
|
|
312
|
+
return Object.fromEntries(
|
|
313
|
+
names
|
|
314
|
+
.filter((name) => source?.[name])
|
|
315
|
+
.map((name) => [name, source[name]])
|
|
316
|
+
)
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function toPosixPath(filePath) {
|
|
320
|
+
return filePath.split(path.sep).join('/')
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
function seedProjectSchema(schema, projectName, displayName) {
|
|
324
|
+
return {
|
|
325
|
+
...schema,
|
|
326
|
+
meta: {
|
|
327
|
+
...schema.meta,
|
|
328
|
+
projectName,
|
|
329
|
+
},
|
|
330
|
+
appShell: {
|
|
331
|
+
header: null,
|
|
332
|
+
footer: null,
|
|
333
|
+
},
|
|
334
|
+
pages: [createStarterHomePage(displayName)],
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function createStarterHomePage(displayName) {
|
|
339
|
+
return {
|
|
340
|
+
id: 'page-home',
|
|
341
|
+
name: 'Home',
|
|
342
|
+
route: '/',
|
|
343
|
+
sourcePath: 'src/pages/HomePage.tsx',
|
|
344
|
+
root: {
|
|
345
|
+
id: 'page-home:root',
|
|
346
|
+
type: 'main',
|
|
347
|
+
label: 'Page root',
|
|
348
|
+
props: {
|
|
349
|
+
className:
|
|
350
|
+
'min-h-screen bg-slate-950 text-white flex items-center justify-center px-6',
|
|
351
|
+
},
|
|
352
|
+
children: [],
|
|
353
|
+
},
|
|
354
|
+
components: [
|
|
355
|
+
{
|
|
356
|
+
id: 'starter-shell',
|
|
357
|
+
type: 'section',
|
|
358
|
+
label: 'Starter',
|
|
359
|
+
props: {
|
|
360
|
+
className:
|
|
361
|
+
'w-full max-w-5xl rounded-2xl border border-white/10 bg-white/10 px-8 py-12 text-center shadow-2xl backdrop-blur',
|
|
362
|
+
},
|
|
363
|
+
children: [
|
|
364
|
+
{
|
|
365
|
+
id: 'starter-eyebrow',
|
|
366
|
+
type: 'p',
|
|
367
|
+
props: {
|
|
368
|
+
text: 'VisualBuild starter',
|
|
369
|
+
className:
|
|
370
|
+
'mb-4 text-xs font-semibold uppercase tracking-[0.28em] text-blue-300',
|
|
371
|
+
},
|
|
372
|
+
children: [],
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
id: 'starter-title',
|
|
376
|
+
type: 'h1',
|
|
377
|
+
props: {
|
|
378
|
+
text: `Welcome to ${displayName}`,
|
|
379
|
+
className: 'text-4xl font-bold tracking-tight sm:text-5xl',
|
|
380
|
+
},
|
|
381
|
+
children: [],
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
id: 'starter-subtitle',
|
|
385
|
+
type: 'p',
|
|
386
|
+
props: {
|
|
387
|
+
text: 'Build your app visually with drag and drop, then keep the real React code.',
|
|
388
|
+
className: 'mx-auto mt-5 max-w-2xl text-base text-slate-300',
|
|
389
|
+
},
|
|
390
|
+
children: [],
|
|
391
|
+
},
|
|
392
|
+
{
|
|
393
|
+
id: 'starter-actions',
|
|
394
|
+
type: 'div',
|
|
395
|
+
props: {
|
|
396
|
+
className:
|
|
397
|
+
'mt-8 flex flex-wrap items-center justify-center gap-3',
|
|
398
|
+
},
|
|
399
|
+
children: [
|
|
400
|
+
{
|
|
401
|
+
id: 'starter-primary-action',
|
|
402
|
+
type: 'a',
|
|
403
|
+
props: {
|
|
404
|
+
text: 'Open Visual Builder',
|
|
405
|
+
href: 'http://127.0.0.1:7371',
|
|
406
|
+
target: '_blank',
|
|
407
|
+
className:
|
|
408
|
+
'rounded-lg bg-blue-500 px-5 py-3 text-sm font-semibold text-white shadow-lg shadow-blue-500/25 hover:bg-blue-400',
|
|
409
|
+
},
|
|
410
|
+
children: [],
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
id: 'starter-secondary-action',
|
|
414
|
+
type: 'span',
|
|
415
|
+
props: {
|
|
416
|
+
text: 'Edit this page from visual-builder',
|
|
417
|
+
className: 'text-sm text-slate-400',
|
|
418
|
+
},
|
|
419
|
+
children: [],
|
|
420
|
+
},
|
|
421
|
+
],
|
|
422
|
+
},
|
|
423
|
+
],
|
|
424
|
+
},
|
|
425
|
+
],
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
async function regenerateSource(appDir) {
|
|
430
|
+
await execFileAsync(
|
|
431
|
+
process.execPath,
|
|
432
|
+
[path.join(repoRoot, 'scripts', 'vb-generate.mjs'), appDir],
|
|
433
|
+
{
|
|
434
|
+
cwd: repoRoot,
|
|
435
|
+
}
|
|
436
|
+
)
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
async function writeWorkspaceReadme(targetBaseDir, projectName, displayName) {
|
|
440
|
+
const readmePath = path.join(targetBaseDir, `${projectName}-README.md`)
|
|
441
|
+
const workspaceGuidePath = path.join(targetBaseDir, 'VISUALBUILD-GUIDE.md')
|
|
442
|
+
const contents = `# ${displayName} VisualBuild Workspace
|
|
443
|
+
|
|
444
|
+
This workspace contains two separate projects:
|
|
445
|
+
|
|
446
|
+
- \`${projectName}\`: deployable React app source.
|
|
447
|
+
- \`${BUILDER_FOLDER_NAME}\`: VisualBuild editor and generator tooling.
|
|
448
|
+
|
|
449
|
+
Read [VISUALBUILD-GUIDE.md](./VISUALBUILD-GUIDE.md) for the complete workflow,
|
|
450
|
+
including visual editing, page and component registration, the larger code
|
|
451
|
+
workspace, source ownership, two-way sync limits, configuration, deployment,
|
|
452
|
+
and error recovery.
|
|
453
|
+
|
|
454
|
+
Install and run the app:
|
|
455
|
+
|
|
456
|
+
\`\`\`powershell
|
|
457
|
+
cd ${projectName}
|
|
458
|
+
npm.cmd install
|
|
459
|
+
npm.cmd run dev
|
|
460
|
+
\`\`\`
|
|
461
|
+
|
|
462
|
+
In another terminal opened in this workspace, install and run the visual builder:
|
|
463
|
+
|
|
464
|
+
\`\`\`powershell
|
|
465
|
+
cd ${BUILDER_FOLDER_NAME}
|
|
466
|
+
npm.cmd install
|
|
467
|
+
npm.cmd run visual-builder
|
|
468
|
+
\`\`\`
|
|
469
|
+
|
|
470
|
+
If that terminal starts inside \`${projectName}\`, use
|
|
471
|
+
\`cd ..\\${BUILDER_FOLDER_NAME}\` instead.
|
|
472
|
+
|
|
473
|
+
The builder writes to \`${projectName}/visualbuild/pages.json\` and regenerates \`${projectName}/src\`.
|
|
474
|
+
The React app package does not include builder-only drag-and-drop dependencies.
|
|
475
|
+
`
|
|
476
|
+
|
|
477
|
+
await fs.copyFile(userGuidePath, workspaceGuidePath)
|
|
478
|
+
await fs.writeFile(readmePath, contents)
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
async function main() {
|
|
482
|
+
const projectName = toProjectSlug(process.argv[2])
|
|
483
|
+
const displayName = toDisplayName(projectName)
|
|
484
|
+
const targetBaseDir = getTargetBaseDir()
|
|
485
|
+
const appDir = path.resolve(targetBaseDir, projectName)
|
|
486
|
+
const builderDir = path.resolve(targetBaseDir, BUILDER_FOLDER_NAME)
|
|
487
|
+
|
|
488
|
+
await assertScaffoldAssetsExist()
|
|
489
|
+
await assertScaffoldTargetsAreWritable(appDir, builderDir)
|
|
490
|
+
await copyAppTemplate(appDir)
|
|
491
|
+
await copyBuilderTemplate(builderDir)
|
|
492
|
+
await seedProject(appDir, builderDir, projectName, displayName)
|
|
493
|
+
await regenerateSource(appDir)
|
|
494
|
+
await writeWorkspaceReadme(targetBaseDir, projectName, displayName)
|
|
495
|
+
|
|
496
|
+
console.log(`Created VisualBuild app "${displayName}" in ${appDir}`)
|
|
497
|
+
console.log(`Created VisualBuild editor in ${builderDir}`)
|
|
498
|
+
console.log('')
|
|
499
|
+
console.log('Next steps:')
|
|
500
|
+
console.log(` cd ${projectName}`)
|
|
501
|
+
console.log(' npm install')
|
|
502
|
+
console.log(' npm.cmd run dev')
|
|
503
|
+
console.log('')
|
|
504
|
+
console.log('In another terminal opened in the workspace, run the visual builder:')
|
|
505
|
+
console.log(` cd ${BUILDER_FOLDER_NAME}`)
|
|
506
|
+
console.log(' npm install')
|
|
507
|
+
console.log(' npm.cmd run visual-builder')
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
main().catch((error) => {
|
|
511
|
+
console.error(error.message)
|
|
512
|
+
process.exit(1)
|
|
513
|
+
})
|