create-what 0.5.3 → 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.
Files changed (3) hide show
  1. package/README.md +77 -0
  2. package/index.js +827 -88
  3. package/package.json +2 -2
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,47 +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 --yes (skip prompts, use defaults)
7
8
 
8
9
  import { mkdirSync, writeFileSync, existsSync } from 'node:fs';
9
10
  import { join } from 'node:path';
11
+ import { createInterface } from 'node:readline';
10
12
 
13
+ // ---------------------------------------------------------------------------
14
+ // CLI arguments
15
+ // ---------------------------------------------------------------------------
11
16
  const args = process.argv.slice(2);
12
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
+ }
13
85
 
14
- const projectName = positional[0] || 'my-what-app';
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'
15
93
 
16
- const root = join(process.cwd(), projectName);
94
+ if (skipPrompts) {
95
+ projectName = projectName || 'my-what-app';
96
+ return { projectName, reactCompat, cssApproach };
97
+ }
17
98
 
18
- if (existsSync(root)) {
19
- console.error(`\nError: "${projectName}" already exists.`);
20
- process.exit(1);
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 };
21
118
  }
22
119
 
23
- mkdirSync(join(root, 'src'), { recursive: true });
24
- mkdirSync(join(root, 'public'), { recursive: true });
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
+ }
25
166
 
26
- writeFileSync(join(root, '.gitignore'), `node_modules\ndist\n.DS_Store\n`);
167
+ function generateViteConfig({ reactCompat, cssApproach }) {
168
+ const imports = [];
169
+ const plugins = [];
27
170
 
28
- writeFileSync(join(root, 'package.json'), JSON.stringify({
29
- name: projectName,
30
- private: true,
31
- version: '0.1.0',
32
- type: 'module',
33
- scripts: {
34
- dev: 'vite',
35
- build: 'vite build',
36
- preview: 'vite preview',
37
- },
38
- dependencies: {
39
- 'what-framework': '^0.5.2',
40
- },
41
- devDependencies: {
42
- vite: '^5.4.0',
43
- 'what-compiler': '^0.5.2',
44
- },
45
- }, null, 2) + '\n');
171
+ imports.push(`import { defineConfig } from 'vite';`);
172
+
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()');
46
189
 
47
- writeFileSync(join(root, 'index.html'), `<!doctype html>
190
+ if (cssApproach === 'tailwind') {
191
+ imports.push(`import tailwindcss from '@tailwindcss/vite';`);
192
+ plugins.push('tailwindcss()');
193
+ }
194
+
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>
48
220
  <html lang="en">
49
221
  <head>
50
222
  <meta charset="UTF-8" />
@@ -58,62 +230,441 @@ writeFileSync(join(root, 'index.html'), `<!doctype html>
58
230
  <script type="module" src="/src/main.jsx"></script>
59
231
  </body>
60
232
  </html>
61
- `);
233
+ `;
234
+ }
62
235
 
63
- writeFileSync(join(root, 'public', 'favicon.svg'), `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
64
- <defs>
65
- <linearGradient id="g" x1="0" x2="1" y1="0" y2="1">
66
- <stop offset="0%" stop-color="#2563eb" />
67
- <stop offset="100%" stop-color="#1d4ed8" />
68
- </linearGradient>
69
- </defs>
70
- <rect width="64" height="64" rx="14" fill="url(#g)" />
71
- <path d="M17 20h10l5 20 5-20h10L36 49h-8z" fill="#fff" />
72
- </svg>
73
- `);
236
+ function generateStyles({ cssApproach }) {
237
+ if (cssApproach === 'tailwind') {
238
+ // Tailwind v4: just one import, utility classes handle the rest
239
+ return `@import "tailwindcss";
74
240
 
75
- writeFileSync(join(root, 'vite.config.js'), `import { defineConfig } from 'vite';
76
- import what from 'what-compiler/vite';
241
+ /* Custom styles use Tailwind utility classes in your JSX instead */
242
+ `;
243
+ }
77
244
 
78
- export default defineConfig({
79
- plugins: [what()],
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
+ }
250
+
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';
317
+
318
+ function App() {
319
+ const count = useSignal(0);
320
+
321
+ return (
322
+ <main className="app-shell">
323
+ <h1>What Framework</h1>
324
+ <p>Compiler-first JSX, React-familiar authoring.</p>
325
+
326
+ <section className="counter">
327
+ <button onClick={() => count.set(c => c - 1)}>-</button>
328
+ <output>{count()}</output>
329
+ <button onClick={() => count.set(c => c + 1)}>+</button>
330
+ </section>
331
+ </main>
332
+ );
333
+ }
334
+
335
+ mount(<App />, '#app');
336
+ `;
337
+ }
338
+
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
+ },
80
427
  });
81
- `);
82
428
 
83
- // TypeScript configuration (works for both .jsx and .tsx projects)
84
- writeFileSync(join(root, 'tsconfig.json'), JSON.stringify({
85
- compilerOptions: {
86
- target: 'ES2022',
87
- module: 'ESNext',
88
- moduleResolution: 'bundler',
89
- jsx: 'preserve',
90
- jsxImportSource: 'what-core',
91
- strict: true,
92
- noEmit: true,
93
- skipLibCheck: true,
94
- esModuleInterop: true,
95
- resolveJsonModule: true,
96
- isolatedModules: true,
97
- types: ['vite/client'],
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
+ }
493
+
494
+ function App() {
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>
522
+ );
523
+ }
524
+
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,
98
575
  },
99
- include: ['src'],
100
- }, null, 2) + '\n');
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
+ }
101
614
 
102
- // VS Code workspace settings
103
- mkdirSync(join(root, '.vscode'), { recursive: true });
615
+ function App() {
616
+ const count = useSignal(0);
104
617
 
105
- writeFileSync(join(root, '.vscode', 'settings.json'), JSON.stringify({
106
- 'typescript.tsdk': 'node_modules/typescript/lib',
107
- 'editor.formatOnSave': true,
108
- }, null, 2) + '\n');
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
+ }
109
635
 
110
- writeFileSync(join(root, '.vscode', 'extensions.json'), JSON.stringify({
111
- recommendations: [
112
- 'zvndev.thenjs',
113
- ],
114
- }, null, 2) + '\n');
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);
115
656
 
116
- writeFileSync(join(root, 'src', 'main.jsx'), `import { mount, useSignal } from 'what-framework';
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
+ );
667
+ }
117
668
 
118
669
  function App() {
119
670
  const count = useSignal(0);
@@ -121,21 +672,25 @@ function App() {
121
672
  return (
122
673
  <main className="app-shell">
123
674
  <h1>What Framework</h1>
124
- <p>Compiler-first JSX, React-familiar authoring.</p>
675
+ <p>React compat enabled — use React libraries with signals.</p>
125
676
 
126
677
  <section className="counter">
127
678
  <button onClick={() => count.set(c => c - 1)}>-</button>
128
679
  <output>{count()}</output>
129
680
  <button onClick={() => count.set(c => c + 1)}>+</button>
130
681
  </section>
682
+
683
+ <BearCounter />
131
684
  </main>
132
685
  );
133
686
  }
134
687
 
135
688
  mount(<App />, '#app');
136
- `);
689
+ `;
690
+ }
137
691
 
138
- writeFileSync(join(root, 'src', 'styles.css'), `:root {
692
+ function generateStylesWithReactCompat() {
693
+ return `:root {
139
694
  color-scheme: light;
140
695
  font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif;
141
696
  }
@@ -188,9 +743,78 @@ output {
188
743
  text-align: center;
189
744
  font-weight: 700;
190
745
  }
191
- `);
192
746
 
193
- writeFileSync(join(root, 'README.md'), `# ${projectName}
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}
194
818
 
195
819
  ## Run
196
820
 
@@ -203,15 +827,130 @@ Open [http://localhost:5173](http://localhost:5173).
203
827
 
204
828
  ## Notes
205
829
 
206
- - Canonical package name is \`what-framework\`.
207
- - Uses the What compiler for JSX transforms and automatic reactivity.
208
- - Vite is preconfigured; use \`npm run dev/build/preview\`.
209
- - Event handlers accept both \`onClick\` and \`onclick\`; docs and templates use \`onClick\`.
210
- - Bun is also supported: \`bun create what@latest\`, \`bun run dev\`.
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>
211
873
  `);
212
874
 
213
- console.log(`\nCreated ${projectName}.`);
214
- console.log('Next steps:');
215
- console.log(` cd ${projectName}`);
216
- console.log(' npm install');
217
- console.log(' npm run dev\n');
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",
3
+ "version": "0.5.4",
4
4
  "description": "Scaffold a new What Framework project",
5
5
  "type": "module",
6
6
  "bin": {
@@ -26,5 +26,5 @@
26
26
  "bugs": {
27
27
  "url": "https://github.com/zvndev/what-fw/issues"
28
28
  },
29
- "homepage": "https://whatframework.dev"
29
+ "homepage": "https://whatfw.com"
30
30
  }