create-visualbuild-app 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 (44) hide show
  1. package/README.md +123 -0
  2. package/package.json +85 -0
  3. package/scripts/create-builder-app.mjs +501 -0
  4. package/scripts/vb-dev.mjs +32 -0
  5. package/scripts/vb-generate.mjs +224 -0
  6. package/templates/default/app/README.md +21 -0
  7. package/templates/default/app/eslint.config.js +25 -0
  8. package/templates/default/app/index.html +15 -0
  9. package/templates/default/app/src/index.css +23 -0
  10. package/templates/default/app/src/main.tsx +10 -0
  11. package/templates/default/app/tsconfig.app.json +21 -0
  12. package/templates/default/app/tsconfig.json +7 -0
  13. package/templates/default/app/tsconfig.node.json +19 -0
  14. package/templates/default/app/visualbuild/generated-files.json +3 -0
  15. package/templates/default/app/visualbuild/pages.json +12 -0
  16. package/templates/default/app/vite.config.ts +7 -0
  17. package/templates/default/builder/README.md +21 -0
  18. package/templates/default/builder/eslint.config.js +25 -0
  19. package/templates/default/builder/tsconfig.app.json +21 -0
  20. package/templates/default/builder/tsconfig.json +7 -0
  21. package/templates/default/builder/tsconfig.node.json +22 -0
  22. package/visual-app-builder/index.html +15 -0
  23. package/visual-app-builder/server/parseReactPage.ts +571 -0
  24. package/visual-app-builder/shared/generateReactSource.d.mts +37 -0
  25. package/visual-app-builder/shared/generateReactSource.mjs +443 -0
  26. package/visual-app-builder/src/App.tsx +874 -0
  27. package/visual-app-builder/src/components/Canvas.tsx +1059 -0
  28. package/visual-app-builder/src/components/CodePanel.tsx +812 -0
  29. package/visual-app-builder/src/components/ComponentSidebar.tsx +302 -0
  30. package/visual-app-builder/src/components/PageSwitcher.tsx +161 -0
  31. package/visual-app-builder/src/components/ProjectExplorer.tsx +1054 -0
  32. package/visual-app-builder/src/components/PropertiesPanel.tsx +692 -0
  33. package/visual-app-builder/src/components/Topbar.tsx +128 -0
  34. package/visual-app-builder/src/data/componentRegistry.tsx +292 -0
  35. package/visual-app-builder/src/data/primitiveRegistry.ts +368 -0
  36. package/visual-app-builder/src/index.css +111 -0
  37. package/visual-app-builder/src/main.tsx +10 -0
  38. package/visual-app-builder/src/stores/useAppStore.ts +1265 -0
  39. package/visual-app-builder/src/tailwindGeneratedSafelist.ts +36 -0
  40. package/visual-app-builder/src/types/index.ts +261 -0
  41. package/visual-app-builder/src/utils/codegen.ts +66 -0
  42. package/visual-app-builder/src/utils/projectFiles.ts +146 -0
  43. package/visual-app-builder/src/utils/projectPersistence.ts +177 -0
  44. package/visual-app-builder/vite.config.ts +1479 -0
package/README.md ADDED
@@ -0,0 +1,123 @@
1
+ # VisualBuild
2
+
3
+ VisualBuild is a visual development framework for building React applications
4
+ with drag and drop while retaining normal, editable source code.
5
+
6
+ The visual editor writes the project schema, the generator writes real React
7
+ files, and supported static JSX/TSX edits can sync back into the canvas.
8
+
9
+ ## Public CLI
10
+
11
+ The canonical npm package is `create-visualbuild-app`.
12
+
13
+ After the first public release, create a named project with either command:
14
+
15
+ ```bash
16
+ npm create visualbuild-app@latest my-blog
17
+ ```
18
+
19
+ ```bash
20
+ npx create-visualbuild-app@latest my-blog
21
+ ```
22
+
23
+ Omitting the name creates `my-app`:
24
+
25
+ ```bash
26
+ npm create visualbuild-app@latest
27
+ ```
28
+
29
+ The public package has not been published yet. Maintainers can exercise the
30
+ same CLI from this repository:
31
+
32
+ ```powershell
33
+ npm.cmd run create-visualbuild-app -- my-blog
34
+ ```
35
+
36
+ The previous local script remains available as a compatibility alias:
37
+
38
+ ```powershell
39
+ npm.cmd run create-builder-app -- my-blog
40
+ ```
41
+
42
+ ## Scaffold Output
43
+
44
+ VisualBuild deliberately separates the deployable application from the editor:
45
+
46
+ ```text
47
+ workspace/
48
+ my-blog/ # developer-owned React application
49
+ visual-builder/ # visual editor, generator, and file watcher
50
+ my-blog-README.md # generated setup instructions
51
+ ```
52
+
53
+ The React app does not install editor-only packages such as dnd-kit or Zustand.
54
+ It can be built, deployed, or retained after deleting `visual-builder/`.
55
+
56
+ ## Run A Generated Project
57
+
58
+ Install and start the React application:
59
+
60
+ ```powershell
61
+ cd my-blog
62
+ npm install
63
+ npm.cmd run dev
64
+ ```
65
+
66
+ In another terminal, install and start the visual editor:
67
+
68
+ ```powershell
69
+ cd visual-builder
70
+ npm install
71
+ npm.cmd run visual-builder
72
+ ```
73
+
74
+ Changes saved in the editor update `my-blog/visualbuild/pages.json` and the
75
+ generated React source. Supported static source edits are watched and reflected
76
+ back into the canvas.
77
+
78
+ ## Architecture
79
+
80
+ ```text
81
+ Visual Editor
82
+ |
83
+ v
84
+ visualbuild/pages.json
85
+ |
86
+ v
87
+ Babel AST Generator
88
+ |
89
+ v
90
+ React Project
91
+ ```
92
+
93
+ `visualbuild/pages.json` remains the safe schema boundary. Reverse sync accepts
94
+ documented static JSX/TSX structures; unsupported dynamic React code is left
95
+ untouched and produces an editor diagnostic.
96
+
97
+ ## Development
98
+
99
+ ```powershell
100
+ npm install
101
+ npm.cmd run visual-builder
102
+ ```
103
+
104
+ Useful checks:
105
+
106
+ ```powershell
107
+ npm.cmd run lint
108
+ npm.cmd run build
109
+ npm.cmd run test:week3
110
+ npm.cmd pack --dry-run
111
+ ```
112
+
113
+ ## Status
114
+
115
+ Current version: `0.1.0`
116
+
117
+ Week 3 is in progress. The stable published template, CLI identity, and
118
+ generator diagnostics are complete. The remaining release work is publishing
119
+ to npm and validating clean public-registry scaffolds.
120
+
121
+ ## License
122
+
123
+ UNLICENSED
package/package.json ADDED
@@ -0,0 +1,85 @@
1
+ {
2
+ "name": "create-visualbuild-app",
3
+ "version": "0.1.0",
4
+ "description": "Scaffold a React app with a separate VisualBuild drag-and-drop editor.",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-visualbuild-app": "scripts/create-builder-app.mjs"
8
+ },
9
+ "keywords": [
10
+ "visualbuild",
11
+ "react",
12
+ "vite",
13
+ "visual-editor",
14
+ "app-builder",
15
+ "scaffold"
16
+ ],
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/LORD-JINXXX/builder-app.git"
20
+ },
21
+ "bugs": {
22
+ "url": "https://github.com/LORD-JINXXX/builder-app/issues"
23
+ },
24
+ "homepage": "https://github.com/LORD-JINXXX/builder-app#readme",
25
+ "license": "UNLICENSED",
26
+ "files": [
27
+ "visual-app-builder/index.html",
28
+ "visual-app-builder/server",
29
+ "visual-app-builder/shared",
30
+ "visual-app-builder/src",
31
+ "visual-app-builder/vite.config.ts",
32
+ "templates",
33
+ "scripts/create-builder-app.mjs",
34
+ "scripts/vb-dev.mjs",
35
+ "scripts/vb-generate.mjs",
36
+ "README.md"
37
+ ],
38
+ "scripts": {
39
+ "dev": "vite",
40
+ "build": "tsc -b && vite build",
41
+ "lint": "eslint .",
42
+ "preview": "vite preview",
43
+ "test": "npm run test:week3",
44
+ "test:generator": "node --test tests/generate-react-source.test.mjs",
45
+ "test:registry": "node --test tests/component-registry.test.mjs",
46
+ "test:performance": "node --test tests/generator-performance.test.mjs",
47
+ "test:scaffold": "node --test tests/create-builder-app.test.mjs",
48
+ "test:diagnostics": "node --test tests/generator-diagnostics.test.mjs",
49
+ "test:reverse-sync": "node --experimental-strip-types --test tests/parse-react-page.test.ts",
50
+ "test:week3": "node --experimental-strip-types --test tests/parse-react-page.test.ts tests/generate-react-source.test.mjs tests/component-registry.test.mjs tests/generator-performance.test.mjs tests/create-builder-app.test.mjs tests/managed-page-deletion.test.mjs tests/managed-page-move.test.mjs tests/generator-diagnostics.test.mjs",
51
+ "create-visualbuild-app": "node scripts/create-builder-app.mjs",
52
+ "create-builder-app": "npm run create-visualbuild-app --",
53
+ "visual-builder": "node scripts/vb-dev.mjs",
54
+ "vb:generate": "node scripts/vb-generate.mjs"
55
+ },
56
+ "dependencies": {
57
+ "@babel/generator": "^7.29.7",
58
+ "@babel/parser": "^7.29.7",
59
+ "@babel/types": "^7.29.7",
60
+ "@dnd-kit/core": "^6.3.1",
61
+ "@dnd-kit/sortable": "^10.0.0",
62
+ "@dnd-kit/utilities": "^3.2.2",
63
+ "@tailwindcss/vite": "^4.3.1",
64
+ "chokidar": "^4.0.3",
65
+ "react": "^19.2.7",
66
+ "react-dom": "^19.2.7",
67
+ "uuid": "^14.0.1",
68
+ "zustand": "^5.0.14"
69
+ },
70
+ "devDependencies": {
71
+ "@eslint/js": "^10.0.1",
72
+ "@types/node": "^24.13.2",
73
+ "@types/react": "^19.2.17",
74
+ "@types/react-dom": "^19.2.3",
75
+ "@vitejs/plugin-react": "^6.0.2",
76
+ "eslint": "^10.5.0",
77
+ "eslint-plugin-react-hooks": "^7.1.1",
78
+ "eslint-plugin-react-refresh": "^0.5.3",
79
+ "globals": "^17.6.0",
80
+ "tailwindcss": "^4.3.1",
81
+ "typescript": "~6.0.2",
82
+ "typescript-eslint": "^8.61.0",
83
+ "vite": "^8.1.0"
84
+ }
85
+ }
@@ -0,0 +1,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 scriptFilesToCopy = ['vb-dev.mjs', 'vb-generate.mjs']
18
+
19
+ const appPackageDependencies = ['react', 'react-dom']
20
+ const appPackageDevDependencies = [
21
+ '@eslint/js',
22
+ '@tailwindcss/vite',
23
+ '@types/node',
24
+ '@types/react',
25
+ '@types/react-dom',
26
+ '@vitejs/plugin-react',
27
+ 'eslint',
28
+ 'eslint-plugin-react-hooks',
29
+ 'eslint-plugin-react-refresh',
30
+ 'globals',
31
+ 'tailwindcss',
32
+ 'typescript',
33
+ 'typescript-eslint',
34
+ 'vite',
35
+ ]
36
+
37
+ const builderPackageDependencies = [
38
+ '@babel/generator',
39
+ '@babel/parser',
40
+ '@babel/types',
41
+ '@dnd-kit/core',
42
+ '@dnd-kit/sortable',
43
+ '@dnd-kit/utilities',
44
+ '@tailwindcss/vite',
45
+ 'chokidar',
46
+ 'react',
47
+ 'react-dom',
48
+ 'uuid',
49
+ 'zustand',
50
+ ]
51
+
52
+ const builderPackageDevDependencies = [
53
+ '@eslint/js',
54
+ '@types/node',
55
+ '@types/react',
56
+ '@types/react-dom',
57
+ '@vitejs/plugin-react',
58
+ 'eslint',
59
+ 'eslint-plugin-react-hooks',
60
+ 'eslint-plugin-react-refresh',
61
+ 'globals',
62
+ 'tailwindcss',
63
+ 'typescript',
64
+ 'typescript-eslint',
65
+ 'vite',
66
+ ]
67
+
68
+ function getTargetBaseDir() {
69
+ return path.resolve(process.env.INIT_CWD || process.cwd())
70
+ }
71
+
72
+ function toProjectSlug(input) {
73
+ const slug = String(input || DEFAULT_PROJECT_NAME)
74
+ .trim()
75
+ .toLowerCase()
76
+ .replace(/['"]/g, '')
77
+ .replace(/[^a-z0-9]+/g, '-')
78
+ .replace(/^-+|-+$/g, '')
79
+
80
+ return slug || DEFAULT_PROJECT_NAME
81
+ }
82
+
83
+ function toDisplayName(slug) {
84
+ return slug
85
+ .split(/[-_]+/)
86
+ .filter(Boolean)
87
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
88
+ .join(' ')
89
+ }
90
+
91
+ async function pathExists(filePath) {
92
+ try {
93
+ await fs.access(filePath)
94
+ return true
95
+ } catch {
96
+ return false
97
+ }
98
+ }
99
+
100
+ async function assertTargetIsWritable(targetDir) {
101
+ if (!(await pathExists(targetDir))) {
102
+ return
103
+ }
104
+
105
+ const entries = await fs.readdir(targetDir)
106
+
107
+ if (entries.length > 0) {
108
+ throw new Error(
109
+ `Target folder "${targetDir}" already exists and is not empty.`
110
+ )
111
+ }
112
+ }
113
+
114
+ async function assertScaffoldTargetsAreWritable(appDir, builderDir) {
115
+ await assertTargetIsWritable(appDir)
116
+ await assertTargetIsWritable(builderDir)
117
+ }
118
+
119
+ async function assertScaffoldAssetsExist() {
120
+ const requiredAssets = [
121
+ appTemplateDir,
122
+ builderTemplateDir,
123
+ editorSourceDir,
124
+ ...scriptFilesToCopy.map((file) => path.join(repoRoot, 'scripts', file)),
125
+ ]
126
+
127
+ for (const assetPath of requiredAssets) {
128
+ if (!(await pathExists(assetPath))) {
129
+ throw new Error(
130
+ `Required scaffold asset is missing: ${path.relative(repoRoot, assetPath)}`
131
+ )
132
+ }
133
+ }
134
+ }
135
+
136
+ async function copyAppTemplate(appDir) {
137
+ await fs.cp(appTemplateDir, appDir, { recursive: true })
138
+ }
139
+
140
+ async function copyBuilderTemplate(builderDir) {
141
+ await fs.cp(builderTemplateDir, builderDir, { recursive: true })
142
+ await fs.cp(
143
+ editorSourceDir,
144
+ path.join(builderDir, 'visual-app-builder'),
145
+ { recursive: true }
146
+ )
147
+
148
+ await fs.mkdir(path.join(builderDir, 'scripts'), { recursive: true })
149
+
150
+ for (const file of scriptFilesToCopy) {
151
+ await fs.copyFile(
152
+ path.join(repoRoot, 'scripts', file),
153
+ path.join(builderDir, 'scripts', file)
154
+ )
155
+ }
156
+ }
157
+
158
+ async function rewriteJsonFile(filePath, update) {
159
+ const contents = await fs.readFile(filePath, 'utf8')
160
+ const json = JSON.parse(contents)
161
+ const nextJson = update(json)
162
+ await fs.writeFile(filePath, `${JSON.stringify(nextJson, null, 2)}\n`)
163
+ }
164
+
165
+ async function replaceInFile(filePath, replacements) {
166
+ if (!(await pathExists(filePath))) {
167
+ return
168
+ }
169
+
170
+ let contents = await fs.readFile(filePath, 'utf8')
171
+
172
+ for (const [from, to] of replacements) {
173
+ contents = contents.split(from).join(to)
174
+ }
175
+
176
+ await fs.writeFile(filePath, contents)
177
+ }
178
+
179
+ async function seedProject(appDir, builderDir, projectName, displayName) {
180
+ const appPackageJson = await readRootPackageJson()
181
+ const builderPackageJson = await readRootPackageJson()
182
+ const relativeAppDir = toPosixPath(path.relative(builderDir, appDir))
183
+
184
+ await fs.writeFile(
185
+ path.join(appDir, 'package.json'),
186
+ `${JSON.stringify(
187
+ createAppPackageJson(appPackageJson, projectName),
188
+ null,
189
+ 2
190
+ )}\n`
191
+ )
192
+
193
+ await fs.writeFile(
194
+ path.join(builderDir, 'package.json'),
195
+ `${JSON.stringify(
196
+ createBuilderPackageJson(
197
+ builderPackageJson,
198
+ relativeAppDir
199
+ ),
200
+ null,
201
+ 2
202
+ )}\n`
203
+ )
204
+
205
+ await rewriteJsonFile(
206
+ path.join(appDir, 'visualbuild', 'pages.json'),
207
+ (schema) => seedProjectSchema(schema, projectName, displayName)
208
+ )
209
+
210
+ const templateReplacements = [
211
+ ['__DISPLAY_NAME__', displayName],
212
+ ['__PROJECT_NAME__', projectName],
213
+ ]
214
+
215
+ await replaceInFile(path.join(appDir, 'index.html'), templateReplacements)
216
+ await replaceInFile(path.join(appDir, 'README.md'), templateReplacements)
217
+ await replaceInFile(path.join(builderDir, 'README.md'), templateReplacements)
218
+
219
+ const sourceReplacements = [
220
+ ['my-blog', projectName],
221
+ ['My Blog', displayName],
222
+ ['Welcome to My Blog', `Welcome to ${displayName}`],
223
+ ]
224
+
225
+ await replaceInFile(
226
+ path.join(builderDir, 'visual-app-builder', 'src', 'stores', 'useAppStore.ts'),
227
+ sourceReplacements
228
+ )
229
+ await replaceInFile(
230
+ path.join(
231
+ builderDir,
232
+ 'visual-app-builder',
233
+ 'src',
234
+ 'utils',
235
+ 'projectPersistence.ts'
236
+ ),
237
+ sourceReplacements
238
+ )
239
+ await replaceInFile(
240
+ path.join(
241
+ builderDir,
242
+ 'visual-app-builder',
243
+ 'src',
244
+ 'data',
245
+ 'componentRegistry.tsx'
246
+ ),
247
+ sourceReplacements
248
+ )
249
+ }
250
+
251
+ async function readRootPackageJson() {
252
+ return JSON.parse(await fs.readFile(path.join(repoRoot, 'package.json'), 'utf8'))
253
+ }
254
+
255
+ function createAppPackageJson(packageJson, projectName) {
256
+ const allDependencies = mergeDependencies(packageJson)
257
+
258
+ return {
259
+ name: projectName,
260
+ private: true,
261
+ version: packageJson.version,
262
+ type: 'module',
263
+ scripts: {
264
+ dev: 'vite',
265
+ build: 'tsc -b && vite build',
266
+ lint: 'eslint .',
267
+ preview: 'vite preview',
268
+ },
269
+ dependencies: pickDependencies(allDependencies, appPackageDependencies),
270
+ devDependencies: pickDependencies(
271
+ allDependencies,
272
+ appPackageDevDependencies
273
+ ),
274
+ }
275
+ }
276
+
277
+ function createBuilderPackageJson(packageJson, relativeAppDir) {
278
+ const allDependencies = mergeDependencies(packageJson)
279
+
280
+ return {
281
+ name: BUILDER_FOLDER_NAME,
282
+ private: true,
283
+ version: packageJson.version,
284
+ type: 'module',
285
+ scripts: {
286
+ 'visual-builder': `node scripts/vb-dev.mjs ${JSON.stringify(relativeAppDir)}`,
287
+ 'vb:generate': `node scripts/vb-generate.mjs ${JSON.stringify(relativeAppDir)}`,
288
+ build: 'vite build --config visual-app-builder/vite.config.ts',
289
+ lint: 'eslint .',
290
+ },
291
+ dependencies: pickDependencies(
292
+ allDependencies,
293
+ builderPackageDependencies
294
+ ),
295
+ devDependencies: pickDependencies(
296
+ allDependencies,
297
+ builderPackageDevDependencies
298
+ ),
299
+ }
300
+ }
301
+
302
+ function mergeDependencies(packageJson) {
303
+ return {
304
+ ...(packageJson.dependencies ?? {}),
305
+ ...(packageJson.devDependencies ?? {}),
306
+ }
307
+ }
308
+
309
+ function pickDependencies(source, names) {
310
+ return Object.fromEntries(
311
+ names
312
+ .filter((name) => source?.[name])
313
+ .map((name) => [name, source[name]])
314
+ )
315
+ }
316
+
317
+ function toPosixPath(filePath) {
318
+ return filePath.split(path.sep).join('/')
319
+ }
320
+
321
+ function seedProjectSchema(schema, projectName, displayName) {
322
+ return {
323
+ ...schema,
324
+ meta: {
325
+ ...schema.meta,
326
+ projectName,
327
+ },
328
+ appShell: {
329
+ header: null,
330
+ footer: null,
331
+ },
332
+ pages: [createStarterHomePage(displayName)],
333
+ }
334
+ }
335
+
336
+ function createStarterHomePage(displayName) {
337
+ return {
338
+ id: 'page-home',
339
+ name: 'Home',
340
+ route: '/',
341
+ sourcePath: 'src/pages/HomePage.tsx',
342
+ root: {
343
+ id: 'page-home:root',
344
+ type: 'main',
345
+ label: 'Page root',
346
+ props: {
347
+ className:
348
+ 'min-h-screen bg-slate-950 text-white flex items-center justify-center px-6',
349
+ },
350
+ children: [],
351
+ },
352
+ components: [
353
+ {
354
+ id: 'starter-shell',
355
+ type: 'section',
356
+ label: 'Starter',
357
+ props: {
358
+ className:
359
+ 'w-full max-w-5xl rounded-2xl border border-white/10 bg-white/10 px-8 py-12 text-center shadow-2xl backdrop-blur',
360
+ },
361
+ children: [
362
+ {
363
+ id: 'starter-eyebrow',
364
+ type: 'p',
365
+ props: {
366
+ text: 'VisualBuild starter',
367
+ className:
368
+ 'mb-4 text-xs font-semibold uppercase tracking-[0.28em] text-blue-300',
369
+ },
370
+ children: [],
371
+ },
372
+ {
373
+ id: 'starter-title',
374
+ type: 'h1',
375
+ props: {
376
+ text: `Welcome to ${displayName}`,
377
+ className: 'text-4xl font-bold tracking-tight sm:text-5xl',
378
+ },
379
+ children: [],
380
+ },
381
+ {
382
+ id: 'starter-subtitle',
383
+ type: 'p',
384
+ props: {
385
+ text: 'Build your app visually with drag and drop, then keep the real React code.',
386
+ className: 'mx-auto mt-5 max-w-2xl text-base text-slate-300',
387
+ },
388
+ children: [],
389
+ },
390
+ {
391
+ id: 'starter-actions',
392
+ type: 'div',
393
+ props: {
394
+ className:
395
+ 'mt-8 flex flex-wrap items-center justify-center gap-3',
396
+ },
397
+ children: [
398
+ {
399
+ id: 'starter-primary-action',
400
+ type: 'a',
401
+ props: {
402
+ text: 'Open Visual Builder',
403
+ href: 'http://127.0.0.1:7371',
404
+ target: '_blank',
405
+ className:
406
+ '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',
407
+ },
408
+ children: [],
409
+ },
410
+ {
411
+ id: 'starter-secondary-action',
412
+ type: 'span',
413
+ props: {
414
+ text: 'Edit this page from visual-builder',
415
+ className: 'text-sm text-slate-400',
416
+ },
417
+ children: [],
418
+ },
419
+ ],
420
+ },
421
+ ],
422
+ },
423
+ ],
424
+ }
425
+ }
426
+
427
+ async function regenerateSource(appDir) {
428
+ await execFileAsync(
429
+ process.execPath,
430
+ [path.join(repoRoot, 'scripts', 'vb-generate.mjs'), appDir],
431
+ {
432
+ cwd: repoRoot,
433
+ }
434
+ )
435
+ }
436
+
437
+ async function writeWorkspaceReadme(targetBaseDir, projectName, displayName) {
438
+ const readmePath = path.join(targetBaseDir, `${projectName}-README.md`)
439
+ const contents = `# ${displayName} VisualBuild Workspace
440
+
441
+ This workspace contains two separate projects:
442
+
443
+ - \`${projectName}\`: deployable React app source.
444
+ - \`${BUILDER_FOLDER_NAME}\`: VisualBuild editor and generator tooling.
445
+
446
+ Install and run the app:
447
+
448
+ \`\`\`powershell
449
+ cd ${projectName}
450
+ npm.cmd install
451
+ npm.cmd run dev
452
+ \`\`\`
453
+
454
+ Install and run the visual builder:
455
+
456
+ \`\`\`powershell
457
+ cd ..\\${BUILDER_FOLDER_NAME}
458
+ npm.cmd install
459
+ npm.cmd run visual-builder
460
+ \`\`\`
461
+
462
+ The builder writes to \`${projectName}/visualbuild/pages.json\` and regenerates \`${projectName}/src\`.
463
+ The React app package does not include builder-only drag-and-drop dependencies.
464
+ `
465
+
466
+ await fs.writeFile(readmePath, contents)
467
+ }
468
+
469
+ async function main() {
470
+ const projectName = toProjectSlug(process.argv[2])
471
+ const displayName = toDisplayName(projectName)
472
+ const targetBaseDir = getTargetBaseDir()
473
+ const appDir = path.resolve(targetBaseDir, projectName)
474
+ const builderDir = path.resolve(targetBaseDir, BUILDER_FOLDER_NAME)
475
+
476
+ await assertScaffoldAssetsExist()
477
+ await assertScaffoldTargetsAreWritable(appDir, builderDir)
478
+ await copyAppTemplate(appDir)
479
+ await copyBuilderTemplate(builderDir)
480
+ await seedProject(appDir, builderDir, projectName, displayName)
481
+ await regenerateSource(appDir)
482
+ await writeWorkspaceReadme(targetBaseDir, projectName, displayName)
483
+
484
+ console.log(`Created VisualBuild app "${displayName}" in ${appDir}`)
485
+ console.log(`Created VisualBuild editor in ${builderDir}`)
486
+ console.log('')
487
+ console.log('Next steps:')
488
+ console.log(` cd ${projectName}`)
489
+ console.log(' npm install')
490
+ console.log(' npm.cmd run dev')
491
+ console.log('')
492
+ console.log('Run the visual builder:')
493
+ console.log(` cd ..\\${BUILDER_FOLDER_NAME}`)
494
+ console.log(' npm install')
495
+ console.log(' npm.cmd run visual-builder')
496
+ }
497
+
498
+ main().catch((error) => {
499
+ console.error(error.message)
500
+ process.exit(1)
501
+ })