create-what 0.5.3 → 0.5.5

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