hadars 0.4.2 → 0.4.3-rc.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -147,25 +147,8 @@ var SLIM_ELEMENT = Symbol.for("react.element");
147
147
  var REACT19_ELEMENT = Symbol.for("react.transitional.element");
148
148
  var FRAGMENT_TYPE = Symbol.for("react.fragment");
149
149
  var SUSPENSE_TYPE = Symbol.for("react.suspense");
150
- // src/slim-react/jsx.ts
151
- function createElement(type, props, ...children) {
152
- const normalizedProps = { ...props || {} };
153
- if (children.length === 1) {
154
- normalizedProps.children = children[0];
155
- } else if (children.length > 1) {
156
- normalizedProps.children = children;
157
- }
158
- const key = normalizedProps.key ?? null;
159
- delete normalizedProps.key;
160
- return {
161
- $$typeof: SLIM_ELEMENT,
162
- type,
163
- props: normalizedProps,
164
- key
165
- };
166
- }
167
-
168
150
  // src/slim-react/renderContext.ts
151
+ import { createRequire as _nodeCreateRequire } from "node:module";
169
152
  var MAP_KEY = "__slimReactContextMap";
170
153
  var _g = globalThis;
171
154
  if (!("__slimReactContextMap" in _g))
@@ -189,8 +172,7 @@ function getContextValue(context) {
189
172
  const map = _g[MAP_KEY];
190
173
  if (map && map.has(context))
191
174
  return map.get(context);
192
- const c = context;
193
- return "_defaultValue" in c ? c._defaultValue : c._currentValue;
175
+ return context._currentValue;
194
176
  }
195
177
  function pushContextValue(context, value) {
196
178
  let map = _g[MAP_KEY];
@@ -198,8 +180,7 @@ function pushContextValue(context, value) {
198
180
  map = new Map;
199
181
  _g[MAP_KEY] = map;
200
182
  }
201
- const c = context;
202
- const prev = map.has(context) ? map.get(context) : ("_defaultValue" in c) ? c._defaultValue : c._currentValue;
183
+ const prev = map.has(context) ? map.get(context) : context._currentValue;
203
184
  map.set(context, value);
204
185
  return prev;
205
186
  }
@@ -304,14 +285,54 @@ function getTreeId() {
304
285
  const stripped = (id & ~(1 << 31 - Math.clz32(id))).toString(32);
305
286
  return stripped + overflow;
306
287
  }
288
+ var _detectReact = () => {
289
+ if (typeof __HADARS_REACT_MAJOR__ !== "undefined") {
290
+ const major = parseInt(String(__HADARS_REACT_MAJOR__), 10);
291
+ return {
292
+ major,
293
+ version: major < 19 ? "18.3.1" : "19.1.1"
294
+ };
295
+ }
296
+ const parse = (ver) => ({ major: parseInt(ver.split(".")[0], 10), version: ver });
297
+ try {
298
+ return parse(__require("react").version);
299
+ } catch {}
300
+ try {
301
+ const req = _nodeCreateRequire(process.cwd() + "/__hadars__.js");
302
+ return parse(req("react").version);
303
+ } catch {}
304
+ return { major: 19, version: "19.1.1" };
305
+ };
306
+ var _react = _detectReact();
307
+ var REACT_MAJOR = _react.major;
308
+ var REACT_VERSION = _react.version;
307
309
  function makeId() {
308
310
  const st = s();
309
311
  const treeId = getTreeId();
310
312
  const n = st.localIdCounter++;
311
- let id = "_R_" + st.idPrefix + treeId;
312
- if (n > 0)
313
- id += "H" + n.toString(32);
314
- return id + "_";
313
+ const suffix = n > 0 ? "H" + n.toString(32) : "";
314
+ if (REACT_MAJOR < 19) {
315
+ return ":" + st.idPrefix + "R" + treeId + suffix + ":";
316
+ }
317
+ return "_R_" + st.idPrefix + treeId + suffix + "_";
318
+ }
319
+
320
+ // src/slim-react/jsx.ts
321
+ function createElement(type, props, ...children) {
322
+ const normalizedProps = { ...props || {} };
323
+ if (children.length === 1) {
324
+ normalizedProps.children = children[0];
325
+ } else if (children.length > 1) {
326
+ normalizedProps.children = children;
327
+ }
328
+ const key = normalizedProps.key ?? null;
329
+ delete normalizedProps.key;
330
+ return {
331
+ $$typeof: SLIM_ELEMENT,
332
+ type,
333
+ props: normalizedProps,
334
+ key
335
+ };
315
336
  }
316
337
 
317
338
  // src/slim-react/hooks.ts
@@ -364,8 +385,22 @@ function use(usable) {
364
385
  }
365
386
  // src/slim-react/dispatcher.ts
366
387
  import * as ReactNS from "react";
367
- var _reactInternalsKey = "__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE";
368
- var _internals = ReactNS[_reactInternalsKey];
388
+ var _r19;
389
+ var _r18;
390
+ var _detected = false;
391
+ function _detect() {
392
+ if (_detected)
393
+ return;
394
+ _detected = true;
395
+ const r19 = ReactNS.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
396
+ if (r19) {
397
+ _r19 = r19;
398
+ return;
399
+ }
400
+ const raw = ReactNS.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
401
+ if (raw?.ReactCurrentDispatcher)
402
+ _r18 = raw;
403
+ }
369
404
  var slimDispatcher = {
370
405
  useId: makeId,
371
406
  readContext: (ctx) => getContextValue(ctx),
@@ -391,15 +426,25 @@ var slimDispatcher = {
391
426
  useHostTransitionStatus: () => false
392
427
  };
393
428
  function installDispatcher() {
394
- if (!_internals)
395
- return null;
396
- const prev = _internals.H;
397
- _internals.H = slimDispatcher;
398
- return prev;
429
+ _detect();
430
+ if (_r19) {
431
+ const prev = _r19.H;
432
+ _r19.H = slimDispatcher;
433
+ return prev;
434
+ }
435
+ if (_r18) {
436
+ const prev = _r18.ReactCurrentDispatcher.current;
437
+ _r18.ReactCurrentDispatcher.current = slimDispatcher;
438
+ return prev;
439
+ }
440
+ return null;
399
441
  }
400
442
  function restoreDispatcher(prev) {
401
- if (_internals)
402
- _internals.H = prev;
443
+ _detect();
444
+ if (_r19)
445
+ _r19.H = prev;
446
+ else if (_r18)
447
+ _r18.ReactCurrentDispatcher.current = prev;
403
448
  }
404
449
 
405
450
  // src/slim-react/render.ts
@@ -660,7 +705,7 @@ function writeAttributes(writer, props, isSvg, skip) {
660
705
  if (isSvg && key in SVG_ATTR_MAP) {
661
706
  attrName = SVG_ATTR_MAP[key];
662
707
  } else {
663
- attrName = key === "className" ? "class" : key === "htmlFor" ? "for" : key === "tabIndex" ? "tabindex" : key === "defaultValue" ? "value" : key === "defaultChecked" ? "checked" : key;
708
+ attrName = key === "className" ? "class" : key === "htmlFor" ? "for" : key === "tabIndex" ? "tabindex" : key === "defaultValue" ? "value" : key === "defaultChecked" ? "checked" : key === "contentEditable" && REACT_MAJOR < 19 ? "contenteditable" : key;
664
709
  }
665
710
  if (value === false || value == null) {
666
711
  if (value === false && (attrName.charCodeAt(0) === 97 && attrName.startsWith("aria-") || attrName.charCodeAt(0) === 100 && attrName.startsWith("data-"))) {
@@ -871,7 +916,8 @@ function renderComponent(type, props, writer, isSvg, _suspenseRetries = 0) {
871
916
  const LazyComp = resolved?.default ?? resolved;
872
917
  return renderComponent(LazyComp, props, writer, isSvg);
873
918
  }
874
- if (typeOf === REACT_CONSUMER) {
919
+ const isConsumer = typeOf === REACT_CONSUMER || typeOf === REACT_CONTEXT && "_context" in type && !("value" in props);
920
+ if (isConsumer) {
875
921
  const ctx2 = type._context;
876
922
  const value = ctx2 ? getContextValue(ctx2) : undefined;
877
923
  const result2 = typeof props.children === "function" ? props.children(value) : null;
@@ -2448,6 +2494,20 @@ var HadarsFolder = "./.hadars";
2448
2494
  var StaticPath = `${HadarsFolder}/static`;
2449
2495
  var HADARS_TMP_DIR = pathMod2.join(os.tmpdir(), "hadars");
2450
2496
  var ensureHadarsTmpDir = () => fs.mkdir(HADARS_TMP_DIR, { recursive: true });
2497
+ var readReactMajor = async () => {
2498
+ let dir = process.cwd();
2499
+ while (true) {
2500
+ try {
2501
+ const pkgPath = pathMod2.join(dir, "node_modules", "react", "package.json");
2502
+ const pkg = JSON.parse(await fs.readFile(pkgPath, "utf-8"));
2503
+ return parseInt(pkg.version.split(".")[0], 10);
2504
+ } catch {}
2505
+ const parent = pathMod2.dirname(dir);
2506
+ if (parent === dir)
2507
+ return 19;
2508
+ dir = parent;
2509
+ }
2510
+ };
2451
2511
  var validateOptions = (options) => {
2452
2512
  if (!options.entry) {
2453
2513
  throw new Error("Entry file is required");
@@ -2567,6 +2627,8 @@ var dev = async (options) => {
2567
2627
  });
2568
2628
  const workerCmd = resolveWorkerCmd(packageDir2);
2569
2629
  console.log("Spawning SSR worker:", workerCmd.join(" "), "entry:", entry);
2630
+ const reactMajor = await readReactMajor();
2631
+ const ssrDefine = { __HADARS_REACT_MAJOR__: String(reactMajor), ...options.define };
2570
2632
  const child = spawn(workerCmd[0], [
2571
2633
  ...workerCmd.slice(1),
2572
2634
  `--entry=${entry}`,
@@ -2574,7 +2636,7 @@ var dev = async (options) => {
2574
2636
  `--outFile=${SSR_FILENAME}`,
2575
2637
  `--base=${baseURL}`,
2576
2638
  ...options.swcPlugins ? [`--swcPlugins=${JSON.stringify(options.swcPlugins)}`] : [],
2577
- ...options.define ? [`--define=${JSON.stringify(options.define)}`] : [],
2639
+ `--define=${JSON.stringify(ssrDefine)}`,
2578
2640
  ...options.moduleRules ? [`--moduleRules=${JSON.stringify(options.moduleRules, (_k, v) => v instanceof RegExp ? { __re: v.source, __flags: v.flags } : v)}`] : []
2579
2641
  ], { stdio: "pipe" });
2580
2642
  child.stdin?.end();
@@ -2748,6 +2810,7 @@ var build = async (options) => {
2748
2810
  const tmpFilePath = pathMod2.join(HADARS_TMP_DIR, `client-${Date.now()}.tsx`);
2749
2811
  await fs.writeFile(tmpFilePath, clientScript);
2750
2812
  const resolvedHtmlTemplate = options.htmlTemplate ? await processHtmlTemplate(pathMod2.resolve(__dirname3, options.htmlTemplate)) : undefined;
2813
+ const reactMajor = await readReactMajor();
2751
2814
  console.log("Building client and server bundles in parallel...");
2752
2815
  await Promise.all([
2753
2816
  compileEntry(tmpFilePath, {
@@ -2779,7 +2842,7 @@ var build = async (options) => {
2779
2842
  target: "node",
2780
2843
  mode: "production",
2781
2844
  swcPlugins: options.swcPlugins,
2782
- define: options.define,
2845
+ define: { __HADARS_REACT_MAJOR__: String(reactMajor), ...options.define },
2783
2846
  moduleRules: options.moduleRules,
2784
2847
  plugins: options.plugins,
2785
2848
  postcssPlugins: options.postcssPlugins
@@ -3184,8 +3247,166 @@ Deploy instructions:`);
3184
3247
  await unlink(shimPath).catch(() => {});
3185
3248
  }
3186
3249
  }
3187
- var TEMPLATES = {
3188
- "package.json": (name) => JSON.stringify({
3250
+ var _R = "\x1B[0m";
3251
+ var _B = "\x1B[1m";
3252
+ var _D = "\x1B[2m";
3253
+ var _C = "\x1B[36m";
3254
+ var _G = "\x1B[32m";
3255
+ var _UP_KEY = "\x1B[A";
3256
+ var _DOWN_KEY = "\x1B[B";
3257
+ var _HIDE = "\x1B[?25l";
3258
+ var _SHOW = "\x1B[?25h";
3259
+ var _cl = () => "\r\x1B[2K";
3260
+ var _up = (n) => n > 0 ? `\x1B[${n}A` : "";
3261
+ function _readKeys(handler) {
3262
+ return new Promise((resolve2) => {
3263
+ const { stdin } = process;
3264
+ const wasRaw = stdin.isTTY && stdin.isRaw;
3265
+ if (stdin.isTTY)
3266
+ stdin.setRawMode(true);
3267
+ stdin.resume();
3268
+ stdin.setEncoding("utf-8");
3269
+ const onData = (key) => {
3270
+ if (key === "\x03") {
3271
+ cleanup();
3272
+ process.stdout.write(_SHOW);
3273
+ process.exit(130);
3274
+ }
3275
+ if (handler(key))
3276
+ cleanup();
3277
+ };
3278
+ const cleanup = () => {
3279
+ stdin.removeListener("data", onData);
3280
+ if (stdin.isTTY && !wasRaw)
3281
+ stdin.setRawMode(false);
3282
+ stdin.pause();
3283
+ resolve2();
3284
+ };
3285
+ stdin.on("data", onData);
3286
+ });
3287
+ }
3288
+ async function promptRadio(question, options) {
3289
+ const out = process.stdout;
3290
+ let cursor = 0;
3291
+ const total = 1 + options.length;
3292
+ const render = (redraw) => {
3293
+ if (redraw)
3294
+ out.write(_up(total));
3295
+ out.write(`${_cl()} ${_B}${question}${_R}
3296
+ `);
3297
+ for (let i = 0;i < options.length; i++) {
3298
+ const arrow = i === cursor ? `${_C}❯${_R}` : " ";
3299
+ const text = i === cursor ? `${_B}${options[i]}${_R}` : `${_D}${options[i]}${_R}`;
3300
+ out.write(`${_cl()} ${arrow} ${text}
3301
+ `);
3302
+ }
3303
+ };
3304
+ out.write(_HIDE);
3305
+ render(false);
3306
+ await _readKeys((key) => {
3307
+ if (key === _UP_KEY && cursor > 0) {
3308
+ cursor--;
3309
+ render(true);
3310
+ } else if (key === _DOWN_KEY && cursor < options.length - 1) {
3311
+ cursor++;
3312
+ render(true);
3313
+ } else if (key === "\r") {
3314
+ return true;
3315
+ }
3316
+ return false;
3317
+ });
3318
+ out.write(_up(total));
3319
+ out.write(`${_cl()} ${question} ${_C}${_B}${options[cursor]}${_R}
3320
+ `);
3321
+ for (let i = 0;i < options.length; i++)
3322
+ out.write(`${_cl()}
3323
+ `);
3324
+ out.write(_up(options.length));
3325
+ out.write(_SHOW);
3326
+ return cursor;
3327
+ }
3328
+ async function promptMultiSelect(question, options) {
3329
+ const out = process.stdout;
3330
+ let cursor = 0;
3331
+ const selected = new Set;
3332
+ const total = 2 + options.length;
3333
+ const render = (redraw) => {
3334
+ if (redraw)
3335
+ out.write(_up(total));
3336
+ out.write(`${_cl()} ${_B}${question}${_R}
3337
+ `);
3338
+ out.write(`${_cl()} ${_D}↑↓ navigate · Space select · Enter confirm${_R}
3339
+ `);
3340
+ for (let i = 0;i < options.length; i++) {
3341
+ const arrow = i === cursor ? `${_C}❯${_R}` : " ";
3342
+ const box = selected.has(i) ? `${_G}●${_R}` : `${_D}○${_R}`;
3343
+ const text = i === cursor ? `${_B}${options[i]}${_R}` : `${_D}${options[i]}${_R}`;
3344
+ out.write(`${_cl()} ${arrow} ${box} ${text}
3345
+ `);
3346
+ }
3347
+ };
3348
+ out.write(_HIDE);
3349
+ render(false);
3350
+ await _readKeys((key) => {
3351
+ if (key === _UP_KEY && cursor > 0) {
3352
+ cursor--;
3353
+ render(true);
3354
+ } else if (key === _DOWN_KEY && cursor < options.length - 1) {
3355
+ cursor++;
3356
+ render(true);
3357
+ } else if (key === " ") {
3358
+ selected.has(cursor) ? selected.delete(cursor) : selected.add(cursor);
3359
+ render(true);
3360
+ } else if (key === "\r") {
3361
+ return true;
3362
+ }
3363
+ return false;
3364
+ });
3365
+ const picked = [...selected].sort((a, b) => a - b);
3366
+ const summary = picked.length > 0 ? picked.map((i) => options[i]).join(", ") : "none";
3367
+ out.write(_up(total));
3368
+ out.write(`${_cl()} ${question} ${_C}${_B}${summary}${_R}
3369
+ `);
3370
+ for (let i = 0;i < total - 1; i++)
3371
+ out.write(`${_cl()}
3372
+ `);
3373
+ out.write(_up(total - 1));
3374
+ out.write(_SHOW);
3375
+ return picked;
3376
+ }
3377
+ var PLUGINS = [
3378
+ { pkg: "@swc/plugin-emotion", version: "12.0.0", label: "Emotion (CSS-in-JS)" },
3379
+ { pkg: "@swc/plugin-styled-components", version: "10.0.0", label: "styled-components" },
3380
+ { pkg: "@swc/plugin-relay", version: "10.0.0", label: "Relay (GraphQL)" },
3381
+ { pkg: "@swc/plugin-styled-jsx", version: "11.0.0", label: "styled-jsx" },
3382
+ { pkg: "@swc/plugin-transform-imports", version: "10.0.0", label: "transform-imports" },
3383
+ { pkg: "@swc/plugin-loadable-components", version: "9.0.0", label: "Loadable Components" },
3384
+ { pkg: "@swc/plugin-formatjs", version: "7.0.0", label: "FormatJS (i18n)" }
3385
+ ];
3386
+ function renderSwcPluginsConfig(plugins) {
3387
+ if (plugins.length === 0)
3388
+ return "";
3389
+ const lines = plugins.map((p) => {
3390
+ if (p.pkg === "@swc/plugin-relay") {
3391
+ return ` ['${p.pkg}', { rootDir: process.cwd(), artifactDirectory: 'src/__generated__' }],`;
3392
+ }
3393
+ return ` ['${p.pkg}', {}],`;
3394
+ });
3395
+ return `
3396
+ swcPlugins: [
3397
+ ${lines.join(`
3398
+ `)}
3399
+ ],`;
3400
+ }
3401
+ function buildTemplates(name, opts) {
3402
+ const { useTypeScript, plugins } = opts;
3403
+ const appExt = useTypeScript ? "tsx" : "jsx";
3404
+ const cfgExt = useTypeScript ? "ts" : "js";
3405
+ const tsOrJs = useTypeScript ? "tsconfig.json" : "jsconfig.json";
3406
+ const pluginDeps = {};
3407
+ for (const p of plugins)
3408
+ pluginDeps[p.pkg] = p.version;
3409
+ const packageJson = JSON.stringify({
3189
3410
  name,
3190
3411
  version: "0.1.0",
3191
3412
  type: "module",
@@ -3199,19 +3420,28 @@ var TEMPLATES = {
3199
3420
  hadars: "latest",
3200
3421
  react: "^19.0.0",
3201
3422
  "react-dom": "^19.0.0"
3202
- }
3423
+ },
3424
+ ...Object.keys(pluginDeps).length > 0 ? { devDependencies: pluginDeps } : {}
3203
3425
  }, null, 2) + `
3204
- `,
3205
- "hadars.config.ts": () => `import type { HadarsOptions } from 'hadars';
3426
+ `;
3427
+ const swcSection = renderSwcPluginsConfig(plugins);
3428
+ const hadarsConfig = useTypeScript ? `import type { HadarsOptions } from 'hadars';
3206
3429
 
3207
3430
  const config: HadarsOptions = {
3208
3431
  entry: 'src/App.tsx',
3209
- port: 3000,
3432
+ port: 3000,${swcSection}
3210
3433
  };
3211
3434
 
3212
3435
  export default config;
3213
- `,
3214
- "tsconfig.json": () => JSON.stringify({
3436
+ ` : `/** @type {import('hadars').HadarsOptions} */
3437
+ const config = {
3438
+ entry: 'src/App.jsx',
3439
+ port: 3000,${swcSection}
3440
+ };
3441
+
3442
+ export default config;
3443
+ `;
3444
+ const tsConfigContent = useTypeScript ? JSON.stringify({
3215
3445
  compilerOptions: {
3216
3446
  lib: ["ESNext", "DOM"],
3217
3447
  target: "ESNext",
@@ -3226,215 +3456,89 @@ export default config;
3226
3456
  skipLibCheck: true
3227
3457
  }
3228
3458
  }, null, 2) + `
3229
- `,
3230
- ".gitignore": () => `node_modules/
3231
- .hadars/
3232
- dist/
3233
- `,
3234
- "src/App.tsx": () => `import React from 'react';
3235
- import { HadarsHead, type HadarsApp } from 'hadars';
3236
-
3237
- const css = \`
3238
- *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
3239
-
3240
- body {
3241
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
3242
- background: #0f0f13;
3243
- color: #e2e8f0;
3244
- min-height: 100vh;
3245
- }
3246
-
3247
- .nav {
3248
- display: flex;
3249
- align-items: center;
3250
- justify-content: space-between;
3251
- padding: 1rem 2rem;
3252
- border-bottom: 1px solid #1e1e2e;
3253
- }
3254
- .nav-brand { font-weight: 700; font-size: 1.1rem; color: #a78bfa; letter-spacing: -0.02em; }
3255
- .nav-links { display: flex; gap: 1.5rem; }
3256
- .nav-links a { color: #94a3b8; text-decoration: none; font-size: 0.9rem; }
3257
- .nav-links a:hover { color: #e2e8f0; }
3258
-
3259
- .hero {
3260
- text-align: center;
3261
- padding: 5rem 1rem 4rem;
3262
- max-width: 680px;
3263
- margin: 0 auto;
3264
- }
3265
- .hero-badge {
3266
- display: inline-block;
3267
- background: #1e1a2e;
3268
- border: 1px solid #4c1d95;
3269
- color: #a78bfa;
3270
- font-size: 0.75rem;
3271
- font-weight: 600;
3272
- letter-spacing: 0.05em;
3273
- text-transform: uppercase;
3274
- padding: 0.3rem 0.8rem;
3275
- border-radius: 999px;
3276
- margin-bottom: 1.5rem;
3277
- }
3278
- .hero h1 {
3279
- font-size: clamp(2rem, 5vw, 3.25rem);
3280
- font-weight: 800;
3281
- letter-spacing: -0.03em;
3282
- line-height: 1.15;
3283
- margin-bottom: 1rem;
3284
- }
3285
- .hero h1 span { color: #a78bfa; }
3286
- .hero p {
3287
- font-size: 1.1rem;
3288
- color: #94a3b8;
3289
- line-height: 1.7;
3290
- margin-bottom: 2.5rem;
3291
- }
3292
- .hero-actions { display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap; }
3293
- .btn {
3294
- display: inline-flex;
3295
- align-items: center;
3296
- gap: 0.4rem;
3297
- padding: 0.65rem 1.4rem;
3298
- border-radius: 8px;
3299
- font-size: 0.9rem;
3300
- font-weight: 600;
3301
- cursor: pointer;
3302
- border: none;
3303
- transition: opacity 0.15s, transform 0.1s;
3304
- text-decoration: none;
3305
- }
3306
- .btn:hover { opacity: 0.85; transform: translateY(-1px); }
3307
- .btn:active { transform: translateY(0); }
3308
- .btn-primary { background: #7c3aed; color: #fff; }
3309
- .btn-ghost { background: #1e1e2e; color: #e2e8f0; border: 1px solid #2d2d3e; }
3310
-
3311
- .features {
3312
- display: grid;
3313
- grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
3314
- gap: 1rem;
3315
- max-width: 900px;
3316
- margin: 0 auto 4rem;
3317
- padding: 0 1.5rem;
3318
- }
3319
- .card {
3320
- background: #16161f;
3321
- border: 1px solid #1e1e2e;
3322
- border-radius: 12px;
3323
- padding: 1.5rem;
3324
- }
3325
- .card-icon { font-size: 1.5rem; margin-bottom: 0.75rem; }
3326
- .card h3 { font-size: 0.95rem; font-weight: 700; margin-bottom: 0.4rem; }
3327
- .card p { font-size: 0.85rem; color: #64748b; line-height: 1.6; }
3328
-
3329
- .demo {
3330
- max-width: 480px;
3331
- margin: 0 auto 4rem;
3332
- padding: 0 1.5rem;
3333
- text-align: center;
3334
- }
3335
- .demo-box {
3336
- background: #16161f;
3337
- border: 1px solid #1e1e2e;
3338
- border-radius: 12px;
3339
- padding: 2rem;
3340
- }
3341
- .demo-box h2 { font-size: 0.8rem; font-weight: 600; color: #64748b; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 1.25rem; }
3342
- .counter { font-size: 3.5rem; font-weight: 800; color: #a78bfa; letter-spacing: -0.04em; margin-bottom: 1.25rem; }
3343
- .demo-actions { display: flex; gap: 0.75rem; justify-content: center; }
3344
-
3345
- \`;
3459
+ ` : JSON.stringify({
3460
+ compilerOptions: {
3461
+ lib: ["ESNext", "DOM"],
3462
+ target: "ESNext",
3463
+ module: "ESNext",
3464
+ moduleResolution: "bundler",
3465
+ jsx: "react-jsx",
3466
+ checkJs: true,
3467
+ noEmit: true,
3468
+ skipLibCheck: true
3469
+ }
3470
+ }, null, 2) + `
3471
+ `;
3472
+ const appImports = useTypeScript ? `import React from 'react';
3473
+ import { HadarsHead, type HadarsApp } from 'hadars';` : `import React from 'react';
3474
+ import { HadarsHead } from 'hadars';`;
3475
+ const appSignature = useTypeScript ? `const App: HadarsApp<{}> = () => {` : `const App = () => {`;
3476
+ const appContent = `${appImports}
3346
3477
 
3347
- const App: HadarsApp<{}> = () => {
3478
+ ${appSignature}
3348
3479
  const [count, setCount] = React.useState(0);
3349
3480
 
3350
3481
  return (
3351
3482
  <>
3352
3483
  <HadarsHead status={200}>
3353
- <title>My App</title>
3484
+ <title>${name}</title>
3354
3485
  <meta name="viewport" content="width=device-width, initial-scale=1" />
3355
- <style data-id="app-styles" dangerouslySetInnerHTML={{ __html: css }} />
3356
3486
  </HadarsHead>
3357
3487
 
3358
- <nav className="nav">
3359
- <span className="nav-brand">my-app</span>
3360
- <div className="nav-links">
3361
- <a href="https://github.com/dpostolachi/hadar" target="_blank" rel="noopener">github</a>
3362
- </div>
3363
- </nav>
3364
-
3365
- <section className="hero">
3366
- <div className="hero-badge">built with hadars</div>
3367
- <h1>Ship <span>React apps</span><br />at full speed</h1>
3368
- <p>
3369
- SSR out of the box, zero config, instant hot-reload.
3370
- Edit <code>src/App.tsx</code> to get started.
3488
+ <main style={{ fontFamily: 'sans-serif', maxWidth: 480, margin: '4rem auto', padding: '0 1rem', textAlign: 'center' }}>
3489
+ <h1>${name}</h1>
3490
+ <p style={{ color: '#666', margin: '1rem 0 2rem' }}>
3491
+ Edit <code>src/App.${appExt}</code> to get started.
3371
3492
  </p>
3372
- <div className="hero-actions">
3373
- <button className="btn btn-primary" onClick={() => setCount(c => c + 1)}>
3374
- Try the counter
3375
- </button>
3493
+ <p style={{ fontSize: '3rem', margin: '1rem 0' }}>{count}</p>
3494
+ <div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'center' }}>
3495
+ <button onClick={() => setCount(c => c - 1)}>−</button>
3496
+ <button onClick={() => setCount(c => c + 1)}>+</button>
3376
3497
  </div>
3377
- </section>
3378
-
3379
- <div className="features">
3380
- <div className="card">
3381
- <div className="card-icon">⚡</div>
3382
- <h3>Server-side rendering</h3>
3383
- <p>Pages render on the server and hydrate on the client — great for SEO and first paint.</p>
3384
- </div>
3385
- <div className="card">
3386
- <div className="card-icon">\uD83D\uDD25</div>
3387
- <h3>Hot module reload</h3>
3388
- <p>Changes in <code>src/App.tsx</code> reflect instantly in the browser during development.</p>
3389
- </div>
3390
- <div className="card">
3391
- <div className="card-icon">\uD83D\uDCE6</div>
3392
- <h3>Zero config</h3>
3393
- <p>One config file. Export a React component, run <code>hadars dev</code>, done.</p>
3394
- </div>
3395
- <div className="card">
3396
- <div className="card-icon">\uD83D\uDDC4️</div>
3397
- <h3>Server data hooks</h3>
3398
- <p>Use <code>useServerData</code> to fetch data on the server without extra round-trips.</p>
3399
- </div>
3400
- </div>
3401
-
3402
- <div className="demo">
3403
- <div className="demo-box">
3404
- <h2>Client interactivity works</h2>
3405
- <div className="counter">{count}</div>
3406
- <div className="demo-actions">
3407
- <button className="btn btn-ghost" onClick={() => setCount(c => c - 1)}>− dec</button>
3408
- <button className="btn btn-primary" onClick={() => setCount(c => c + 1)}>+ inc</button>
3409
- </div>
3410
- </div>
3411
- </div>
3412
-
3498
+ </main>
3413
3499
  </>
3414
3500
  );
3415
3501
  };
3416
3502
 
3417
3503
  export default App;
3418
- `
3419
- };
3504
+ `;
3505
+ return {
3506
+ "package.json": packageJson,
3507
+ [`hadars.config.${cfgExt}`]: hadarsConfig,
3508
+ [tsOrJs]: tsConfigContent,
3509
+ ".gitignore": `node_modules/
3510
+ .hadars/
3511
+ dist/
3512
+ `,
3513
+ [`src/App.${appExt}`]: appContent
3514
+ };
3515
+ }
3420
3516
  async function createProject(name, cwd) {
3421
3517
  const dir = resolve(cwd, name);
3422
3518
  if (existsSync3(dir)) {
3423
3519
  console.error(`Directory already exists: ${dir}`);
3424
3520
  process.exit(1);
3425
3521
  }
3426
- console.log(`Creating hadars project in ${dir}`);
3522
+ const useTypeScript = await promptRadio("Language?", ["TypeScript", "JavaScript"]) === 0;
3523
+ const selectedIndices = await promptMultiSelect("Select SWC plugins to enable (optional):", PLUGINS.map((p) => p.label));
3524
+ const plugins = selectedIndices.map((i) => PLUGINS[i]);
3525
+ console.log(`
3526
+ Creating hadars project in ${dir}`);
3427
3527
  await mkdir2(join2(dir, "src"), { recursive: true });
3428
- for (const [file, template] of Object.entries(TEMPLATES)) {
3429
- const content = template(name);
3528
+ const files = buildTemplates(name, { useTypeScript, plugins });
3529
+ for (const [file, content] of Object.entries(files)) {
3430
3530
  await writeFile2(join2(dir, file), content, "utf-8");
3431
3531
  console.log(` created ${file}`);
3432
3532
  }
3533
+ const installHint = plugins.length > 0 ? `
3534
+ # Also install selected SWC plugins:
3535
+ npm install -D ${plugins.map((p) => `${p.pkg}@${p.version}`).join(" ")}
3536
+ ` : "";
3433
3537
  console.log(`
3434
3538
  Done! Next steps:
3435
3539
 
3436
3540
  cd ${name}
3437
- npm install # or: bun install / pnpm install
3541
+ npm install # or: bun install / pnpm install${installHint}
3438
3542
  npm run dev # or: bun run dev
3439
3543
  `);
3440
3544
  }