hadars 1.0.1 → 1.0.3

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
@@ -148,7 +148,6 @@ 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
150
  // src/slim-react/renderContext.ts
151
- import { createRequire as _nodeCreateRequire } from "node:module";
152
151
  var MAP_KEY = "__slimReactContextMap";
153
152
  var _g = globalThis;
154
153
  if (!("__slimReactContextMap" in _g))
@@ -285,27 +284,8 @@ function getTreeId() {
285
284
  const stripped = (id & ~(1 << 31 - Math.clz32(id))).toString(32);
286
285
  return stripped + overflow;
287
286
  }
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;
287
+ var REACT_MAJOR = typeof __HADARS_REACT_MAJOR__ !== "undefined" ? parseInt(String(__HADARS_REACT_MAJOR__), 10) : 19;
288
+ var REACT_VERSION = REACT_MAJOR < 19 ? "18.3.1" : "19.1.1";
309
289
  function makeId() {
310
290
  const st = s();
311
291
  const treeId = getTreeId();
@@ -385,9 +365,24 @@ function use(usable) {
385
365
  }
386
366
  // src/slim-react/dispatcher.ts
387
367
  import * as ReactNS from "react";
388
- var _r19 = ReactNS.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
389
- var _r18Raw = !_r19 ? ReactNS.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED : undefined;
390
- var _r18 = _r18Raw?.ReactCurrentDispatcher ? _r18Raw : undefined;
368
+ var _r19;
369
+ var _r18;
370
+ var _detected = false;
371
+ var _k19 = "__CLIENT_INTERNALS_DO_NOT_USE" + "_OR_WARN_USERS_THEY_CANNOT_UPGRADE";
372
+ var _k18 = "__SECRET_INTERNALS_DO_NOT_USE" + "_OR_YOU_WILL_BE_FIRED";
373
+ function _detect() {
374
+ if (_detected)
375
+ return;
376
+ _detected = true;
377
+ const r19 = ReactNS[_k19];
378
+ if (r19) {
379
+ _r19 = r19;
380
+ return;
381
+ }
382
+ const raw = ReactNS[_k18];
383
+ if (raw?.ReactCurrentDispatcher)
384
+ _r18 = raw;
385
+ }
391
386
  var slimDispatcher = {
392
387
  useId: makeId,
393
388
  readContext: (ctx) => getContextValue(ctx),
@@ -413,6 +408,7 @@ var slimDispatcher = {
413
408
  useHostTransitionStatus: () => false
414
409
  };
415
410
  function installDispatcher() {
411
+ _detect();
416
412
  if (_r19) {
417
413
  const prev = _r19.H;
418
414
  _r19.H = slimDispatcher;
@@ -426,6 +422,7 @@ function installDispatcher() {
426
422
  return null;
427
423
  }
428
424
  function restoreDispatcher(prev) {
425
+ _detect();
429
426
  if (_r19)
430
427
  _r19.H = prev;
431
428
  else if (_r18)
@@ -1223,14 +1220,28 @@ var getReactResponse = async (req, opts) => {
1223
1220
  globalThis.__hadarsUnsuspend = unsuspend;
1224
1221
  globalThis.__hadarsContext = context;
1225
1222
  const element = createElement(App, props);
1226
- try {
1227
- await renderPreflight(element);
1228
- } finally {
1229
- globalThis.__hadarsUnsuspend = null;
1230
- globalThis.__hadarsContext = null;
1223
+ let bodyHtml = null;
1224
+ if (opts.singlePass) {
1225
+ globalThis.__hadarsUnsuspend = unsuspend;
1226
+ globalThis.__hadarsContext = context;
1227
+ try {
1228
+ bodyHtml = await renderToString(element);
1229
+ } finally {
1230
+ globalThis.__hadarsUnsuspend = null;
1231
+ globalThis.__hadarsContext = null;
1232
+ }
1233
+ } else {
1234
+ try {
1235
+ await renderPreflight(element);
1236
+ } finally {
1237
+ globalThis.__hadarsUnsuspend = null;
1238
+ globalThis.__hadarsContext = null;
1239
+ }
1231
1240
  }
1232
1241
  const status = context.head.status;
1233
1242
  const getAppBody = async () => {
1243
+ if (bodyHtml !== null)
1244
+ return bodyHtml;
1234
1245
  globalThis.__hadarsUnsuspend = unsuspend;
1235
1246
  globalThis.__hadarsContext = context;
1236
1247
  try {
@@ -1826,15 +1837,23 @@ async function transformStream(data, stream) {
1826
1837
  }
1827
1838
  return out;
1828
1839
  }
1840
+ var _zlibGzip = null;
1841
+ var _zlibGunzip = null;
1829
1842
  async function zlibGzip(d) {
1830
- const zlib = await import("node:zlib");
1831
- const { promisify } = await import("node:util");
1832
- return promisify(zlib.gzip)(d);
1843
+ if (!_zlibGzip) {
1844
+ const zlib = await import("node:zlib");
1845
+ const { promisify } = await import("node:util");
1846
+ _zlibGzip = promisify(zlib.gzip);
1847
+ }
1848
+ return _zlibGzip(d);
1833
1849
  }
1834
1850
  async function zlibGunzip(d) {
1835
- const zlib = await import("node:zlib");
1836
- const { promisify } = await import("node:util");
1837
- return promisify(zlib.gunzip)(d);
1851
+ if (!_zlibGunzip) {
1852
+ const zlib = await import("node:zlib");
1853
+ const { promisify } = await import("node:util");
1854
+ _zlibGunzip = promisify(zlib.gunzip);
1855
+ }
1856
+ return _zlibGunzip(d);
1838
1857
  }
1839
1858
  var gzipCompress = (d) => globalThis.CompressionStream ? transformStream(d, new globalThis.CompressionStream("gzip")) : zlibGzip(d);
1840
1859
  var gzipDecompress = (d) => globalThis.DecompressionStream ? transformStream(d, new globalThis.DecompressionStream("gzip")) : zlibGunzip(d);
@@ -2568,6 +2587,8 @@ var dev = async (options) => {
2568
2587
  const tmpFilePath = pathMod2.join(HADARS_TMP_DIR, `client-${Date.now()}.tsx`);
2569
2588
  await fs.writeFile(tmpFilePath, clientScript);
2570
2589
  let ssrBuildId = crypto.randomBytes(4).toString("hex");
2590
+ let cachedSsrModule = null;
2591
+ let cachedSsrBuildId = "";
2571
2592
  const resolvedHtmlTemplate = options.htmlTemplate ? await processHtmlTemplate(pathMod2.resolve(__dirname3, options.htmlTemplate)) : undefined;
2572
2593
  const clientCompiler = createClientCompiler(tmpFilePath, {
2573
2594
  target: "web",
@@ -2742,13 +2763,17 @@ var dev = async (options) => {
2742
2763
  if (projectRes)
2743
2764
  return projectRes;
2744
2765
  const ssrComponentPath = pathMod2.join(__dirname3, HadarsFolder, SSR_FILENAME);
2745
- const importPath = pathToFileURL(ssrComponentPath).href + `?t=${ssrBuildId}`;
2746
2766
  try {
2767
+ if (ssrBuildId !== cachedSsrBuildId) {
2768
+ const importPath = pathToFileURL(ssrComponentPath).href + `?t=${ssrBuildId}`;
2769
+ cachedSsrModule = await import(importPath);
2770
+ cachedSsrBuildId = ssrBuildId;
2771
+ }
2747
2772
  const {
2748
2773
  default: Component,
2749
2774
  getInitProps,
2750
2775
  getFinalProps
2751
- } = await import(importPath);
2776
+ } = cachedSsrModule;
2752
2777
  globalThis.__hadarsGraphQL = devStaticCtx?.graphql;
2753
2778
  const { head, status, getAppBody, finalize } = await getReactResponse(request, {
2754
2779
  document: {
@@ -2757,7 +2782,8 @@ var dev = async (options) => {
2757
2782
  getInitProps,
2758
2783
  getFinalProps
2759
2784
  },
2760
- staticCtx: devStaticCtx
2785
+ staticCtx: devStaticCtx,
2786
+ singlePass: true
2761
2787
  });
2762
2788
  if (request.headers.get("Accept") === "application/json") {
2763
2789
  const { clientProps } = await finalize();
@@ -2916,7 +2942,8 @@ var run = async (options) => {
2916
2942
  lang: "en",
2917
2943
  getInitProps,
2918
2944
  getFinalProps
2919
- }
2945
+ },
2946
+ singlePass: true
2920
2947
  });
2921
2948
  if (request.headers.get("Accept") === "application/json") {
2922
2949
  const { clientProps } = await finalize();
@@ -3232,8 +3259,166 @@ Deploy instructions:`);
3232
3259
  await unlink(shimPath).catch(() => {});
3233
3260
  }
3234
3261
  }
3235
- var TEMPLATES = {
3236
- "package.json": (name) => JSON.stringify({
3262
+ var _R = "\x1B[0m";
3263
+ var _B = "\x1B[1m";
3264
+ var _D = "\x1B[2m";
3265
+ var _C = "\x1B[36m";
3266
+ var _G = "\x1B[32m";
3267
+ var _UP_KEY = "\x1B[A";
3268
+ var _DOWN_KEY = "\x1B[B";
3269
+ var _HIDE = "\x1B[?25l";
3270
+ var _SHOW = "\x1B[?25h";
3271
+ var _cl = () => "\r\x1B[2K";
3272
+ var _up = (n) => n > 0 ? `\x1B[${n}A` : "";
3273
+ function _readKeys(handler) {
3274
+ return new Promise((resolve2) => {
3275
+ const { stdin } = process;
3276
+ const wasRaw = stdin.isTTY && stdin.isRaw;
3277
+ if (stdin.isTTY)
3278
+ stdin.setRawMode(true);
3279
+ stdin.resume();
3280
+ stdin.setEncoding("utf-8");
3281
+ const onData = (key) => {
3282
+ if (key === "\x03") {
3283
+ cleanup();
3284
+ process.stdout.write(_SHOW);
3285
+ process.exit(130);
3286
+ }
3287
+ if (handler(key))
3288
+ cleanup();
3289
+ };
3290
+ const cleanup = () => {
3291
+ stdin.removeListener("data", onData);
3292
+ if (stdin.isTTY && !wasRaw)
3293
+ stdin.setRawMode(false);
3294
+ stdin.pause();
3295
+ resolve2();
3296
+ };
3297
+ stdin.on("data", onData);
3298
+ });
3299
+ }
3300
+ async function promptRadio(question, options) {
3301
+ const out = process.stdout;
3302
+ let cursor = 0;
3303
+ const total = 1 + options.length;
3304
+ const render = (redraw) => {
3305
+ if (redraw)
3306
+ out.write(_up(total));
3307
+ out.write(`${_cl()} ${_B}${question}${_R}
3308
+ `);
3309
+ for (let i = 0;i < options.length; i++) {
3310
+ const arrow = i === cursor ? `${_C}❯${_R}` : " ";
3311
+ const text = i === cursor ? `${_B}${options[i]}${_R}` : `${_D}${options[i]}${_R}`;
3312
+ out.write(`${_cl()} ${arrow} ${text}
3313
+ `);
3314
+ }
3315
+ };
3316
+ out.write(_HIDE);
3317
+ render(false);
3318
+ await _readKeys((key) => {
3319
+ if (key === _UP_KEY && cursor > 0) {
3320
+ cursor--;
3321
+ render(true);
3322
+ } else if (key === _DOWN_KEY && cursor < options.length - 1) {
3323
+ cursor++;
3324
+ render(true);
3325
+ } else if (key === "\r") {
3326
+ return true;
3327
+ }
3328
+ return false;
3329
+ });
3330
+ out.write(_up(total));
3331
+ out.write(`${_cl()} ${question} ${_C}${_B}${options[cursor]}${_R}
3332
+ `);
3333
+ for (let i = 0;i < options.length; i++)
3334
+ out.write(`${_cl()}
3335
+ `);
3336
+ out.write(_up(options.length));
3337
+ out.write(_SHOW);
3338
+ return cursor;
3339
+ }
3340
+ async function promptMultiSelect(question, options) {
3341
+ const out = process.stdout;
3342
+ let cursor = 0;
3343
+ const selected = new Set;
3344
+ const total = 2 + options.length;
3345
+ const render = (redraw) => {
3346
+ if (redraw)
3347
+ out.write(_up(total));
3348
+ out.write(`${_cl()} ${_B}${question}${_R}
3349
+ `);
3350
+ out.write(`${_cl()} ${_D}↑↓ navigate · Space select · Enter confirm${_R}
3351
+ `);
3352
+ for (let i = 0;i < options.length; i++) {
3353
+ const arrow = i === cursor ? `${_C}❯${_R}` : " ";
3354
+ const box = selected.has(i) ? `${_G}●${_R}` : `${_D}○${_R}`;
3355
+ const text = i === cursor ? `${_B}${options[i]}${_R}` : `${_D}${options[i]}${_R}`;
3356
+ out.write(`${_cl()} ${arrow} ${box} ${text}
3357
+ `);
3358
+ }
3359
+ };
3360
+ out.write(_HIDE);
3361
+ render(false);
3362
+ await _readKeys((key) => {
3363
+ if (key === _UP_KEY && cursor > 0) {
3364
+ cursor--;
3365
+ render(true);
3366
+ } else if (key === _DOWN_KEY && cursor < options.length - 1) {
3367
+ cursor++;
3368
+ render(true);
3369
+ } else if (key === " ") {
3370
+ selected.has(cursor) ? selected.delete(cursor) : selected.add(cursor);
3371
+ render(true);
3372
+ } else if (key === "\r") {
3373
+ return true;
3374
+ }
3375
+ return false;
3376
+ });
3377
+ const picked = [...selected].sort((a, b) => a - b);
3378
+ const summary = picked.length > 0 ? picked.map((i) => options[i]).join(", ") : "none";
3379
+ out.write(_up(total));
3380
+ out.write(`${_cl()} ${question} ${_C}${_B}${summary}${_R}
3381
+ `);
3382
+ for (let i = 0;i < total - 1; i++)
3383
+ out.write(`${_cl()}
3384
+ `);
3385
+ out.write(_up(total - 1));
3386
+ out.write(_SHOW);
3387
+ return picked;
3388
+ }
3389
+ var PLUGINS = [
3390
+ { pkg: "@swc/plugin-emotion", version: "12.0.0", label: "Emotion (CSS-in-JS)" },
3391
+ { pkg: "@swc/plugin-styled-components", version: "10.0.0", label: "styled-components" },
3392
+ { pkg: "@swc/plugin-relay", version: "10.0.0", label: "Relay (GraphQL)" },
3393
+ { pkg: "@swc/plugin-styled-jsx", version: "11.0.0", label: "styled-jsx" },
3394
+ { pkg: "@swc/plugin-transform-imports", version: "10.0.0", label: "transform-imports" },
3395
+ { pkg: "@swc/plugin-loadable-components", version: "9.0.0", label: "Loadable Components" },
3396
+ { pkg: "@swc/plugin-formatjs", version: "7.0.0", label: "FormatJS (i18n)" }
3397
+ ];
3398
+ function renderSwcPluginsConfig(plugins) {
3399
+ if (plugins.length === 0)
3400
+ return "";
3401
+ const lines = plugins.map((p) => {
3402
+ if (p.pkg === "@swc/plugin-relay") {
3403
+ return ` ['${p.pkg}', { rootDir: process.cwd(), artifactDirectory: 'src/__generated__' }],`;
3404
+ }
3405
+ return ` ['${p.pkg}', {}],`;
3406
+ });
3407
+ return `
3408
+ swcPlugins: [
3409
+ ${lines.join(`
3410
+ `)}
3411
+ ],`;
3412
+ }
3413
+ function buildTemplates(name, opts) {
3414
+ const { useTypeScript, plugins } = opts;
3415
+ const appExt = useTypeScript ? "tsx" : "jsx";
3416
+ const cfgExt = useTypeScript ? "ts" : "js";
3417
+ const tsOrJs = useTypeScript ? "tsconfig.json" : "jsconfig.json";
3418
+ const pluginDeps = {};
3419
+ for (const p of plugins)
3420
+ pluginDeps[p.pkg] = p.version;
3421
+ const packageJson = JSON.stringify({
3237
3422
  name,
3238
3423
  version: "0.1.0",
3239
3424
  type: "module",
@@ -3247,19 +3432,28 @@ var TEMPLATES = {
3247
3432
  hadars: "latest",
3248
3433
  react: "^19.0.0",
3249
3434
  "react-dom": "^19.0.0"
3250
- }
3435
+ },
3436
+ ...Object.keys(pluginDeps).length > 0 ? { devDependencies: pluginDeps } : {}
3251
3437
  }, null, 2) + `
3252
- `,
3253
- "hadars.config.ts": () => `import type { HadarsOptions } from 'hadars';
3438
+ `;
3439
+ const swcSection = renderSwcPluginsConfig(plugins);
3440
+ const hadarsConfig = useTypeScript ? `import type { HadarsOptions } from 'hadars';
3254
3441
 
3255
3442
  const config: HadarsOptions = {
3256
3443
  entry: 'src/App.tsx',
3257
- port: 3000,
3444
+ port: 3000,${swcSection}
3258
3445
  };
3259
3446
 
3260
3447
  export default config;
3261
- `,
3262
- "tsconfig.json": () => JSON.stringify({
3448
+ ` : `/** @type {import('hadars').HadarsOptions} */
3449
+ const config = {
3450
+ entry: 'src/App.jsx',
3451
+ port: 3000,${swcSection}
3452
+ };
3453
+
3454
+ export default config;
3455
+ `;
3456
+ const tsConfigContent = useTypeScript ? JSON.stringify({
3263
3457
  compilerOptions: {
3264
3458
  lib: ["ESNext", "DOM"],
3265
3459
  target: "ESNext",
@@ -3274,215 +3468,184 @@ export default config;
3274
3468
  skipLibCheck: true
3275
3469
  }
3276
3470
  }, null, 2) + `
3277
- `,
3278
- ".gitignore": () => `node_modules/
3279
- .hadars/
3280
- dist/
3281
- `,
3282
- "src/App.tsx": () => `import React from 'react';
3283
- import { HadarsHead, type HadarsApp } from 'hadars';
3471
+ ` : JSON.stringify({
3472
+ compilerOptions: {
3473
+ lib: ["ESNext", "DOM"],
3474
+ target: "ESNext",
3475
+ module: "ESNext",
3476
+ moduleResolution: "bundler",
3477
+ jsx: "react-jsx",
3478
+ checkJs: true,
3479
+ noEmit: true,
3480
+ skipLibCheck: true
3481
+ }
3482
+ }, null, 2) + `
3483
+ `;
3484
+ const appImports = useTypeScript ? `import React from 'react';
3485
+ import { HadarsHead, type HadarsApp } from 'hadars';` : `import React from 'react';
3486
+ import { HadarsHead } from 'hadars';`;
3487
+ const appSignature = useTypeScript ? `const App: HadarsApp<{}> = () => {` : `const App = () => {`;
3488
+ const appContent = `${appImports}
3284
3489
 
3285
3490
  const css = \`
3286
- *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
3491
+ *, *::before, *::after { box-sizing: border-box; }
3287
3492
 
3288
- body {
3289
- font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
3290
- background: #0f0f13;
3291
- color: #e2e8f0;
3292
- min-height: 100vh;
3493
+ :root {
3494
+ font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
3495
+ line-height: 1.5;
3496
+ font-weight: 400;
3497
+ color-scheme: light dark;
3498
+ color: rgba(255, 255, 255, 0.87);
3499
+ background-color: #242424;
3500
+ font-synthesis: none;
3501
+ text-rendering: optimizeLegibility;
3502
+ -webkit-font-smoothing: antialiased;
3293
3503
  }
3294
3504
 
3295
- .nav {
3296
- display: flex;
3297
- align-items: center;
3298
- justify-content: space-between;
3299
- padding: 1rem 2rem;
3300
- border-bottom: 1px solid #1e1e2e;
3301
- }
3302
- .nav-brand { font-weight: 700; font-size: 1.1rem; color: #a78bfa; letter-spacing: -0.02em; }
3303
- .nav-links { display: flex; gap: 1.5rem; }
3304
- .nav-links a { color: #94a3b8; text-decoration: none; font-size: 0.9rem; }
3305
- .nav-links a:hover { color: #e2e8f0; }
3505
+ body { margin: 0; }
3306
3506
 
3307
- .hero {
3308
- text-align: center;
3309
- padding: 5rem 1rem 4rem;
3310
- max-width: 680px;
3507
+ #app {
3508
+ max-width: 1280px;
3311
3509
  margin: 0 auto;
3312
- }
3313
- .hero-badge {
3314
- display: inline-block;
3315
- background: #1e1a2e;
3316
- border: 1px solid #4c1d95;
3317
- color: #a78bfa;
3318
- font-size: 0.75rem;
3319
- font-weight: 600;
3320
- letter-spacing: 0.05em;
3321
- text-transform: uppercase;
3322
- padding: 0.3rem 0.8rem;
3323
- border-radius: 999px;
3324
- margin-bottom: 1.5rem;
3325
- }
3326
- .hero h1 {
3327
- font-size: clamp(2rem, 5vw, 3.25rem);
3328
- font-weight: 800;
3329
- letter-spacing: -0.03em;
3330
- line-height: 1.15;
3331
- margin-bottom: 1rem;
3332
- }
3333
- .hero h1 span { color: #a78bfa; }
3334
- .hero p {
3335
- font-size: 1.1rem;
3336
- color: #94a3b8;
3337
- line-height: 1.7;
3338
- margin-bottom: 2.5rem;
3339
- }
3340
- .hero-actions { display: flex; gap: 1rem; justify-content: center; flex-wrap: wrap; }
3341
- .btn {
3342
- display: inline-flex;
3510
+ padding: 2rem;
3511
+ text-align: center;
3512
+ display: flex;
3513
+ flex-direction: column;
3343
3514
  align-items: center;
3344
- gap: 0.4rem;
3345
- padding: 0.65rem 1.4rem;
3515
+ justify-content: center;
3516
+ min-height: 100vh;
3517
+ }
3518
+
3519
+ .logos { display: flex; align-items: center; justify-content: center; gap: 1rem; margin-bottom: 1.5rem; }
3520
+
3521
+ .logo {
3522
+ height: 6em;
3523
+ padding: 1.5em;
3524
+ will-change: filter;
3525
+ transition: filter 300ms;
3526
+ }
3527
+ .logo-react { animation: spin 20s linear infinite; }
3528
+ .logo-react:hover { filter: drop-shadow(0 0 2em #61dafbaa); }
3529
+
3530
+ @keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }
3531
+ @media (prefers-reduced-motion: reduce) { .logo-react { animation: none; } }
3532
+
3533
+ h1 { font-size: 3.2em; line-height: 1.1; }
3534
+
3535
+ .card { padding: 2em; }
3536
+ .card p { color: #aaa; }
3537
+
3538
+ button {
3346
3539
  border-radius: 8px;
3347
- font-size: 0.9rem;
3348
- font-weight: 600;
3540
+ border: 1px solid transparent;
3541
+ padding: 0.6em 1.2em;
3542
+ font-size: 1em;
3543
+ font-weight: 500;
3544
+ font-family: inherit;
3545
+ background-color: #1a1a1a;
3349
3546
  cursor: pointer;
3350
- border: none;
3351
- transition: opacity 0.15s, transform 0.1s;
3352
- text-decoration: none;
3353
- }
3354
- .btn:hover { opacity: 0.85; transform: translateY(-1px); }
3355
- .btn:active { transform: translateY(0); }
3356
- .btn-primary { background: #7c3aed; color: #fff; }
3357
- .btn-ghost { background: #1e1e2e; color: #e2e8f0; border: 1px solid #2d2d3e; }
3547
+ transition: border-color 0.25s;
3548
+ }
3549
+ button:hover { border-color: #a78bfa; }
3550
+ button:focus-visible { outline: 4px auto -webkit-focus-ring-color; }
3358
3551
 
3359
- .features {
3360
- display: grid;
3361
- grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
3362
- gap: 1rem;
3363
- max-width: 900px;
3364
- margin: 0 auto 4rem;
3365
- padding: 0 1.5rem;
3366
- }
3367
- .card {
3368
- background: #16161f;
3369
- border: 1px solid #1e1e2e;
3370
- border-radius: 12px;
3371
- padding: 1.5rem;
3372
- }
3373
- .card-icon { font-size: 1.5rem; margin-bottom: 0.75rem; }
3374
- .card h3 { font-size: 0.95rem; font-weight: 700; margin-bottom: 0.4rem; }
3375
- .card p { font-size: 0.85rem; color: #64748b; line-height: 1.6; }
3552
+ .hint { color: #555; font-size: 0.9em; }
3553
+ .hint a { color: inherit; }
3554
+ .hint a:hover { color: #a78bfa; }
3376
3555
 
3377
- .demo {
3378
- max-width: 480px;
3379
- margin: 0 auto 4rem;
3380
- padding: 0 1.5rem;
3381
- text-align: center;
3556
+ @media (prefers-color-scheme: light) {
3557
+ :root { color: #213547; background-color: #ffffff; }
3558
+ button { background-color: #f9f9f9; }
3559
+ .card p { color: #666; }
3560
+ .hint { color: #999; }
3382
3561
  }
3383
- .demo-box {
3384
- background: #16161f;
3385
- border: 1px solid #1e1e2e;
3386
- border-radius: 12px;
3387
- padding: 2rem;
3388
- }
3389
- .demo-box h2 { font-size: 0.8rem; font-weight: 600; color: #64748b; text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 1.25rem; }
3390
- .counter { font-size: 3.5rem; font-weight: 800; color: #a78bfa; letter-spacing: -0.04em; margin-bottom: 1.25rem; }
3391
- .demo-actions { display: flex; gap: 0.75rem; justify-content: center; }
3392
-
3393
3562
  \`;
3394
3563
 
3395
- const App: HadarsApp<{}> = () => {
3564
+ ${appSignature}
3396
3565
  const [count, setCount] = React.useState(0);
3397
3566
 
3398
3567
  return (
3399
3568
  <>
3400
3569
  <HadarsHead status={200}>
3401
- <title>My App</title>
3570
+ <title>${name}</title>
3402
3571
  <meta name="viewport" content="width=device-width, initial-scale=1" />
3403
3572
  <style data-id="app-styles" dangerouslySetInnerHTML={{ __html: css }} />
3404
3573
  </HadarsHead>
3405
3574
 
3406
- <nav className="nav">
3407
- <span className="nav-brand">my-app</span>
3408
- <div className="nav-links">
3409
- <a href="https://github.com/dpostolachi/hadar" target="_blank" rel="noopener">github</a>
3575
+ <div id="app">
3576
+ <div className="logos">
3577
+ <a href="https://react.dev" target="_blank" rel="noopener noreferrer">
3578
+ <svg className="logo logo-react" xmlns="http://www.w3.org/2000/svg" viewBox="-11.5 -10.232 23 20.463" aria-label="React">
3579
+ <circle cx="0" cy="0" r="2.05" fill="#61dafb" />
3580
+ <g stroke="#61dafb" strokeWidth="1" fill="none">
3581
+ <ellipse rx="11" ry="4.2" />
3582
+ <ellipse rx="11" ry="4.2" transform="rotate(60)" />
3583
+ <ellipse rx="11" ry="4.2" transform="rotate(120)" />
3584
+ </g>
3585
+ </svg>
3586
+ </a>
3410
3587
  </div>
3411
- </nav>
3412
3588
 
3413
- <section className="hero">
3414
- <div className="hero-badge">built with hadars</div>
3415
- <h1>Ship <span>React apps</span><br />at full speed</h1>
3416
- <p>
3417
- SSR out of the box, zero config, instant hot-reload.
3418
- Edit <code>src/App.tsx</code> to get started.
3419
- </p>
3420
- <div className="hero-actions">
3421
- <button className="btn btn-primary" onClick={() => setCount(c => c + 1)}>
3422
- Try the counter ↓
3423
- </button>
3424
- </div>
3425
- </section>
3589
+ <h1>React + hadars</h1>
3426
3590
 
3427
- <div className="features">
3428
- <div className="card">
3429
- <div className="card-icon">⚡</div>
3430
- <h3>Server-side rendering</h3>
3431
- <p>Pages render on the server and hydrate on the client — great for SEO and first paint.</p>
3432
- </div>
3433
- <div className="card">
3434
- <div className="card-icon">\uD83D\uDD25</div>
3435
- <h3>Hot module reload</h3>
3436
- <p>Changes in <code>src/App.tsx</code> reflect instantly in the browser during development.</p>
3437
- </div>
3438
- <div className="card">
3439
- <div className="card-icon">\uD83D\uDCE6</div>
3440
- <h3>Zero config</h3>
3441
- <p>One config file. Export a React component, run <code>hadars dev</code>, done.</p>
3442
- </div>
3443
3591
  <div className="card">
3444
- <div className="card-icon">\uD83D\uDDC4️</div>
3445
- <h3>Server data hooks</h3>
3446
- <p>Use <code>useServerData</code> to fetch data on the server without extra round-trips.</p>
3592
+ <button onClick={() => setCount(c => c + 1)}>
3593
+ count is {count}
3594
+ </button>
3595
+ <p>
3596
+ Edit <code>src/App.${appExt}</code> and save to test HMR
3597
+ </p>
3447
3598
  </div>
3448
- </div>
3449
3599
 
3450
- <div className="demo">
3451
- <div className="demo-box">
3452
- <h2>Client interactivity works</h2>
3453
- <div className="counter">{count}</div>
3454
- <div className="demo-actions">
3455
- <button className="btn btn-ghost" onClick={() => setCount(c => c - 1)}>− dec</button>
3456
- <button className="btn btn-primary" onClick={() => setCount(c => c + 1)}>+ inc</button>
3457
- </div>
3458
- </div>
3600
+ <p className="hint">
3601
+ <a href="https://hadars.xyz" target="_blank" rel="noopener noreferrer">hadars docs</a>
3602
+ &nbsp;·&nbsp;
3603
+ <a href="https://react.dev" target="_blank" rel="noopener noreferrer">react docs</a>
3604
+ </p>
3459
3605
  </div>
3460
-
3461
3606
  </>
3462
3607
  );
3463
3608
  };
3464
3609
 
3465
3610
  export default App;
3466
- `
3467
- };
3611
+ `;
3612
+ return {
3613
+ "package.json": packageJson,
3614
+ [`hadars.config.${cfgExt}`]: hadarsConfig,
3615
+ [tsOrJs]: tsConfigContent,
3616
+ ".gitignore": `node_modules/
3617
+ .hadars/
3618
+ dist/
3619
+ `,
3620
+ [`src/App.${appExt}`]: appContent
3621
+ };
3622
+ }
3468
3623
  async function createProject(name, cwd) {
3469
3624
  const dir = resolve(cwd, name);
3470
3625
  if (existsSync3(dir)) {
3471
3626
  console.error(`Directory already exists: ${dir}`);
3472
3627
  process.exit(1);
3473
3628
  }
3474
- console.log(`Creating hadars project in ${dir}`);
3629
+ const useTypeScript = await promptRadio("Language?", ["TypeScript", "JavaScript"]) === 0;
3630
+ const selectedIndices = await promptMultiSelect("Select SWC plugins to enable (optional):", PLUGINS.map((p) => p.label));
3631
+ const plugins = selectedIndices.map((i) => PLUGINS[i]);
3632
+ console.log(`
3633
+ Creating hadars project in ${dir}`);
3475
3634
  await mkdir2(join2(dir, "src"), { recursive: true });
3476
- for (const [file, template] of Object.entries(TEMPLATES)) {
3477
- const content = template(name);
3635
+ const files = buildTemplates(name, { useTypeScript, plugins });
3636
+ for (const [file, content] of Object.entries(files)) {
3478
3637
  await writeFile2(join2(dir, file), content, "utf-8");
3479
3638
  console.log(` created ${file}`);
3480
3639
  }
3640
+ const installHint = plugins.length > 0 ? `
3641
+ # Also install selected SWC plugins:
3642
+ npm install -D ${plugins.map((p) => `${p.pkg}@${p.version}`).join(" ")}
3643
+ ` : "";
3481
3644
  console.log(`
3482
3645
  Done! Next steps:
3483
3646
 
3484
3647
  cd ${name}
3485
- npm install # or: bun install / pnpm install
3648
+ npm install # or: bun install / pnpm install${installHint}
3486
3649
  npm run dev # or: bun run dev
3487
3650
  `);
3488
3651
  }