create-avalon 0.1.25 → 0.2.1

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 +27 -10
  2. package/dist/cli.js +385 -218
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -19,11 +19,22 @@ bun create avalon
19
19
  The CLI walks you through:
20
20
 
21
21
  - Project name and directory
22
- - Framework selection (React, Preact, Vue, Svelte, Solid, Lit, Qwik — or multiple)
23
- - Styling approach (CSS Modules, Tailwind, vanilla CSS)
24
- - Optional features (API routes, middleware, layouts, MDX)
25
- - Scheduled jobs (cron) scaffolds an example task and wires up `nitro.cron`
26
- - Package manager preference
22
+ - Rendering engine (Preact or React) and island integrations
23
+ - Styling approach (CSS Modules, Tailwind, shadcn)
24
+ - Plugins (SEO, agent optimization). MDX syntax highlighting is always on.
25
+ - Middleware (h3, Hono, Elysia)
26
+ - Deploy target (**Cloudflare Pages**, Netlify, or none)
27
+ - Scheduled jobs (cron)
28
+
29
+ ## Deploy targets
30
+
31
+ | Choice | What you get |
32
+ |--------|----------------|
33
+ | **Cloudflare Pages** | `wrangler.toml`, `public/_headers`, `DEPLOY.md`, `build` / `preview` / `deploy` scripts. `build.mjs` sets `NITRO_PRESET=cloudflare_pages` when unset. |
34
+ | **Netlify** | `netlify.toml` (`NITRO_PRESET=netlify`), soft SSR redirect, `DEPLOY.md`. |
35
+ | **None** | Node server preset; still gets `build.mjs` + `post-build.mjs`. |
36
+
37
+ Always run Avalon's post-build (`node post-build.mjs` via `bun run build`) — it patches the Cloudflare worker, CSS manifests, and prerenders HTML.
27
38
 
28
39
  ## What you get
29
40
 
@@ -33,10 +44,11 @@ A ready-to-run Avalon project with file-system routing, islands architecture, an
33
44
  my-project/
34
45
  ├── app/
35
46
  │ ├── modules/
36
- │ │ └── home/
37
- │ │ ├── pages/ # File-system routes
38
- │ │ ├── components/ # Interactive components
39
- │ │ └── layouts/ # Module layouts
47
+ │ │ ├── main/ # Site root (/)
48
+ │ │├── pages/ # File-system routes for this module
49
+ │ │├── components/
50
+ │ │└── layouts/
51
+ │ │ └── about/ # /about — a module name is a URL prefix
40
52
  │ └── shared/
41
53
  │ ├── layouts/ # Root layout
42
54
  │ ├── components/ # Shared components
@@ -46,7 +58,12 @@ my-project/
46
58
  │ └── api/ # API routes
47
59
  ├── tasks/ # Scheduled jobs (cron) — optional
48
60
  ├── server/ # Server config & env
49
- ├── public/ # Static assets
61
+ ├── public/ # Static assets (+ _headers for Cloudflare)
62
+ ├── build.mjs # Vite hang workaround + post-build
63
+ ├── post-build.mjs # Avalon production patches / prerender
64
+ ├── DEPLOY.md # Platform-specific deploy steps
65
+ ├── wrangler.toml # Cloudflare Pages (optional)
66
+ ├── netlify.toml # Netlify (optional)
50
67
  ├── vite.config.ts
51
68
  └── package.json
52
69
  ```
package/dist/cli.js CHANGED
@@ -4,21 +4,37 @@ var __getProtoOf = Object.getPrototypeOf;
4
4
  var __defProp = Object.defineProperty;
5
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
6
  var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ function __accessProp(key) {
8
+ return this[key];
9
+ }
10
+ var __toESMCache_node;
11
+ var __toESMCache_esm;
7
12
  var __toESM = (mod, isNodeMode, target) => {
13
+ var canCache = mod != null && typeof mod === "object";
14
+ if (canCache) {
15
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
16
+ var cached = cache.get(mod);
17
+ if (cached)
18
+ return cached;
19
+ }
8
20
  target = mod != null ? __create(__getProtoOf(mod)) : {};
9
- const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
10
- for (let key of __getOwnPropNames(mod))
11
- if (!__hasOwnProp.call(to, key))
12
- __defProp(to, key, {
13
- get: () => mod[key],
14
- enumerable: true
15
- });
21
+ const to = isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
22
+ if (mod && typeof mod === "object" || typeof mod === "function") {
23
+ for (let key of __getOwnPropNames(mod))
24
+ if (!__hasOwnProp.call(to, key))
25
+ __defProp(to, key, {
26
+ get: __accessProp.bind(mod, key),
27
+ enumerable: true
28
+ });
29
+ }
30
+ if (canCache)
31
+ cache.set(mod, to);
16
32
  return to;
17
33
  };
18
34
  var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
19
35
 
20
36
  // ../../node_modules/.bun/sisteransi@1.0.5/node_modules/sisteransi/src/index.js
21
- var require_src = __commonJS((exports, module) => {
37
+ var require_src = __commonJS(function(exports, module) {
22
38
  var ESC = "\x1B";
23
39
  var CSI = `${ESC}[`;
24
40
  var beep = "\x07";
@@ -99,17 +115,17 @@ var STYLING_OPTIONS = [
99
115
  "tailwind",
100
116
  "shadcn"
101
117
  ];
102
- var PLUGINS = [
103
- "seo",
104
- "agent-optimization",
105
- "syntax-highlighting"
106
- ];
118
+ var PLUGINS = ["seo", "agent-optimization"];
107
119
  var MIDDLEWARE_OPTIONS = [
108
120
  "h3",
109
121
  "hono",
110
122
  "elysia"
111
123
  ];
112
- var DEPLOY_TARGETS = ["netlify", "none"];
124
+ var DEPLOY_TARGETS = [
125
+ "cloudflare",
126
+ "netlify",
127
+ "none"
128
+ ];
113
129
  var INTEGRATION_PACKAGES = {
114
130
  preact: "@useavalon/preact",
115
131
  react: "@useavalon/react",
@@ -123,6 +139,9 @@ var BASE_DIRS = [
123
139
  "app/modules/main/pages",
124
140
  "app/modules/main/components",
125
141
  "app/modules/main/layouts",
142
+ "app/modules/about/pages",
143
+ "app/modules/about/components",
144
+ "app/modules/about/layouts",
126
145
  "app/shared/layouts",
127
146
  "app/shared/components",
128
147
  "app/shared/styles",
@@ -209,8 +228,8 @@ function resolveConfigNonInteractive(args) {
209
228
  if (styling === "shadcn" && core !== "react") {
210
229
  throw new CliArgError("--styling=shadcn requires --core=react (shadcn is Radix/React based).");
211
230
  }
212
- if (core === "react" && !integrations.includes("react")) {
213
- integrations.push("react");
231
+ if (!integrations.includes(core)) {
232
+ integrations.push(core);
214
233
  }
215
234
  return {
216
235
  projectName: args.projectName ?? ".",
@@ -580,82 +599,6 @@ class B {
580
599
  }
581
600
  }
582
601
  }
583
- function wt(t, e) {
584
- if (t === undefined || e.length === 0)
585
- return 0;
586
- const s = e.findIndex((i) => i.value === t);
587
- return s !== -1 ? s : 0;
588
- }
589
- function Dt(t, e) {
590
- return (e.label ?? String(e.value)).toLowerCase().includes(t.toLowerCase());
591
- }
592
- function St(t, e) {
593
- if (e)
594
- return t ? e : e[0];
595
- }
596
-
597
- class Vt extends B {
598
- filteredOptions;
599
- multiple;
600
- isNavigating = false;
601
- selectedValues = [];
602
- focusedValue;
603
- #t = 0;
604
- #s = "";
605
- #i;
606
- #e;
607
- get cursor() {
608
- return this.#t;
609
- }
610
- get userInputWithCursor() {
611
- if (!this.userInput)
612
- return D(["inverse", "hidden"], "_");
613
- if (this._cursor >= this.userInput.length)
614
- return `${this.userInput}█`;
615
- const e = this.userInput.slice(0, this._cursor), [s, ...i] = this.userInput.slice(this._cursor);
616
- return `${e}${D("inverse", s)}${i.join("")}`;
617
- }
618
- get options() {
619
- return typeof this.#e == "function" ? this.#e() : this.#e;
620
- }
621
- constructor(e) {
622
- super(e), this.#e = e.options;
623
- const s = this.options;
624
- this.filteredOptions = [...s], this.multiple = e.multiple === true, this.#i = e.filter ?? Dt;
625
- let i;
626
- if (e.initialValue && Array.isArray(e.initialValue) ? this.multiple ? i = e.initialValue : i = e.initialValue.slice(0, 1) : !this.multiple && this.options.length > 0 && (i = [this.options[0].value]), i)
627
- for (const r of i) {
628
- const n = s.findIndex((u) => u.value === r);
629
- n !== -1 && (this.toggleSelected(r), this.#t = n);
630
- }
631
- this.focusedValue = this.options[this.#t]?.value, this.on("key", (r, n) => this.#r(r, n)), this.on("userInput", (r) => this.#n(r));
632
- }
633
- _isActionKey(e, s) {
634
- return e === "\t" || this.multiple && this.isNavigating && s.name === "space" && e !== undefined && e !== "";
635
- }
636
- #r(e, s) {
637
- const i = s.name === "up", r = s.name === "down", n = s.name === "return";
638
- i || r ? (this.#t = x(this.#t, i ? -1 : 1, this.filteredOptions), this.focusedValue = this.filteredOptions[this.#t]?.value, this.multiple || (this.selectedValues = [this.focusedValue]), this.isNavigating = true) : n ? this.value = St(this.multiple, this.selectedValues) : this.multiple ? this.focusedValue !== undefined && (s.name === "tab" || this.isNavigating && s.name === "space") ? this.toggleSelected(this.focusedValue) : this.isNavigating = false : (this.focusedValue && (this.selectedValues = [this.focusedValue]), this.isNavigating = false);
639
- }
640
- deselectAll() {
641
- this.selectedValues = [];
642
- }
643
- toggleSelected(e) {
644
- this.filteredOptions.length !== 0 && (this.multiple ? this.selectedValues.includes(e) ? this.selectedValues = this.selectedValues.filter((s) => s !== e) : this.selectedValues = [...this.selectedValues, e] : this.selectedValues = [e]);
645
- }
646
- #n(e) {
647
- if (e !== this.#s) {
648
- this.#s = e;
649
- const s = this.options;
650
- e ? this.filteredOptions = s.filter((n) => this.#i(e, n)) : this.filteredOptions = [...s];
651
- const i = wt(this.focusedValue, this.filteredOptions);
652
- this.#t = x(i, 0, this.filteredOptions);
653
- const r = this.filteredOptions[this.#t];
654
- r && !r.disabled ? this.focusedValue = r.value : this.focusedValue = undefined, this.multiple || (this.focusedValue !== undefined ? this.toggleSelected(this.focusedValue) : this.deselectAll());
655
- }
656
- }
657
- }
658
-
659
602
  class kt extends B {
660
603
  get cursor() {
661
604
  return this.value ? 0 : 1;
@@ -673,54 +616,6 @@ class kt extends B {
673
616
  });
674
617
  }
675
618
  }
676
-
677
- class yt extends B {
678
- options;
679
- cursor = 0;
680
- #t;
681
- getGroupItems(e) {
682
- return this.options.filter((s) => s.group === e);
683
- }
684
- isGroupSelected(e) {
685
- const s = this.getGroupItems(e), i = this.value;
686
- return i === undefined ? false : s.every((r) => i.includes(r.value));
687
- }
688
- toggleValue() {
689
- const e = this.options[this.cursor];
690
- if (this.value === undefined && (this.value = []), e.group === true) {
691
- const s = e.value, i = this.getGroupItems(s);
692
- this.isGroupSelected(s) ? this.value = this.value.filter((r) => i.findIndex((n) => n.value === r) === -1) : this.value = [...this.value, ...i.map((r) => r.value)], this.value = Array.from(new Set(this.value));
693
- } else {
694
- const s = this.value.includes(e.value);
695
- this.value = s ? this.value.filter((i) => i !== e.value) : [...this.value, e.value];
696
- }
697
- }
698
- constructor(e) {
699
- super(e, false);
700
- const { options: s } = e;
701
- this.#t = e.selectableGroups !== false, this.options = Object.entries(s).flatMap(([i, r]) => [{ value: i, group: true, label: i }, ...r.map((n) => ({ ...n, group: i }))]), this.value = [...e.initialValues ?? []], this.cursor = Math.max(this.options.findIndex(({ value: i }) => i === e.cursorAt), this.#t ? 0 : 1), this.on("cursor", (i) => {
702
- switch (i) {
703
- case "left":
704
- case "up": {
705
- this.cursor = this.cursor === 0 ? this.options.length - 1 : this.cursor - 1;
706
- const r = this.options[this.cursor]?.group === true;
707
- !this.#t && r && (this.cursor = this.cursor === 0 ? this.options.length - 1 : this.cursor - 1);
708
- break;
709
- }
710
- case "down":
711
- case "right": {
712
- this.cursor = this.cursor === this.options.length - 1 ? 0 : this.cursor + 1;
713
- const r = this.options[this.cursor]?.group === true;
714
- !this.#t && r && (this.cursor = this.cursor === this.options.length - 1 ? 0 : this.cursor + 1);
715
- break;
716
- }
717
- case "space":
718
- this.toggleValue();
719
- break;
720
- }
721
- });
722
- }
723
- }
724
619
  var Lt = class extends B {
725
620
  options;
726
621
  cursor = 0;
@@ -889,59 +784,59 @@ var Ft2 = /\p{M}+/gu;
889
784
  var yt2 = { limit: 1 / 0, ellipsis: "" };
890
785
  var Le = (e, r = {}, s = {}) => {
891
786
  const i = r.limit ?? 1 / 0, a = r.ellipsis ?? "", o = r?.ellipsisWidth ?? (a ? Le(a, yt2, s).width : 0), u = s.ansiWidth ?? 0, l = s.controlWidth ?? 0, n = s.tabWidth ?? 8, c = s.ambiguousWidth ?? 1, p = s.emojiWidth ?? 2, f = s.fullWidthWidth ?? 2, g = s.regularWidth ?? 1, E = s.wideWidth ?? 2;
892
- let $ = 0, m = 0, d = e.length, F = 0, y2 = false, v = d, C = Math.max(0, i - o), A = 0, b = 0, w = 0, S2 = 0;
787
+ let $ = 0, m = 0, d = e.length, F = 0, y = false, v = d, C = Math.max(0, i - o), A = 0, b = 0, w = 0, S = 0;
893
788
  e:
894
789
  for (;; ) {
895
790
  if (b > A || m >= d && m > $) {
896
- const T2 = e.slice(A, b) || e.slice($, m);
791
+ const T = e.slice(A, b) || e.slice($, m);
897
792
  F = 0;
898
- for (const M2 of T2.replaceAll(Ft2, "")) {
899
- const O2 = M2.codePointAt(0) || 0;
900
- if (gt2(O2) ? S2 = f : ft2(O2) ? S2 = E : c !== g && mt2(O2) ? S2 = c : S2 = g, w + S2 > C && (v = Math.min(v, Math.max(A, $) + F)), w + S2 > i) {
901
- y2 = true;
793
+ for (const M of T.replaceAll(Ft2, "")) {
794
+ const O = M.codePointAt(0) || 0;
795
+ if (gt2(O) ? S = f : ft2(O) ? S = E : c !== g && mt2(O) ? S = c : S = g, w + S > C && (v = Math.min(v, Math.max(A, $) + F)), w + S > i) {
796
+ y = true;
902
797
  break e;
903
798
  }
904
- F += M2.length, w += S2;
799
+ F += M.length, w += S;
905
800
  }
906
801
  A = b = 0;
907
802
  }
908
803
  if (m >= d)
909
804
  break;
910
805
  if (ne.lastIndex = m, ne.test(e)) {
911
- if (F = ne.lastIndex - m, S2 = F * g, w + S2 > C && (v = Math.min(v, m + Math.floor((C - w) / g))), w + S2 > i) {
912
- y2 = true;
806
+ if (F = ne.lastIndex - m, S = F * g, w + S > C && (v = Math.min(v, m + Math.floor((C - w) / g))), w + S > i) {
807
+ y = true;
913
808
  break;
914
809
  }
915
- w += S2, A = $, b = m, m = $ = ne.lastIndex;
810
+ w += S, A = $, b = m, m = $ = ne.lastIndex;
916
811
  continue;
917
812
  }
918
813
  if (we.lastIndex = m, we.test(e)) {
919
814
  if (w + u > C && (v = Math.min(v, m)), w + u > i) {
920
- y2 = true;
815
+ y = true;
921
816
  break;
922
817
  }
923
818
  w += u, A = $, b = m, m = $ = we.lastIndex;
924
819
  continue;
925
820
  }
926
821
  if (re.lastIndex = m, re.test(e)) {
927
- if (F = re.lastIndex - m, S2 = F * l, w + S2 > C && (v = Math.min(v, m + Math.floor((C - w) / l))), w + S2 > i) {
928
- y2 = true;
822
+ if (F = re.lastIndex - m, S = F * l, w + S > C && (v = Math.min(v, m + Math.floor((C - w) / l))), w + S > i) {
823
+ y = true;
929
824
  break;
930
825
  }
931
- w += S2, A = $, b = m, m = $ = re.lastIndex;
826
+ w += S, A = $, b = m, m = $ = re.lastIndex;
932
827
  continue;
933
828
  }
934
829
  if (ie.lastIndex = m, ie.test(e)) {
935
- if (F = ie.lastIndex - m, S2 = F * n, w + S2 > C && (v = Math.min(v, m + Math.floor((C - w) / n))), w + S2 > i) {
936
- y2 = true;
830
+ if (F = ie.lastIndex - m, S = F * n, w + S > C && (v = Math.min(v, m + Math.floor((C - w) / n))), w + S > i) {
831
+ y = true;
937
832
  break;
938
833
  }
939
- w += S2, A = $, b = m, m = $ = ie.lastIndex;
834
+ w += S, A = $, b = m, m = $ = ie.lastIndex;
940
835
  continue;
941
836
  }
942
837
  if (Ae.lastIndex = m, Ae.test(e)) {
943
838
  if (w + p > C && (v = Math.min(v, m)), w + p > i) {
944
- y2 = true;
839
+ y = true;
945
840
  break;
946
841
  }
947
842
  w += p, A = $, b = m, m = $ = Ae.lastIndex;
@@ -949,7 +844,7 @@ var Le = (e, r = {}, s = {}) => {
949
844
  }
950
845
  m += 1;
951
846
  }
952
- return { width: y2 ? C : w, index: y2 ? v : d, truncated: y2, ellipsed: y2 && i >= o };
847
+ return { width: y ? C : w, index: y ? v : d, truncated: y, ellipsed: y && i >= o };
953
848
  };
954
849
  var Et2 = { limit: 1 / 0, ellipsis: "", ellipsisWidth: 0 };
955
850
  var D2 = (e, r = {}) => Le(e, Et2, r).width;
@@ -958,9 +853,9 @@ var je = "›";
958
853
  var vt2 = 39;
959
854
  var Ce = "\x07";
960
855
  var ke = "[";
961
- var wt2 = "]";
856
+ var wt = "]";
962
857
  var Ve = "m";
963
- var Se = `${wt2}8;;`;
858
+ var Se = `${wt}8;;`;
964
859
  var He = new RegExp(`(?:\\${ke}(?<code>\\d+)m|\\${Se}(?<uri>.*)${Ce})`, "y");
965
860
  var At2 = (e) => {
966
861
  if (e >= 30 && e <= 37 || e >= 90 && e <= 97)
@@ -994,7 +889,7 @@ var Ie = (e, r, s) => {
994
889
  }
995
890
  u = e.at(-1), !l && u !== undefined && u.length > 0 && e.length > 1 && (e[e.length - 2] += e.pop());
996
891
  };
997
- var St2 = (e) => {
892
+ var St = (e) => {
998
893
  const r = e.split(" ");
999
894
  let s = r.length;
1000
895
  for (;s > 0 && !(D2(r[s - 1]) > 0); )
@@ -1011,8 +906,8 @@ var It2 = (e, r, s = {}) => {
1011
906
  s.trim !== false && (n[n.length - 1] = (n.at(-1) ?? "").trimStart());
1012
907
  let d = D2(n.at(-1) ?? "");
1013
908
  if ($ !== 0 && (d >= r && (s.wordWrap === false || s.trim === false) && (n.push(""), d = 0), (d > 0 || s.trim === false) && (n[n.length - 1] += " ", d++)), s.hard && l[$] > r) {
1014
- const F = r - d, y2 = 1 + Math.floor((l[$] - F - 1) / r);
1015
- Math.floor((l[$] - 1) / r) < y2 && n.push(""), Ie(n, m, r);
909
+ const F = r - d, y = 1 + Math.floor((l[$] - F - 1) / r);
910
+ Math.floor((l[$] - 1) / r) < y && n.push(""), Ie(n, m, r);
1016
911
  continue;
1017
912
  }
1018
913
  if (d + l[$] > r && d > 0 && l[$] > 0) {
@@ -1028,7 +923,7 @@ var It2 = (e, r, s = {}) => {
1028
923
  }
1029
924
  n[n.length - 1] += m;
1030
925
  }
1031
- s.trim !== false && (n = n.map(($) => St2($)));
926
+ s.trim !== false && (n = n.map(($) => St($)));
1032
927
  const c = n.join(`
1033
928
  `), p = c[Symbol.iterator]();
1034
929
  let f = p.next(), g = p.next(), E = 0;
@@ -1036,12 +931,12 @@ var It2 = (e, r, s = {}) => {
1036
931
  const $ = f.value, m = g.value;
1037
932
  if (i += $, $ === ae || $ === je) {
1038
933
  He.lastIndex = E + 1;
1039
- const y2 = He.exec(c)?.groups;
1040
- if (y2?.code !== undefined) {
1041
- const v = Number.parseFloat(y2.code);
934
+ const y = He.exec(c)?.groups;
935
+ if (y?.code !== undefined) {
936
+ const v = Number.parseFloat(y.code);
1042
937
  a = v === vt2 ? undefined : v;
1043
938
  } else
1044
- y2?.uri !== undefined && (o = y2.uri.length === 0 ? undefined : y2.uri);
939
+ y?.uri !== undefined && (o = y.uri.length === 0 ? undefined : y.uri);
1045
940
  }
1046
941
  const d = a ? At2(a) : undefined;
1047
942
  m === `
@@ -1074,16 +969,16 @@ var X2 = ({ cursor: e, options: r, style: s, output: i = process.stdout, maxItem
1074
969
  const m = Math.min(g + f, r.length), d = [];
1075
970
  let F = 0;
1076
971
  E && F++, $ && F++;
1077
- const y2 = g + (E ? 1 : 0), v = m - ($ ? 1 : 0);
1078
- for (let A = y2;A < v; A++) {
972
+ const y = g + (E ? 1 : 0), v = m - ($ ? 1 : 0);
973
+ for (let A = y;A < v; A++) {
1079
974
  const b = J(s(r[A], A === e), l, { hard: true, trim: false }).split(`
1080
975
  `);
1081
976
  d.push(b), F += b.length;
1082
977
  }
1083
978
  if (F > p) {
1084
979
  let A = 0, b = 0, w = F;
1085
- const S2 = e - y2, T2 = (M2, O2) => bt2(d, w, M2, O2, p);
1086
- E ? ({ lineCount: w, removals: A } = T2(0, S2), w > p && ({ lineCount: w, removals: b } = T2(S2 + 1, d.length))) : ({ lineCount: w, removals: b } = T2(S2 + 1, d.length), w > p && ({ lineCount: w, removals: A } = T2(0, S2))), A > 0 && (E = true, d.splice(0, A)), b > 0 && ($ = true, d.splice(d.length - b, b));
980
+ const S = e - y, T = (M, O) => bt2(d, w, M, O, p);
981
+ E ? ({ lineCount: w, removals: A } = T(0, S), w > p && ({ lineCount: w, removals: b } = T(S + 1, d.length))) : ({ lineCount: w, removals: b } = T(S + 1, d.length), w > p && ({ lineCount: w, removals: A } = T(0, S))), A > 0 && (E = true, d.splice(0, A)), b > 0 && ($ = true, d.splice(d.length - b, b));
1087
982
  }
1088
983
  const C = [];
1089
984
  E && C.push(c);
@@ -1350,11 +1245,6 @@ async function collectProjectConfig(initialName) {
1350
1245
  value: "agent-optimization",
1351
1246
  label: "agent-optimization",
1352
1247
  hint: "LLM/AI optimization (llms.txt, markdown, sitemap)"
1353
- },
1354
- {
1355
- value: "syntax-highlighting",
1356
- label: "syntax-highlighting",
1357
- hint: "Code block highlighting for MDX (rehype-highlight)"
1358
1248
  }
1359
1249
  ],
1360
1250
  initialValues: ["seo"],
@@ -1379,12 +1269,17 @@ async function collectProjectConfig(initialName) {
1379
1269
  const deployResult = await Jt({
1380
1270
  message: "Where will you deploy?",
1381
1271
  options: [
1272
+ {
1273
+ value: "cloudflare",
1274
+ label: "Cloudflare Pages",
1275
+ hint: "wrangler.toml, public/_headers, build + deploy scripts"
1276
+ },
1382
1277
  {
1383
1278
  value: "netlify",
1384
1279
  label: "Netlify",
1385
- hint: "Generates netlify.toml, build.mjs, post-build.mjs"
1280
+ hint: "netlify.toml, build.mjs, post-build.mjs"
1386
1281
  },
1387
- { value: "none", label: "None / Other", hint: "Node server preset, no deploy config" }
1282
+ { value: "none", label: "None / Other", hint: "Node server preset, no platform config" }
1388
1283
  ]
1389
1284
  });
1390
1285
  if (Ct(deployResult)) {
@@ -1401,8 +1296,8 @@ async function collectProjectConfig(initialName) {
1401
1296
  }
1402
1297
  const core = coreResult;
1403
1298
  const integrations = integrationsResult;
1404
- if (core === "react" && !integrations.includes("react")) {
1405
- integrations.push("react");
1299
+ if (!integrations.includes(core)) {
1300
+ integrations.push(core);
1406
1301
  }
1407
1302
  return {
1408
1303
  projectName,
@@ -1456,6 +1351,10 @@ export default defineCronJob({
1456
1351
  }
1457
1352
 
1458
1353
  // src/templates/deploy.ts
1354
+ function cloudflareProjectName(projectName) {
1355
+ const slug = projectName.toLowerCase().replaceAll(/[^a-z0-9-]+/g, "-").replaceAll(/-+/g, "-").replaceAll(/^-|-$/g, "");
1356
+ return slug || "avalon-app";
1357
+ }
1459
1358
  function generateNetlifyToml(_config) {
1460
1359
  return `[build]
1461
1360
  base = "."
@@ -1480,6 +1379,117 @@ function generateNetlifyToml(_config) {
1480
1379
  status = 200
1481
1380
  `;
1482
1381
  }
1382
+ function generateWranglerToml(config) {
1383
+ const name = cloudflareProjectName(config.projectName);
1384
+ return `# Cloudflare Pages — production output is dist/_worker.js + static assets.
1385
+ # Date must be >= 2025-09-15 so nodejs_compat includes \`node:fs\` (needed by the Nitro worker).
1386
+ name = "${name}"
1387
+ compatibility_date = "2026-09-04"
1388
+ compatibility_flags = ["nodejs_compat", "enable_nodejs_fs_module"]
1389
+ pages_build_output_dir = "./dist"
1390
+ `;
1391
+ }
1392
+ function generateCloudflareHeaders() {
1393
+ return `# Cloudflare Pages static headers (Worker routeRules still apply to SSR).
1394
+ /*.html
1395
+ Cache-Control: public, max-age=3600, s-maxage=31536000
1396
+
1397
+ /
1398
+ Cache-Control: public, max-age=3600, s-maxage=31536000
1399
+
1400
+ /islands/*
1401
+ Cache-Control: public, max-age=0, must-revalidate
1402
+ `;
1403
+ }
1404
+ function generateDeployReadme(config) {
1405
+ if (config.deploy === "cloudflare") {
1406
+ const name = cloudflareProjectName(config.projectName);
1407
+ return `# Deploying to Cloudflare Pages
1408
+
1409
+ This project is set up for \`NITRO_PRESET=cloudflare_pages\`.
1410
+
1411
+ ## Build
1412
+
1413
+ \`\`\`bash
1414
+ bun run build
1415
+ \`\`\`
1416
+
1417
+ \`build.mjs\` detects \`wrangler.toml\` and sets the Nitro preset when \`NITRO_PRESET\` is unset.
1418
+ Avalon's post-build patches the Cloudflare worker (DOM stub, CSS manifest, \`_routes.json\`).
1419
+
1420
+ ## Preview locally
1421
+
1422
+ \`\`\`bash
1423
+ bun run preview
1424
+ \`\`\`
1425
+
1426
+ ## Deploy
1427
+
1428
+ 1. Create an API token with **Account → Cloudflare Pages → Edit** and **Account Settings → Read**.
1429
+ 2. Set \`CLOUDFLARE_API_TOKEN\` and \`CLOUDFLARE_ACCOUNT_ID\` in your environment (or CI secrets).
1430
+ 3. Create the Pages project once (Wrangler 4 does not auto-create):
1431
+
1432
+ \`\`\`bash
1433
+ bunx wrangler@4 pages project create ${name} \\
1434
+ --production-branch=main \\
1435
+ --compatibility-flags=nodejs_compat \\
1436
+ --compatibility-date=2026-09-04
1437
+ \`\`\`
1438
+
1439
+ 4. Deploy:
1440
+
1441
+ \`\`\`bash
1442
+ bun run deploy
1443
+ \`\`\`
1444
+
1445
+ Prefer **one** deployer (e.g. GitHub Actions with Wrangler). If you also connect the
1446
+ GitHub repo in the Cloudflare dashboard, disable Cloudflare's own build to avoid
1447
+ double deploys.
1448
+
1449
+ Do not rely on Cloudflare's Git integration to run \`vite build\` without Avalon's
1450
+ \`post-build.mjs\` — prerendered CSS and worker patches will be missing.
1451
+ `;
1452
+ }
1453
+ if (config.deploy === "netlify") {
1454
+ return `# Deploying to Netlify
1455
+
1456
+ This project is set up for \`NITRO_PRESET=netlify\` via \`netlify.toml\`.
1457
+
1458
+ ## Build
1459
+
1460
+ \`\`\`bash
1461
+ bun run build
1462
+ \`\`\`
1463
+
1464
+ Or connect the repo in Netlify — \`netlify.toml\` runs \`bun install && bun build.mjs\`
1465
+ and publishes \`dist/\`. Avalon's post-build copies the server function and prerenders routes.
1466
+
1467
+ ## Preview locally
1468
+
1469
+ \`\`\`bash
1470
+ bun run preview
1471
+ \`\`\`
1472
+
1473
+ ## Notes
1474
+
1475
+ - Functions live under \`.netlify/functions-internal\` (\`node_bundler = "none"\`).
1476
+ - The \`/*\` → \`/.netlify/functions/server\` redirect is a soft SSR fallback: static
1477
+ prerendered HTML in \`dist/\` is served first when present.
1478
+ `;
1479
+ }
1480
+ return `# Deploy
1481
+
1482
+ No platform config was generated. Build with:
1483
+
1484
+ \`\`\`bash
1485
+ bun run build
1486
+ \`\`\`
1487
+
1488
+ Default Nitro preset is \`node_server\` (\`.output/server\`). Set \`NITRO_PRESET\` for
1489
+ other targets (\`netlify\`, \`cloudflare_pages\`, \`vercel\`, …) and add the matching
1490
+ platform config — or re-run \`create-avalon\` with a deploy target.
1491
+ `;
1492
+ }
1483
1493
  function generateBuildMjs() {
1484
1494
  return `/**
1485
1495
  * Build wrapper.
@@ -1487,20 +1497,49 @@ function generateBuildMjs() {
1487
1497
  * Vite/Nitro leaves open handles after the build completes, preventing
1488
1498
  * the Node process from exiting. This wrapper detects when the build
1489
1499
  * output is ready, kills the entire process group, then runs post-build.
1500
+ *
1501
+ * When NITRO_PRESET is unset, defaults from wrangler.toml / netlify.toml
1502
+ * so local \`bun run build\` matches the chosen create-avalon deploy target.
1490
1503
  */
1491
1504
 
1492
1505
  import { spawn, execSync } from 'node:child_process';
1493
- import { existsSync, rmSync } from 'node:fs';
1506
+ import { existsSync, rmSync, statSync } from 'node:fs';
1494
1507
  import { join } from 'node:path';
1495
1508
 
1496
1509
  const CWD = process.cwd();
1497
1510
  const NITRO_JSON = join(CWD, '.netlify', 'functions-internal', 'nitro.json');
1498
1511
  const SERVER_MJS = join(CWD, '.netlify', 'functions-internal', 'server', 'server.mjs');
1499
1512
  const OUTPUT_SSR = join(CWD, '.output', 'server', '_ssr', 'ssr.mjs');
1513
+ const CF_WORKER = join(CWD, 'dist', '_worker.js');
1514
+
1515
+ if (!process.env.NITRO_PRESET) {
1516
+ if (existsSync(join(CWD, 'wrangler.toml'))) {
1517
+ process.env.NITRO_PRESET = 'cloudflare_pages';
1518
+ } else if (existsSync(join(CWD, 'netlify.toml'))) {
1519
+ process.env.NITRO_PRESET = 'netlify';
1520
+ }
1521
+ }
1522
+
1523
+ /** True when Nitro finished a Cloudflare worker file, not an empty client-build directory. */
1524
+ function isCloudflareWorkerReady() {
1525
+ try {
1526
+ const st = statSync(CF_WORKER);
1527
+ if (st.isFile()) return true;
1528
+ if (st.isDirectory()) {
1529
+ return existsSync(join(CF_WORKER, 'index.js')) || existsSync(join(CF_WORKER, 'index.mjs'));
1530
+ }
1531
+ } catch {
1532
+ return false;
1533
+ }
1534
+ return false;
1535
+ }
1500
1536
 
1501
1537
  console.log('[build] Starting vite build...');
1538
+ if (process.env.NITRO_PRESET) {
1539
+ console.log(\`[build] NITRO_PRESET=\${process.env.NITRO_PRESET}\`);
1540
+ }
1502
1541
 
1503
- for (const dir of ['.netlify', '.output', 'netlify']) {
1542
+ for (const dir of ['.netlify', '.output', 'netlify', 'dist']) {
1504
1543
  const full = join(CWD, dir);
1505
1544
  if (existsSync(full)) {
1506
1545
  rmSync(full, { recursive: true, force: true });
@@ -1512,10 +1551,13 @@ const child = spawn('bunx', ['--bun', 'vite', 'build'], {
1512
1551
  cwd: CWD,
1513
1552
  stdio: 'inherit',
1514
1553
  detached: true,
1554
+ env: process.env,
1515
1555
  });
1516
1556
 
1517
1557
  const childPid = child.pid;
1518
1558
  let done = false;
1559
+ let killedEarly = false;
1560
+ let viteExitCode = 0;
1519
1561
 
1520
1562
  function killTree() {
1521
1563
  try { process.kill(-childPid, 'SIGKILL'); } catch {}
@@ -1527,29 +1569,40 @@ function finish() {
1527
1569
  done = true;
1528
1570
  clearInterval(poll);
1529
1571
  clearTimeout(absoluteTimeout);
1530
- killTree();
1572
+
1573
+ if (killedEarly) killTree();
1531
1574
 
1532
1575
  setTimeout(() => {
1533
1576
  console.log('[build] Running post-build...');
1534
1577
  try {
1535
- execSync('node post-build.mjs', { cwd: CWD, stdio: 'inherit', timeout: 120_000 });
1578
+ execSync('node post-build.mjs', { cwd: CWD, stdio: 'inherit', timeout: 120_000, env: process.env });
1536
1579
  } catch (err) {
1537
- console.error('[build] post-build warning:', err.message);
1580
+ console.error('[build] post-build failed:', err.message);
1581
+ process.exit(1);
1538
1582
  }
1539
1583
 
1540
1584
  const V1_SERVER = join(CWD, '.netlify', 'v1', 'functions', 'server', 'server.mjs');
1541
- if (existsSync(V1_SERVER)) console.log('[build] ✅ Server function found (v1 API)');
1585
+ const cloudflareReady = isCloudflareWorkerReady();
1586
+ const ok =
1587
+ cloudflareReady ||
1588
+ existsSync(V1_SERVER) ||
1589
+ existsSync(SERVER_MJS) ||
1590
+ existsSync(OUTPUT_SSR);
1591
+ if (cloudflareReady) console.log('[build] ✅ Cloudflare worker found (dist/_worker.js)');
1592
+ else if (existsSync(V1_SERVER)) console.log('[build] ✅ Server function found (v1 API)');
1542
1593
  else if (existsSync(SERVER_MJS)) console.log('[build] ✅ Server function found (legacy)');
1543
1594
  else if (existsSync(OUTPUT_SSR)) console.log('[build] ✅ SSR bundle found');
1544
1595
  else console.error('[build] ❌ No server output found');
1545
1596
 
1597
+ if (!ok) process.exit(1);
1546
1598
  console.log('[build] ✅ Complete');
1547
- process.exit(0);
1599
+ process.exit(killedEarly || viteExitCode === 0 ? 0 : viteExitCode);
1548
1600
  }, 500);
1549
1601
  }
1550
1602
 
1551
1603
  child.on('exit', (code) => {
1552
1604
  console.log(\`[build] vite build exited with code \${code}\`);
1605
+ viteExitCode = code ?? (killedEarly ? 0 : 1);
1553
1606
  finish();
1554
1607
  });
1555
1608
 
@@ -1559,10 +1612,14 @@ child.on('error', (err) => {
1559
1612
  });
1560
1613
 
1561
1614
  const poll = setInterval(() => {
1615
+ // Cloudflare \`_worker.js\` appears during the client build; killing then
1616
+ // aborts SSR. Only force-stop the Netlify/Node hang.
1562
1617
  const netlifyReady = existsSync(NITRO_JSON) && existsSync(SERVER_MJS);
1563
1618
  const nodeServerReady = existsSync(OUTPUT_SSR);
1564
1619
  if (netlifyReady || nodeServerReady) {
1565
- console.log(\`[build] Output detected (\${netlifyReady ? 'netlify' : 'node-server'}), waiting 3s for final writes...\`);
1620
+ killedEarly = true;
1621
+ const kind = netlifyReady ? 'netlify' : 'node-server';
1622
+ console.log(\`[build] Output detected (\${kind}), waiting 3s for final writes...\`);
1566
1623
  clearInterval(poll);
1567
1624
  setTimeout(finish, 3_000);
1568
1625
  }
@@ -1570,6 +1627,7 @@ const poll = setInterval(() => {
1570
1627
 
1571
1628
  const absoluteTimeout = setTimeout(() => {
1572
1629
  console.error('[build] Timeout — killing build');
1630
+ killedEarly = true;
1573
1631
  finish();
1574
1632
  }, 240_000);
1575
1633
  `;
@@ -1630,6 +1688,22 @@ User-agent: AI2Bot
1630
1688
  Allow: /
1631
1689
  `;
1632
1690
  }
1691
+ function generateGitignore(_config) {
1692
+ return [
1693
+ "node_modules/",
1694
+ "dist/",
1695
+ ".output/",
1696
+ ".netlify/",
1697
+ ".wrangler/",
1698
+ "*.log",
1699
+ ".DS_Store",
1700
+ ".env",
1701
+ ".env.*",
1702
+ "!.env.example",
1703
+ ""
1704
+ ].join(`
1705
+ `);
1706
+ }
1633
1707
 
1634
1708
  // src/templates/favicon.ts
1635
1709
  var FAVICON_BASE64 = "AAABAAEAICAAAAEAIACoEAAAFgAAACgAAAAgAAAAQAAAAAEAIAAAAAAAABAAACMuAAAjLgAAAAAAAAAAAAAAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8BAAD/AQAA/wEAAP8BAAD/AQAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wIBAP9DIQr/f0AT/4BAE/+AQBP/f0AT/34/E/9+PxL/fj8T/39AE/+BQBP/cjkR/yAQBf8GAwH/Wy4N/4hEFP+HRBT/h0QU/4dEFP+GQxT/hUMU/4VDE/+EQhP/hEIT/2QzD/8PCAL/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/KxYG/8BgHP/YbSD/12wg/9dsIP/XbCD/12wg/9dsIP/XbCD/12wg/9dsIP/YbSD/iEUU/1AoDP/OaB7/2Gwg/9dsIP/XbCD/12wg/9dsIP/XbCD/12wg/9dsIP/XbCD/1Gsf/18wDv8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8+Hwn/z2ge/9NqH//Tah//02of/9NqH//Tah//02of/9NqH//Tah//02of/9RqH//FYx3/jUcV/9BpH//Tah//02of/9NqH//Tah//02of/9NqH//Tah//02of/9NqH//Vax//r1ga/xgMBP8AAAD/AAAA/wAAAP8AAAD/AAAA/xEIAv+lUxj/1msf/9NqH//Tah//02of/9NqH//Tah//02of/9NqH//Tah//02of/9VrH/+iURj/tlsb/9VrH//Tah//02of/9NqH//Tah//02of/9NqH//Tah//02of/9NqH//Tah//XzAO/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/1UrDP/RaR//02of/9NqH//Tah//02of/9NqH//Tah//02of/9NqH//Tah//1Gof/8RiHf+ZTRb/0Wkf/9NqH//Tah//02of/9NqH//Tah//02of/9NqH//Tah//02of/9VrH/+tVxn/FgsD/wAAAP8AAAD/AAAA/wAAAP8AAAD/EwoD/6lVGf/Wax//02of/9NqH//Tah//02of/9NqH//Tah//02of/9NqH//Tah//1Gsf/6FRGP+6XRv/1Wsf/9NqH//Tah//02of/9NqH//Tah//02of/9NqH//Tah//02of/9BoH/9BIQr/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/Wi0N/9JqH//Tah//02of/9NqH//Tah//02of/9NqH//Tah//02of/9NqH//Uax//wmId/51PF//Sah//02of/9NqH//Tah//02of/9NqH//Tah//02of/9NqH//Vax//ul4b/yQSBf8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8WCwP/rVcZ/9VrH//Tah//02of/9NqH//Tah//02of/9NqH//Tah//02of/9NqH//Uah//olEY/71fHP/Uax//02of/9NqH//Tah//02of/9NqH//Tah//02of/9FpH/9eLw7/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP9fLw7/02of/9NqH//Tah//02of/9NqH//Tah//02of/9NqH//Tah//02of/9RrH//BYRz/oFEX/9NqH//Tah//02of/9NqH//Tah//02of/9NqH//Wax//l0wW/w0HAv8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/xgMBP+wWRr/1Wsf/9NqH//Tah//02of/9NqH//Tah//02of/9NqH//Tah//02of/9NqH/+iUhj/wWEc/9RrH//Tah//02of/9NqH//Tah//1Gsf/8BhHP8xGQf/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/2QyD//Uah//02of/9NqH//Tah//02of/9NqH//Tah//02of/9NqH//Tah//1Gsf/8BgHP+kUxj/02of/9NqH//Tah//02of/9NqH//Tah//ZzQP/wEAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/HA4E/7RaGv/Vax//02of/9NqH//Tah//02of/9NqH//Tah//02of/9NqH//Tah//02of/6NSGP/EYh3/1Gof/9NqH//Tah//1msf/59QF/8RCQP/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/aTUP/9RrH//Tah//02of/9NqH//Tah//02of/9NqH//Tah//02of/9NqH//Uax//v2Ac/6hVGf/Uah//02of/9RrH//FYx3/ORwI/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8fDwT/t1wb/9VrH//Tah//02of/9NqH//Tah//02of/9NqH//Tah//02of/9NqH//Sah//pVMY/8dkHf/Uax//1Gsf/3E5EP8CAQD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP9uNxD/1Wsf/9NqH//Tah//02of/9NqH//Tah//02of/9NqH//Tah//02of/9RrH/+8Xhz/m04X/81nHv+dTxf/FgsD/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/yIRBf+6Xhv/1Wsf/9NqH//Tah//02of/9NqH//Tah//02of/9NqH//Tah//1Gsf/79gHP8yGQf/MxoI/xcMA/8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/3Q6Ef/Vax//02of/9NqH//Tah//02of/9NqH//Tah//02of/9NqH//Uax//dDoR/wMBAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/JhMG/71fHP/Uax//02of/9NqH//Tah//02of/9NqH//Tah//1msf/6dUGP8XDAP/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8BAQD/eT0S/9ZrH//Tah//02of/9NqH//Tah//02of/9RqH//IZB3/QCAJ/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8qFQb/wGEc/9RrH//Tah//02of/9NqH//Tah//1Gsf/3c8Ev8DAgH/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wIBAP9+PxP/1msf/9NqH//Tah//02of/9ZrH/+nVBn/GAwE/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/y4XB//DYh3/1Gsf/9NqH//Uax//xWMd/zweCf8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AwIA/4NCE//Wax//02of/9NqH/9rNhD/AgEA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/MhkH/8VjHf/XbCD/mk4X/xAIAv8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8FAgH/iUUU/8BhHP8vGAf/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8yGQf/TygM/wEAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wEBAP8CAQD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
@@ -1651,8 +1725,8 @@ function generateRootLayout(config) {
1651
1725
  `)}
1652
1726
 
1653
1727
  export default async function RootLayout({ children, frontmatter }: Readonly<LayoutProps>) {
1654
- const title = frontmatter?.title ?? '${safeName}';
1655
- const description = frontmatter?.description ?? '';
1728
+ const title = typeof frontmatter?.title === 'string' ? frontmatter.title : '${safeName}';
1729
+ const description = typeof frontmatter?.description === 'string' ? frontmatter.description : '';
1656
1730
 
1657
1731
  return (
1658
1732
  <html lang="en">
@@ -1660,8 +1734,9 @@ export default async function RootLayout({ children, frontmatter }: Readonly<Lay
1660
1734
  <meta charset="UTF-8" />
1661
1735
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
1662
1736
  <title>{title}</title>
1663
- {description && <meta name="description" content={description} />}
1737
+ {description ? <meta name="description" content={description} /> : null}
1664
1738
  <link rel="icon" href="/favicon.ico" />
1739
+ <link rel="stylesheet" href="/syntax-highlighting.css" />
1665
1740
  </head>
1666
1741
  <body style={{ margin: 0 }}>
1667
1742
  {children}
@@ -1679,6 +1754,14 @@ export default async function MainLayout({ children }: Readonly<LayoutProps>) {
1679
1754
  }
1680
1755
  `;
1681
1756
  }
1757
+ function generateAboutLayout(_config) {
1758
+ return `import type { LayoutProps } from '@useavalon/avalon';
1759
+
1760
+ export default async function AboutLayout({ children }: Readonly<LayoutProps>) {
1761
+ return <>{children}</>;
1762
+ }
1763
+ `;
1764
+ }
1682
1765
 
1683
1766
  // src/templates/middleware.ts
1684
1767
  function generateSampleMiddleware(_config) {
@@ -1709,7 +1792,9 @@ function generatePackageJson(config) {
1709
1792
  const dependencies = {
1710
1793
  "@useavalon/avalon": "latest"
1711
1794
  };
1712
- for (const integration of config.integrations) {
1795
+ const selectedIntegrations = new Set(config.integrations);
1796
+ selectedIntegrations.add(config.core);
1797
+ for (const integration of selectedIntegrations) {
1713
1798
  dependencies[INTEGRATION_PACKAGES[integration]] = "latest";
1714
1799
  Object.assign(dependencies, INTEGRATION_RUNTIME_DEPS[integration]);
1715
1800
  }
@@ -1719,9 +1804,7 @@ function generatePackageJson(config) {
1719
1804
  if (config.plugins.includes("agent-optimization")) {
1720
1805
  dependencies["@useavalon/agent-optimization"] = "latest";
1721
1806
  }
1722
- if (config.plugins.includes("syntax-highlighting")) {
1723
- dependencies["rehype-highlight"] = "latest";
1724
- }
1807
+ dependencies["rehype-highlight"] = "latest";
1725
1808
  switch (config.middleware) {
1726
1809
  case "hono":
1727
1810
  dependencies.hono = "latest";
@@ -1731,10 +1814,10 @@ function generatePackageJson(config) {
1731
1814
  break;
1732
1815
  }
1733
1816
  const devDependencies = {
1734
- vite: "latest",
1735
- typescript: "latest",
1736
- nitro: "latest",
1737
- "vite-imagetools": "latest"
1817
+ vite: "^8.0.0",
1818
+ typescript: "^5.0.0",
1819
+ nitro: "^3.0.260311-beta",
1820
+ "vite-imagetools": "^7.0.0"
1738
1821
  };
1739
1822
  switch (config.styling) {
1740
1823
  case "tailwind":
@@ -1749,15 +1832,21 @@ function generatePackageJson(config) {
1749
1832
  dependencies.clsx = "^2.1.1";
1750
1833
  break;
1751
1834
  }
1835
+ const scripts = {
1836
+ dev: "bunx --bun vite dev",
1837
+ build: "node build.mjs",
1838
+ preview: "node .output/server/index.mjs"
1839
+ };
1840
+ if (config.deploy === "cloudflare") {
1841
+ const name = cloudflareProjectName(config.projectName);
1842
+ scripts.preview = "bunx wrangler@4 pages dev dist";
1843
+ scripts.deploy = `bunx wrangler@4 pages deploy --project-name=${name}`;
1844
+ }
1752
1845
  const pkg = {
1753
1846
  name: config.projectName,
1754
1847
  type: "module",
1755
1848
  private: true,
1756
- scripts: {
1757
- dev: "bunx --bun vite dev",
1758
- build: "node build.mjs",
1759
- preview: "node .output/server/index.mjs"
1760
- },
1849
+ scripts,
1761
1850
  dependencies,
1762
1851
  devDependencies
1763
1852
  };
@@ -1805,6 +1894,10 @@ export default async function HomePage() {
1805
1894
  <code style={{ display: 'block', padding: '0.6rem 1.2rem', borderRadius: '6px', background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(255,255,255,0.08)', color: '#a5b4fc', fontSize: '0.85rem', fontFamily: 'ui-monospace, monospace' }}>
1806
1895
  Edit app/modules/main/pages/index.tsx
1807
1896
  </code>
1897
+ <p style={{ fontSize: '0.85rem', color: '#94a3b8', margin: '0.5rem 0 0' }}>
1898
+ <a href="/about" style={{ color: '#818cf8', textDecoration: 'none' }}>About</a>
1899
+ {' '}is the about module — app/modules/about/pages/index.tsx → /about
1900
+ </p>
1808
1901
  </div>
1809
1902
 
1810
1903
  <footer style={{ position: 'relative', zIndex: 1, marginTop: '4rem', paddingTop: '2rem', borderTop: '1px solid rgba(255,255,255,0.06)', width: '100%', maxWidth: '640px' }}>
@@ -1818,6 +1911,28 @@ export default async function HomePage() {
1818
1911
  }
1819
1912
  `;
1820
1913
  }
1914
+ function generateAboutPage() {
1915
+ return `export const metadata = {
1916
+ title: 'About',
1917
+ description: 'The about module maps to the /about route.',
1918
+ };
1919
+
1920
+ export default function AboutPage() {
1921
+ return (
1922
+ <div style={{ minHeight: '100vh', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', background: 'linear-gradient(145deg, #0a0a12 0%, #0d1117 40%, #111827 100%)', color: '#e2e8f0', fontFamily: 'system-ui, -apple-system, sans-serif', padding: '2rem', textAlign: 'center' }}>
1923
+ <p style={{ fontSize: '0.8rem', letterSpacing: '0.15em', textTransform: 'uppercase', color: '#818cf8', marginBottom: '1.5rem', fontWeight: 500 }}>Module route</p>
1924
+ <h1 style={{ fontSize: 'clamp(2rem, 5vw, 3rem)', fontWeight: 700, lineHeight: 1.1, margin: '0 0 1.5rem' }}>About</h1>
1925
+ <p style={{ fontSize: '1.05rem', lineHeight: 1.7, color: '#94a3b8', marginBottom: '2rem', maxWidth: '480px' }}>
1926
+ This page is <code style={{ color: '#a5b4fc' }}>app/modules/about/pages/index.tsx</code>.
1927
+ A module name is a URL prefix, so the about module is <code style={{ color: '#a5b4fc' }}>/about</code>.
1928
+ The main module is the site root.
1929
+ </p>
1930
+ <a href="/" style={{ color: '#818cf8', textDecoration: 'none', fontWeight: 500 }}>Home</a>
1931
+ </div>
1932
+ );
1933
+ }
1934
+ `;
1935
+ }
1821
1936
  function generate404Page() {
1822
1937
  return `export const metadata = {
1823
1938
  title: '404 - Page Not Found',
@@ -1844,7 +1959,8 @@ function generatePostBuildMjs() {
1844
1959
  ` * Post-build script — delegates to Avalon's built-in post-build.`,
1845
1960
  ` *`,
1846
1961
  ` * All the heavy lifting (CSS patching, island redirects, prerendering,`,
1847
- ` * Netlify function copying) is handled by the framework.`,
1962
+ ` * Cloudflare worker patches, Netlify function copying) is handled by`,
1963
+ ` * the framework.`,
1848
1964
  ` */`,
1849
1965
  `import { runPostBuild } from '@useavalon/avalon/post-build';`,
1850
1966
  ``,
@@ -1864,6 +1980,7 @@ function generatePostBuildMjs() {
1864
1980
  function generateStylingFiles(config) {
1865
1981
  const files = new Map;
1866
1982
  files.set("app/shared/styles/main.css", generateMainCss(config));
1983
+ files.set("public/syntax-highlighting.css", generateSyntaxHighlightingCss());
1867
1984
  if (config.styling === "css-modules") {
1868
1985
  files.set("app/shared/styles/reset.css", generateResetCss());
1869
1986
  }
@@ -2057,6 +2174,30 @@ function generateShadcnComponentsJson(config) {
2057
2174
  return JSON.stringify(componentsConfig, null, 2) + `
2058
2175
  `;
2059
2176
  }
2177
+ function generateSyntaxHighlightingCss() {
2178
+ return `code.hljs { padding: 3px 5px; }
2179
+ pre code.hljs { display: block; overflow-x: auto; padding: 1em; }
2180
+ .hljs { color: #c9d1d9; background: transparent; }
2181
+ .hljs-doctag, .hljs-keyword, .hljs-meta .hljs-keyword, .hljs-template-tag,
2182
+ .hljs-template-variable, .hljs-type, .hljs-variable.language_ { color: #ff7b72; }
2183
+ .hljs-title, .hljs-title.class_, .hljs-title.class_.inherited__,
2184
+ .hljs-title.function_ { color: #d2a8ff; }
2185
+ .hljs-attr, .hljs-attribute, .hljs-literal, .hljs-meta, .hljs-number,
2186
+ .hljs-operator, .hljs-variable, .hljs-selector-attr, .hljs-selector-class,
2187
+ .hljs-selector-id { color: #79c0ff; }
2188
+ .hljs-regexp, .hljs-string, .hljs-meta .hljs-string { color: #a5d6ff; }
2189
+ .hljs-built_in, .hljs-symbol { color: #ffa657; }
2190
+ .hljs-comment, .hljs-code, .hljs-formula { color: #8b949e; }
2191
+ .hljs-name, .hljs-quote, .hljs-selector-tag, .hljs-selector-pseudo { color: #7ee787; }
2192
+ .hljs-subst { color: #c9d1d9; }
2193
+ .hljs-section { color: #1f6feb; font-weight: bold; }
2194
+ .hljs-bullet { color: #f2cc60; }
2195
+ .hljs-emphasis { color: #c9d1d9; font-style: italic; }
2196
+ .hljs-strong { color: #c9d1d9; font-weight: bold; }
2197
+ .hljs-addition { color: #aff5b4; background-color: #033a16; }
2198
+ .hljs-deletion { color: #ffdcd7; background-color: #67060c; }
2199
+ `;
2200
+ }
2060
2201
 
2061
2202
  // src/templates/tsconfig.ts
2062
2203
  function generateTsConfig(core = "preact") {
@@ -2180,6 +2321,7 @@ function generateViteConfig(config) {
2180
2321
  ` { schedule: '${EXAMPLE_CRON_SCHEDULE}', handler: '${EXAMPLE_CRON_HANDLER}' },`,
2181
2322
  ` ],`
2182
2323
  ] : [];
2324
+ const compatLines = config.deploy === "cloudflare" ? [` compatibilityDate: '2026-09-04',`] : [];
2183
2325
  const lines = [
2184
2326
  imports.join(`
2185
2327
  `),
@@ -2194,10 +2336,11 @@ function generateViteConfig(config) {
2194
2336
  ` nitro: {`,
2195
2337
  ` preset: process.env.NITRO_PRESET || 'node_server',`,
2196
2338
  ` streaming: true,`,
2339
+ ...compatLines,
2197
2340
  ` clientEntry: 'app/entry-client',`,
2198
2341
  ` globalCSS: ['app/shared/styles/main.css'],`,
2199
2342
  ` prerender: {`,
2200
- ` routes: ['/'],`,
2343
+ ` routes: ['/', '/about'],`,
2201
2344
  ` crawlLinks: true,`,
2202
2345
  ` ignore: [],`,
2203
2346
  ` },`,
@@ -2298,6 +2441,8 @@ async function scaffoldProject(config, targetDir) {
2298
2441
  await writeFile(join(targetDir, "app/modules/main/layouts/_layout.tsx"), generateMainLayout(config));
2299
2442
  await writeFile(join(targetDir, "app/modules/main/pages/index.tsx"), generateMainPage(config));
2300
2443
  await writeFile(join(targetDir, "app/modules/main/pages/404.tsx"), generate404Page());
2444
+ await writeFile(join(targetDir, "app/modules/about/layouts/_layout.tsx"), generateAboutLayout(config));
2445
+ await writeFile(join(targetDir, "app/modules/about/pages/index.tsx"), generateAboutPage());
2301
2446
  await writeFile(join(targetDir, "middleware/01.logger.ts"), generateSampleMiddleware(config));
2302
2447
  await writeFile(join(targetDir, "routes/api/hello.ts"), generateHelloRoute(config));
2303
2448
  if (config.cron) {
@@ -2324,13 +2469,10 @@ async function scaffoldProject(config, targetDir) {
2324
2469
  `);
2325
2470
  await writeFile(join(targetDir, "server/renderer.ts"), [
2326
2471
  `/**`,
2327
- ` * SSR Renderer provided by Avalon's virtual module system.`,
2328
- ` *`,
2329
- ` * Avalon auto-discovers layouts, injects client assets, and handles`,
2330
- ` * layout wrapping. Import from the virtual modules directly to customize:`,
2472
+ ` * Nitro SSR catch-all. Keep this re-export for the default renderer.`,
2331
2473
  ` *`,
2332
- ` * import { wrapWithLayouts } from 'virtual:avalon/layouts';`,
2333
- ` * import { injectAssets } from 'virtual:avalon/assets';`,
2474
+ ` * Customize only when you need a custom hydration directive or to wrap`,
2475
+ ` * the handler. See https://useavalon.dev/docs/ssr-renderer`,
2334
2476
  ` */`,
2335
2477
  `export { default } from 'virtual:avalon/renderer';`,
2336
2478
  ``
@@ -2338,9 +2480,15 @@ async function scaffoldProject(config, targetDir) {
2338
2480
  `));
2339
2481
  await writeFile(join(targetDir, "build.mjs"), generateBuildMjs());
2340
2482
  await writeFile(join(targetDir, "post-build.mjs"), generatePostBuildMjs());
2483
+ await writeFile(join(targetDir, ".gitignore"), generateGitignore(config));
2484
+ await writeFile(join(targetDir, "DEPLOY.md"), generateDeployReadme(config));
2341
2485
  if (config.deploy === "netlify") {
2342
2486
  await writeFile(join(targetDir, "netlify.toml"), generateNetlifyToml(config));
2343
2487
  }
2488
+ if (config.deploy === "cloudflare") {
2489
+ await writeFile(join(targetDir, "wrangler.toml"), generateWranglerToml(config));
2490
+ await writeFile(join(targetDir, "public/_headers"), generateCloudflareHeaders());
2491
+ }
2344
2492
  }
2345
2493
 
2346
2494
  // src/summary.ts
@@ -2350,15 +2498,34 @@ var STYLING_LABELS = {
2350
2498
  shadcn: "shadcn"
2351
2499
  };
2352
2500
  var DEPLOY_LABELS = {
2501
+ cloudflare: "Cloudflare Pages",
2353
2502
  netlify: "Netlify",
2354
2503
  none: "None"
2355
2504
  };
2505
+ function deployNextSteps(config) {
2506
+ if (config.deploy === "cloudflare") {
2507
+ return [
2508
+ " bun run build",
2509
+ " bun run preview # wrangler pages dev",
2510
+ " # See DEPLOY.md for Cloudflare Pages deploy steps"
2511
+ ];
2512
+ }
2513
+ if (config.deploy === "netlify") {
2514
+ return [" bun run build", " # Connect the repo in Netlify, or see DEPLOY.md"];
2515
+ }
2516
+ return [" bun run build"];
2517
+ }
2356
2518
  function formatSummary(config, scaffoldedInPlace = false) {
2357
2519
  const integrations = config.integrations.length > 0 ? config.integrations.join(", ") : "none";
2358
2520
  const styling = STYLING_LABELS[config.styling] ?? config.styling;
2359
2521
  const plugins = config.plugins.length > 0 ? config.plugins.join(", ") : "none";
2360
2522
  const deploy = DEPLOY_LABELS[config.deploy] ?? config.deploy;
2361
- const nextSteps = scaffoldedInPlace ? [" bun install", " bun run dev"] : [` cd ${config.projectName}`, " bun install", " bun run dev"];
2523
+ const nextSteps = scaffoldedInPlace ? [" bun install", " bun run dev", ...deployNextSteps(config)] : [
2524
+ ` cd ${config.projectName}`,
2525
+ " bun install",
2526
+ " bun run dev",
2527
+ ...deployNextSteps(config)
2528
+ ];
2362
2529
  return [
2363
2530
  "",
2364
2531
  ` Project: ${config.projectName}`,
@@ -2366,7 +2533,7 @@ function formatSummary(config, scaffoldedInPlace = false) {
2366
2533
  ` Styling: ${styling}`,
2367
2534
  ` Plugins: ${plugins}`,
2368
2535
  ` Middleware: ${config.middleware}`,
2369
- ` Deploy: ${deploy}`,
2536
+ ` Deploy: ${deploy}`,
2370
2537
  ` Cron: ${config.cron ? "yes" : "no"}`,
2371
2538
  "",
2372
2539
  " Next steps:",
@@ -2402,9 +2569,9 @@ async function main() {
2402
2569
  " --core Rendering engine: preact (default) | react",
2403
2570
  " --integrations Comma list: preact,react,vue,svelte,solid,lit,qwik",
2404
2571
  " --styling css-modules (default) | tailwind | shadcn",
2405
- " --plugins Comma list: seo (default),agent-optimization,syntax-highlighting",
2572
+ " --plugins Comma list: seo (default),agent-optimization",
2406
2573
  " --middleware h3 (default) | hono | elysia",
2407
- " --deploy netlify | none (default)",
2574
+ " --deploy cloudflare | netlify | none (default)",
2408
2575
  " --cron Scaffold an example cron task + config",
2409
2576
  "",
2410
2577
  "Example:",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-avalon",
3
- "version": "0.1.25",
3
+ "version": "0.2.1",
4
4
  "description": "Scaffold a new Avalon project with multi-framework islands architecture",
5
5
  "license": "MIT",
6
6
  "type": "module",