create-avalon 0.1.23 → 0.1.25

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 +2 -0
  2. package/dist/cli.js +430 -57
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -22,6 +22,7 @@ The CLI walks you through:
22
22
  - Framework selection (React, Preact, Vue, Svelte, Solid, Lit, Qwik — or multiple)
23
23
  - Styling approach (CSS Modules, Tailwind, vanilla CSS)
24
24
  - Optional features (API routes, middleware, layouts, MDX)
25
+ - Scheduled jobs (cron) — scaffolds an example task and wires up `nitro.cron`
25
26
  - Package manager preference
26
27
 
27
28
  ## What you get
@@ -43,6 +44,7 @@ my-project/
43
44
  ├── middleware/ # Server middleware
44
45
  ├── routes/
45
46
  │ └── api/ # API routes
47
+ ├── tasks/ # Scheduled jobs (cron) — optional
46
48
  ├── server/ # Server config & env
47
49
  ├── public/ # Static assets
48
50
  ├── vite.config.ts
package/dist/cli.js CHANGED
@@ -76,12 +76,65 @@ var require_src = __commonJS((exports, module) => {
76
76
  });
77
77
 
78
78
  // src/cli.ts
79
- import { basename, resolve } from "node:path";
80
79
  import { createRequire } from "node:module";
80
+ import { basename, resolve } from "node:path";
81
81
 
82
82
  // src/cli-utils.ts
83
- import { parseArgs } from "node:util";
84
83
  import { existsSync, readdirSync } from "node:fs";
84
+ import { parseArgs } from "node:util";
85
+
86
+ // src/types.ts
87
+ var RENDER_ENGINES = ["preact", "react"];
88
+ var INTEGRATIONS = [
89
+ "preact",
90
+ "react",
91
+ "vue",
92
+ "svelte",
93
+ "solid",
94
+ "lit",
95
+ "qwik"
96
+ ];
97
+ var STYLING_OPTIONS = [
98
+ "css-modules",
99
+ "tailwind",
100
+ "shadcn"
101
+ ];
102
+ var PLUGINS = [
103
+ "seo",
104
+ "agent-optimization",
105
+ "syntax-highlighting"
106
+ ];
107
+ var MIDDLEWARE_OPTIONS = [
108
+ "h3",
109
+ "hono",
110
+ "elysia"
111
+ ];
112
+ var DEPLOY_TARGETS = ["netlify", "none"];
113
+ var INTEGRATION_PACKAGES = {
114
+ preact: "@useavalon/preact",
115
+ react: "@useavalon/react",
116
+ vue: "@useavalon/vue",
117
+ svelte: "@useavalon/svelte",
118
+ solid: "@useavalon/solid",
119
+ lit: "@useavalon/lit",
120
+ qwik: "@useavalon/qwik"
121
+ };
122
+ var BASE_DIRS = [
123
+ "app/modules/main/pages",
124
+ "app/modules/main/components",
125
+ "app/modules/main/layouts",
126
+ "app/shared/layouts",
127
+ "app/shared/components",
128
+ "app/shared/styles",
129
+ "middleware",
130
+ "routes/api",
131
+ "public",
132
+ "server"
133
+ ];
134
+
135
+ // src/cli-utils.ts
136
+ class CliArgError extends Error {
137
+ }
85
138
  function validateDirectory(dir) {
86
139
  if (!existsSync(dir)) {
87
140
  return { valid: true };
@@ -100,7 +153,15 @@ function parseCliArgs(argv) {
100
153
  args: argv,
101
154
  options: {
102
155
  help: { type: "boolean", default: false, short: "h" },
103
- version: { type: "boolean", default: false, short: "v" }
156
+ version: { type: "boolean", default: false, short: "v" },
157
+ yes: { type: "boolean", default: false, short: "y" },
158
+ core: { type: "string" },
159
+ integrations: { type: "string" },
160
+ styling: { type: "string" },
161
+ plugins: { type: "string" },
162
+ middleware: { type: "string" },
163
+ deploy: { type: "string" },
164
+ cron: { type: "boolean", default: false }
104
165
  },
105
166
  strict: true,
106
167
  allowPositionals: true
@@ -108,7 +169,58 @@ function parseCliArgs(argv) {
108
169
  return {
109
170
  projectName: positionals[0] ?? undefined,
110
171
  help: values.help ?? false,
111
- version: values.version ?? false
172
+ version: values.version ?? false,
173
+ yes: values.yes ?? false,
174
+ core: values.core,
175
+ integrations: values.integrations,
176
+ styling: values.styling,
177
+ plugins: values.plugins,
178
+ middleware: values.middleware,
179
+ deploy: values.deploy,
180
+ cron: values.cron ?? false
181
+ };
182
+ }
183
+ var csv = (value) => value ? value.split(",").map((v) => v.trim()).filter(Boolean) : undefined;
184
+ function assertOneOf(value, allowed, flag) {
185
+ if (value === undefined)
186
+ return;
187
+ if (!allowed.includes(value)) {
188
+ throw new CliArgError(`Invalid value "${value}" for --${flag}. Allowed: ${allowed.join(", ")}.`);
189
+ }
190
+ return value;
191
+ }
192
+ function assertAllOf(values, allowed, flag) {
193
+ if (values === undefined)
194
+ return;
195
+ for (const value of values) {
196
+ if (!allowed.includes(value)) {
197
+ throw new CliArgError(`Invalid value "${value}" for --${flag}. Allowed: ${allowed.join(", ")}.`);
198
+ }
199
+ }
200
+ return values;
201
+ }
202
+ function resolveConfigNonInteractive(args) {
203
+ const core = assertOneOf(args.core, RENDER_ENGINES, "core") ?? "preact";
204
+ const integrations = assertAllOf(csv(args.integrations), INTEGRATIONS, "integrations") ?? [];
205
+ const styling = assertOneOf(args.styling, STYLING_OPTIONS, "styling") ?? "css-modules";
206
+ const plugins = assertAllOf(csv(args.plugins), PLUGINS, "plugins") ?? ["seo"];
207
+ const middleware = assertOneOf(args.middleware, MIDDLEWARE_OPTIONS, "middleware") ?? "h3";
208
+ const deploy = assertOneOf(args.deploy, DEPLOY_TARGETS, "deploy") ?? "none";
209
+ if (styling === "shadcn" && core !== "react") {
210
+ throw new CliArgError("--styling=shadcn requires --core=react (shadcn is Radix/React based).");
211
+ }
212
+ if (core === "react" && !integrations.includes("react")) {
213
+ integrations.push("react");
214
+ }
215
+ return {
216
+ projectName: args.projectName ?? ".",
217
+ core,
218
+ integrations,
219
+ styling,
220
+ plugins,
221
+ middleware,
222
+ deploy,
223
+ cron: args.cron
112
224
  };
113
225
  }
114
226
 
@@ -543,6 +655,25 @@ class Vt extends B {
543
655
  }
544
656
  }
545
657
  }
658
+
659
+ class kt extends B {
660
+ get cursor() {
661
+ return this.value ? 0 : 1;
662
+ }
663
+ get _value() {
664
+ return this.cursor === 0;
665
+ }
666
+ constructor(e) {
667
+ super(e, false), this.value = !!e.initialValue, this.on("userInput", () => {
668
+ this.value = this._value;
669
+ }), this.on("confirm", (s) => {
670
+ this.output.write(import_sisteransi.cursor.move(0, -1)), this.value = s, this.state = "submit", this.close();
671
+ }), this.on("cursor", () => {
672
+ this.value = !this.value;
673
+ });
674
+ }
675
+ }
676
+
546
677
  class yt extends B {
547
678
  options;
548
679
  cursor = 0;
@@ -961,6 +1092,33 @@ var X2 = ({ cursor: e, options: r, style: s, output: i = process.stdout, maxItem
961
1092
  C.push(b);
962
1093
  return $ && C.push(c), C;
963
1094
  };
1095
+ var Rt = (e) => {
1096
+ const r = e.active ?? "Yes", s = e.inactive ?? "No";
1097
+ return new kt({ active: r, inactive: s, signal: e.signal, input: e.input, output: e.output, initialValue: e.initialValue ?? true, render() {
1098
+ const i = e.withGuide ?? _.withGuide, a = `${i ? `${t("gray", h)}
1099
+ ` : ""}${W2(this.state)} ${e.message}
1100
+ `, o = this.value ? r : s;
1101
+ switch (this.state) {
1102
+ case "submit": {
1103
+ const u = i ? `${t("gray", h)} ` : "";
1104
+ return `${a}${u}${t("dim", o)}`;
1105
+ }
1106
+ case "cancel": {
1107
+ const u = i ? `${t("gray", h)} ` : "";
1108
+ return `${a}${u}${t(["strikethrough", "dim"], o)}${i ? `
1109
+ ${t("gray", h)}` : ""}`;
1110
+ }
1111
+ default: {
1112
+ const u = i ? `${t("cyan", h)} ` : "", l = i ? t("cyan", x2) : "";
1113
+ return `${a}${u}${this.value ? `${t("green", z2)} ${r}` : `${t("dim", H2)} ${t("dim", r)}`}${e.vertical ? i ? `
1114
+ ${t("cyan", h)} ` : `
1115
+ ` : ` ${t("dim", "/")} `}${this.value ? `${t("dim", H2)} ${t("dim", s)}` : `${t("green", z2)} ${s}`}
1116
+ ${l}
1117
+ `;
1118
+ }
1119
+ }
1120
+ } }).prompt();
1121
+ };
964
1122
  var Nt = (e = "", r) => {
965
1123
  const s = r?.output ?? process.stdout, i = r?.withGuide ?? _.withGuide ? `${t("gray", x2)} ` : "";
966
1124
  s.write(`${i}${t("red", e)}
@@ -1124,6 +1282,26 @@ async function collectProjectConfig(initialName) {
1124
1282
  }
1125
1283
  projectName = nameResult;
1126
1284
  }
1285
+ const coreResult = await Jt({
1286
+ message: "Which rendering engine should render your pages (the shell)?",
1287
+ options: [
1288
+ {
1289
+ value: "preact",
1290
+ label: "Preact",
1291
+ hint: "Smallest runtime (default). React libs run via preact/compat."
1292
+ },
1293
+ {
1294
+ value: "react",
1295
+ label: "React",
1296
+ hint: "Real react-dom/server. React libs like Radix/shadcn work natively."
1297
+ }
1298
+ ],
1299
+ initialValue: "preact"
1300
+ });
1301
+ if (Ct(coreResult)) {
1302
+ Nt("Operation cancelled.");
1303
+ process.exit(1);
1304
+ }
1127
1305
  const integrationsResult = await Lt2({
1128
1306
  message: "Which integrations would you like to include? (use space to toggle, enter to confirm)",
1129
1307
  options: [
@@ -1141,13 +1319,20 @@ async function collectProjectConfig(initialName) {
1141
1319
  Nt("Operation cancelled.");
1142
1320
  process.exit(1);
1143
1321
  }
1322
+ const stylingOptions = [
1323
+ { value: "css-modules", label: "CSS Modules" },
1324
+ { value: "tailwind", label: "Tailwind CSS" }
1325
+ ];
1326
+ if (coreResult === "react") {
1327
+ stylingOptions.push({
1328
+ value: "shadcn",
1329
+ label: "shadcn",
1330
+ hint: "Radix-based components — requires the React engine"
1331
+ });
1332
+ }
1144
1333
  const stylingResult = await Jt({
1145
1334
  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
- ]
1335
+ options: stylingOptions
1151
1336
  });
1152
1337
  if (Ct(stylingResult)) {
1153
1338
  Nt("Operation cancelled.");
@@ -1156,13 +1341,23 @@ async function collectProjectConfig(initialName) {
1156
1341
  const pluginsResult = await Lt2({
1157
1342
  message: "Which plugins would you like to include? (use space to toggle, enter to confirm)",
1158
1343
  options: [
1159
- { value: "agent-optimization", label: "agent-optimization", hint: "LLM/AI optimization" },
1344
+ {
1345
+ value: "seo",
1346
+ label: "seo",
1347
+ hint: "Auto-injects OG, Twitter cards, JSON-LD, canonical URLs"
1348
+ },
1349
+ {
1350
+ value: "agent-optimization",
1351
+ label: "agent-optimization",
1352
+ hint: "LLM/AI optimization (llms.txt, markdown, sitemap)"
1353
+ },
1160
1354
  {
1161
1355
  value: "syntax-highlighting",
1162
1356
  label: "syntax-highlighting",
1163
1357
  hint: "Code block highlighting for MDX (rehype-highlight)"
1164
1358
  }
1165
1359
  ],
1360
+ initialValues: ["seo"],
1166
1361
  required: false
1167
1362
  });
1168
1363
  if (Ct(pluginsResult)) {
@@ -1184,7 +1379,11 @@ async function collectProjectConfig(initialName) {
1184
1379
  const deployResult = await Jt({
1185
1380
  message: "Where will you deploy?",
1186
1381
  options: [
1187
- { value: "netlify", label: "Netlify", hint: "Generates netlify.toml, build.mjs, post-build.mjs" },
1382
+ {
1383
+ value: "netlify",
1384
+ label: "Netlify",
1385
+ hint: "Generates netlify.toml, build.mjs, post-build.mjs"
1386
+ },
1188
1387
  { value: "none", label: "None / Other", hint: "Node server preset, no deploy config" }
1189
1388
  ]
1190
1389
  });
@@ -1192,13 +1391,28 @@ async function collectProjectConfig(initialName) {
1192
1391
  Nt("Operation cancelled.");
1193
1392
  process.exit(1);
1194
1393
  }
1394
+ const cronResult = await Rt({
1395
+ message: "Set up scheduled jobs (cron)?",
1396
+ initialValue: false
1397
+ });
1398
+ if (Ct(cronResult)) {
1399
+ Nt("Operation cancelled.");
1400
+ process.exit(1);
1401
+ }
1402
+ const core = coreResult;
1403
+ const integrations = integrationsResult;
1404
+ if (core === "react" && !integrations.includes("react")) {
1405
+ integrations.push("react");
1406
+ }
1195
1407
  return {
1196
1408
  projectName,
1197
- integrations: integrationsResult,
1409
+ core,
1410
+ integrations,
1198
1411
  styling: stylingResult,
1199
1412
  plugins: pluginsResult,
1200
1413
  middleware: middlewareResult,
1201
- deploy: deployResult
1414
+ deploy: deployResult,
1415
+ cron: cronResult
1202
1416
  };
1203
1417
  }
1204
1418
 
@@ -1216,6 +1430,31 @@ export default defineHandler(() => {
1216
1430
  `;
1217
1431
  }
1218
1432
 
1433
+ // src/templates/cron.ts
1434
+ var EXAMPLE_CRON_HANDLER = "tasks/cleanup.ts";
1435
+ var EXAMPLE_CRON_SCHEDULE = "0 * * * *";
1436
+ function generateExampleCronTask(_config) {
1437
+ return `import { defineCronJob } from '@useavalon/avalon/cron';
1438
+
1439
+ /**
1440
+ * Example scheduled job. Runs on the schedule defined in vite.config.ts
1441
+ * (\`nitro.cron\`). The task name is derived from this file path: "cleanup".
1442
+ *
1443
+ * In production this runs via your deployment preset's scheduler
1444
+ * (Vercel Cron, Cloudflare Triggers, or the Node server's in-process
1445
+ * scheduler). In development Avalon runs it inside the Vite dev server.
1446
+ */
1447
+ export default defineCronJob({
1448
+ meta: { description: 'Example scheduled job' },
1449
+ async run() {
1450
+ console.log('[cron] cleanup ran at', new Date().toISOString());
1451
+ // TODO: replace with your scheduled work.
1452
+ return { result: 'ok' };
1453
+ },
1454
+ });
1455
+ `;
1456
+ }
1457
+
1219
1458
  // src/templates/deploy.ts
1220
1459
  function generateNetlifyToml(_config) {
1221
1460
  return `[build]
@@ -1335,6 +1574,62 @@ const absoluteTimeout = setTimeout(() => {
1335
1574
  }, 240_000);
1336
1575
  `;
1337
1576
  }
1577
+ function generateRobotsTxt(sitemapUrl = "https://YOUR_DOMAIN/sitemap.xml") {
1578
+ return `# robots.txt
1579
+
1580
+ User-agent: *
1581
+ Allow: /
1582
+
1583
+ # Sitemap
1584
+ Sitemap: ${sitemapUrl}
1585
+
1586
+ # AI Crawlers — explicitly allowed
1587
+ User-agent: GPTBot
1588
+ Allow: /
1589
+
1590
+ User-agent: ChatGPT-User
1591
+ Allow: /
1592
+
1593
+ User-agent: Google-Extended
1594
+ Allow: /
1595
+
1596
+ User-agent: PerplexityBot
1597
+ Allow: /
1598
+
1599
+ User-agent: OAI-SearchBot
1600
+ Allow: /
1601
+
1602
+ User-agent: Applebot-Extended
1603
+ Allow: /
1604
+
1605
+ User-agent: Amazonbot
1606
+ Allow: /
1607
+
1608
+ User-agent: ClaudeBot
1609
+ Allow: /
1610
+
1611
+ User-agent: Bytespider
1612
+ Allow: /
1613
+
1614
+ User-agent: cohere-ai
1615
+ Allow: /
1616
+
1617
+ User-agent: Diffbot
1618
+ Allow: /
1619
+
1620
+ User-agent: anthropic-ai
1621
+ Allow: /
1622
+
1623
+ User-agent: Claude-Web
1624
+ Allow: /
1625
+
1626
+ User-agent: CCBot
1627
+ Allow: /
1628
+
1629
+ User-agent: AI2Bot
1630
+ Allow: /
1631
+ `;
1632
+ }
1338
1633
 
1339
1634
  // src/templates/favicon.ts
1340
1635
  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=";
@@ -1351,16 +1646,21 @@ function generateRootLayout(config) {
1351
1646
  } else {
1352
1647
  imports.push(`import '../styles/main.css';`);
1353
1648
  }
1649
+ const safeName = config.projectName.replace(/\\/g, "\\\\").replace(/'/g, "\\'").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
1354
1650
  return `${imports.join(`
1355
1651
  `)}
1356
1652
 
1357
- export default async function RootLayout({ children }: Readonly<LayoutProps>) {
1653
+ export default async function RootLayout({ children, frontmatter }: Readonly<LayoutProps>) {
1654
+ const title = frontmatter?.title ?? '${safeName}';
1655
+ const description = frontmatter?.description ?? '';
1656
+
1358
1657
  return (
1359
1658
  <html lang="en">
1360
1659
  <head>
1361
1660
  <meta charset="UTF-8" />
1362
1661
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
1363
- <title>${config.projectName}</title>
1662
+ <title>{title}</title>
1663
+ {description && <meta name="description" content={description} />}
1364
1664
  <link rel="icon" href="/favicon.ico" />
1365
1665
  </head>
1366
1666
  <body style={{ margin: 0 }}>
@@ -1371,10 +1671,10 @@ export default async function RootLayout({ children }: Readonly<LayoutProps>) {
1371
1671
  }
1372
1672
  `;
1373
1673
  }
1374
- function generateHomeLayout(config) {
1674
+ function generateMainLayout(config) {
1375
1675
  return `import type { LayoutProps } from '@useavalon/avalon';
1376
1676
 
1377
- export default async function HomeLayout({ children }: Readonly<LayoutProps>) {
1677
+ export default async function MainLayout({ children }: Readonly<LayoutProps>) {
1378
1678
  return <>{children}</>;
1379
1679
  }
1380
1680
  `;
@@ -1390,36 +1690,31 @@ export default defineHandler((event) => {
1390
1690
  `;
1391
1691
  }
1392
1692
 
1393
- // src/types.ts
1394
- var INTEGRATION_PACKAGES = {
1395
- preact: "@useavalon/preact",
1396
- react: "@useavalon/react",
1397
- vue: "@useavalon/vue",
1398
- svelte: "@useavalon/svelte",
1399
- solid: "@useavalon/solid",
1400
- lit: "@useavalon/lit",
1401
- qwik: "@useavalon/qwik"
1402
- };
1403
- var BASE_DIRS = [
1404
- "app/modules/home/pages",
1405
- "app/modules/home/components",
1406
- "app/modules/home/layouts",
1407
- "app/shared/layouts",
1408
- "app/shared/components",
1409
- "app/shared/styles",
1410
- "middleware",
1411
- "routes/api",
1412
- "public",
1413
- "server"
1414
- ];
1415
-
1416
1693
  // src/templates/package-json.ts
1694
+ var INTEGRATION_RUNTIME_DEPS = {
1695
+ preact: { preact: "^10.0.0", "preact-render-to-string": "^6.0.0" },
1696
+ react: { react: "^19.0.0", "react-dom": "^19.0.0" },
1697
+ vue: { vue: "^3.4.0" },
1698
+ svelte: { svelte: "^5.0.0" },
1699
+ solid: { "solid-js": "^1.8.0" },
1700
+ lit: {
1701
+ lit: "^3.0.0",
1702
+ "@lit-labs/ssr": "^4.0.0",
1703
+ "@lit-labs/ssr-client": "^1.0.0",
1704
+ "@lit-labs/ssr-dom-shim": "^1.0.0"
1705
+ },
1706
+ qwik: { "@builder.io/qwik": "^1.5.0" }
1707
+ };
1417
1708
  function generatePackageJson(config) {
1418
1709
  const dependencies = {
1419
1710
  "@useavalon/avalon": "latest"
1420
1711
  };
1421
1712
  for (const integration of config.integrations) {
1422
1713
  dependencies[INTEGRATION_PACKAGES[integration]] = "latest";
1714
+ Object.assign(dependencies, INTEGRATION_RUNTIME_DEPS[integration]);
1715
+ }
1716
+ if (config.plugins.includes("seo")) {
1717
+ dependencies["@useavalon/seo"] = "latest";
1423
1718
  }
1424
1719
  if (config.plugins.includes("agent-optimization")) {
1425
1720
  dependencies["@useavalon/agent-optimization"] = "latest";
@@ -1470,7 +1765,7 @@ function generatePackageJson(config) {
1470
1765
  }
1471
1766
 
1472
1767
  // src/templates/pages.ts
1473
- function generateHomePage(config) {
1768
+ function generateMainPage(config) {
1474
1769
  return `export const metadata = {
1475
1770
  title: 'Avalon — Islands Architecture',
1476
1771
  description: 'A multi-framework islands architecture project powered by Avalon.',
@@ -1508,7 +1803,7 @@ export default async function HomePage() {
1508
1803
  <div style={{ position: 'relative', zIndex: 1, marginTop: '4rem', display: 'flex', flexDirection: 'column', alignItems: 'center', gap: '0.75rem' }}>
1509
1804
  <p style={{ fontSize: '0.75rem', color: '#64748b', letterSpacing: '0.1em', textTransform: 'uppercase' }}>Get started</p>
1510
1805
  <code style={{ display: 'block', padding: '0.6rem 1.2rem', borderRadius: '6px', background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(255,255,255,0.08)', color: '#a5b4fc', fontSize: '0.85rem', fontFamily: 'ui-monospace, monospace' }}>
1511
- Edit app/modules/home/pages/index.tsx
1806
+ Edit app/modules/main/pages/index.tsx
1512
1807
  </code>
1513
1808
  </div>
1514
1809
 
@@ -1523,6 +1818,24 @@ export default async function HomePage() {
1523
1818
  }
1524
1819
  `;
1525
1820
  }
1821
+ function generate404Page() {
1822
+ return `export const metadata = {
1823
+ title: '404 - Page Not Found',
1824
+ description: 'The page you are looking for does not exist.',
1825
+ robots: 'noindex, nofollow',
1826
+ };
1827
+
1828
+ export default function NotFoundPage() {
1829
+ return (
1830
+ <div style={{ minHeight: '100vh', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', fontFamily: 'system-ui, -apple-system, sans-serif', textAlign: 'center', padding: '2rem' }}>
1831
+ <h1 style={{ fontSize: '4rem', margin: '0 0 1rem' }}>404</h1>
1832
+ <p style={{ fontSize: '1.25rem', color: '#64748b', margin: '0 0 2rem' }}>Page not found</p>
1833
+ <a href="/" style={{ color: '#6366f1', textDecoration: 'none', fontWeight: 500 }}>Go Home</a>
1834
+ </div>
1835
+ );
1836
+ }
1837
+ `;
1838
+ }
1526
1839
 
1527
1840
  // src/templates/post-build.ts
1528
1841
  function generatePostBuildMjs() {
@@ -1558,8 +1871,8 @@ function generateStylingFiles(config) {
1558
1871
  case "css-modules":
1559
1872
  files.set("app/shared/styles/tokens.css", generateTokensCss());
1560
1873
  files.set("app/shared/layouts/_layout.module.css", generateLayoutModuleCss());
1561
- files.set("app/modules/home/pages/index.module.css", generatePageModuleCss());
1562
- files.set("app/modules/home/layouts/_layout.module.css", generateLayoutModuleCss());
1874
+ files.set("app/modules/main/pages/index.module.css", generatePageModuleCss());
1875
+ files.set("app/modules/main/layouts/_layout.module.css", generateLayoutModuleCss());
1563
1876
  break;
1564
1877
  case "tailwind":
1565
1878
  files.set("tailwind.config.js", generateTailwindConfig());
@@ -1746,7 +2059,7 @@ function generateShadcnComponentsJson(config) {
1746
2059
  }
1747
2060
 
1748
2061
  // src/templates/tsconfig.ts
1749
- function generateTsConfig() {
2062
+ function generateTsConfig(core = "preact") {
1750
2063
  const tsconfig = {
1751
2064
  compilerOptions: {
1752
2065
  target: "ESNext",
@@ -1759,6 +2072,7 @@ function generateTsConfig() {
1759
2072
  allowImportingTsExtensions: true,
1760
2073
  noEmit: true,
1761
2074
  jsx: "react-jsx",
2075
+ jsxImportSource: core,
1762
2076
  paths: {
1763
2077
  "@shared/*": ["./app/shared/*"],
1764
2078
  "@modules/*": ["./app/modules/*"]
@@ -1819,20 +2133,37 @@ function generateViteConfig(config) {
1819
2133
  if (needsTailwind) {
1820
2134
  imports.push(`import tailwindcss from '@tailwindcss/vite';`);
1821
2135
  }
2136
+ const hasSeo = config.plugins.includes("seo");
2137
+ if (hasSeo) {
2138
+ imports.push(`import { seo } from '@useavalon/seo';`);
2139
+ }
1822
2140
  const hasAgentOptimization = config.plugins.includes("agent-optimization");
1823
2141
  if (hasAgentOptimization) {
1824
2142
  imports.push(`import { agentOptimization } from '@useavalon/agent-optimization';`);
1825
2143
  }
1826
2144
  const integrationsList = config.integrations.map((i) => `'${i}'`).join(", ");
1827
2145
  const pluginEntries = [];
2146
+ if (hasSeo) {
2147
+ pluginEntries.push(` seo({
2148
+ siteUrl: 'http://localhost:3000',
2149
+ siteName: '${config.projectName.replace(/'/g, "\\'")}',
2150
+ defaultDescription: 'Built with Avalon',
2151
+ defaultOgImage: {
2152
+ url: '/og-image.png',
2153
+ width: 1200,
2154
+ height: 630,
2155
+ },
2156
+ breadcrumbs: true,
2157
+ speakable: true,
2158
+ }),`);
2159
+ }
1828
2160
  if (hasAgentOptimization) {
1829
2161
  pluginEntries.push(` agentOptimization({
1830
2162
  sitemap: { siteUrl: 'http://localhost:3000' },
1831
2163
  markdown: true,
1832
- structuredData: true,
1833
2164
  llms: {
1834
2165
  siteUrl: 'http://localhost:3000',
1835
- siteName: 'My Avalon App',
2166
+ siteName: '${config.projectName.replace(/'/g, "\\'")}',
1836
2167
  siteDescription: 'Built with Avalon',
1837
2168
  sections: { 'Pages': ['/'] },
1838
2169
  },
@@ -1842,12 +2173,20 @@ function generateViteConfig(config) {
1842
2173
  if (needsTailwind) {
1843
2174
  pluginEntries.push(` tailwindcss(),`);
1844
2175
  }
2176
+ const cronLines = config.cron ? [
2177
+ ` // Scheduled jobs (cron). Each entry maps a schedule to a task file`,
2178
+ ` // in tasks/. See https://useavalon.dev/docs/cron-jobs`,
2179
+ ` cron: [`,
2180
+ ` { schedule: '${EXAMPLE_CRON_SCHEDULE}', handler: '${EXAMPLE_CRON_HANDLER}' },`,
2181
+ ` ],`
2182
+ ] : [];
1845
2183
  const lines = [
1846
2184
  imports.join(`
1847
2185
  `),
1848
2186
  "",
1849
2187
  `export default defineConfig(async (): Promise<UserConfig> => {`,
1850
2188
  ` const avalonPlugins = await avalon({`,
2189
+ ` core: '${config.core}',`,
1851
2190
  ` integrations: [${integrationsList}],`,
1852
2191
  ` modules: 'app/modules',`,
1853
2192
  ` layoutsDir: 'app/shared/layouts',`,
@@ -1862,6 +2201,7 @@ function generateViteConfig(config) {
1862
2201
  ` crawlLinks: true,`,
1863
2202
  ` ignore: [],`,
1864
2203
  ` },`,
2204
+ ...cronLines,
1865
2205
  ` },`,
1866
2206
  ` });`,
1867
2207
  "",
@@ -1952,13 +2292,19 @@ async function scaffoldProject(config, targetDir) {
1952
2292
  await mkdir(join(targetDir, dir), { recursive: true });
1953
2293
  }
1954
2294
  await writeFile(join(targetDir, "package.json"), generatePackageJson(config));
1955
- await writeFile(join(targetDir, "tsconfig.json"), generateTsConfig());
2295
+ await writeFile(join(targetDir, "tsconfig.json"), generateTsConfig(config.core));
1956
2296
  await writeFile(join(targetDir, "vite.config.ts"), generateViteConfig(config));
1957
2297
  await writeFile(join(targetDir, "app/shared/layouts/_layout.tsx"), generateRootLayout(config));
1958
- await writeFile(join(targetDir, "app/modules/home/layouts/_layout.tsx"), generateHomeLayout(config));
1959
- await writeFile(join(targetDir, "app/modules/home/pages/index.tsx"), generateHomePage(config));
2298
+ await writeFile(join(targetDir, "app/modules/main/layouts/_layout.tsx"), generateMainLayout(config));
2299
+ await writeFile(join(targetDir, "app/modules/main/pages/index.tsx"), generateMainPage(config));
2300
+ await writeFile(join(targetDir, "app/modules/main/pages/404.tsx"), generate404Page());
1960
2301
  await writeFile(join(targetDir, "middleware/01.logger.ts"), generateSampleMiddleware(config));
1961
2302
  await writeFile(join(targetDir, "routes/api/hello.ts"), generateHelloRoute(config));
2303
+ if (config.cron) {
2304
+ const cronPath = join(targetDir, EXAMPLE_CRON_HANDLER);
2305
+ await mkdir(dirname(cronPath), { recursive: true });
2306
+ await writeFile(cronPath, generateExampleCronTask(config));
2307
+ }
1962
2308
  if (config.middleware === "hono") {
1963
2309
  await writeFile(join(targetDir, "server.ts"), generateHonoServerEntry());
1964
2310
  } else if (config.middleware === "elysia") {
@@ -1970,9 +2316,12 @@ async function scaffoldProject(config, targetDir) {
1970
2316
  await writeFile(join(targetDir, filePath), content);
1971
2317
  }
1972
2318
  await writeFile(join(targetDir, "public/favicon.ico"), getFaviconBuffer());
2319
+ await writeFile(join(targetDir, "public/robots.txt"), generateRobotsTxt());
1973
2320
  await writeFile(join(targetDir, "server/env.d.ts"), `/// <reference types="nitro" />
1974
2321
  `);
1975
2322
  await writeFile(join(targetDir, "app/env.d.ts"), generateEnvDts(config.integrations));
2323
+ await writeFile(join(targetDir, "app/entry-client.ts"), `import "virtual:avalon/client-entry";
2324
+ `);
1976
2325
  await writeFile(join(targetDir, "server/renderer.ts"), [
1977
2326
  `/**`,
1978
2327
  ` * SSR Renderer — provided by Avalon's virtual module system.`,
@@ -2018,6 +2367,7 @@ function formatSummary(config, scaffoldedInPlace = false) {
2018
2367
  ` Plugins: ${plugins}`,
2019
2368
  ` Middleware: ${config.middleware}`,
2020
2369
  ` Deploy: ${deploy}`,
2370
+ ` Cron: ${config.cron ? "yes" : "no"}`,
2021
2371
  "",
2022
2372
  " Next steps:",
2023
2373
  ...nextSteps,
@@ -2039,11 +2389,28 @@ async function main() {
2039
2389
  process.exit(0);
2040
2390
  }
2041
2391
  if (args.help) {
2042
- console.log(`Usage: create-avalon [project-name]
2043
-
2044
- Options:
2045
- -v, --version Show version number
2046
- -h, --help Show help`);
2392
+ console.log([
2393
+ "Usage: create-avalon [project-name] [options]",
2394
+ "",
2395
+ "Runs interactively by default. Pass --yes (or run without a TTY, e.g. in",
2396
+ "CI/Docker) to scaffold non-interactively from flags + defaults.",
2397
+ "",
2398
+ "Options:",
2399
+ " -v, --version Show version number",
2400
+ " -h, --help Show help",
2401
+ " -y, --yes Skip prompts; use flags and defaults",
2402
+ " --core Rendering engine: preact (default) | react",
2403
+ " --integrations Comma list: preact,react,vue,svelte,solid,lit,qwik",
2404
+ " --styling css-modules (default) | tailwind | shadcn",
2405
+ " --plugins Comma list: seo (default),agent-optimization,syntax-highlighting",
2406
+ " --middleware h3 (default) | hono | elysia",
2407
+ " --deploy netlify | none (default)",
2408
+ " --cron Scaffold an example cron task + config",
2409
+ "",
2410
+ "Example:",
2411
+ " create-avalon my-app --yes --core react --integrations react,vue --styling shadcn"
2412
+ ].join(`
2413
+ `));
2047
2414
  process.exit(0);
2048
2415
  }
2049
2416
  if (args.projectName && args.projectName !== ".") {
@@ -2053,7 +2420,8 @@ Options:
2053
2420
  process.exit(1);
2054
2421
  }
2055
2422
  }
2056
- const config = await collectProjectConfig(args.projectName);
2423
+ const nonInteractive = args.yes || !process.stdin.isTTY;
2424
+ const config = nonInteractive ? resolveConfigNonInteractive(args) : await collectProjectConfig(args.projectName);
2057
2425
  if (!args.projectName && config.projectName !== ".") {
2058
2426
  const dirResult = validateDirectory(resolve(config.projectName));
2059
2427
  if (!dirResult.valid) {
@@ -2070,4 +2438,9 @@ Options:
2070
2438
  printSummary(config, scaffoldedInPlace);
2071
2439
  process.exit(0);
2072
2440
  }
2073
- main();
2441
+ try {
2442
+ await main();
2443
+ } catch (error) {
2444
+ console.error(error instanceof CliArgError ? error.message : error);
2445
+ process.exit(1);
2446
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-avalon",
3
- "version": "0.1.23",
3
+ "version": "0.1.25",
4
4
  "description": "Scaffold a new Avalon project with multi-framework islands architecture",
5
5
  "license": "MIT",
6
6
  "type": "module",