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.
- package/README.md +24 -6
- package/dist/cli.js +611 -227
- 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
|
-
-
|
|
23
|
-
- Styling approach (CSS Modules, Tailwind,
|
|
24
|
-
-
|
|
25
|
-
-
|
|
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
|
-
│ │ └──
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
507
|
-
return
|
|
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
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
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,
|
|
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
|
|
792
|
+
const T = e.slice(A, b) || e.slice($, m);
|
|
766
793
|
F = 0;
|
|
767
|
-
for (const
|
|
768
|
-
const
|
|
769
|
-
if (gt2(
|
|
770
|
-
|
|
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 +=
|
|
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,
|
|
781
|
-
|
|
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 +=
|
|
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
|
-
|
|
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,
|
|
797
|
-
|
|
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 +=
|
|
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,
|
|
805
|
-
|
|
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 +=
|
|
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
|
-
|
|
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:
|
|
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
|
|
857
|
+
var wt = "]";
|
|
831
858
|
var Ve = "m";
|
|
832
|
-
var Se = `${
|
|
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
|
|
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,
|
|
884
|
-
Math.floor((l[$] - 1) / 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(($) =>
|
|
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
|
|
909
|
-
if (
|
|
910
|
-
const v = Number.parseFloat(
|
|
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
|
-
|
|
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
|
|
947
|
-
for (let 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
|
|
955
|
-
E ? ({ lineCount: w, removals: A } =
|
|
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
|
-
{
|
|
1160
|
-
|
|
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
|
-
{
|
|
1190
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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: "
|
|
1506
|
-
typescript: "
|
|
1507
|
-
nitro: "
|
|
1508
|
-
"vite-imagetools": "
|
|
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
|
|
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"] : [
|
|
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:
|
|
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(
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
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
|
|
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
|
-
|
|
2555
|
+
try {
|
|
2556
|
+
await main();
|
|
2557
|
+
} catch (error) {
|
|
2558
|
+
console.error(error instanceof CliArgError ? error.message : error);
|
|
2559
|
+
process.exit(1);
|
|
2560
|
+
}
|