create-avalon 0.1.24 → 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 +24 -6
  2. package/dist/cli.js +611 -227
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -19,10 +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
- - 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.
26
38
 
27
39
  ## What you get
28
40
 
@@ -32,7 +44,7 @@ A ready-to-run Avalon project with file-system routing, islands architecture, an
32
44
  my-project/
33
45
  ├── app/
34
46
  │ ├── modules/
35
- │ │ └── home/
47
+ │ │ └── main/
36
48
  │ │ ├── pages/ # File-system routes
37
49
  │ │ ├── components/ # Interactive components
38
50
  │ │ └── layouts/ # Module layouts
@@ -43,8 +55,14 @@ my-project/
43
55
  ├── middleware/ # Server middleware
44
56
  ├── routes/
45
57
  │ └── api/ # API routes
58
+ ├── tasks/ # Scheduled jobs (cron) — optional
46
59
  ├── server/ # Server config & env
47
- ├── 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)
48
66
  ├── vite.config.ts
49
67
  └── package.json
50
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";
@@ -76,12 +92,69 @@ var require_src = __commonJS((exports, module) => {
76
92
  });
77
93
 
78
94
  // src/cli.ts
79
- import { basename, resolve } from "node:path";
80
95
  import { createRequire } from "node:module";
96
+ import { basename, resolve } from "node:path";
81
97
 
82
98
  // src/cli-utils.ts
83
- import { parseArgs } from "node:util";
84
99
  import { existsSync, readdirSync } from "node:fs";
100
+ import { parseArgs } from "node:util";
101
+
102
+ // src/types.ts
103
+ var RENDER_ENGINES = ["preact", "react"];
104
+ var INTEGRATIONS = [
105
+ "preact",
106
+ "react",
107
+ "vue",
108
+ "svelte",
109
+ "solid",
110
+ "lit",
111
+ "qwik"
112
+ ];
113
+ var STYLING_OPTIONS = [
114
+ "css-modules",
115
+ "tailwind",
116
+ "shadcn"
117
+ ];
118
+ var PLUGINS = [
119
+ "seo",
120
+ "agent-optimization",
121
+ "syntax-highlighting"
122
+ ];
123
+ var MIDDLEWARE_OPTIONS = [
124
+ "h3",
125
+ "hono",
126
+ "elysia"
127
+ ];
128
+ var DEPLOY_TARGETS = [
129
+ "cloudflare",
130
+ "netlify",
131
+ "none"
132
+ ];
133
+ var INTEGRATION_PACKAGES = {
134
+ preact: "@useavalon/preact",
135
+ react: "@useavalon/react",
136
+ vue: "@useavalon/vue",
137
+ svelte: "@useavalon/svelte",
138
+ solid: "@useavalon/solid",
139
+ lit: "@useavalon/lit",
140
+ qwik: "@useavalon/qwik"
141
+ };
142
+ var BASE_DIRS = [
143
+ "app/modules/main/pages",
144
+ "app/modules/main/components",
145
+ "app/modules/main/layouts",
146
+ "app/shared/layouts",
147
+ "app/shared/components",
148
+ "app/shared/styles",
149
+ "middleware",
150
+ "routes/api",
151
+ "public",
152
+ "server"
153
+ ];
154
+
155
+ // src/cli-utils.ts
156
+ class CliArgError extends Error {
157
+ }
85
158
  function validateDirectory(dir) {
86
159
  if (!existsSync(dir)) {
87
160
  return { valid: true };
@@ -100,7 +173,15 @@ function parseCliArgs(argv) {
100
173
  args: argv,
101
174
  options: {
102
175
  help: { type: "boolean", default: false, short: "h" },
103
- version: { type: "boolean", default: false, short: "v" }
176
+ version: { type: "boolean", default: false, short: "v" },
177
+ yes: { type: "boolean", default: false, short: "y" },
178
+ core: { type: "string" },
179
+ integrations: { type: "string" },
180
+ styling: { type: "string" },
181
+ plugins: { type: "string" },
182
+ middleware: { type: "string" },
183
+ deploy: { type: "string" },
184
+ cron: { type: "boolean", default: false }
104
185
  },
105
186
  strict: true,
106
187
  allowPositionals: true
@@ -108,7 +189,58 @@ function parseCliArgs(argv) {
108
189
  return {
109
190
  projectName: positionals[0] ?? undefined,
110
191
  help: values.help ?? false,
111
- version: values.version ?? false
192
+ version: values.version ?? false,
193
+ yes: values.yes ?? false,
194
+ core: values.core,
195
+ integrations: values.integrations,
196
+ styling: values.styling,
197
+ plugins: values.plugins,
198
+ middleware: values.middleware,
199
+ deploy: values.deploy,
200
+ cron: values.cron ?? false
201
+ };
202
+ }
203
+ var csv = (value) => value ? value.split(",").map((v) => v.trim()).filter(Boolean) : undefined;
204
+ function assertOneOf(value, allowed, flag) {
205
+ if (value === undefined)
206
+ return;
207
+ if (!allowed.includes(value)) {
208
+ throw new CliArgError(`Invalid value "${value}" for --${flag}. Allowed: ${allowed.join(", ")}.`);
209
+ }
210
+ return value;
211
+ }
212
+ function assertAllOf(values, allowed, flag) {
213
+ if (values === undefined)
214
+ return;
215
+ for (const value of values) {
216
+ if (!allowed.includes(value)) {
217
+ throw new CliArgError(`Invalid value "${value}" for --${flag}. Allowed: ${allowed.join(", ")}.`);
218
+ }
219
+ }
220
+ return values;
221
+ }
222
+ function resolveConfigNonInteractive(args) {
223
+ const core = assertOneOf(args.core, RENDER_ENGINES, "core") ?? "preact";
224
+ const integrations = assertAllOf(csv(args.integrations), INTEGRATIONS, "integrations") ?? [];
225
+ const styling = assertOneOf(args.styling, STYLING_OPTIONS, "styling") ?? "css-modules";
226
+ const plugins = assertAllOf(csv(args.plugins), PLUGINS, "plugins") ?? ["seo"];
227
+ const middleware = assertOneOf(args.middleware, MIDDLEWARE_OPTIONS, "middleware") ?? "h3";
228
+ const deploy = assertOneOf(args.deploy, DEPLOY_TARGETS, "deploy") ?? "none";
229
+ if (styling === "shadcn" && core !== "react") {
230
+ throw new CliArgError("--styling=shadcn requires --core=react (shadcn is Radix/React based).");
231
+ }
232
+ if (!integrations.includes(core)) {
233
+ integrations.push(core);
234
+ }
235
+ return {
236
+ projectName: args.projectName ?? ".",
237
+ core,
238
+ integrations,
239
+ styling,
240
+ plugins,
241
+ middleware,
242
+ deploy,
243
+ cron: args.cron
112
244
  };
113
245
  }
114
246
 
@@ -468,125 +600,20 @@ class B {
468
600
  }
469
601
  }
470
602
  }
471
- function wt(t, e) {
472
- if (t === undefined || e.length === 0)
473
- return 0;
474
- const s = e.findIndex((i) => i.value === t);
475
- return s !== -1 ? s : 0;
476
- }
477
- function Dt(t, e) {
478
- return (e.label ?? String(e.value)).toLowerCase().includes(t.toLowerCase());
479
- }
480
- function St(t, e) {
481
- if (e)
482
- return t ? e : e[0];
483
- }
484
-
485
- class Vt extends B {
486
- filteredOptions;
487
- multiple;
488
- isNavigating = false;
489
- selectedValues = [];
490
- focusedValue;
491
- #t = 0;
492
- #s = "";
493
- #i;
494
- #e;
603
+ class kt extends B {
495
604
  get cursor() {
496
- return this.#t;
497
- }
498
- get userInputWithCursor() {
499
- if (!this.userInput)
500
- return D(["inverse", "hidden"], "_");
501
- if (this._cursor >= this.userInput.length)
502
- return `${this.userInput}█`;
503
- const e = this.userInput.slice(0, this._cursor), [s, ...i] = this.userInput.slice(this._cursor);
504
- return `${e}${D("inverse", s)}${i.join("")}`;
605
+ return this.value ? 0 : 1;
505
606
  }
506
- get options() {
507
- return typeof this.#e == "function" ? this.#e() : this.#e;
508
- }
509
- constructor(e) {
510
- super(e), this.#e = e.options;
511
- const s = this.options;
512
- this.filteredOptions = [...s], this.multiple = e.multiple === true, this.#i = e.filter ?? Dt;
513
- let i;
514
- 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)
515
- for (const r of i) {
516
- const n = s.findIndex((u) => u.value === r);
517
- n !== -1 && (this.toggleSelected(r), this.#t = n);
518
- }
519
- this.focusedValue = this.options[this.#t]?.value, this.on("key", (r, n) => this.#r(r, n)), this.on("userInput", (r) => this.#n(r));
520
- }
521
- _isActionKey(e, s) {
522
- return e === "\t" || this.multiple && this.isNavigating && s.name === "space" && e !== undefined && e !== "";
523
- }
524
- #r(e, s) {
525
- const i = s.name === "up", r = s.name === "down", n = s.name === "return";
526
- 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);
527
- }
528
- deselectAll() {
529
- this.selectedValues = [];
530
- }
531
- toggleSelected(e) {
532
- 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]);
533
- }
534
- #n(e) {
535
- if (e !== this.#s) {
536
- this.#s = e;
537
- const s = this.options;
538
- e ? this.filteredOptions = s.filter((n) => this.#i(e, n)) : this.filteredOptions = [...s];
539
- const i = wt(this.focusedValue, this.filteredOptions);
540
- this.#t = x(i, 0, this.filteredOptions);
541
- const r = this.filteredOptions[this.#t];
542
- r && !r.disabled ? this.focusedValue = r.value : this.focusedValue = undefined, this.multiple || (this.focusedValue !== undefined ? this.toggleSelected(this.focusedValue) : this.deselectAll());
543
- }
544
- }
545
- }
546
- class yt extends B {
547
- options;
548
- cursor = 0;
549
- #t;
550
- getGroupItems(e) {
551
- return this.options.filter((s) => s.group === e);
552
- }
553
- isGroupSelected(e) {
554
- const s = this.getGroupItems(e), i = this.value;
555
- return i === undefined ? false : s.every((r) => i.includes(r.value));
556
- }
557
- toggleValue() {
558
- const e = this.options[this.cursor];
559
- if (this.value === undefined && (this.value = []), e.group === true) {
560
- const s = e.value, i = this.getGroupItems(s);
561
- 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));
562
- } else {
563
- const s = this.value.includes(e.value);
564
- this.value = s ? this.value.filter((i) => i !== e.value) : [...this.value, e.value];
565
- }
607
+ get _value() {
608
+ return this.cursor === 0;
566
609
  }
567
610
  constructor(e) {
568
- super(e, false);
569
- const { options: s } = e;
570
- 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) => {
571
- switch (i) {
572
- case "left":
573
- case "up": {
574
- this.cursor = this.cursor === 0 ? this.options.length - 1 : this.cursor - 1;
575
- const r = this.options[this.cursor]?.group === true;
576
- !this.#t && r && (this.cursor = this.cursor === 0 ? this.options.length - 1 : this.cursor - 1);
577
- break;
578
- }
579
- case "down":
580
- case "right": {
581
- this.cursor = this.cursor === this.options.length - 1 ? 0 : this.cursor + 1;
582
- const r = this.options[this.cursor]?.group === true;
583
- !this.#t && r && (this.cursor = this.cursor === this.options.length - 1 ? 0 : this.cursor + 1);
584
- break;
585
- }
586
- case "space":
587
- this.toggleValue();
588
- break;
589
- }
611
+ super(e, false), this.value = !!e.initialValue, this.on("userInput", () => {
612
+ this.value = this._value;
613
+ }), this.on("confirm", (s) => {
614
+ this.output.write(import_sisteransi.cursor.move(0, -1)), this.value = s, this.state = "submit", this.close();
615
+ }), this.on("cursor", () => {
616
+ this.value = !this.value;
590
617
  });
591
618
  }
592
619
  }
@@ -758,59 +785,59 @@ var Ft2 = /\p{M}+/gu;
758
785
  var yt2 = { limit: 1 / 0, ellipsis: "" };
759
786
  var Le = (e, r = {}, s = {}) => {
760
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;
761
- 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;
762
789
  e:
763
790
  for (;; ) {
764
791
  if (b > A || m >= d && m > $) {
765
- const T2 = e.slice(A, b) || e.slice($, m);
792
+ const T = e.slice(A, b) || e.slice($, m);
766
793
  F = 0;
767
- for (const M2 of T2.replaceAll(Ft2, "")) {
768
- const O2 = M2.codePointAt(0) || 0;
769
- 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) {
770
- 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;
771
798
  break e;
772
799
  }
773
- F += M2.length, w += S2;
800
+ F += M.length, w += S;
774
801
  }
775
802
  A = b = 0;
776
803
  }
777
804
  if (m >= d)
778
805
  break;
779
806
  if (ne.lastIndex = m, ne.test(e)) {
780
- if (F = ne.lastIndex - m, S2 = F * g, w + S2 > C && (v = Math.min(v, m + Math.floor((C - w) / g))), w + S2 > i) {
781
- 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;
782
809
  break;
783
810
  }
784
- w += S2, A = $, b = m, m = $ = ne.lastIndex;
811
+ w += S, A = $, b = m, m = $ = ne.lastIndex;
785
812
  continue;
786
813
  }
787
814
  if (we.lastIndex = m, we.test(e)) {
788
815
  if (w + u > C && (v = Math.min(v, m)), w + u > i) {
789
- y2 = true;
816
+ y = true;
790
817
  break;
791
818
  }
792
819
  w += u, A = $, b = m, m = $ = we.lastIndex;
793
820
  continue;
794
821
  }
795
822
  if (re.lastIndex = m, re.test(e)) {
796
- if (F = re.lastIndex - m, S2 = F * l, w + S2 > C && (v = Math.min(v, m + Math.floor((C - w) / l))), w + S2 > i) {
797
- 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;
798
825
  break;
799
826
  }
800
- w += S2, A = $, b = m, m = $ = re.lastIndex;
827
+ w += S, A = $, b = m, m = $ = re.lastIndex;
801
828
  continue;
802
829
  }
803
830
  if (ie.lastIndex = m, ie.test(e)) {
804
- if (F = ie.lastIndex - m, S2 = F * n, w + S2 > C && (v = Math.min(v, m + Math.floor((C - w) / n))), w + S2 > i) {
805
- 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;
806
833
  break;
807
834
  }
808
- w += S2, A = $, b = m, m = $ = ie.lastIndex;
835
+ w += S, A = $, b = m, m = $ = ie.lastIndex;
809
836
  continue;
810
837
  }
811
838
  if (Ae.lastIndex = m, Ae.test(e)) {
812
839
  if (w + p > C && (v = Math.min(v, m)), w + p > i) {
813
- y2 = true;
840
+ y = true;
814
841
  break;
815
842
  }
816
843
  w += p, A = $, b = m, m = $ = Ae.lastIndex;
@@ -818,7 +845,7 @@ var Le = (e, r = {}, s = {}) => {
818
845
  }
819
846
  m += 1;
820
847
  }
821
- 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 };
822
849
  };
823
850
  var Et2 = { limit: 1 / 0, ellipsis: "", ellipsisWidth: 0 };
824
851
  var D2 = (e, r = {}) => Le(e, Et2, r).width;
@@ -827,9 +854,9 @@ var je = "›";
827
854
  var vt2 = 39;
828
855
  var Ce = "\x07";
829
856
  var ke = "[";
830
- var wt2 = "]";
857
+ var wt = "]";
831
858
  var Ve = "m";
832
- var Se = `${wt2}8;;`;
859
+ var Se = `${wt}8;;`;
833
860
  var He = new RegExp(`(?:\\${ke}(?<code>\\d+)m|\\${Se}(?<uri>.*)${Ce})`, "y");
834
861
  var At2 = (e) => {
835
862
  if (e >= 30 && e <= 37 || e >= 90 && e <= 97)
@@ -863,7 +890,7 @@ var Ie = (e, r, s) => {
863
890
  }
864
891
  u = e.at(-1), !l && u !== undefined && u.length > 0 && e.length > 1 && (e[e.length - 2] += e.pop());
865
892
  };
866
- var St2 = (e) => {
893
+ var St = (e) => {
867
894
  const r = e.split(" ");
868
895
  let s = r.length;
869
896
  for (;s > 0 && !(D2(r[s - 1]) > 0); )
@@ -880,8 +907,8 @@ var It2 = (e, r, s = {}) => {
880
907
  s.trim !== false && (n[n.length - 1] = (n.at(-1) ?? "").trimStart());
881
908
  let d = D2(n.at(-1) ?? "");
882
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) {
883
- const F = r - d, y2 = 1 + Math.floor((l[$] - F - 1) / r);
884
- 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);
885
912
  continue;
886
913
  }
887
914
  if (d + l[$] > r && d > 0 && l[$] > 0) {
@@ -897,7 +924,7 @@ var It2 = (e, r, s = {}) => {
897
924
  }
898
925
  n[n.length - 1] += m;
899
926
  }
900
- s.trim !== false && (n = n.map(($) => St2($)));
927
+ s.trim !== false && (n = n.map(($) => St($)));
901
928
  const c = n.join(`
902
929
  `), p = c[Symbol.iterator]();
903
930
  let f = p.next(), g = p.next(), E = 0;
@@ -905,12 +932,12 @@ var It2 = (e, r, s = {}) => {
905
932
  const $ = f.value, m = g.value;
906
933
  if (i += $, $ === ae || $ === je) {
907
934
  He.lastIndex = E + 1;
908
- const y2 = He.exec(c)?.groups;
909
- if (y2?.code !== undefined) {
910
- 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);
911
938
  a = v === vt2 ? undefined : v;
912
939
  } else
913
- y2?.uri !== undefined && (o = y2.uri.length === 0 ? undefined : y2.uri);
940
+ y?.uri !== undefined && (o = y.uri.length === 0 ? undefined : y.uri);
914
941
  }
915
942
  const d = a ? At2(a) : undefined;
916
943
  m === `
@@ -943,16 +970,16 @@ var X2 = ({ cursor: e, options: r, style: s, output: i = process.stdout, maxItem
943
970
  const m = Math.min(g + f, r.length), d = [];
944
971
  let F = 0;
945
972
  E && F++, $ && F++;
946
- const y2 = g + (E ? 1 : 0), v = m - ($ ? 1 : 0);
947
- 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++) {
948
975
  const b = J(s(r[A], A === e), l, { hard: true, trim: false }).split(`
949
976
  `);
950
977
  d.push(b), F += b.length;
951
978
  }
952
979
  if (F > p) {
953
980
  let A = 0, b = 0, w = F;
954
- const S2 = e - y2, T2 = (M2, O2) => bt2(d, w, M2, O2, p);
955
- 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));
956
983
  }
957
984
  const C = [];
958
985
  E && C.push(c);
@@ -961,6 +988,33 @@ var X2 = ({ cursor: e, options: r, style: s, output: i = process.stdout, maxItem
961
988
  C.push(b);
962
989
  return $ && C.push(c), C;
963
990
  };
991
+ var Rt = (e) => {
992
+ const r = e.active ?? "Yes", s = e.inactive ?? "No";
993
+ return new kt({ active: r, inactive: s, signal: e.signal, input: e.input, output: e.output, initialValue: e.initialValue ?? true, render() {
994
+ const i = e.withGuide ?? _.withGuide, a = `${i ? `${t("gray", h)}
995
+ ` : ""}${W2(this.state)} ${e.message}
996
+ `, o = this.value ? r : s;
997
+ switch (this.state) {
998
+ case "submit": {
999
+ const u = i ? `${t("gray", h)} ` : "";
1000
+ return `${a}${u}${t("dim", o)}`;
1001
+ }
1002
+ case "cancel": {
1003
+ const u = i ? `${t("gray", h)} ` : "";
1004
+ return `${a}${u}${t(["strikethrough", "dim"], o)}${i ? `
1005
+ ${t("gray", h)}` : ""}`;
1006
+ }
1007
+ default: {
1008
+ const u = i ? `${t("cyan", h)} ` : "", l = i ? t("cyan", x2) : "";
1009
+ return `${a}${u}${this.value ? `${t("green", z2)} ${r}` : `${t("dim", H2)} ${t("dim", r)}`}${e.vertical ? i ? `
1010
+ ${t("cyan", h)} ` : `
1011
+ ` : ` ${t("dim", "/")} `}${this.value ? `${t("dim", H2)} ${t("dim", s)}` : `${t("green", z2)} ${s}`}
1012
+ ${l}
1013
+ `;
1014
+ }
1015
+ }
1016
+ } }).prompt();
1017
+ };
964
1018
  var Nt = (e = "", r) => {
965
1019
  const s = r?.output ?? process.stdout, i = r?.withGuide ?? _.withGuide ? `${t("gray", x2)} ` : "";
966
1020
  s.write(`${i}${t("red", e)}
@@ -1124,6 +1178,26 @@ async function collectProjectConfig(initialName) {
1124
1178
  }
1125
1179
  projectName = nameResult;
1126
1180
  }
1181
+ const coreResult = await Jt({
1182
+ message: "Which rendering engine should render your pages (the shell)?",
1183
+ options: [
1184
+ {
1185
+ value: "preact",
1186
+ label: "Preact",
1187
+ hint: "Smallest runtime (default). React libs run via preact/compat."
1188
+ },
1189
+ {
1190
+ value: "react",
1191
+ label: "React",
1192
+ hint: "Real react-dom/server. React libs like Radix/shadcn work natively."
1193
+ }
1194
+ ],
1195
+ initialValue: "preact"
1196
+ });
1197
+ if (Ct(coreResult)) {
1198
+ Nt("Operation cancelled.");
1199
+ process.exit(1);
1200
+ }
1127
1201
  const integrationsResult = await Lt2({
1128
1202
  message: "Which integrations would you like to include? (use space to toggle, enter to confirm)",
1129
1203
  options: [
@@ -1141,13 +1215,20 @@ async function collectProjectConfig(initialName) {
1141
1215
  Nt("Operation cancelled.");
1142
1216
  process.exit(1);
1143
1217
  }
1218
+ const stylingOptions = [
1219
+ { value: "css-modules", label: "CSS Modules" },
1220
+ { value: "tailwind", label: "Tailwind CSS" }
1221
+ ];
1222
+ if (coreResult === "react") {
1223
+ stylingOptions.push({
1224
+ value: "shadcn",
1225
+ label: "shadcn",
1226
+ hint: "Radix-based components — requires the React engine"
1227
+ });
1228
+ }
1144
1229
  const stylingResult = await Jt({
1145
1230
  message: "Which styling approach would you like to use?",
1146
- options: [
1147
- { value: "css-modules", label: "CSS Modules" },
1148
- { value: "tailwind", label: "Tailwind CSS" },
1149
- { value: "shadcn", label: "shadcn" }
1150
- ]
1231
+ options: stylingOptions
1151
1232
  });
1152
1233
  if (Ct(stylingResult)) {
1153
1234
  Nt("Operation cancelled.");
@@ -1156,8 +1237,16 @@ async function collectProjectConfig(initialName) {
1156
1237
  const pluginsResult = await Lt2({
1157
1238
  message: "Which plugins would you like to include? (use space to toggle, enter to confirm)",
1158
1239
  options: [
1159
- { value: "seo", label: "seo", hint: "Auto-injects OG, Twitter cards, JSON-LD, canonical URLs" },
1160
- { value: "agent-optimization", label: "agent-optimization", hint: "LLM/AI optimization (llms.txt, markdown, sitemap)" },
1240
+ {
1241
+ value: "seo",
1242
+ label: "seo",
1243
+ hint: "Auto-injects OG, Twitter cards, JSON-LD, canonical URLs"
1244
+ },
1245
+ {
1246
+ value: "agent-optimization",
1247
+ label: "agent-optimization",
1248
+ hint: "LLM/AI optimization (llms.txt, markdown, sitemap)"
1249
+ },
1161
1250
  {
1162
1251
  value: "syntax-highlighting",
1163
1252
  label: "syntax-highlighting",
@@ -1186,21 +1275,45 @@ async function collectProjectConfig(initialName) {
1186
1275
  const deployResult = await Jt({
1187
1276
  message: "Where will you deploy?",
1188
1277
  options: [
1189
- { value: "netlify", label: "Netlify", hint: "Generates netlify.toml, build.mjs, post-build.mjs" },
1190
- { value: "none", label: "None / Other", hint: "Node server preset, no deploy config" }
1278
+ {
1279
+ value: "cloudflare",
1280
+ label: "Cloudflare Pages",
1281
+ hint: "wrangler.toml, public/_headers, build + deploy scripts"
1282
+ },
1283
+ {
1284
+ value: "netlify",
1285
+ label: "Netlify",
1286
+ hint: "netlify.toml, build.mjs, post-build.mjs"
1287
+ },
1288
+ { value: "none", label: "None / Other", hint: "Node server preset, no platform config" }
1191
1289
  ]
1192
1290
  });
1193
1291
  if (Ct(deployResult)) {
1194
1292
  Nt("Operation cancelled.");
1195
1293
  process.exit(1);
1196
1294
  }
1295
+ const cronResult = await Rt({
1296
+ message: "Set up scheduled jobs (cron)?",
1297
+ initialValue: false
1298
+ });
1299
+ if (Ct(cronResult)) {
1300
+ Nt("Operation cancelled.");
1301
+ process.exit(1);
1302
+ }
1303
+ const core = coreResult;
1304
+ const integrations = integrationsResult;
1305
+ if (!integrations.includes(core)) {
1306
+ integrations.push(core);
1307
+ }
1197
1308
  return {
1198
1309
  projectName,
1199
- integrations: integrationsResult,
1310
+ core,
1311
+ integrations,
1200
1312
  styling: stylingResult,
1201
1313
  plugins: pluginsResult,
1202
1314
  middleware: middlewareResult,
1203
- deploy: deployResult
1315
+ deploy: deployResult,
1316
+ cron: cronResult
1204
1317
  };
1205
1318
  }
1206
1319
 
@@ -1218,7 +1331,36 @@ export default defineHandler(() => {
1218
1331
  `;
1219
1332
  }
1220
1333
 
1334
+ // src/templates/cron.ts
1335
+ var EXAMPLE_CRON_HANDLER = "tasks/cleanup.ts";
1336
+ var EXAMPLE_CRON_SCHEDULE = "0 * * * *";
1337
+ function generateExampleCronTask(_config) {
1338
+ return `import { defineCronJob } from '@useavalon/avalon/cron';
1339
+
1340
+ /**
1341
+ * Example scheduled job. Runs on the schedule defined in vite.config.ts
1342
+ * (\`nitro.cron\`). The task name is derived from this file path: "cleanup".
1343
+ *
1344
+ * In production this runs via your deployment preset's scheduler
1345
+ * (Vercel Cron, Cloudflare Triggers, or the Node server's in-process
1346
+ * scheduler). In development Avalon runs it inside the Vite dev server.
1347
+ */
1348
+ export default defineCronJob({
1349
+ meta: { description: 'Example scheduled job' },
1350
+ async run() {
1351
+ console.log('[cron] cleanup ran at', new Date().toISOString());
1352
+ // TODO: replace with your scheduled work.
1353
+ return { result: 'ok' };
1354
+ },
1355
+ });
1356
+ `;
1357
+ }
1358
+
1221
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
+ }
1222
1364
  function generateNetlifyToml(_config) {
1223
1365
  return `[build]
1224
1366
  base = "."
@@ -1243,6 +1385,117 @@ function generateNetlifyToml(_config) {
1243
1385
  status = 200
1244
1386
  `;
1245
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
+ }
1246
1499
  function generateBuildMjs() {
1247
1500
  return `/**
1248
1501
  * Build wrapper.
@@ -1250,20 +1503,49 @@ function generateBuildMjs() {
1250
1503
  * Vite/Nitro leaves open handles after the build completes, preventing
1251
1504
  * the Node process from exiting. This wrapper detects when the build
1252
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.
1253
1509
  */
1254
1510
 
1255
1511
  import { spawn, execSync } from 'node:child_process';
1256
- import { existsSync, rmSync } from 'node:fs';
1512
+ import { existsSync, rmSync, statSync } from 'node:fs';
1257
1513
  import { join } from 'node:path';
1258
1514
 
1259
1515
  const CWD = process.cwd();
1260
1516
  const NITRO_JSON = join(CWD, '.netlify', 'functions-internal', 'nitro.json');
1261
1517
  const SERVER_MJS = join(CWD, '.netlify', 'functions-internal', 'server', 'server.mjs');
1262
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
+ }
1263
1542
 
1264
1543
  console.log('[build] Starting vite build...');
1544
+ if (process.env.NITRO_PRESET) {
1545
+ console.log(\`[build] NITRO_PRESET=\${process.env.NITRO_PRESET}\`);
1546
+ }
1265
1547
 
1266
- for (const dir of ['.netlify', '.output', 'netlify']) {
1548
+ for (const dir of ['.netlify', '.output', 'netlify', 'dist']) {
1267
1549
  const full = join(CWD, dir);
1268
1550
  if (existsSync(full)) {
1269
1551
  rmSync(full, { recursive: true, force: true });
@@ -1275,10 +1557,13 @@ const child = spawn('bunx', ['--bun', 'vite', 'build'], {
1275
1557
  cwd: CWD,
1276
1558
  stdio: 'inherit',
1277
1559
  detached: true,
1560
+ env: process.env,
1278
1561
  });
1279
1562
 
1280
1563
  const childPid = child.pid;
1281
1564
  let done = false;
1565
+ let killedEarly = false;
1566
+ let viteExitCode = 0;
1282
1567
 
1283
1568
  function killTree() {
1284
1569
  try { process.kill(-childPid, 'SIGKILL'); } catch {}
@@ -1290,29 +1575,40 @@ function finish() {
1290
1575
  done = true;
1291
1576
  clearInterval(poll);
1292
1577
  clearTimeout(absoluteTimeout);
1293
- killTree();
1578
+
1579
+ if (killedEarly) killTree();
1294
1580
 
1295
1581
  setTimeout(() => {
1296
1582
  console.log('[build] Running post-build...');
1297
1583
  try {
1298
- 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 });
1299
1585
  } catch (err) {
1300
- console.error('[build] post-build warning:', err.message);
1586
+ console.error('[build] post-build failed:', err.message);
1587
+ process.exit(1);
1301
1588
  }
1302
1589
 
1303
1590
  const V1_SERVER = join(CWD, '.netlify', 'v1', 'functions', 'server', 'server.mjs');
1304
- 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)');
1305
1599
  else if (existsSync(SERVER_MJS)) console.log('[build] ✅ Server function found (legacy)');
1306
1600
  else if (existsSync(OUTPUT_SSR)) console.log('[build] ✅ SSR bundle found');
1307
1601
  else console.error('[build] ❌ No server output found');
1308
1602
 
1603
+ if (!ok) process.exit(1);
1309
1604
  console.log('[build] ✅ Complete');
1310
- process.exit(0);
1605
+ process.exit(killedEarly || viteExitCode === 0 ? 0 : viteExitCode);
1311
1606
  }, 500);
1312
1607
  }
1313
1608
 
1314
1609
  child.on('exit', (code) => {
1315
1610
  console.log(\`[build] vite build exited with code \${code}\`);
1611
+ viteExitCode = code ?? (killedEarly ? 0 : 1);
1316
1612
  finish();
1317
1613
  });
1318
1614
 
@@ -1322,10 +1618,14 @@ child.on('error', (err) => {
1322
1618
  });
1323
1619
 
1324
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.
1325
1623
  const netlifyReady = existsSync(NITRO_JSON) && existsSync(SERVER_MJS);
1326
1624
  const nodeServerReady = existsSync(OUTPUT_SSR);
1327
1625
  if (netlifyReady || nodeServerReady) {
1328
- 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...\`);
1329
1629
  clearInterval(poll);
1330
1630
  setTimeout(finish, 3_000);
1331
1631
  }
@@ -1333,6 +1633,7 @@ const poll = setInterval(() => {
1333
1633
 
1334
1634
  const absoluteTimeout = setTimeout(() => {
1335
1635
  console.error('[build] Timeout — killing build');
1636
+ killedEarly = true;
1336
1637
  finish();
1337
1638
  }, 240_000);
1338
1639
  `;
@@ -1393,6 +1694,22 @@ User-agent: AI2Bot
1393
1694
  Allow: /
1394
1695
  `;
1395
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
+ }
1396
1713
 
1397
1714
  // src/templates/favicon.ts
1398
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=";
@@ -1453,36 +1770,30 @@ export default defineHandler((event) => {
1453
1770
  `;
1454
1771
  }
1455
1772
 
1456
- // src/types.ts
1457
- var INTEGRATION_PACKAGES = {
1458
- preact: "@useavalon/preact",
1459
- react: "@useavalon/react",
1460
- vue: "@useavalon/vue",
1461
- svelte: "@useavalon/svelte",
1462
- solid: "@useavalon/solid",
1463
- lit: "@useavalon/lit",
1464
- qwik: "@useavalon/qwik"
1465
- };
1466
- var BASE_DIRS = [
1467
- "app/modules/main/pages",
1468
- "app/modules/main/components",
1469
- "app/modules/main/layouts",
1470
- "app/shared/layouts",
1471
- "app/shared/components",
1472
- "app/shared/styles",
1473
- "middleware",
1474
- "routes/api",
1475
- "public",
1476
- "server"
1477
- ];
1478
-
1479
1773
  // src/templates/package-json.ts
1774
+ var INTEGRATION_RUNTIME_DEPS = {
1775
+ preact: { preact: "^10.0.0", "preact-render-to-string": "^6.0.0" },
1776
+ react: { react: "^19.0.0", "react-dom": "^19.0.0" },
1777
+ vue: { vue: "^3.4.0" },
1778
+ svelte: { svelte: "^5.0.0" },
1779
+ solid: { "solid-js": "^1.8.0" },
1780
+ lit: {
1781
+ lit: "^3.0.0",
1782
+ "@lit-labs/ssr": "^4.0.0",
1783
+ "@lit-labs/ssr-client": "^1.0.0",
1784
+ "@lit-labs/ssr-dom-shim": "^1.0.0"
1785
+ },
1786
+ qwik: { "@builder.io/qwik": "^1.5.0" }
1787
+ };
1480
1788
  function generatePackageJson(config) {
1481
1789
  const dependencies = {
1482
1790
  "@useavalon/avalon": "latest"
1483
1791
  };
1484
- for (const integration of config.integrations) {
1792
+ const selectedIntegrations = new Set(config.integrations);
1793
+ selectedIntegrations.add(config.core);
1794
+ for (const integration of selectedIntegrations) {
1485
1795
  dependencies[INTEGRATION_PACKAGES[integration]] = "latest";
1796
+ Object.assign(dependencies, INTEGRATION_RUNTIME_DEPS[integration]);
1486
1797
  }
1487
1798
  if (config.plugins.includes("seo")) {
1488
1799
  dependencies["@useavalon/seo"] = "latest";
@@ -1490,9 +1801,7 @@ function generatePackageJson(config) {
1490
1801
  if (config.plugins.includes("agent-optimization")) {
1491
1802
  dependencies["@useavalon/agent-optimization"] = "latest";
1492
1803
  }
1493
- if (config.plugins.includes("syntax-highlighting")) {
1494
- dependencies["rehype-highlight"] = "latest";
1495
- }
1804
+ dependencies["rehype-highlight"] = "latest";
1496
1805
  switch (config.middleware) {
1497
1806
  case "hono":
1498
1807
  dependencies.hono = "latest";
@@ -1502,10 +1811,10 @@ function generatePackageJson(config) {
1502
1811
  break;
1503
1812
  }
1504
1813
  const devDependencies = {
1505
- vite: "latest",
1506
- typescript: "latest",
1507
- nitro: "latest",
1508
- "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"
1509
1818
  };
1510
1819
  switch (config.styling) {
1511
1820
  case "tailwind":
@@ -1520,15 +1829,21 @@ function generatePackageJson(config) {
1520
1829
  dependencies.clsx = "^2.1.1";
1521
1830
  break;
1522
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
+ }
1523
1842
  const pkg = {
1524
1843
  name: config.projectName,
1525
1844
  type: "module",
1526
1845
  private: true,
1527
- scripts: {
1528
- dev: "bunx --bun vite dev",
1529
- build: "node build.mjs",
1530
- preview: "node .output/server/index.mjs"
1531
- },
1846
+ scripts,
1532
1847
  dependencies,
1533
1848
  devDependencies
1534
1849
  };
@@ -1615,7 +1930,8 @@ function generatePostBuildMjs() {
1615
1930
  ` * Post-build script — delegates to Avalon's built-in post-build.`,
1616
1931
  ` *`,
1617
1932
  ` * All the heavy lifting (CSS patching, island redirects, prerendering,`,
1618
- ` * Netlify function copying) is handled by the framework.`,
1933
+ ` * Cloudflare worker patches, Netlify function copying) is handled by`,
1934
+ ` * the framework.`,
1619
1935
  ` */`,
1620
1936
  `import { runPostBuild } from '@useavalon/avalon/post-build';`,
1621
1937
  ``,
@@ -1830,7 +2146,7 @@ function generateShadcnComponentsJson(config) {
1830
2146
  }
1831
2147
 
1832
2148
  // src/templates/tsconfig.ts
1833
- function generateTsConfig() {
2149
+ function generateTsConfig(core = "preact") {
1834
2150
  const tsconfig = {
1835
2151
  compilerOptions: {
1836
2152
  target: "ESNext",
@@ -1843,6 +2159,7 @@ function generateTsConfig() {
1843
2159
  allowImportingTsExtensions: true,
1844
2160
  noEmit: true,
1845
2161
  jsx: "react-jsx",
2162
+ jsxImportSource: core,
1846
2163
  paths: {
1847
2164
  "@shared/*": ["./app/shared/*"],
1848
2165
  "@modules/*": ["./app/modules/*"]
@@ -1943,12 +2260,21 @@ function generateViteConfig(config) {
1943
2260
  if (needsTailwind) {
1944
2261
  pluginEntries.push(` tailwindcss(),`);
1945
2262
  }
2263
+ const cronLines = config.cron ? [
2264
+ ` // Scheduled jobs (cron). Each entry maps a schedule to a task file`,
2265
+ ` // in tasks/. See https://useavalon.dev/docs/cron-jobs`,
2266
+ ` cron: [`,
2267
+ ` { schedule: '${EXAMPLE_CRON_SCHEDULE}', handler: '${EXAMPLE_CRON_HANDLER}' },`,
2268
+ ` ],`
2269
+ ] : [];
2270
+ const compatLines = config.deploy === "cloudflare" ? [` compatibilityDate: '2026-09-04',`] : [];
1946
2271
  const lines = [
1947
2272
  imports.join(`
1948
2273
  `),
1949
2274
  "",
1950
2275
  `export default defineConfig(async (): Promise<UserConfig> => {`,
1951
2276
  ` const avalonPlugins = await avalon({`,
2277
+ ` core: '${config.core}',`,
1952
2278
  ` integrations: [${integrationsList}],`,
1953
2279
  ` modules: 'app/modules',`,
1954
2280
  ` layoutsDir: 'app/shared/layouts',`,
@@ -1956,6 +2282,7 @@ function generateViteConfig(config) {
1956
2282
  ` nitro: {`,
1957
2283
  ` preset: process.env.NITRO_PRESET || 'node_server',`,
1958
2284
  ` streaming: true,`,
2285
+ ...compatLines,
1959
2286
  ` clientEntry: 'app/entry-client',`,
1960
2287
  ` globalCSS: ['app/shared/styles/main.css'],`,
1961
2288
  ` prerender: {`,
@@ -1963,6 +2290,7 @@ function generateViteConfig(config) {
1963
2290
  ` crawlLinks: true,`,
1964
2291
  ` ignore: [],`,
1965
2292
  ` },`,
2293
+ ...cronLines,
1966
2294
  ` },`,
1967
2295
  ` });`,
1968
2296
  "",
@@ -2053,7 +2381,7 @@ async function scaffoldProject(config, targetDir) {
2053
2381
  await mkdir(join(targetDir, dir), { recursive: true });
2054
2382
  }
2055
2383
  await writeFile(join(targetDir, "package.json"), generatePackageJson(config));
2056
- await writeFile(join(targetDir, "tsconfig.json"), generateTsConfig());
2384
+ await writeFile(join(targetDir, "tsconfig.json"), generateTsConfig(config.core));
2057
2385
  await writeFile(join(targetDir, "vite.config.ts"), generateViteConfig(config));
2058
2386
  await writeFile(join(targetDir, "app/shared/layouts/_layout.tsx"), generateRootLayout(config));
2059
2387
  await writeFile(join(targetDir, "app/modules/main/layouts/_layout.tsx"), generateMainLayout(config));
@@ -2061,6 +2389,11 @@ async function scaffoldProject(config, targetDir) {
2061
2389
  await writeFile(join(targetDir, "app/modules/main/pages/404.tsx"), generate404Page());
2062
2390
  await writeFile(join(targetDir, "middleware/01.logger.ts"), generateSampleMiddleware(config));
2063
2391
  await writeFile(join(targetDir, "routes/api/hello.ts"), generateHelloRoute(config));
2392
+ if (config.cron) {
2393
+ const cronPath = join(targetDir, EXAMPLE_CRON_HANDLER);
2394
+ await mkdir(dirname(cronPath), { recursive: true });
2395
+ await writeFile(cronPath, generateExampleCronTask(config));
2396
+ }
2064
2397
  if (config.middleware === "hono") {
2065
2398
  await writeFile(join(targetDir, "server.ts"), generateHonoServerEntry());
2066
2399
  } else if (config.middleware === "elysia") {
@@ -2076,6 +2409,8 @@ async function scaffoldProject(config, targetDir) {
2076
2409
  await writeFile(join(targetDir, "server/env.d.ts"), `/// <reference types="nitro" />
2077
2410
  `);
2078
2411
  await writeFile(join(targetDir, "app/env.d.ts"), generateEnvDts(config.integrations));
2412
+ await writeFile(join(targetDir, "app/entry-client.ts"), `import "virtual:avalon/client-entry";
2413
+ `);
2079
2414
  await writeFile(join(targetDir, "server/renderer.ts"), [
2080
2415
  `/**`,
2081
2416
  ` * SSR Renderer — provided by Avalon's virtual module system.`,
@@ -2092,9 +2427,15 @@ async function scaffoldProject(config, targetDir) {
2092
2427
  `));
2093
2428
  await writeFile(join(targetDir, "build.mjs"), generateBuildMjs());
2094
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));
2095
2432
  if (config.deploy === "netlify") {
2096
2433
  await writeFile(join(targetDir, "netlify.toml"), generateNetlifyToml(config));
2097
2434
  }
2435
+ if (config.deploy === "cloudflare") {
2436
+ await writeFile(join(targetDir, "wrangler.toml"), generateWranglerToml(config));
2437
+ await writeFile(join(targetDir, "public/_headers"), generateCloudflareHeaders());
2438
+ }
2098
2439
  }
2099
2440
 
2100
2441
  // src/summary.ts
@@ -2104,15 +2445,34 @@ var STYLING_LABELS = {
2104
2445
  shadcn: "shadcn"
2105
2446
  };
2106
2447
  var DEPLOY_LABELS = {
2448
+ cloudflare: "Cloudflare Pages",
2107
2449
  netlify: "Netlify",
2108
2450
  none: "None"
2109
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
+ }
2110
2465
  function formatSummary(config, scaffoldedInPlace = false) {
2111
2466
  const integrations = config.integrations.length > 0 ? config.integrations.join(", ") : "none";
2112
2467
  const styling = STYLING_LABELS[config.styling] ?? config.styling;
2113
2468
  const plugins = config.plugins.length > 0 ? config.plugins.join(", ") : "none";
2114
2469
  const deploy = DEPLOY_LABELS[config.deploy] ?? config.deploy;
2115
- 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
+ ];
2116
2476
  return [
2117
2477
  "",
2118
2478
  ` Project: ${config.projectName}`,
@@ -2120,7 +2480,8 @@ function formatSummary(config, scaffoldedInPlace = false) {
2120
2480
  ` Styling: ${styling}`,
2121
2481
  ` Plugins: ${plugins}`,
2122
2482
  ` Middleware: ${config.middleware}`,
2123
- ` Deploy: ${deploy}`,
2483
+ ` Deploy: ${deploy}`,
2484
+ ` Cron: ${config.cron ? "yes" : "no"}`,
2124
2485
  "",
2125
2486
  " Next steps:",
2126
2487
  ...nextSteps,
@@ -2142,11 +2503,28 @@ async function main() {
2142
2503
  process.exit(0);
2143
2504
  }
2144
2505
  if (args.help) {
2145
- console.log(`Usage: create-avalon [project-name]
2146
-
2147
- Options:
2148
- -v, --version Show version number
2149
- -h, --help Show help`);
2506
+ console.log([
2507
+ "Usage: create-avalon [project-name] [options]",
2508
+ "",
2509
+ "Runs interactively by default. Pass --yes (or run without a TTY, e.g. in",
2510
+ "CI/Docker) to scaffold non-interactively from flags + defaults.",
2511
+ "",
2512
+ "Options:",
2513
+ " -v, --version Show version number",
2514
+ " -h, --help Show help",
2515
+ " -y, --yes Skip prompts; use flags and defaults",
2516
+ " --core Rendering engine: preact (default) | react",
2517
+ " --integrations Comma list: preact,react,vue,svelte,solid,lit,qwik",
2518
+ " --styling css-modules (default) | tailwind | shadcn",
2519
+ " --plugins Comma list: seo (default),agent-optimization,syntax-highlighting",
2520
+ " --middleware h3 (default) | hono | elysia",
2521
+ " --deploy cloudflare | netlify | none (default)",
2522
+ " --cron Scaffold an example cron task + config",
2523
+ "",
2524
+ "Example:",
2525
+ " create-avalon my-app --yes --core react --integrations react,vue --styling shadcn"
2526
+ ].join(`
2527
+ `));
2150
2528
  process.exit(0);
2151
2529
  }
2152
2530
  if (args.projectName && args.projectName !== ".") {
@@ -2156,7 +2534,8 @@ Options:
2156
2534
  process.exit(1);
2157
2535
  }
2158
2536
  }
2159
- const config = await collectProjectConfig(args.projectName);
2537
+ const nonInteractive = args.yes || !process.stdin.isTTY;
2538
+ const config = nonInteractive ? resolveConfigNonInteractive(args) : await collectProjectConfig(args.projectName);
2160
2539
  if (!args.projectName && config.projectName !== ".") {
2161
2540
  const dirResult = validateDirectory(resolve(config.projectName));
2162
2541
  if (!dirResult.valid) {
@@ -2173,4 +2552,9 @@ Options:
2173
2552
  printSummary(config, scaffoldedInPlace);
2174
2553
  process.exit(0);
2175
2554
  }
2176
- main();
2555
+ try {
2556
+ await main();
2557
+ } catch (error) {
2558
+ console.error(error instanceof CliArgError ? error.message : error);
2559
+ process.exit(1);
2560
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-avalon",
3
- "version": "0.1.24",
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",