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.
- package/README.md +77 -0
- package/index.js +810 -88
- 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
|
-
|
|
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
|
-
|
|
94
|
+
if (skipPrompts) {
|
|
95
|
+
projectName = projectName || 'my-what-app';
|
|
96
|
+
return { projectName, reactCompat, cssApproach };
|
|
97
|
+
}
|
|
17
98
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
|
|
24
|
-
|
|
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
|
-
|
|
166
|
+
function generateViteConfig({ reactCompat, cssApproach }) {
|
|
167
|
+
const imports = [];
|
|
168
|
+
const plugins = [];
|
|
27
169
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
'what
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
}
|
|
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
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
|
|
76
|
-
|
|
235
|
+
/* Custom styles — use Tailwind utility classes in your JSX instead */
|
|
236
|
+
`;
|
|
237
|
+
}
|
|
77
238
|
|
|
78
|
-
|
|
79
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
100
|
-
|
|
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
|
-
|
|
103
|
-
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
}
|
|
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
|
-
|
|
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>
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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
|
+
"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://
|
|
28
|
+
"homepage": "https://whatfw.com"
|
|
30
29
|
}
|