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.
- package/README.md +123 -0
- package/package.json +85 -0
- package/scripts/create-builder-app.mjs +501 -0
- package/scripts/vb-dev.mjs +32 -0
- package/scripts/vb-generate.mjs +224 -0
- package/templates/default/app/README.md +21 -0
- package/templates/default/app/eslint.config.js +25 -0
- package/templates/default/app/index.html +15 -0
- package/templates/default/app/src/index.css +23 -0
- package/templates/default/app/src/main.tsx +10 -0
- package/templates/default/app/tsconfig.app.json +21 -0
- package/templates/default/app/tsconfig.json +7 -0
- package/templates/default/app/tsconfig.node.json +19 -0
- package/templates/default/app/visualbuild/generated-files.json +3 -0
- package/templates/default/app/visualbuild/pages.json +12 -0
- package/templates/default/app/vite.config.ts +7 -0
- package/templates/default/builder/README.md +21 -0
- package/templates/default/builder/eslint.config.js +25 -0
- package/templates/default/builder/tsconfig.app.json +21 -0
- package/templates/default/builder/tsconfig.json +7 -0
- package/templates/default/builder/tsconfig.node.json +22 -0
- package/visual-app-builder/index.html +15 -0
- package/visual-app-builder/server/parseReactPage.ts +571 -0
- package/visual-app-builder/shared/generateReactSource.d.mts +37 -0
- package/visual-app-builder/shared/generateReactSource.mjs +443 -0
- package/visual-app-builder/src/App.tsx +874 -0
- package/visual-app-builder/src/components/Canvas.tsx +1059 -0
- package/visual-app-builder/src/components/CodePanel.tsx +812 -0
- package/visual-app-builder/src/components/ComponentSidebar.tsx +302 -0
- package/visual-app-builder/src/components/PageSwitcher.tsx +161 -0
- package/visual-app-builder/src/components/ProjectExplorer.tsx +1054 -0
- package/visual-app-builder/src/components/PropertiesPanel.tsx +692 -0
- package/visual-app-builder/src/components/Topbar.tsx +128 -0
- package/visual-app-builder/src/data/componentRegistry.tsx +292 -0
- package/visual-app-builder/src/data/primitiveRegistry.ts +368 -0
- package/visual-app-builder/src/index.css +111 -0
- package/visual-app-builder/src/main.tsx +10 -0
- package/visual-app-builder/src/stores/useAppStore.ts +1265 -0
- package/visual-app-builder/src/tailwindGeneratedSafelist.ts +36 -0
- package/visual-app-builder/src/types/index.ts +261 -0
- package/visual-app-builder/src/utils/codegen.ts +66 -0
- package/visual-app-builder/src/utils/projectFiles.ts +146 -0
- package/visual-app-builder/src/utils/projectPersistence.ts +177 -0
- 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
|
+
})
|