create-what 0.5.1 → 0.5.4
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 +77 -0
- package/index.js +824 -84
- package/package.json +7 -3
package/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# create-what
|
|
2
|
+
|
|
3
|
+
Scaffold a new [What Framework](https://whatfw.com) project with one command.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx create-what my-app
|
|
9
|
+
cd my-app
|
|
10
|
+
npm install
|
|
11
|
+
npm run dev
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Or with Bun:
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
bun create what@latest my-app
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Skip prompts
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx create-what my-app --yes
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Options
|
|
27
|
+
|
|
28
|
+
The scaffolder prompts you for:
|
|
29
|
+
|
|
30
|
+
1. **Project name** -- directory to create
|
|
31
|
+
2. **React compat** -- include `what-react` for using React libraries (zustand, TanStack Query, etc.)
|
|
32
|
+
3. **CSS approach** -- vanilla CSS, Tailwind CSS v4, or StyleX
|
|
33
|
+
|
|
34
|
+
## What You Get
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
my-app/
|
|
38
|
+
src/
|
|
39
|
+
main.jsx # App entry point with counter example
|
|
40
|
+
styles.css # Styles (vanilla, Tailwind, or StyleX)
|
|
41
|
+
public/
|
|
42
|
+
favicon.svg # What Framework logo
|
|
43
|
+
index.html # HTML entry
|
|
44
|
+
vite.config.js # Pre-configured Vite (What compiler or React compat)
|
|
45
|
+
tsconfig.json # TypeScript config
|
|
46
|
+
package.json
|
|
47
|
+
.gitignore
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### With React compat enabled
|
|
51
|
+
|
|
52
|
+
The scaffold includes a working zustand demo showing a React state library running on What's signal engine.
|
|
53
|
+
|
|
54
|
+
### With Tailwind CSS
|
|
55
|
+
|
|
56
|
+
Tailwind v4 is configured via `@tailwindcss/vite`. The counter example uses utility classes.
|
|
57
|
+
|
|
58
|
+
### With StyleX
|
|
59
|
+
|
|
60
|
+
StyleX is configured via `vite-plugin-stylex`. The counter example uses `stylex.create()` and `stylex.props()`.
|
|
61
|
+
|
|
62
|
+
## Scripts
|
|
63
|
+
|
|
64
|
+
| Script | Command |
|
|
65
|
+
|---|---|
|
|
66
|
+
| `npm run dev` | Start Vite dev server |
|
|
67
|
+
| `npm run build` | Production build |
|
|
68
|
+
| `npm run preview` | Preview production build |
|
|
69
|
+
|
|
70
|
+
## Links
|
|
71
|
+
|
|
72
|
+
- [Documentation](https://whatfw.com)
|
|
73
|
+
- [GitHub](https://github.com/zvndev/what-fw)
|
|
74
|
+
|
|
75
|
+
## License
|
|
76
|
+
|
|
77
|
+
MIT
|
package/index.js
CHANGED
|
@@ -4,53 +4,219 @@
|
|
|
4
4
|
// Canonical scaffold for What Framework projects.
|
|
5
5
|
// Usage:
|
|
6
6
|
// npx create-what my-app
|
|
7
|
-
// npx create-what my-app --
|
|
7
|
+
// npx create-what my-app --yes (skip prompts, use defaults)
|
|
8
8
|
|
|
9
9
|
import { mkdirSync, writeFileSync, existsSync } from 'node:fs';
|
|
10
10
|
import { join } from 'node:path';
|
|
11
|
+
import { createInterface } from 'node:readline';
|
|
11
12
|
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// CLI arguments
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
12
16
|
const args = process.argv.slice(2);
|
|
13
|
-
const flags = args.filter(a => a.startsWith('-'));
|
|
14
17
|
const positional = args.filter(a => !a.startsWith('-'));
|
|
18
|
+
const flags = new Set(args.filter(a => a.startsWith('-')));
|
|
19
|
+
const skipPrompts = flags.has('--yes') || flags.has('-y');
|
|
20
|
+
|
|
21
|
+
// ---------------------------------------------------------------------------
|
|
22
|
+
// Prompt helpers (zero-dependency, uses Node readline)
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Creates an async prompt interface that works with both TTY and piped stdin.
|
|
27
|
+
* When stdin is piped, readline can close before all questions are asked.
|
|
28
|
+
* We buffer all incoming lines to handle this gracefully.
|
|
29
|
+
*/
|
|
30
|
+
function createPrompter() {
|
|
31
|
+
const lines = [];
|
|
32
|
+
let lineResolve = null;
|
|
33
|
+
let closed = false;
|
|
34
|
+
|
|
35
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: process.stdin.isTTY ?? false });
|
|
36
|
+
|
|
37
|
+
rl.on('line', (line) => {
|
|
38
|
+
if (lineResolve) {
|
|
39
|
+
const resolve = lineResolve;
|
|
40
|
+
lineResolve = null;
|
|
41
|
+
resolve(line);
|
|
42
|
+
} else {
|
|
43
|
+
lines.push(line);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
rl.on('close', () => {
|
|
48
|
+
closed = true;
|
|
49
|
+
if (lineResolve) {
|
|
50
|
+
const resolve = lineResolve;
|
|
51
|
+
lineResolve = null;
|
|
52
|
+
resolve('');
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
function ask(question) {
|
|
57
|
+
process.stdout.write(question);
|
|
58
|
+
if (lines.length > 0) return Promise.resolve(lines.shift());
|
|
59
|
+
if (closed) return Promise.resolve('');
|
|
60
|
+
return new Promise(resolve => { lineResolve = resolve; });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function confirm(message, defaultYes = false) {
|
|
64
|
+
const hint = defaultYes ? '[Y/n]' : '[y/N]';
|
|
65
|
+
const answer = (await ask(` ${message} ${hint} `)).trim().toLowerCase();
|
|
66
|
+
if (answer === '') return defaultYes;
|
|
67
|
+
return answer === 'y' || answer === 'yes';
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function select(message, choices) {
|
|
71
|
+
console.log(` ${message}`);
|
|
72
|
+
choices.forEach((c, i) => console.log(` ${i + 1}) ${c.label}`));
|
|
73
|
+
const answer = (await ask(` Choice [1]: `)).trim();
|
|
74
|
+
const idx = answer === '' ? 0 : parseInt(answer, 10) - 1;
|
|
75
|
+
if (idx >= 0 && idx < choices.length) return choices[idx].value;
|
|
76
|
+
return choices[0].value;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function close() {
|
|
80
|
+
rl.close();
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return { ask, confirm, select, close };
|
|
84
|
+
}
|
|
15
85
|
|
|
16
|
-
|
|
17
|
-
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
// Gather options
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
async function gatherOptions() {
|
|
90
|
+
let projectName = positional[0];
|
|
91
|
+
let reactCompat = false;
|
|
92
|
+
let cssApproach = 'none'; // 'none' | 'tailwind' | 'stylex'
|
|
18
93
|
|
|
19
|
-
|
|
94
|
+
if (skipPrompts) {
|
|
95
|
+
projectName = projectName || 'my-what-app';
|
|
96
|
+
return { projectName, reactCompat, cssApproach };
|
|
97
|
+
}
|
|
20
98
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
99
|
+
const prompter = createPrompter();
|
|
100
|
+
|
|
101
|
+
console.log('\n create-what - scaffold a What Framework project\n');
|
|
102
|
+
|
|
103
|
+
if (!projectName) {
|
|
104
|
+
projectName = (await prompter.ask(' Project name: ')).trim() || 'my-what-app';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
reactCompat = await prompter.confirm('Add React library support? (what-react)');
|
|
108
|
+
|
|
109
|
+
cssApproach = await prompter.select('CSS approach:', [
|
|
110
|
+
{ label: 'None (vanilla CSS)', value: 'none' },
|
|
111
|
+
{ label: 'Tailwind CSS v4', value: 'tailwind' },
|
|
112
|
+
{ label: 'StyleX', value: 'stylex' },
|
|
113
|
+
]);
|
|
114
|
+
|
|
115
|
+
prompter.close();
|
|
116
|
+
|
|
117
|
+
return { projectName, reactCompat, cssApproach };
|
|
24
118
|
}
|
|
25
119
|
|
|
26
|
-
|
|
27
|
-
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
// File generators
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
|
|
124
|
+
function generatePackageJson(projectName, { reactCompat, cssApproach }) {
|
|
125
|
+
const deps = {
|
|
126
|
+
'what-framework': '^0.5.3',
|
|
127
|
+
};
|
|
128
|
+
const devDeps = {
|
|
129
|
+
vite: '^6.0.0',
|
|
130
|
+
'what-compiler': '^0.5.3',
|
|
131
|
+
'what-devtools': '^0.5.3',
|
|
132
|
+
'what-devtools-mcp': '^0.1.0',
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
if (reactCompat) {
|
|
136
|
+
deps['what-react'] = '^0.1.0';
|
|
137
|
+
deps['what-core'] = '^0.5.3';
|
|
138
|
+
// Include zustand as a demo React library
|
|
139
|
+
deps['zustand'] = '^5.0.0';
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (cssApproach === 'tailwind') {
|
|
143
|
+
devDeps['tailwindcss'] = '^4.0.0';
|
|
144
|
+
devDeps['@tailwindcss/vite'] = '^4.0.0';
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (cssApproach === 'stylex') {
|
|
148
|
+
devDeps['@stylexjs/stylex'] = '^0.10.0';
|
|
149
|
+
devDeps['vite-plugin-stylex'] = '^0.12.0';
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return JSON.stringify({
|
|
153
|
+
name: projectName,
|
|
154
|
+
private: true,
|
|
155
|
+
version: '0.1.0',
|
|
156
|
+
type: 'module',
|
|
157
|
+
scripts: {
|
|
158
|
+
dev: 'vite',
|
|
159
|
+
build: 'vite build',
|
|
160
|
+
preview: 'vite preview',
|
|
161
|
+
},
|
|
162
|
+
dependencies: deps,
|
|
163
|
+
devDependencies: devDeps,
|
|
164
|
+
}, null, 2) + '\n';
|
|
165
|
+
}
|
|
28
166
|
|
|
29
|
-
|
|
30
|
-
const
|
|
167
|
+
function generateViteConfig({ reactCompat, cssApproach }) {
|
|
168
|
+
const imports = [];
|
|
169
|
+
const plugins = [];
|
|
31
170
|
|
|
32
|
-
|
|
171
|
+
imports.push(`import { defineConfig } from 'vite';`);
|
|
33
172
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
'what-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
173
|
+
// AI-powered debugging — auto-injects devtools in dev mode
|
|
174
|
+
imports.push(`import whatDevToolsMCP from 'what-devtools-mcp/vite-plugin';`);
|
|
175
|
+
|
|
176
|
+
if (reactCompat) {
|
|
177
|
+
// React compat projects use the what-react Vite plugin instead of the
|
|
178
|
+
// What compiler. The what-react plugin aliases react/react-dom imports
|
|
179
|
+
// to what-react, which runs React code on What Framework's signal engine.
|
|
180
|
+
imports.push(`import { reactCompat } from 'what-react/vite';`);
|
|
181
|
+
plugins.push('reactCompat()');
|
|
182
|
+
} else {
|
|
183
|
+
// Standard What Framework projects use the What compiler for JSX
|
|
184
|
+
imports.push(`import what from 'what-compiler/vite';`);
|
|
185
|
+
plugins.push('what()');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
plugins.push('whatDevToolsMCP()');
|
|
189
|
+
|
|
190
|
+
if (cssApproach === 'tailwind') {
|
|
191
|
+
imports.push(`import tailwindcss from '@tailwindcss/vite';`);
|
|
192
|
+
plugins.push('tailwindcss()');
|
|
193
|
+
}
|
|
52
194
|
|
|
53
|
-
|
|
195
|
+
if (cssApproach === 'stylex') {
|
|
196
|
+
imports.push(`import stylexPlugin from 'vite-plugin-stylex';`);
|
|
197
|
+
plugins.push('stylexPlugin()');
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
let config = `${imports.join('\n')}
|
|
201
|
+
|
|
202
|
+
export default defineConfig({
|
|
203
|
+
plugins: [${plugins.join(', ')}],`;
|
|
204
|
+
|
|
205
|
+
if (reactCompat) {
|
|
206
|
+
// Note: the what-react/vite plugin handles optimizeDeps.exclude and
|
|
207
|
+
// resolve.alias automatically. No manual configuration needed.
|
|
208
|
+
config += `
|
|
209
|
+
// what-react/vite handles all React aliasing and optimizeDeps automatically.
|
|
210
|
+
// Any React library you install (zustand, @tanstack/react-query, etc.)
|
|
211
|
+
// will be auto-detected and excluded from pre-bundling.`;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
config += '\n});\n';
|
|
215
|
+
return config;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function generateIndexHtml(projectName) {
|
|
219
|
+
return `<!doctype html>
|
|
54
220
|
<html lang="en">
|
|
55
221
|
<head>
|
|
56
222
|
<meta charset="UTF-8" />
|
|
@@ -61,33 +227,93 @@ writeFileSync(join(root, 'index.html'), `<!doctype html>
|
|
|
61
227
|
</head>
|
|
62
228
|
<body>
|
|
63
229
|
<div id="app"></div>
|
|
64
|
-
<script type="module" src="
|
|
230
|
+
<script type="module" src="/src/main.jsx"></script>
|
|
65
231
|
</body>
|
|
66
232
|
</html>
|
|
67
|
-
|
|
233
|
+
`;
|
|
234
|
+
}
|
|
68
235
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
<stop offset="100%" stop-color="#1d4ed8" />
|
|
74
|
-
</linearGradient>
|
|
75
|
-
</defs>
|
|
76
|
-
<rect width="64" height="64" rx="14" fill="url(#g)" />
|
|
77
|
-
<path d="M17 20h10l5 20 5-20h10L36 49h-8z" fill="#fff" />
|
|
78
|
-
</svg>
|
|
79
|
-
`);
|
|
236
|
+
function generateStyles({ cssApproach }) {
|
|
237
|
+
if (cssApproach === 'tailwind') {
|
|
238
|
+
// Tailwind v4: just one import, utility classes handle the rest
|
|
239
|
+
return `@import "tailwindcss";
|
|
80
240
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
241
|
+
/* Custom styles — use Tailwind utility classes in your JSX instead */
|
|
242
|
+
`;
|
|
243
|
+
}
|
|
84
244
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
245
|
+
// Vanilla CSS and StyleX both get a baseline stylesheet
|
|
246
|
+
return `:root {
|
|
247
|
+
color-scheme: light;
|
|
248
|
+
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif;
|
|
249
|
+
}
|
|
89
250
|
|
|
90
|
-
|
|
251
|
+
* {
|
|
252
|
+
box-sizing: border-box;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
body {
|
|
256
|
+
margin: 0;
|
|
257
|
+
min-height: 100vh;
|
|
258
|
+
display: grid;
|
|
259
|
+
place-items: center;
|
|
260
|
+
background: #f4f6fb;
|
|
261
|
+
color: #0f172a;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.app-shell {
|
|
265
|
+
width: min(560px, calc(100vw - 2rem));
|
|
266
|
+
background: #ffffff;
|
|
267
|
+
border: 1px solid #dbe2ee;
|
|
268
|
+
border-radius: 16px;
|
|
269
|
+
padding: 2rem;
|
|
270
|
+
box-shadow: 0 16px 40px rgba(15, 23, 42, 0.06);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
.counter {
|
|
274
|
+
margin-top: 1rem;
|
|
275
|
+
display: inline-flex;
|
|
276
|
+
align-items: center;
|
|
277
|
+
gap: 0.75rem;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
button {
|
|
281
|
+
border: 1px solid #9aa7bb;
|
|
282
|
+
background: #ffffff;
|
|
283
|
+
color: #0f172a;
|
|
284
|
+
width: 2.25rem;
|
|
285
|
+
height: 2.25rem;
|
|
286
|
+
border-radius: 10px;
|
|
287
|
+
cursor: pointer;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
button:hover {
|
|
291
|
+
border-color: #2563eb;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
output {
|
|
295
|
+
min-width: 2ch;
|
|
296
|
+
text-align: center;
|
|
297
|
+
font-weight: 700;
|
|
298
|
+
}
|
|
299
|
+
`;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
function generateMainJsx({ reactCompat, cssApproach }) {
|
|
303
|
+
if (reactCompat) {
|
|
304
|
+
return generateMainWithReactCompat({ cssApproach });
|
|
305
|
+
}
|
|
306
|
+
if (cssApproach === 'stylex') {
|
|
307
|
+
return generateMainWithStyleX();
|
|
308
|
+
}
|
|
309
|
+
if (cssApproach === 'tailwind') {
|
|
310
|
+
return generateMainWithTailwind();
|
|
311
|
+
}
|
|
312
|
+
return generateMainDefault();
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
function generateMainDefault() {
|
|
316
|
+
return `import { mount, useSignal } from 'what-framework';
|
|
91
317
|
|
|
92
318
|
function App() {
|
|
93
319
|
const count = useSignal(0);
|
|
@@ -107,34 +333,364 @@ function App() {
|
|
|
107
333
|
}
|
|
108
334
|
|
|
109
335
|
mount(<App />, '#app');
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
writeFileSync(join(root, 'vite.config.js'), `import { defineConfig } from 'vite';
|
|
336
|
+
`;
|
|
337
|
+
}
|
|
113
338
|
|
|
114
|
-
|
|
115
|
-
`
|
|
339
|
+
function generateMainWithTailwind() {
|
|
340
|
+
return `import { mount, useSignal } from 'what-framework';
|
|
341
|
+
|
|
342
|
+
function App() {
|
|
343
|
+
const count = useSignal(0);
|
|
344
|
+
|
|
345
|
+
return (
|
|
346
|
+
<main className="min-h-screen flex items-center justify-center bg-slate-50">
|
|
347
|
+
<div className="w-full max-w-md bg-white border border-slate-200 rounded-2xl p-8 shadow-lg">
|
|
348
|
+
<h1 className="text-2xl font-bold text-slate-900">What Framework</h1>
|
|
349
|
+
<p className="mt-1 text-slate-500">Compiler-first JSX + Tailwind CSS.</p>
|
|
350
|
+
|
|
351
|
+
<section className="mt-6 inline-flex items-center gap-3">
|
|
352
|
+
<button
|
|
353
|
+
className="w-9 h-9 rounded-lg border border-slate-300 bg-white text-slate-900 hover:border-blue-600 cursor-pointer"
|
|
354
|
+
onClick={() => count.set(c => c - 1)}
|
|
355
|
+
>
|
|
356
|
+
-
|
|
357
|
+
</button>
|
|
358
|
+
<output className="min-w-[2ch] text-center font-bold">{count()}</output>
|
|
359
|
+
<button
|
|
360
|
+
className="w-9 h-9 rounded-lg border border-slate-300 bg-white text-slate-900 hover:border-blue-600 cursor-pointer"
|
|
361
|
+
onClick={() => count.set(c => c + 1)}
|
|
362
|
+
>
|
|
363
|
+
+
|
|
364
|
+
</button>
|
|
365
|
+
</section>
|
|
366
|
+
</div>
|
|
367
|
+
</main>
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
mount(<App />, '#app');
|
|
372
|
+
`;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
function generateMainWithStyleX() {
|
|
376
|
+
return `import { mount, useSignal } from 'what-framework';
|
|
377
|
+
import * as stylex from '@stylexjs/stylex';
|
|
378
|
+
|
|
379
|
+
const styles = stylex.create({
|
|
380
|
+
page: {
|
|
381
|
+
minHeight: '100vh',
|
|
382
|
+
display: 'grid',
|
|
383
|
+
placeItems: 'center',
|
|
384
|
+
background: '#f4f6fb',
|
|
385
|
+
fontFamily: 'ui-sans-serif, system-ui, -apple-system, sans-serif',
|
|
386
|
+
},
|
|
387
|
+
shell: {
|
|
388
|
+
width: 'min(560px, calc(100vw - 2rem))',
|
|
389
|
+
background: '#ffffff',
|
|
390
|
+
border: '1px solid #dbe2ee',
|
|
391
|
+
borderRadius: 16,
|
|
392
|
+
padding: '2rem',
|
|
393
|
+
boxShadow: '0 16px 40px rgba(15, 23, 42, 0.06)',
|
|
394
|
+
},
|
|
395
|
+
heading: {
|
|
396
|
+
fontSize: '1.5rem',
|
|
397
|
+
fontWeight: 700,
|
|
398
|
+
color: '#0f172a',
|
|
399
|
+
},
|
|
400
|
+
subtitle: {
|
|
401
|
+
marginTop: '0.25rem',
|
|
402
|
+
color: '#64748b',
|
|
403
|
+
},
|
|
404
|
+
counter: {
|
|
405
|
+
marginTop: '1rem',
|
|
406
|
+
display: 'inline-flex',
|
|
407
|
+
alignItems: 'center',
|
|
408
|
+
gap: '0.75rem',
|
|
409
|
+
},
|
|
410
|
+
button: {
|
|
411
|
+
width: '2.25rem',
|
|
412
|
+
height: '2.25rem',
|
|
413
|
+
borderRadius: 10,
|
|
414
|
+
border: '1px solid #9aa7bb',
|
|
415
|
+
background: '#ffffff',
|
|
416
|
+
color: '#0f172a',
|
|
417
|
+
cursor: 'pointer',
|
|
418
|
+
':hover': {
|
|
419
|
+
borderColor: '#2563eb',
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
output: {
|
|
423
|
+
minWidth: '2ch',
|
|
424
|
+
textAlign: 'center',
|
|
425
|
+
fontWeight: 700,
|
|
426
|
+
},
|
|
427
|
+
});
|
|
116
428
|
|
|
117
|
-
|
|
429
|
+
function App() {
|
|
430
|
+
const count = useSignal(0);
|
|
431
|
+
|
|
432
|
+
return (
|
|
433
|
+
<main {...stylex.props(styles.page)}>
|
|
434
|
+
<div {...stylex.props(styles.shell)}>
|
|
435
|
+
<h1 {...stylex.props(styles.heading)}>What Framework</h1>
|
|
436
|
+
<p {...stylex.props(styles.subtitle)}>Compiler-first JSX + StyleX.</p>
|
|
437
|
+
|
|
438
|
+
<section {...stylex.props(styles.counter)}>
|
|
439
|
+
<button {...stylex.props(styles.button)} onClick={() => count.set(c => c - 1)}>-</button>
|
|
440
|
+
<output {...stylex.props(styles.output)}>{count()}</output>
|
|
441
|
+
<button {...stylex.props(styles.button)} onClick={() => count.set(c => c + 1)}>+</button>
|
|
442
|
+
</section>
|
|
443
|
+
</div>
|
|
444
|
+
</main>
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
mount(<App />, '#app');
|
|
449
|
+
`;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function generateMainWithReactCompat({ cssApproach }) {
|
|
453
|
+
// React compat demo: uses zustand (a real React library) to show it works
|
|
454
|
+
// with What Framework under the hood.
|
|
455
|
+
if (cssApproach === 'tailwind') {
|
|
456
|
+
return `import { mount, useSignal } from 'what-framework';
|
|
457
|
+
import { create } from 'zustand';
|
|
458
|
+
|
|
459
|
+
// A real React state library — works with What Framework via what-react!
|
|
460
|
+
const useStore = create((set) => ({
|
|
461
|
+
bears: 0,
|
|
462
|
+
increase: () => set((state) => ({ bears: state.bears + 1 })),
|
|
463
|
+
reset: () => set({ bears: 0 }),
|
|
464
|
+
}));
|
|
465
|
+
|
|
466
|
+
function BearCounter() {
|
|
467
|
+
// useStore is a React hook from zustand — what-react makes it work seamlessly
|
|
468
|
+
const bears = useStore((state) => state.bears);
|
|
469
|
+
const increase = useStore((state) => state.increase);
|
|
470
|
+
const reset = useStore((state) => state.reset);
|
|
471
|
+
|
|
472
|
+
return (
|
|
473
|
+
<div className="mt-6 p-4 bg-amber-50 border border-amber-200 rounded-xl">
|
|
474
|
+
<p className="text-sm text-amber-700 font-medium">Zustand Store (React library)</p>
|
|
475
|
+
<p className="mt-1 text-2xl font-bold text-amber-900">{bears} bears</p>
|
|
476
|
+
<div className="mt-2 flex gap-2">
|
|
477
|
+
<button
|
|
478
|
+
className="px-3 py-1 rounded-lg border border-amber-300 bg-white text-amber-800 hover:border-amber-500 cursor-pointer text-sm"
|
|
479
|
+
onClick={increase}
|
|
480
|
+
>
|
|
481
|
+
Add bear
|
|
482
|
+
</button>
|
|
483
|
+
<button
|
|
484
|
+
className="px-3 py-1 rounded-lg border border-amber-300 bg-white text-amber-800 hover:border-amber-500 cursor-pointer text-sm"
|
|
485
|
+
onClick={reset}
|
|
486
|
+
>
|
|
487
|
+
Reset
|
|
488
|
+
</button>
|
|
489
|
+
</div>
|
|
490
|
+
</div>
|
|
491
|
+
);
|
|
492
|
+
}
|
|
118
493
|
|
|
119
494
|
function App() {
|
|
120
|
-
const count =
|
|
121
|
-
|
|
122
|
-
return
|
|
123
|
-
h
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
495
|
+
const count = useSignal(0);
|
|
496
|
+
|
|
497
|
+
return (
|
|
498
|
+
<main className="min-h-screen flex items-center justify-center bg-slate-50">
|
|
499
|
+
<div className="w-full max-w-md bg-white border border-slate-200 rounded-2xl p-8 shadow-lg">
|
|
500
|
+
<h1 className="text-2xl font-bold text-slate-900">What Framework</h1>
|
|
501
|
+
<p className="mt-1 text-slate-500">React compat + Tailwind CSS.</p>
|
|
502
|
+
|
|
503
|
+
<section className="mt-6 inline-flex items-center gap-3">
|
|
504
|
+
<button
|
|
505
|
+
className="w-9 h-9 rounded-lg border border-slate-300 bg-white text-slate-900 hover:border-blue-600 cursor-pointer"
|
|
506
|
+
onClick={() => count.set(c => c - 1)}
|
|
507
|
+
>
|
|
508
|
+
-
|
|
509
|
+
</button>
|
|
510
|
+
<output className="min-w-[2ch] text-center font-bold">{count()}</output>
|
|
511
|
+
<button
|
|
512
|
+
className="w-9 h-9 rounded-lg border border-slate-300 bg-white text-slate-900 hover:border-blue-600 cursor-pointer"
|
|
513
|
+
onClick={() => count.set(c => c + 1)}
|
|
514
|
+
>
|
|
515
|
+
+
|
|
516
|
+
</button>
|
|
517
|
+
</section>
|
|
518
|
+
|
|
519
|
+
<BearCounter />
|
|
520
|
+
</div>
|
|
521
|
+
</main>
|
|
130
522
|
);
|
|
131
523
|
}
|
|
132
524
|
|
|
133
|
-
mount(
|
|
134
|
-
|
|
525
|
+
mount(<App />, '#app');
|
|
526
|
+
`;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
if (cssApproach === 'stylex') {
|
|
530
|
+
return `import { mount, useSignal } from 'what-framework';
|
|
531
|
+
import { create } from 'zustand';
|
|
532
|
+
import * as stylex from '@stylexjs/stylex';
|
|
533
|
+
|
|
534
|
+
const styles = stylex.create({
|
|
535
|
+
page: {
|
|
536
|
+
minHeight: '100vh',
|
|
537
|
+
display: 'grid',
|
|
538
|
+
placeItems: 'center',
|
|
539
|
+
background: '#f4f6fb',
|
|
540
|
+
fontFamily: 'ui-sans-serif, system-ui, -apple-system, sans-serif',
|
|
541
|
+
},
|
|
542
|
+
shell: {
|
|
543
|
+
width: 'min(560px, calc(100vw - 2rem))',
|
|
544
|
+
background: '#ffffff',
|
|
545
|
+
border: '1px solid #dbe2ee',
|
|
546
|
+
borderRadius: 16,
|
|
547
|
+
padding: '2rem',
|
|
548
|
+
boxShadow: '0 16px 40px rgba(15, 23, 42, 0.06)',
|
|
549
|
+
},
|
|
550
|
+
heading: { fontSize: '1.5rem', fontWeight: 700, color: '#0f172a' },
|
|
551
|
+
subtitle: { marginTop: '0.25rem', color: '#64748b' },
|
|
552
|
+
counter: {
|
|
553
|
+
marginTop: '1rem',
|
|
554
|
+
display: 'inline-flex',
|
|
555
|
+
alignItems: 'center',
|
|
556
|
+
gap: '0.75rem',
|
|
557
|
+
},
|
|
558
|
+
button: {
|
|
559
|
+
width: '2.25rem',
|
|
560
|
+
height: '2.25rem',
|
|
561
|
+
borderRadius: 10,
|
|
562
|
+
border: '1px solid #9aa7bb',
|
|
563
|
+
background: '#ffffff',
|
|
564
|
+
color: '#0f172a',
|
|
565
|
+
cursor: 'pointer',
|
|
566
|
+
':hover': { borderColor: '#2563eb' },
|
|
567
|
+
},
|
|
568
|
+
output: { minWidth: '2ch', textAlign: 'center', fontWeight: 700 },
|
|
569
|
+
zustandBox: {
|
|
570
|
+
marginTop: '1.5rem',
|
|
571
|
+
padding: '1rem',
|
|
572
|
+
background: '#fffbeb',
|
|
573
|
+
border: '1px solid #fde68a',
|
|
574
|
+
borderRadius: 12,
|
|
575
|
+
},
|
|
576
|
+
zustandLabel: { fontSize: '0.875rem', color: '#92400e', fontWeight: 500 },
|
|
577
|
+
zustandCount: { marginTop: '0.25rem', fontSize: '1.5rem', fontWeight: 700, color: '#78350f' },
|
|
578
|
+
zustandButtons: { marginTop: '0.5rem', display: 'flex', gap: '0.5rem' },
|
|
579
|
+
zustandBtn: {
|
|
580
|
+
padding: '0.25rem 0.75rem',
|
|
581
|
+
borderRadius: 8,
|
|
582
|
+
border: '1px solid #fbbf24',
|
|
583
|
+
background: '#ffffff',
|
|
584
|
+
color: '#78350f',
|
|
585
|
+
cursor: 'pointer',
|
|
586
|
+
fontSize: '0.875rem',
|
|
587
|
+
':hover': { borderColor: '#d97706' },
|
|
588
|
+
},
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
// A real React state library — works with What Framework via what-react!
|
|
592
|
+
const useStore = create((set) => ({
|
|
593
|
+
bears: 0,
|
|
594
|
+
increase: () => set((state) => ({ bears: state.bears + 1 })),
|
|
595
|
+
reset: () => set({ bears: 0 }),
|
|
596
|
+
}));
|
|
597
|
+
|
|
598
|
+
function BearCounter() {
|
|
599
|
+
const bears = useStore((state) => state.bears);
|
|
600
|
+
const increase = useStore((state) => state.increase);
|
|
601
|
+
const reset = useStore((state) => state.reset);
|
|
602
|
+
|
|
603
|
+
return (
|
|
604
|
+
<div {...stylex.props(styles.zustandBox)}>
|
|
605
|
+
<p {...stylex.props(styles.zustandLabel)}>Zustand Store (React library)</p>
|
|
606
|
+
<p {...stylex.props(styles.zustandCount)}>{bears} bears</p>
|
|
607
|
+
<div {...stylex.props(styles.zustandButtons)}>
|
|
608
|
+
<button {...stylex.props(styles.zustandBtn)} onClick={increase}>Add bear</button>
|
|
609
|
+
<button {...stylex.props(styles.zustandBtn)} onClick={reset}>Reset</button>
|
|
610
|
+
</div>
|
|
611
|
+
</div>
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function App() {
|
|
616
|
+
const count = useSignal(0);
|
|
617
|
+
|
|
618
|
+
return (
|
|
619
|
+
<main {...stylex.props(styles.page)}>
|
|
620
|
+
<div {...stylex.props(styles.shell)}>
|
|
621
|
+
<h1 {...stylex.props(styles.heading)}>What Framework</h1>
|
|
622
|
+
<p {...stylex.props(styles.subtitle)}>React compat + StyleX.</p>
|
|
623
|
+
|
|
624
|
+
<section {...stylex.props(styles.counter)}>
|
|
625
|
+
<button {...stylex.props(styles.button)} onClick={() => count.set(c => c - 1)}>-</button>
|
|
626
|
+
<output {...stylex.props(styles.output)}>{count()}</output>
|
|
627
|
+
<button {...stylex.props(styles.button)} onClick={() => count.set(c => c + 1)}>+</button>
|
|
628
|
+
</section>
|
|
629
|
+
|
|
630
|
+
<BearCounter />
|
|
631
|
+
</div>
|
|
632
|
+
</main>
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
mount(<App />, '#app');
|
|
637
|
+
`;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// React compat with vanilla CSS
|
|
641
|
+
return `import { mount, useSignal } from 'what-framework';
|
|
642
|
+
import { create } from 'zustand';
|
|
643
|
+
|
|
644
|
+
// A real React state library — works with What Framework via what-react!
|
|
645
|
+
const useStore = create((set) => ({
|
|
646
|
+
bears: 0,
|
|
647
|
+
increase: () => set((state) => ({ bears: state.bears + 1 })),
|
|
648
|
+
reset: () => set({ bears: 0 }),
|
|
649
|
+
}));
|
|
650
|
+
|
|
651
|
+
function BearCounter() {
|
|
652
|
+
// useStore is a React hook from zustand — what-react makes it work seamlessly
|
|
653
|
+
const bears = useStore((state) => state.bears);
|
|
654
|
+
const increase = useStore((state) => state.increase);
|
|
655
|
+
const reset = useStore((state) => state.reset);
|
|
656
|
+
|
|
657
|
+
return (
|
|
658
|
+
<div className="zustand-demo">
|
|
659
|
+
<p className="zustand-label">Zustand Store (React library)</p>
|
|
660
|
+
<p className="zustand-count">{bears} bears</p>
|
|
661
|
+
<div className="zustand-buttons">
|
|
662
|
+
<button className="zustand-btn" onClick={increase}>Add bear</button>
|
|
663
|
+
<button className="zustand-btn" onClick={reset}>Reset</button>
|
|
664
|
+
</div>
|
|
665
|
+
</div>
|
|
666
|
+
);
|
|
135
667
|
}
|
|
136
668
|
|
|
137
|
-
|
|
669
|
+
function App() {
|
|
670
|
+
const count = useSignal(0);
|
|
671
|
+
|
|
672
|
+
return (
|
|
673
|
+
<main className="app-shell">
|
|
674
|
+
<h1>What Framework</h1>
|
|
675
|
+
<p>React compat enabled — use React libraries with signals.</p>
|
|
676
|
+
|
|
677
|
+
<section className="counter">
|
|
678
|
+
<button onClick={() => count.set(c => c - 1)}>-</button>
|
|
679
|
+
<output>{count()}</output>
|
|
680
|
+
<button onClick={() => count.set(c => c + 1)}>+</button>
|
|
681
|
+
</section>
|
|
682
|
+
|
|
683
|
+
<BearCounter />
|
|
684
|
+
</main>
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
mount(<App />, '#app');
|
|
689
|
+
`;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
function generateStylesWithReactCompat() {
|
|
693
|
+
return `:root {
|
|
138
694
|
color-scheme: light;
|
|
139
695
|
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif;
|
|
140
696
|
}
|
|
@@ -187,9 +743,78 @@ output {
|
|
|
187
743
|
text-align: center;
|
|
188
744
|
font-weight: 700;
|
|
189
745
|
}
|
|
190
|
-
`);
|
|
191
746
|
|
|
192
|
-
|
|
747
|
+
/* Zustand demo */
|
|
748
|
+
.zustand-demo {
|
|
749
|
+
margin-top: 1.5rem;
|
|
750
|
+
padding: 1rem;
|
|
751
|
+
background: #fffbeb;
|
|
752
|
+
border: 1px solid #fde68a;
|
|
753
|
+
border-radius: 12px;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
.zustand-label {
|
|
757
|
+
font-size: 0.875rem;
|
|
758
|
+
color: #92400e;
|
|
759
|
+
font-weight: 500;
|
|
760
|
+
margin: 0;
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
.zustand-count {
|
|
764
|
+
margin: 0.25rem 0 0;
|
|
765
|
+
font-size: 1.5rem;
|
|
766
|
+
font-weight: 700;
|
|
767
|
+
color: #78350f;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
.zustand-buttons {
|
|
771
|
+
margin-top: 0.5rem;
|
|
772
|
+
display: flex;
|
|
773
|
+
gap: 0.5rem;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
.zustand-btn {
|
|
777
|
+
width: auto;
|
|
778
|
+
height: auto;
|
|
779
|
+
padding: 0.25rem 0.75rem;
|
|
780
|
+
border-radius: 8px;
|
|
781
|
+
border: 1px solid #fbbf24;
|
|
782
|
+
background: #ffffff;
|
|
783
|
+
color: #78350f;
|
|
784
|
+
cursor: pointer;
|
|
785
|
+
font-size: 0.875rem;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
.zustand-btn:hover {
|
|
789
|
+
border-color: #d97706;
|
|
790
|
+
}
|
|
791
|
+
`;
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
function generateReadme(projectName, { reactCompat, cssApproach }) {
|
|
795
|
+
let notes = `- Canonical package name is \`what-framework\`.
|
|
796
|
+
- Uses the What compiler for JSX transforms and automatic reactivity.
|
|
797
|
+
- Vite is preconfigured; use \`npm run dev/build/preview\`.
|
|
798
|
+
- Event handlers accept both \`onClick\` and \`onclick\`; docs and templates use \`onClick\`.
|
|
799
|
+
- Bun is also supported: \`bun create what@latest\`, \`bun run dev\`.`;
|
|
800
|
+
|
|
801
|
+
if (reactCompat) {
|
|
802
|
+
notes += `
|
|
803
|
+
- React compat is enabled via \`what-react\`. Any React library (zustand, @tanstack/react-query, framer-motion, etc.) works out of the box.
|
|
804
|
+
- The \`what-react/vite\` plugin handles all aliasing automatically.`;
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
if (cssApproach === 'tailwind') {
|
|
808
|
+
notes += `
|
|
809
|
+
- Tailwind CSS v4 is configured via the \`@tailwindcss/vite\` plugin.`;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
if (cssApproach === 'stylex') {
|
|
813
|
+
notes += `
|
|
814
|
+
- StyleX is configured via \`vite-plugin-stylex\`. Define styles with \`stylex.create()\` and apply with \`{...stylex.props()}\`.`;
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
return `# ${projectName}
|
|
193
818
|
|
|
194
819
|
## Run
|
|
195
820
|
|
|
@@ -202,15 +827,130 @@ Open [http://localhost:5173](http://localhost:5173).
|
|
|
202
827
|
|
|
203
828
|
## Notes
|
|
204
829
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
830
|
+
${notes}
|
|
831
|
+
`;
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// ---------------------------------------------------------------------------
|
|
835
|
+
// Main
|
|
836
|
+
// ---------------------------------------------------------------------------
|
|
837
|
+
async function main() {
|
|
838
|
+
const options = await gatherOptions();
|
|
839
|
+
const { projectName, reactCompat, cssApproach } = options;
|
|
840
|
+
|
|
841
|
+
const root = join(process.cwd(), projectName);
|
|
842
|
+
|
|
843
|
+
if (existsSync(root)) {
|
|
844
|
+
console.error(`\nError: "${projectName}" already exists.`);
|
|
845
|
+
process.exit(1);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
mkdirSync(join(root, 'src'), { recursive: true });
|
|
849
|
+
mkdirSync(join(root, 'public'), { recursive: true });
|
|
850
|
+
mkdirSync(join(root, '.claude'), { recursive: true });
|
|
851
|
+
mkdirSync(join(root, '.cursor'), { recursive: true });
|
|
852
|
+
|
|
853
|
+
// .gitignore
|
|
854
|
+
writeFileSync(join(root, '.gitignore'), `node_modules\ndist\n.DS_Store\n`);
|
|
855
|
+
|
|
856
|
+
// package.json
|
|
857
|
+
writeFileSync(join(root, 'package.json'), generatePackageJson(projectName, options));
|
|
858
|
+
|
|
859
|
+
// index.html
|
|
860
|
+
writeFileSync(join(root, 'index.html'), generateIndexHtml(projectName));
|
|
861
|
+
|
|
862
|
+
// favicon
|
|
863
|
+
writeFileSync(join(root, 'public', 'favicon.svg'), `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
|
864
|
+
<defs>
|
|
865
|
+
<linearGradient id="g" x1="0" x2="1" y1="0" y2="1">
|
|
866
|
+
<stop offset="0%" stop-color="#2563eb" />
|
|
867
|
+
<stop offset="100%" stop-color="#1d4ed8" />
|
|
868
|
+
</linearGradient>
|
|
869
|
+
</defs>
|
|
870
|
+
<rect width="64" height="64" rx="14" fill="url(#g)" />
|
|
871
|
+
<path d="M17 20h10l5 20 5-20h10L36 49h-8z" fill="#fff" />
|
|
872
|
+
</svg>
|
|
210
873
|
`);
|
|
211
874
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
875
|
+
// vite.config.js
|
|
876
|
+
writeFileSync(join(root, 'vite.config.js'), generateViteConfig(options));
|
|
877
|
+
|
|
878
|
+
// tsconfig.json
|
|
879
|
+
writeFileSync(join(root, 'tsconfig.json'), JSON.stringify({
|
|
880
|
+
compilerOptions: {
|
|
881
|
+
target: 'ES2022',
|
|
882
|
+
module: 'ESNext',
|
|
883
|
+
moduleResolution: 'bundler',
|
|
884
|
+
jsx: 'preserve',
|
|
885
|
+
jsxImportSource: 'what-core',
|
|
886
|
+
strict: true,
|
|
887
|
+
noEmit: true,
|
|
888
|
+
skipLibCheck: true,
|
|
889
|
+
esModuleInterop: true,
|
|
890
|
+
resolveJsonModule: true,
|
|
891
|
+
isolatedModules: true,
|
|
892
|
+
types: ['vite/client'],
|
|
893
|
+
},
|
|
894
|
+
include: ['src'],
|
|
895
|
+
}, null, 2) + '\n');
|
|
896
|
+
|
|
897
|
+
// .vscode
|
|
898
|
+
mkdirSync(join(root, '.vscode'), { recursive: true });
|
|
899
|
+
|
|
900
|
+
writeFileSync(join(root, '.vscode', 'settings.json'), JSON.stringify({
|
|
901
|
+
'typescript.tsdk': 'node_modules/typescript/lib',
|
|
902
|
+
'editor.formatOnSave': true,
|
|
903
|
+
}, null, 2) + '\n');
|
|
904
|
+
|
|
905
|
+
writeFileSync(join(root, '.vscode', 'extensions.json'), JSON.stringify({
|
|
906
|
+
recommendations: [
|
|
907
|
+
'zvndev.thenjs',
|
|
908
|
+
],
|
|
909
|
+
}, null, 2) + '\n');
|
|
910
|
+
|
|
911
|
+
// MCP configs for AI-powered debugging (what-devtools-mcp)
|
|
912
|
+
const mcpConfig = JSON.stringify({
|
|
913
|
+
mcpServers: {
|
|
914
|
+
'what-devtools': {
|
|
915
|
+
command: 'npx',
|
|
916
|
+
args: ['what-devtools-mcp'],
|
|
917
|
+
},
|
|
918
|
+
},
|
|
919
|
+
}, null, 2) + '\n';
|
|
920
|
+
writeFileSync(join(root, '.claude', 'mcp.json'), mcpConfig);
|
|
921
|
+
writeFileSync(join(root, '.cursor', 'mcp.json'), mcpConfig);
|
|
922
|
+
|
|
923
|
+
// src/main.jsx
|
|
924
|
+
writeFileSync(join(root, 'src', 'main.jsx'), generateMainJsx(options));
|
|
925
|
+
|
|
926
|
+
// src/styles.css
|
|
927
|
+
if (reactCompat && cssApproach === 'none') {
|
|
928
|
+
writeFileSync(join(root, 'src', 'styles.css'), generateStylesWithReactCompat());
|
|
929
|
+
} else {
|
|
930
|
+
writeFileSync(join(root, 'src', 'styles.css'), generateStyles(options));
|
|
931
|
+
}
|
|
932
|
+
|
|
933
|
+
// README.md
|
|
934
|
+
writeFileSync(join(root, 'README.md'), generateReadme(projectName, options));
|
|
935
|
+
|
|
936
|
+
// Summary
|
|
937
|
+
console.log(`\nCreated ${projectName}.`);
|
|
938
|
+
|
|
939
|
+
const features = [];
|
|
940
|
+
if (reactCompat) features.push('React compat (what-react + zustand demo)');
|
|
941
|
+
if (cssApproach === 'tailwind') features.push('Tailwind CSS v4');
|
|
942
|
+
if (cssApproach === 'stylex') features.push('StyleX');
|
|
943
|
+
if (features.length > 0) {
|
|
944
|
+
console.log(`Features: ${features.join(', ')}`);
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
console.log('\nNext steps:');
|
|
948
|
+
console.log(` cd ${projectName}`);
|
|
949
|
+
console.log(' npm install');
|
|
950
|
+
console.log(' npm run dev\n');
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
main().catch(err => {
|
|
954
|
+
console.error(err);
|
|
955
|
+
process.exit(1);
|
|
956
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-what",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.4",
|
|
4
4
|
"description": "Scaffold a new What Framework project",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -21,6 +21,10 @@
|
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"repository": {
|
|
23
23
|
"type": "git",
|
|
24
|
-
"url": "
|
|
25
|
-
}
|
|
24
|
+
"url": "https://github.com/zvndev/what-fw"
|
|
25
|
+
},
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/zvndev/what-fw/issues"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://whatfw.com"
|
|
26
30
|
}
|