create-avalon 0.1.25 → 0.2.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.
Files changed (3) hide show
  1. package/README.md +23 -7
  2. package/dist/cli.js +311 -197
  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, syntax highlighting)
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,7 +44,7 @@ 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/
47
+ │ │ └── main/
37
48
  │ │ ├── pages/ # File-system routes
38
49
  │ │ ├── components/ # Interactive components
39
50
  │ │ └── layouts/ # Module layouts
@@ -46,7 +57,12 @@ my-project/
46
57
  │ └── api/ # API routes
47
58
  ├── tasks/ # Scheduled jobs (cron) — optional
48
59
  ├── server/ # Server config & env
49
- ├── public/ # Static assets
60
+ ├── public/ # Static assets (+ _headers for Cloudflare)
61
+ ├── build.mjs # Vite hang workaround + post-build
62
+ ├── post-build.mjs # Avalon production patches / prerender
63
+ ├── DEPLOY.md # Platform-specific deploy steps
64
+ ├── wrangler.toml # Cloudflare Pages (optional)
65
+ ├── netlify.toml # Netlify (optional)
50
66
  ├── vite.config.ts
51
67
  └── package.json
52
68
  ```
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";
@@ -109,7 +125,11 @@ var MIDDLEWARE_OPTIONS = [
109
125
  "hono",
110
126
  "elysia"
111
127
  ];
112
- var DEPLOY_TARGETS = ["netlify", "none"];
128
+ var DEPLOY_TARGETS = [
129
+ "cloudflare",
130
+ "netlify",
131
+ "none"
132
+ ];
113
133
  var INTEGRATION_PACKAGES = {
114
134
  preact: "@useavalon/preact",
115
135
  react: "@useavalon/react",
@@ -209,8 +229,8 @@ function resolveConfigNonInteractive(args) {
209
229
  if (styling === "shadcn" && core !== "react") {
210
230
  throw new CliArgError("--styling=shadcn requires --core=react (shadcn is Radix/React based).");
211
231
  }
212
- if (core === "react" && !integrations.includes("react")) {
213
- integrations.push("react");
232
+ if (!integrations.includes(core)) {
233
+ integrations.push(core);
214
234
  }
215
235
  return {
216
236
  projectName: args.projectName ?? ".",
@@ -580,82 +600,6 @@ class B {
580
600
  }
581
601
  }
582
602
  }
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
603
  class kt extends B {
660
604
  get cursor() {
661
605
  return this.value ? 0 : 1;
@@ -673,54 +617,6 @@ class kt extends B {
673
617
  });
674
618
  }
675
619
  }
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
620
  var Lt = class extends B {
725
621
  options;
726
622
  cursor = 0;
@@ -889,59 +785,59 @@ var Ft2 = /\p{M}+/gu;
889
785
  var yt2 = { limit: 1 / 0, ellipsis: "" };
890
786
  var Le = (e, r = {}, s = {}) => {
891
787
  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;
788
+ 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
789
  e:
894
790
  for (;; ) {
895
791
  if (b > A || m >= d && m > $) {
896
- const T2 = e.slice(A, b) || e.slice($, m);
792
+ const T = e.slice(A, b) || e.slice($, m);
897
793
  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;
794
+ for (const M of T.replaceAll(Ft2, "")) {
795
+ const O = M.codePointAt(0) || 0;
796
+ 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) {
797
+ y = true;
902
798
  break e;
903
799
  }
904
- F += M2.length, w += S2;
800
+ F += M.length, w += S;
905
801
  }
906
802
  A = b = 0;
907
803
  }
908
804
  if (m >= d)
909
805
  break;
910
806
  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;
807
+ if (F = ne.lastIndex - m, S = F * g, w + S > C && (v = Math.min(v, m + Math.floor((C - w) / g))), w + S > i) {
808
+ y = true;
913
809
  break;
914
810
  }
915
- w += S2, A = $, b = m, m = $ = ne.lastIndex;
811
+ w += S, A = $, b = m, m = $ = ne.lastIndex;
916
812
  continue;
917
813
  }
918
814
  if (we.lastIndex = m, we.test(e)) {
919
815
  if (w + u > C && (v = Math.min(v, m)), w + u > i) {
920
- y2 = true;
816
+ y = true;
921
817
  break;
922
818
  }
923
819
  w += u, A = $, b = m, m = $ = we.lastIndex;
924
820
  continue;
925
821
  }
926
822
  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;
823
+ if (F = re.lastIndex - m, S = F * l, w + S > C && (v = Math.min(v, m + Math.floor((C - w) / l))), w + S > i) {
824
+ y = true;
929
825
  break;
930
826
  }
931
- w += S2, A = $, b = m, m = $ = re.lastIndex;
827
+ w += S, A = $, b = m, m = $ = re.lastIndex;
932
828
  continue;
933
829
  }
934
830
  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;
831
+ if (F = ie.lastIndex - m, S = F * n, w + S > C && (v = Math.min(v, m + Math.floor((C - w) / n))), w + S > i) {
832
+ y = true;
937
833
  break;
938
834
  }
939
- w += S2, A = $, b = m, m = $ = ie.lastIndex;
835
+ w += S, A = $, b = m, m = $ = ie.lastIndex;
940
836
  continue;
941
837
  }
942
838
  if (Ae.lastIndex = m, Ae.test(e)) {
943
839
  if (w + p > C && (v = Math.min(v, m)), w + p > i) {
944
- y2 = true;
840
+ y = true;
945
841
  break;
946
842
  }
947
843
  w += p, A = $, b = m, m = $ = Ae.lastIndex;
@@ -949,7 +845,7 @@ var Le = (e, r = {}, s = {}) => {
949
845
  }
950
846
  m += 1;
951
847
  }
952
- return { width: y2 ? C : w, index: y2 ? v : d, truncated: y2, ellipsed: y2 && i >= o };
848
+ return { width: y ? C : w, index: y ? v : d, truncated: y, ellipsed: y && i >= o };
953
849
  };
954
850
  var Et2 = { limit: 1 / 0, ellipsis: "", ellipsisWidth: 0 };
955
851
  var D2 = (e, r = {}) => Le(e, Et2, r).width;
@@ -958,9 +854,9 @@ var je = "›";
958
854
  var vt2 = 39;
959
855
  var Ce = "\x07";
960
856
  var ke = "[";
961
- var wt2 = "]";
857
+ var wt = "]";
962
858
  var Ve = "m";
963
- var Se = `${wt2}8;;`;
859
+ var Se = `${wt}8;;`;
964
860
  var He = new RegExp(`(?:\\${ke}(?<code>\\d+)m|\\${Se}(?<uri>.*)${Ce})`, "y");
965
861
  var At2 = (e) => {
966
862
  if (e >= 30 && e <= 37 || e >= 90 && e <= 97)
@@ -994,7 +890,7 @@ var Ie = (e, r, s) => {
994
890
  }
995
891
  u = e.at(-1), !l && u !== undefined && u.length > 0 && e.length > 1 && (e[e.length - 2] += e.pop());
996
892
  };
997
- var St2 = (e) => {
893
+ var St = (e) => {
998
894
  const r = e.split(" ");
999
895
  let s = r.length;
1000
896
  for (;s > 0 && !(D2(r[s - 1]) > 0); )
@@ -1011,8 +907,8 @@ var It2 = (e, r, s = {}) => {
1011
907
  s.trim !== false && (n[n.length - 1] = (n.at(-1) ?? "").trimStart());
1012
908
  let d = D2(n.at(-1) ?? "");
1013
909
  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);
910
+ const F = r - d, y = 1 + Math.floor((l[$] - F - 1) / r);
911
+ Math.floor((l[$] - 1) / r) < y && n.push(""), Ie(n, m, r);
1016
912
  continue;
1017
913
  }
1018
914
  if (d + l[$] > r && d > 0 && l[$] > 0) {
@@ -1028,7 +924,7 @@ var It2 = (e, r, s = {}) => {
1028
924
  }
1029
925
  n[n.length - 1] += m;
1030
926
  }
1031
- s.trim !== false && (n = n.map(($) => St2($)));
927
+ s.trim !== false && (n = n.map(($) => St($)));
1032
928
  const c = n.join(`
1033
929
  `), p = c[Symbol.iterator]();
1034
930
  let f = p.next(), g = p.next(), E = 0;
@@ -1036,12 +932,12 @@ var It2 = (e, r, s = {}) => {
1036
932
  const $ = f.value, m = g.value;
1037
933
  if (i += $, $ === ae || $ === je) {
1038
934
  He.lastIndex = E + 1;
1039
- const y2 = He.exec(c)?.groups;
1040
- if (y2?.code !== undefined) {
1041
- const v = Number.parseFloat(y2.code);
935
+ const y = He.exec(c)?.groups;
936
+ if (y?.code !== undefined) {
937
+ const v = Number.parseFloat(y.code);
1042
938
  a = v === vt2 ? undefined : v;
1043
939
  } else
1044
- y2?.uri !== undefined && (o = y2.uri.length === 0 ? undefined : y2.uri);
940
+ y?.uri !== undefined && (o = y.uri.length === 0 ? undefined : y.uri);
1045
941
  }
1046
942
  const d = a ? At2(a) : undefined;
1047
943
  m === `
@@ -1074,16 +970,16 @@ var X2 = ({ cursor: e, options: r, style: s, output: i = process.stdout, maxItem
1074
970
  const m = Math.min(g + f, r.length), d = [];
1075
971
  let F = 0;
1076
972
  E && F++, $ && F++;
1077
- const y2 = g + (E ? 1 : 0), v = m - ($ ? 1 : 0);
1078
- for (let A = y2;A < v; A++) {
973
+ const y = g + (E ? 1 : 0), v = m - ($ ? 1 : 0);
974
+ for (let A = y;A < v; A++) {
1079
975
  const b = J(s(r[A], A === e), l, { hard: true, trim: false }).split(`
1080
976
  `);
1081
977
  d.push(b), F += b.length;
1082
978
  }
1083
979
  if (F > p) {
1084
980
  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));
981
+ const S = e - y, T = (M, O) => bt2(d, w, M, O, p);
982
+ 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
983
  }
1088
984
  const C = [];
1089
985
  E && C.push(c);
@@ -1379,12 +1275,17 @@ async function collectProjectConfig(initialName) {
1379
1275
  const deployResult = await Jt({
1380
1276
  message: "Where will you deploy?",
1381
1277
  options: [
1278
+ {
1279
+ value: "cloudflare",
1280
+ label: "Cloudflare Pages",
1281
+ hint: "wrangler.toml, public/_headers, build + deploy scripts"
1282
+ },
1382
1283
  {
1383
1284
  value: "netlify",
1384
1285
  label: "Netlify",
1385
- hint: "Generates netlify.toml, build.mjs, post-build.mjs"
1286
+ hint: "netlify.toml, build.mjs, post-build.mjs"
1386
1287
  },
1387
- { value: "none", label: "None / Other", hint: "Node server preset, no deploy config" }
1288
+ { value: "none", label: "None / Other", hint: "Node server preset, no platform config" }
1388
1289
  ]
1389
1290
  });
1390
1291
  if (Ct(deployResult)) {
@@ -1401,8 +1302,8 @@ async function collectProjectConfig(initialName) {
1401
1302
  }
1402
1303
  const core = coreResult;
1403
1304
  const integrations = integrationsResult;
1404
- if (core === "react" && !integrations.includes("react")) {
1405
- integrations.push("react");
1305
+ if (!integrations.includes(core)) {
1306
+ integrations.push(core);
1406
1307
  }
1407
1308
  return {
1408
1309
  projectName,
@@ -1456,6 +1357,10 @@ export default defineCronJob({
1456
1357
  }
1457
1358
 
1458
1359
  // src/templates/deploy.ts
1360
+ function cloudflareProjectName(projectName) {
1361
+ const slug = projectName.toLowerCase().replaceAll(/[^a-z0-9-]+/g, "-").replaceAll(/-+/g, "-").replaceAll(/^-|-$/g, "");
1362
+ return slug || "avalon-app";
1363
+ }
1459
1364
  function generateNetlifyToml(_config) {
1460
1365
  return `[build]
1461
1366
  base = "."
@@ -1480,6 +1385,117 @@ function generateNetlifyToml(_config) {
1480
1385
  status = 200
1481
1386
  `;
1482
1387
  }
1388
+ function generateWranglerToml(config) {
1389
+ const name = cloudflareProjectName(config.projectName);
1390
+ return `# Cloudflare Pages — production output is dist/_worker.js + static assets.
1391
+ # Date must be >= 2025-09-15 so nodejs_compat includes \`node:fs\` (needed by the Nitro worker).
1392
+ name = "${name}"
1393
+ compatibility_date = "2026-09-04"
1394
+ compatibility_flags = ["nodejs_compat", "enable_nodejs_fs_module"]
1395
+ pages_build_output_dir = "./dist"
1396
+ `;
1397
+ }
1398
+ function generateCloudflareHeaders() {
1399
+ return `# Cloudflare Pages static headers (Worker routeRules still apply to SSR).
1400
+ /*.html
1401
+ Cache-Control: public, max-age=3600, s-maxage=31536000
1402
+
1403
+ /
1404
+ Cache-Control: public, max-age=3600, s-maxage=31536000
1405
+
1406
+ /islands/*
1407
+ Cache-Control: public, max-age=0, must-revalidate
1408
+ `;
1409
+ }
1410
+ function generateDeployReadme(config) {
1411
+ if (config.deploy === "cloudflare") {
1412
+ const name = cloudflareProjectName(config.projectName);
1413
+ return `# Deploying to Cloudflare Pages
1414
+
1415
+ This project is set up for \`NITRO_PRESET=cloudflare_pages\`.
1416
+
1417
+ ## Build
1418
+
1419
+ \`\`\`bash
1420
+ bun run build
1421
+ \`\`\`
1422
+
1423
+ \`build.mjs\` detects \`wrangler.toml\` and sets the Nitro preset when \`NITRO_PRESET\` is unset.
1424
+ Avalon's post-build patches the Cloudflare worker (DOM stub, CSS manifest, \`_routes.json\`).
1425
+
1426
+ ## Preview locally
1427
+
1428
+ \`\`\`bash
1429
+ bun run preview
1430
+ \`\`\`
1431
+
1432
+ ## Deploy
1433
+
1434
+ 1. Create an API token with **Account → Cloudflare Pages → Edit** and **Account Settings → Read**.
1435
+ 2. Set \`CLOUDFLARE_API_TOKEN\` and \`CLOUDFLARE_ACCOUNT_ID\` in your environment (or CI secrets).
1436
+ 3. Create the Pages project once (Wrangler 4 does not auto-create):
1437
+
1438
+ \`\`\`bash
1439
+ bunx wrangler@4 pages project create ${name} \\
1440
+ --production-branch=main \\
1441
+ --compatibility-flags=nodejs_compat \\
1442
+ --compatibility-date=2026-09-04
1443
+ \`\`\`
1444
+
1445
+ 4. Deploy:
1446
+
1447
+ \`\`\`bash
1448
+ bun run deploy
1449
+ \`\`\`
1450
+
1451
+ Prefer **one** deployer (e.g. GitHub Actions with Wrangler). If you also connect the
1452
+ GitHub repo in the Cloudflare dashboard, disable Cloudflare's own build to avoid
1453
+ double deploys.
1454
+
1455
+ Do not rely on Cloudflare's Git integration to run \`vite build\` without Avalon's
1456
+ \`post-build.mjs\` — prerendered CSS and worker patches will be missing.
1457
+ `;
1458
+ }
1459
+ if (config.deploy === "netlify") {
1460
+ return `# Deploying to Netlify
1461
+
1462
+ This project is set up for \`NITRO_PRESET=netlify\` via \`netlify.toml\`.
1463
+
1464
+ ## Build
1465
+
1466
+ \`\`\`bash
1467
+ bun run build
1468
+ \`\`\`
1469
+
1470
+ Or connect the repo in Netlify — \`netlify.toml\` runs \`bun install && bun build.mjs\`
1471
+ and publishes \`dist/\`. Avalon's post-build copies the server function and prerenders routes.
1472
+
1473
+ ## Preview locally
1474
+
1475
+ \`\`\`bash
1476
+ bun run preview
1477
+ \`\`\`
1478
+
1479
+ ## Notes
1480
+
1481
+ - Functions live under \`.netlify/functions-internal\` (\`node_bundler = "none"\`).
1482
+ - The \`/*\` → \`/.netlify/functions/server\` redirect is a soft SSR fallback: static
1483
+ prerendered HTML in \`dist/\` is served first when present.
1484
+ `;
1485
+ }
1486
+ return `# Deploy
1487
+
1488
+ No platform config was generated. Build with:
1489
+
1490
+ \`\`\`bash
1491
+ bun run build
1492
+ \`\`\`
1493
+
1494
+ Default Nitro preset is \`node_server\` (\`.output/server\`). Set \`NITRO_PRESET\` for
1495
+ other targets (\`netlify\`, \`cloudflare_pages\`, \`vercel\`, …) and add the matching
1496
+ platform config — or re-run \`create-avalon\` with a deploy target.
1497
+ `;
1498
+ }
1483
1499
  function generateBuildMjs() {
1484
1500
  return `/**
1485
1501
  * Build wrapper.
@@ -1487,20 +1503,49 @@ function generateBuildMjs() {
1487
1503
  * Vite/Nitro leaves open handles after the build completes, preventing
1488
1504
  * the Node process from exiting. This wrapper detects when the build
1489
1505
  * output is ready, kills the entire process group, then runs post-build.
1506
+ *
1507
+ * When NITRO_PRESET is unset, defaults from wrangler.toml / netlify.toml
1508
+ * so local \`bun run build\` matches the chosen create-avalon deploy target.
1490
1509
  */
1491
1510
 
1492
1511
  import { spawn, execSync } from 'node:child_process';
1493
- import { existsSync, rmSync } from 'node:fs';
1512
+ import { existsSync, rmSync, statSync } from 'node:fs';
1494
1513
  import { join } from 'node:path';
1495
1514
 
1496
1515
  const CWD = process.cwd();
1497
1516
  const NITRO_JSON = join(CWD, '.netlify', 'functions-internal', 'nitro.json');
1498
1517
  const SERVER_MJS = join(CWD, '.netlify', 'functions-internal', 'server', 'server.mjs');
1499
1518
  const OUTPUT_SSR = join(CWD, '.output', 'server', '_ssr', 'ssr.mjs');
1519
+ const CF_WORKER = join(CWD, 'dist', '_worker.js');
1520
+
1521
+ if (!process.env.NITRO_PRESET) {
1522
+ if (existsSync(join(CWD, 'wrangler.toml'))) {
1523
+ process.env.NITRO_PRESET = 'cloudflare_pages';
1524
+ } else if (existsSync(join(CWD, 'netlify.toml'))) {
1525
+ process.env.NITRO_PRESET = 'netlify';
1526
+ }
1527
+ }
1528
+
1529
+ /** True when Nitro finished a Cloudflare worker file, not an empty client-build directory. */
1530
+ function isCloudflareWorkerReady() {
1531
+ try {
1532
+ const st = statSync(CF_WORKER);
1533
+ if (st.isFile()) return true;
1534
+ if (st.isDirectory()) {
1535
+ return existsSync(join(CF_WORKER, 'index.js')) || existsSync(join(CF_WORKER, 'index.mjs'));
1536
+ }
1537
+ } catch {
1538
+ return false;
1539
+ }
1540
+ return false;
1541
+ }
1500
1542
 
1501
1543
  console.log('[build] Starting vite build...');
1544
+ if (process.env.NITRO_PRESET) {
1545
+ console.log(\`[build] NITRO_PRESET=\${process.env.NITRO_PRESET}\`);
1546
+ }
1502
1547
 
1503
- for (const dir of ['.netlify', '.output', 'netlify']) {
1548
+ for (const dir of ['.netlify', '.output', 'netlify', 'dist']) {
1504
1549
  const full = join(CWD, dir);
1505
1550
  if (existsSync(full)) {
1506
1551
  rmSync(full, { recursive: true, force: true });
@@ -1512,10 +1557,13 @@ const child = spawn('bunx', ['--bun', 'vite', 'build'], {
1512
1557
  cwd: CWD,
1513
1558
  stdio: 'inherit',
1514
1559
  detached: true,
1560
+ env: process.env,
1515
1561
  });
1516
1562
 
1517
1563
  const childPid = child.pid;
1518
1564
  let done = false;
1565
+ let killedEarly = false;
1566
+ let viteExitCode = 0;
1519
1567
 
1520
1568
  function killTree() {
1521
1569
  try { process.kill(-childPid, 'SIGKILL'); } catch {}
@@ -1527,29 +1575,40 @@ function finish() {
1527
1575
  done = true;
1528
1576
  clearInterval(poll);
1529
1577
  clearTimeout(absoluteTimeout);
1530
- killTree();
1578
+
1579
+ if (killedEarly) killTree();
1531
1580
 
1532
1581
  setTimeout(() => {
1533
1582
  console.log('[build] Running post-build...');
1534
1583
  try {
1535
- execSync('node post-build.mjs', { cwd: CWD, stdio: 'inherit', timeout: 120_000 });
1584
+ execSync('node post-build.mjs', { cwd: CWD, stdio: 'inherit', timeout: 120_000, env: process.env });
1536
1585
  } catch (err) {
1537
- console.error('[build] post-build warning:', err.message);
1586
+ console.error('[build] post-build failed:', err.message);
1587
+ process.exit(1);
1538
1588
  }
1539
1589
 
1540
1590
  const V1_SERVER = join(CWD, '.netlify', 'v1', 'functions', 'server', 'server.mjs');
1541
- if (existsSync(V1_SERVER)) console.log('[build] ✅ Server function found (v1 API)');
1591
+ const cloudflareReady = isCloudflareWorkerReady();
1592
+ const ok =
1593
+ cloudflareReady ||
1594
+ existsSync(V1_SERVER) ||
1595
+ existsSync(SERVER_MJS) ||
1596
+ existsSync(OUTPUT_SSR);
1597
+ if (cloudflareReady) console.log('[build] ✅ Cloudflare worker found (dist/_worker.js)');
1598
+ else if (existsSync(V1_SERVER)) console.log('[build] ✅ Server function found (v1 API)');
1542
1599
  else if (existsSync(SERVER_MJS)) console.log('[build] ✅ Server function found (legacy)');
1543
1600
  else if (existsSync(OUTPUT_SSR)) console.log('[build] ✅ SSR bundle found');
1544
1601
  else console.error('[build] ❌ No server output found');
1545
1602
 
1603
+ if (!ok) process.exit(1);
1546
1604
  console.log('[build] ✅ Complete');
1547
- process.exit(0);
1605
+ process.exit(killedEarly || viteExitCode === 0 ? 0 : viteExitCode);
1548
1606
  }, 500);
1549
1607
  }
1550
1608
 
1551
1609
  child.on('exit', (code) => {
1552
1610
  console.log(\`[build] vite build exited with code \${code}\`);
1611
+ viteExitCode = code ?? (killedEarly ? 0 : 1);
1553
1612
  finish();
1554
1613
  });
1555
1614
 
@@ -1559,10 +1618,14 @@ child.on('error', (err) => {
1559
1618
  });
1560
1619
 
1561
1620
  const poll = setInterval(() => {
1621
+ // Cloudflare \`_worker.js\` appears during the client build; killing then
1622
+ // aborts SSR. Only force-stop the Netlify/Node hang.
1562
1623
  const netlifyReady = existsSync(NITRO_JSON) && existsSync(SERVER_MJS);
1563
1624
  const nodeServerReady = existsSync(OUTPUT_SSR);
1564
1625
  if (netlifyReady || nodeServerReady) {
1565
- console.log(\`[build] Output detected (\${netlifyReady ? 'netlify' : 'node-server'}), waiting 3s for final writes...\`);
1626
+ killedEarly = true;
1627
+ const kind = netlifyReady ? 'netlify' : 'node-server';
1628
+ console.log(\`[build] Output detected (\${kind}), waiting 3s for final writes...\`);
1566
1629
  clearInterval(poll);
1567
1630
  setTimeout(finish, 3_000);
1568
1631
  }
@@ -1570,6 +1633,7 @@ const poll = setInterval(() => {
1570
1633
 
1571
1634
  const absoluteTimeout = setTimeout(() => {
1572
1635
  console.error('[build] Timeout — killing build');
1636
+ killedEarly = true;
1573
1637
  finish();
1574
1638
  }, 240_000);
1575
1639
  `;
@@ -1630,6 +1694,22 @@ User-agent: AI2Bot
1630
1694
  Allow: /
1631
1695
  `;
1632
1696
  }
1697
+ function generateGitignore(_config) {
1698
+ return [
1699
+ "node_modules/",
1700
+ "dist/",
1701
+ ".output/",
1702
+ ".netlify/",
1703
+ ".wrangler/",
1704
+ "*.log",
1705
+ ".DS_Store",
1706
+ ".env",
1707
+ ".env.*",
1708
+ "!.env.example",
1709
+ ""
1710
+ ].join(`
1711
+ `);
1712
+ }
1633
1713
 
1634
1714
  // src/templates/favicon.ts
1635
1715
  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=";
@@ -1709,7 +1789,9 @@ function generatePackageJson(config) {
1709
1789
  const dependencies = {
1710
1790
  "@useavalon/avalon": "latest"
1711
1791
  };
1712
- for (const integration of config.integrations) {
1792
+ const selectedIntegrations = new Set(config.integrations);
1793
+ selectedIntegrations.add(config.core);
1794
+ for (const integration of selectedIntegrations) {
1713
1795
  dependencies[INTEGRATION_PACKAGES[integration]] = "latest";
1714
1796
  Object.assign(dependencies, INTEGRATION_RUNTIME_DEPS[integration]);
1715
1797
  }
@@ -1719,9 +1801,7 @@ function generatePackageJson(config) {
1719
1801
  if (config.plugins.includes("agent-optimization")) {
1720
1802
  dependencies["@useavalon/agent-optimization"] = "latest";
1721
1803
  }
1722
- if (config.plugins.includes("syntax-highlighting")) {
1723
- dependencies["rehype-highlight"] = "latest";
1724
- }
1804
+ dependencies["rehype-highlight"] = "latest";
1725
1805
  switch (config.middleware) {
1726
1806
  case "hono":
1727
1807
  dependencies.hono = "latest";
@@ -1731,10 +1811,10 @@ function generatePackageJson(config) {
1731
1811
  break;
1732
1812
  }
1733
1813
  const devDependencies = {
1734
- vite: "latest",
1735
- typescript: "latest",
1736
- nitro: "latest",
1737
- "vite-imagetools": "latest"
1814
+ vite: "^8.0.0",
1815
+ typescript: "^5.0.0",
1816
+ nitro: "^3.0.260311-beta",
1817
+ "vite-imagetools": "^7.0.0"
1738
1818
  };
1739
1819
  switch (config.styling) {
1740
1820
  case "tailwind":
@@ -1749,15 +1829,21 @@ function generatePackageJson(config) {
1749
1829
  dependencies.clsx = "^2.1.1";
1750
1830
  break;
1751
1831
  }
1832
+ const scripts = {
1833
+ dev: "bunx --bun vite dev",
1834
+ build: "node build.mjs",
1835
+ preview: "node .output/server/index.mjs"
1836
+ };
1837
+ if (config.deploy === "cloudflare") {
1838
+ const name = cloudflareProjectName(config.projectName);
1839
+ scripts.preview = "bunx wrangler@4 pages dev dist";
1840
+ scripts.deploy = `bunx wrangler@4 pages deploy --project-name=${name}`;
1841
+ }
1752
1842
  const pkg = {
1753
1843
  name: config.projectName,
1754
1844
  type: "module",
1755
1845
  private: true,
1756
- scripts: {
1757
- dev: "bunx --bun vite dev",
1758
- build: "node build.mjs",
1759
- preview: "node .output/server/index.mjs"
1760
- },
1846
+ scripts,
1761
1847
  dependencies,
1762
1848
  devDependencies
1763
1849
  };
@@ -1844,7 +1930,8 @@ function generatePostBuildMjs() {
1844
1930
  ` * Post-build script — delegates to Avalon's built-in post-build.`,
1845
1931
  ` *`,
1846
1932
  ` * All the heavy lifting (CSS patching, island redirects, prerendering,`,
1847
- ` * Netlify function copying) is handled by the framework.`,
1933
+ ` * Cloudflare worker patches, Netlify function copying) is handled by`,
1934
+ ` * the framework.`,
1848
1935
  ` */`,
1849
1936
  `import { runPostBuild } from '@useavalon/avalon/post-build';`,
1850
1937
  ``,
@@ -2180,6 +2267,7 @@ function generateViteConfig(config) {
2180
2267
  ` { schedule: '${EXAMPLE_CRON_SCHEDULE}', handler: '${EXAMPLE_CRON_HANDLER}' },`,
2181
2268
  ` ],`
2182
2269
  ] : [];
2270
+ const compatLines = config.deploy === "cloudflare" ? [` compatibilityDate: '2026-09-04',`] : [];
2183
2271
  const lines = [
2184
2272
  imports.join(`
2185
2273
  `),
@@ -2194,6 +2282,7 @@ function generateViteConfig(config) {
2194
2282
  ` nitro: {`,
2195
2283
  ` preset: process.env.NITRO_PRESET || 'node_server',`,
2196
2284
  ` streaming: true,`,
2285
+ ...compatLines,
2197
2286
  ` clientEntry: 'app/entry-client',`,
2198
2287
  ` globalCSS: ['app/shared/styles/main.css'],`,
2199
2288
  ` prerender: {`,
@@ -2338,9 +2427,15 @@ async function scaffoldProject(config, targetDir) {
2338
2427
  `));
2339
2428
  await writeFile(join(targetDir, "build.mjs"), generateBuildMjs());
2340
2429
  await writeFile(join(targetDir, "post-build.mjs"), generatePostBuildMjs());
2430
+ await writeFile(join(targetDir, ".gitignore"), generateGitignore(config));
2431
+ await writeFile(join(targetDir, "DEPLOY.md"), generateDeployReadme(config));
2341
2432
  if (config.deploy === "netlify") {
2342
2433
  await writeFile(join(targetDir, "netlify.toml"), generateNetlifyToml(config));
2343
2434
  }
2435
+ if (config.deploy === "cloudflare") {
2436
+ await writeFile(join(targetDir, "wrangler.toml"), generateWranglerToml(config));
2437
+ await writeFile(join(targetDir, "public/_headers"), generateCloudflareHeaders());
2438
+ }
2344
2439
  }
2345
2440
 
2346
2441
  // src/summary.ts
@@ -2350,15 +2445,34 @@ var STYLING_LABELS = {
2350
2445
  shadcn: "shadcn"
2351
2446
  };
2352
2447
  var DEPLOY_LABELS = {
2448
+ cloudflare: "Cloudflare Pages",
2353
2449
  netlify: "Netlify",
2354
2450
  none: "None"
2355
2451
  };
2452
+ function deployNextSteps(config) {
2453
+ if (config.deploy === "cloudflare") {
2454
+ return [
2455
+ " bun run build",
2456
+ " bun run preview # wrangler pages dev",
2457
+ " # See DEPLOY.md for Cloudflare Pages deploy steps"
2458
+ ];
2459
+ }
2460
+ if (config.deploy === "netlify") {
2461
+ return [" bun run build", " # Connect the repo in Netlify, or see DEPLOY.md"];
2462
+ }
2463
+ return [" bun run build"];
2464
+ }
2356
2465
  function formatSummary(config, scaffoldedInPlace = false) {
2357
2466
  const integrations = config.integrations.length > 0 ? config.integrations.join(", ") : "none";
2358
2467
  const styling = STYLING_LABELS[config.styling] ?? config.styling;
2359
2468
  const plugins = config.plugins.length > 0 ? config.plugins.join(", ") : "none";
2360
2469
  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"];
2470
+ const nextSteps = scaffoldedInPlace ? [" bun install", " bun run dev", ...deployNextSteps(config)] : [
2471
+ ` cd ${config.projectName}`,
2472
+ " bun install",
2473
+ " bun run dev",
2474
+ ...deployNextSteps(config)
2475
+ ];
2362
2476
  return [
2363
2477
  "",
2364
2478
  ` Project: ${config.projectName}`,
@@ -2366,7 +2480,7 @@ function formatSummary(config, scaffoldedInPlace = false) {
2366
2480
  ` Styling: ${styling}`,
2367
2481
  ` Plugins: ${plugins}`,
2368
2482
  ` Middleware: ${config.middleware}`,
2369
- ` Deploy: ${deploy}`,
2483
+ ` Deploy: ${deploy}`,
2370
2484
  ` Cron: ${config.cron ? "yes" : "no"}`,
2371
2485
  "",
2372
2486
  " Next steps:",
@@ -2404,7 +2518,7 @@ async function main() {
2404
2518
  " --styling css-modules (default) | tailwind | shadcn",
2405
2519
  " --plugins Comma list: seo (default),agent-optimization,syntax-highlighting",
2406
2520
  " --middleware h3 (default) | hono | elysia",
2407
- " --deploy netlify | none (default)",
2521
+ " --deploy cloudflare | netlify | none (default)",
2408
2522
  " --cron Scaffold an example cron task + config",
2409
2523
  "",
2410
2524
  "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.0",
4
4
  "description": "Scaffold a new Avalon project with multi-framework islands architecture",
5
5
  "license": "MIT",
6
6
  "type": "module",