create-avalon 0.1.18 → 0.1.20

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 (2) hide show
  1. package/dist/cli.js +369 -550
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -1204,7 +1204,191 @@ async function collectProjectConfig(initialName) {
1204
1204
 
1205
1205
  // src/scaffold.ts
1206
1206
  import { mkdir, writeFile } from "node:fs/promises";
1207
- import { join, dirname } from "node:path";
1207
+ import { dirname, join } from "node:path";
1208
+
1209
+ // src/templates/api-routes.ts
1210
+ function generateHelloRoute(_config) {
1211
+ return `import { defineHandler } from 'nitro';
1212
+
1213
+ export default defineHandler(() => {
1214
+ return Response.json({ message: 'Hello from Avalon!' });
1215
+ });
1216
+ `;
1217
+ }
1218
+
1219
+ // src/templates/deploy.ts
1220
+ function generateNetlifyToml(config) {
1221
+ return `[build]
1222
+ base = "."
1223
+ command = "bun install && bun build.mjs"
1224
+ publish = "dist"
1225
+
1226
+ [build.environment]
1227
+ NODE_VERSION = "22"
1228
+ BUN_VERSION = "latest"
1229
+ NITRO_PRESET = "netlify"
1230
+
1231
+ [functions]
1232
+ directory = ".netlify/functions-internal"
1233
+ node_bundler = "none"
1234
+
1235
+ # SSR catch-all — Netlify checks for a matching static/prerendered file
1236
+ # first (force=false is the default in netlify.toml). Only requests with
1237
+ # no static file hit the server function.
1238
+ [[redirects]]
1239
+ from = "/*"
1240
+ to = "/.netlify/functions/server"
1241
+ status = 200
1242
+ `;
1243
+ }
1244
+ function generateBuildMjs() {
1245
+ return `/**
1246
+ * Netlify build wrapper.
1247
+ *
1248
+ * Vite/Nitro leaves open handles after the build completes, preventing
1249
+ * the Node process from exiting. This wrapper detects when the build
1250
+ * output is ready, kills the entire process group, then runs post-build.
1251
+ */
1252
+
1253
+ import { spawn, execSync } from 'node:child_process';
1254
+ import { existsSync, rmSync } from 'node:fs';
1255
+ import { join } from 'node:path';
1256
+
1257
+ const CWD = process.cwd();
1258
+ const NITRO_JSON = join(CWD, '.netlify', 'functions-internal', 'nitro.json');
1259
+ const SERVER_MJS = join(CWD, '.netlify', 'functions-internal', 'server', 'server.mjs');
1260
+ const OUTPUT_SSR = join(CWD, '.output', 'server', '_ssr', 'ssr.mjs');
1261
+
1262
+ console.log('[build] Starting vite build...');
1263
+
1264
+ for (const dir of ['.netlify', '.output', 'netlify']) {
1265
+ const full = join(CWD, dir);
1266
+ if (existsSync(full)) {
1267
+ rmSync(full, { recursive: true, force: true });
1268
+ console.log(\`[build] Cleaned stale \${dir}/\`);
1269
+ }
1270
+ }
1271
+
1272
+ const child = spawn('bunx', ['--bun', 'vite', 'build'], {
1273
+ cwd: CWD,
1274
+ stdio: 'inherit',
1275
+ detached: true,
1276
+ });
1277
+
1278
+ const childPid = child.pid;
1279
+ let done = false;
1280
+
1281
+ function killTree() {
1282
+ try { process.kill(-childPid, 'SIGKILL'); } catch {}
1283
+ try { child.kill('SIGKILL'); } catch {}
1284
+ }
1285
+
1286
+ function finish() {
1287
+ if (done) return;
1288
+ done = true;
1289
+ clearInterval(poll);
1290
+ clearTimeout(absoluteTimeout);
1291
+ killTree();
1292
+
1293
+ setTimeout(() => {
1294
+ console.log('[build] Running post-build...');
1295
+ try {
1296
+ execSync('node post-build.mjs', { cwd: CWD, stdio: 'inherit', timeout: 120_000 });
1297
+ } catch (err) {
1298
+ console.error('[build] post-build warning:', err.message);
1299
+ }
1300
+
1301
+ const V1_SERVER = join(CWD, '.netlify', 'v1', 'functions', 'server', 'server.mjs');
1302
+ if (existsSync(V1_SERVER)) console.log('[build] ✅ Server function found (v1 API)');
1303
+ else if (existsSync(SERVER_MJS)) console.log('[build] ✅ Server function found (legacy)');
1304
+ else if (existsSync(OUTPUT_SSR)) console.log('[build] ✅ SSR bundle found');
1305
+ else console.error('[build] ❌ No server output found');
1306
+
1307
+ console.log('[build] ✅ Complete');
1308
+ process.exit(0);
1309
+ }, 500);
1310
+ }
1311
+
1312
+ child.on('exit', (code) => {
1313
+ console.log(\`[build] vite build exited with code \${code}\`);
1314
+ finish();
1315
+ });
1316
+
1317
+ child.on('error', (err) => {
1318
+ console.error('[build] spawn error:', err);
1319
+ process.exit(1);
1320
+ });
1321
+
1322
+ const poll = setInterval(() => {
1323
+ const netlifyReady = existsSync(NITRO_JSON) && existsSync(SERVER_MJS);
1324
+ const nodeServerReady = existsSync(OUTPUT_SSR);
1325
+ if (netlifyReady || nodeServerReady) {
1326
+ console.log(\`[build] Output detected (\${netlifyReady ? 'netlify' : 'node-server'}), waiting 3s for final writes...\`);
1327
+ clearInterval(poll);
1328
+ setTimeout(finish, 3_000);
1329
+ }
1330
+ }, 1_000);
1331
+
1332
+ const absoluteTimeout = setTimeout(() => {
1333
+ console.error('[build] Timeout — killing build');
1334
+ finish();
1335
+ }, 240_000);
1336
+ `;
1337
+ }
1338
+
1339
+ // src/templates/favicon.ts
1340
+ 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=";
1341
+ function getFaviconBuffer() {
1342
+ return Buffer.from(FAVICON_BASE64, "base64");
1343
+ }
1344
+
1345
+ // src/templates/layouts.ts
1346
+ function generateRootLayout(config) {
1347
+ const imports = [];
1348
+ imports.push(`import type { LayoutProps } from '@useavalon/avalon';`);
1349
+ if (config.styling === "css-modules") {
1350
+ imports.push(`import '../styles/main.css';`);
1351
+ } else {
1352
+ imports.push(`import '../styles/main.css';`);
1353
+ }
1354
+ return `${imports.join(`
1355
+ `)}
1356
+
1357
+ export default async function RootLayout({ children }: Readonly<LayoutProps>) {
1358
+ return (
1359
+ <html lang="en">
1360
+ <head>
1361
+ <meta charset="UTF-8" />
1362
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
1363
+ <title>${config.projectName}</title>
1364
+ <link rel="icon" href="/favicon.ico" />
1365
+ </head>
1366
+ <body style={{ margin: 0 }}>
1367
+ {children}
1368
+ </body>
1369
+ </html>
1370
+ );
1371
+ }
1372
+ `;
1373
+ }
1374
+ function generateHomeLayout(config) {
1375
+ return `import type { LayoutProps } from '@useavalon/avalon';
1376
+
1377
+ export default async function HomeLayout({ children }: Readonly<LayoutProps>) {
1378
+ return <>{children}</>;
1379
+ }
1380
+ `;
1381
+ }
1382
+
1383
+ // src/templates/middleware.ts
1384
+ function generateSampleMiddleware(_config) {
1385
+ return `import { defineHandler } from 'nitro';
1386
+
1387
+ export default defineHandler((event) => {
1388
+ console.log(\`[\${new Date().toISOString()}] \${event.req.method} \${event.url.pathname}\`);
1389
+ });
1390
+ `;
1391
+ }
1208
1392
 
1209
1393
  // src/types.ts
1210
1394
  var INTEGRATION_PACKAGES = {
@@ -1290,158 +1474,6 @@ function generatePackageJson(config) {
1290
1474
  return JSON.stringify(pkg, null, 2);
1291
1475
  }
1292
1476
 
1293
- // src/templates/tsconfig.ts
1294
- function generateTsConfig() {
1295
- const tsconfig = {
1296
- compilerOptions: {
1297
- target: "ESNext",
1298
- module: "ESNext",
1299
- moduleResolution: "bundler",
1300
- strict: true,
1301
- esModuleInterop: true,
1302
- skipLibCheck: true,
1303
- allowArbitraryExtensions: true,
1304
- allowImportingTsExtensions: true,
1305
- noEmit: true,
1306
- jsx: "react-jsx",
1307
- paths: {
1308
- "@shared/*": ["./app/shared/*"],
1309
- "@modules/*": ["./app/modules/*"]
1310
- }
1311
- },
1312
- include: ["app/**/*.ts", "app/**/*.tsx", "app/**/*.d.ts", "server/**/*.ts", "routes/**/*.ts", "middleware/**/*.ts"]
1313
- };
1314
- return JSON.stringify(tsconfig, null, 2);
1315
- }
1316
- function generateEnvDts() {
1317
- return `/// <reference types="@useavalon/avalon/types" />
1318
-
1319
- // Virtual module declarations
1320
- declare module 'virtual:avalon/config' {
1321
- const config: Record<string, unknown>;
1322
- export default config;
1323
- }
1324
- `;
1325
- }
1326
-
1327
- // src/templates/vite-config.ts
1328
- function generateViteConfig(config) {
1329
- const imports = [
1330
- `import { resolve } from 'node:path';`,
1331
- `import { defineConfig, type UserConfig } from 'vite';`,
1332
- `import { avalon } from '@useavalon/avalon';`
1333
- ];
1334
- const needsTailwind = config.styling === "tailwind" || config.styling === "shadcn";
1335
- if (needsTailwind) {
1336
- imports.push(`import tailwindcss from '@tailwindcss/vite';`);
1337
- }
1338
- const hasAgentOptimization = config.plugins.includes("agent-optimization");
1339
- if (hasAgentOptimization) {
1340
- imports.push(`import { agentOptimization } from '@useavalon/agent-optimization';`);
1341
- }
1342
- const integrationsList = config.integrations.map((i) => `'${i}'`).join(", ");
1343
- const pluginEntries = [];
1344
- if (hasAgentOptimization) {
1345
- pluginEntries.push(` agentOptimization({
1346
- sitemap: { siteUrl: 'http://localhost:3000' },
1347
- markdown: true,
1348
- structuredData: true,
1349
- llms: {
1350
- siteUrl: 'http://localhost:3000',
1351
- siteName: 'My Avalon App',
1352
- siteDescription: 'Built with Avalon',
1353
- sections: { 'Pages': ['/'] },
1354
- },
1355
- }),`);
1356
- }
1357
- pluginEntries.push(` ...avalonPlugins,`);
1358
- if (needsTailwind) {
1359
- pluginEntries.push(` tailwindcss(),`);
1360
- }
1361
- const lines = [
1362
- imports.join(`
1363
- `),
1364
- "",
1365
- `export default defineConfig(async (): Promise<UserConfig> => {`,
1366
- ` const avalonPlugins = await avalon({`,
1367
- ` integrations: [${integrationsList}],`,
1368
- ` modules: 'app/modules',`,
1369
- ` layoutsDir: 'app/shared/layouts',`,
1370
- ` image: true,`,
1371
- ` nitro: {`,
1372
- ` preset: process.env.NITRO_PRESET || 'node_server',`,
1373
- ` streaming: true,`,
1374
- ` prerender: {`,
1375
- ` routes: ['/'],`,
1376
- ` crawlLinks: true,`,
1377
- ` ignore: [],`,
1378
- ` },`,
1379
- ` },`,
1380
- ` });`,
1381
- "",
1382
- ` return {`,
1383
- ` plugins: [`,
1384
- pluginEntries.join(`
1385
- `),
1386
- ` ],`,
1387
- ``,
1388
- ` resolve: {`,
1389
- ` alias: {`,
1390
- ` '@shared': resolve(__dirname, 'app/shared'),`,
1391
- ` '@modules': resolve(__dirname, 'app/modules'),`,
1392
- ` '@': resolve(__dirname, 'app'),`,
1393
- ` },`,
1394
- ` },`,
1395
- ``,
1396
- ` server: {`,
1397
- ` port: 3000,`,
1398
- ` },`,
1399
- ` };`,
1400
- `});`,
1401
- ""
1402
- ];
1403
- return lines.join(`
1404
- `);
1405
- }
1406
-
1407
- // src/templates/layouts.ts
1408
- function generateRootLayout(config) {
1409
- const imports = [];
1410
- imports.push(`import type { LayoutProps } from '@useavalon/avalon';`);
1411
- if (config.styling === "css-modules") {
1412
- imports.push(`import '../styles/main.css';`);
1413
- } else {
1414
- imports.push(`import '../styles/main.css';`);
1415
- }
1416
- return `${imports.join(`
1417
- `)}
1418
-
1419
- export default async function RootLayout({ children }: Readonly<LayoutProps>) {
1420
- return (
1421
- <html lang="en">
1422
- <head>
1423
- <meta charset="UTF-8" />
1424
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
1425
- <title>${config.projectName}</title>
1426
- <link rel="icon" href="/favicon.ico" />
1427
- </head>
1428
- <body style={{ margin: 0 }}>
1429
- {children}
1430
- </body>
1431
- </html>
1432
- );
1433
- }
1434
- `;
1435
- }
1436
- function generateHomeLayout(config) {
1437
- return `import type { LayoutProps } from '@useavalon/avalon';
1438
-
1439
- export default async function HomeLayout({ children }: Readonly<LayoutProps>) {
1440
- return <>{children}</>;
1441
- }
1442
- `;
1443
- }
1444
-
1445
1477
  // src/templates/pages.ts
1446
1478
  function generateHomePage(config) {
1447
1479
  return `export const metadata = {
@@ -1497,24 +1529,27 @@ export default async function HomePage() {
1497
1529
  `;
1498
1530
  }
1499
1531
 
1500
- // src/templates/middleware.ts
1501
- function generateSampleMiddleware(_config) {
1502
- return `import { defineHandler } from 'nitro';
1503
-
1504
- export default defineHandler((event) => {
1505
- console.log(\`[\${new Date().toISOString()}] \${event.req.method} \${event.url.pathname}\`);
1506
- });
1507
- `;
1508
- }
1509
-
1510
- // src/templates/api-routes.ts
1511
- function generateHelloRoute(_config) {
1512
- return `import { defineHandler } from 'nitro';
1513
-
1514
- export default defineHandler(() => {
1515
- return Response.json({ message: 'Hello from Avalon!' });
1516
- });
1517
- `;
1532
+ // src/templates/post-build.ts
1533
+ function generatePostBuildMjs() {
1534
+ return [
1535
+ `/**`,
1536
+ ` * Post-build script — delegates to Avalon's built-in post-build.`,
1537
+ ` *`,
1538
+ ` * All the heavy lifting (CSS patching, island redirects, prerendering,`,
1539
+ ` * Netlify function copying) is handled by the framework.`,
1540
+ ` */`,
1541
+ `import { runPostBuild } from '@useavalon/avalon/post-build';`,
1542
+ ``,
1543
+ `await runPostBuild({`,
1544
+ ` prerender: {`,
1545
+ ` routes: ['/'],`,
1546
+ ` crawlLinks: true,`,
1547
+ ` failOnError: false,`,
1548
+ ` },`,
1549
+ `});`,
1550
+ ``
1551
+ ].join(`
1552
+ `);
1518
1553
  }
1519
1554
 
1520
1555
  // src/templates/styling.ts
@@ -1715,370 +1750,168 @@ function generateShadcnComponentsJson(config) {
1715
1750
  `;
1716
1751
  }
1717
1752
 
1718
- // src/templates/favicon.ts
1719
- 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=";
1720
- function getFaviconBuffer() {
1721
- return Buffer.from(FAVICON_BASE64, "base64");
1722
- }
1723
-
1724
- // src/templates/deploy.ts
1725
- function generateNetlifyToml(config) {
1726
- return `[build]
1727
- base = "."
1728
- command = "bun install && bun build.mjs"
1729
- publish = "dist"
1730
-
1731
- [build.environment]
1732
- NODE_VERSION = "22"
1733
- BUN_VERSION = "latest"
1734
- NITRO_PRESET = "netlify"
1735
-
1736
- [functions]
1737
- directory = "netlify/functions"
1738
-
1739
- # SSR catch-all — Netlify checks for a matching static/prerendered file
1740
- # first (force=false is the default in netlify.toml). Only requests with
1741
- # no static file hit the server function.
1742
- [[redirects]]
1743
- from = "/*"
1744
- to = "/.netlify/functions/server"
1745
- status = 200
1746
- `;
1747
- }
1748
- function generateBuildMjs() {
1749
- return `/**
1750
- * Netlify build wrapper.
1751
- *
1752
- * Vite/Nitro leaves open handles after the build completes, preventing
1753
- * the Node process from exiting. This wrapper detects when the build
1754
- * output is ready, kills the entire process group, then runs post-build.
1755
- */
1756
-
1757
- import { spawn, execSync } from 'node:child_process';
1758
- import { existsSync, rmSync } from 'node:fs';
1759
- import { join } from 'node:path';
1760
-
1761
- const CWD = process.cwd();
1762
- const NITRO_JSON = join(CWD, '.netlify', 'functions-internal', 'nitro.json');
1763
- const SERVER_MJS = join(CWD, '.netlify', 'functions-internal', 'server', 'server.mjs');
1764
- const OUTPUT_SSR = join(CWD, '.output', 'server', '_ssr', 'ssr.mjs');
1765
-
1766
- console.log('[build] Starting vite build...');
1767
-
1768
- for (const dir of ['.netlify', '.output', 'netlify']) {
1769
- const full = join(CWD, dir);
1770
- if (existsSync(full)) {
1771
- rmSync(full, { recursive: true, force: true });
1772
- console.log(\`[build] Cleaned stale \${dir}/\`);
1773
- }
1774
- }
1775
-
1776
- const child = spawn('bunx', ['--bun', 'vite', 'build'], {
1777
- cwd: CWD,
1778
- stdio: 'inherit',
1779
- detached: true,
1780
- });
1781
-
1782
- const childPid = child.pid;
1783
- let done = false;
1784
-
1785
- function killTree() {
1786
- try { process.kill(-childPid, 'SIGKILL'); } catch {}
1787
- try { child.kill('SIGKILL'); } catch {}
1753
+ // src/templates/tsconfig.ts
1754
+ function generateTsConfig() {
1755
+ const tsconfig = {
1756
+ compilerOptions: {
1757
+ target: "ESNext",
1758
+ module: "ESNext",
1759
+ moduleResolution: "bundler",
1760
+ strict: true,
1761
+ esModuleInterop: true,
1762
+ skipLibCheck: true,
1763
+ allowArbitraryExtensions: true,
1764
+ allowImportingTsExtensions: true,
1765
+ noEmit: true,
1766
+ jsx: "react-jsx",
1767
+ paths: {
1768
+ "@shared/*": ["./app/shared/*"],
1769
+ "@modules/*": ["./app/modules/*"]
1770
+ }
1771
+ },
1772
+ include: [
1773
+ "app/**/*.ts",
1774
+ "app/**/*.tsx",
1775
+ "app/**/*.d.ts",
1776
+ "server/**/*.ts",
1777
+ "routes/**/*.ts",
1778
+ "middleware/**/*.ts"
1779
+ ]
1780
+ };
1781
+ return JSON.stringify(tsconfig, null, 2);
1788
1782
  }
1789
-
1790
- function finish() {
1791
- if (done) return;
1792
- done = true;
1793
- clearInterval(poll);
1794
- clearTimeout(absoluteTimeout);
1795
- killTree();
1796
-
1797
- setTimeout(() => {
1798
- console.log('[build] Running post-build...');
1799
- try {
1800
- execSync('node post-build.mjs', { cwd: CWD, stdio: 'inherit', timeout: 120_000 });
1801
- } catch (err) {
1802
- console.error('[build] post-build warning:', err.message);
1783
+ function generateEnvDts(integrations = []) {
1784
+ const lines = [
1785
+ `/**`,
1786
+ ` * Auto-generated by create-avalon — do not edit manually.`,
1787
+ ` *`,
1788
+ ` * This file provides TypeScript declarations for cross-framework`,
1789
+ ` * island imports, CSS modules, and Nitro virtual asset manifests.`,
1790
+ ` * Re-run the scaffold to regenerate after changing integrations.`,
1791
+ ` */`,
1792
+ `/// <reference types="@useavalon/avalon/types" />`,
1793
+ ``
1794
+ ];
1795
+ const frameworkModules = {
1796
+ vue: { pattern: "*.vue" },
1797
+ svelte: { pattern: "*.svelte" },
1798
+ solid: { pattern: "*.solid.tsx" },
1799
+ lit: { pattern: "*.lit.ts" },
1800
+ qwik: { pattern: "*.qwik.tsx" }
1801
+ };
1802
+ const selected = Object.entries(frameworkModules).filter(([name]) => integrations.includes(name));
1803
+ if (selected.length > 0) {
1804
+ for (const [, { pattern }] of selected) {
1805
+ lines.push(`declare module '${pattern}' {`, ` import type { ComponentType } from 'preact';`, ` const component: ComponentType<Record<string, unknown>>;`, ` export default component;`, `}`, ``);
1803
1806
  }
1804
-
1805
- const V1_SERVER = join(CWD, '.netlify', 'v1', 'functions', 'server', 'server.mjs');
1806
- if (existsSync(V1_SERVER)) console.log('[build] ✅ Server function found (v1 API)');
1807
- else if (existsSync(SERVER_MJS)) console.log('[build] ✅ Server function found (legacy)');
1808
- else if (existsSync(OUTPUT_SSR)) console.log('[build] ✅ SSR bundle found');
1809
- else console.error('[build] ❌ No server output found');
1810
-
1811
- console.log('[build] ✅ Complete');
1812
- process.exit(0);
1813
- }, 500);
1814
- }
1815
-
1816
- child.on('exit', (code) => {
1817
- console.log(\`[build] vite build exited with code \${code}\`);
1818
- finish();
1819
- });
1820
-
1821
- child.on('error', (err) => {
1822
- console.error('[build] spawn error:', err);
1823
- process.exit(1);
1824
- });
1825
-
1826
- const poll = setInterval(() => {
1827
- const netlifyReady = existsSync(NITRO_JSON) && existsSync(SERVER_MJS);
1828
- const nodeServerReady = existsSync(OUTPUT_SSR);
1829
- if (netlifyReady || nodeServerReady) {
1830
- console.log(\`[build] Output detected (\${netlifyReady ? 'netlify' : 'node-server'}), waiting 3s for final writes...\`);
1831
- clearInterval(poll);
1832
- setTimeout(finish, 3_000);
1833
1807
  }
1834
- }, 1_000);
1835
-
1836
- const absoluteTimeout = setTimeout(() => {
1837
- console.error('[build] Timeout — killing build');
1838
- finish();
1839
- }, 240_000);
1840
- `;
1808
+ lines.push(`declare module '*.module.css' {`, ` const classes: Record<string, string>;`, ` export default classes;`, `}`, ``);
1809
+ for (const suffix of ["client", "ssr"]) {
1810
+ lines.push(`declare module '*?assets=${suffix}' {`, ` const assets: {`, ` css: Array<{ href: string; [key: string]: string }>;`, ` js: Array<{ href: string; [key: string]: string }>;`, ` entry: string;`, ` merge(other: unknown): typeof assets;`, ` };`, ` export default assets;`, `}`, ``);
1811
+ }
1812
+ return lines.join(`
1813
+ `);
1841
1814
  }
1842
1815
 
1843
- // src/templates/post-build.ts
1844
- function generatePostBuildMjs() {
1816
+ // src/templates/vite-config.ts
1817
+ function generateViteConfig(config) {
1818
+ const imports = [
1819
+ `import { resolve } from 'node:path';`,
1820
+ `import { defineConfig, type UserConfig } from 'vite';`,
1821
+ `import { avalon } from '@useavalon/avalon';`
1822
+ ];
1823
+ const needsTailwind = config.styling === "tailwind" || config.styling === "shadcn";
1824
+ if (needsTailwind) {
1825
+ imports.push(`import tailwindcss from '@tailwindcss/vite';`);
1826
+ }
1827
+ const hasAgentOptimization = config.plugins.includes("agent-optimization");
1828
+ if (hasAgentOptimization) {
1829
+ imports.push(`import { agentOptimization } from '@useavalon/agent-optimization';`);
1830
+ }
1831
+ const integrationsList = config.integrations.map((i) => `'${i}'`).join(", ");
1832
+ const pluginEntries = [];
1833
+ if (hasAgentOptimization) {
1834
+ pluginEntries.push(` agentOptimization({
1835
+ sitemap: { siteUrl: 'http://localhost:3000' },
1836
+ markdown: true,
1837
+ structuredData: true,
1838
+ llms: {
1839
+ siteUrl: 'http://localhost:3000',
1840
+ siteName: 'My Avalon App',
1841
+ siteDescription: 'Built with Avalon',
1842
+ sections: { 'Pages': ['/'] },
1843
+ },
1844
+ }),`);
1845
+ }
1846
+ pluginEntries.push(` ...avalonPlugins,`);
1847
+ if (needsTailwind) {
1848
+ pluginEntries.push(` tailwindcss(),`);
1849
+ }
1845
1850
  const lines = [
1846
- `/**`,
1847
- ` * Post-build script for Netlify deployment.`,
1848
- ` *`,
1849
- ` * - Removes stale index.html that would shadow SSR routes`,
1850
- ` * - Generates _redirects for island JS path mapping`,
1851
- ` * - Copies island JS to clean paths for local preview`,
1852
- ` * - Copies server function to all Netlify function paths`,
1853
- ` */`,
1854
- ``,
1855
- `import {`,
1856
- ` existsSync, unlinkSync, readFileSync, writeFileSync,`,
1857
- ` readdirSync, copyFileSync, mkdirSync, cpSync,`,
1858
- `} from 'node:fs';`,
1859
- `import { join, dirname, relative } from 'node:path';`,
1860
- ``,
1861
- `const CWD = process.cwd();`,
1862
- `const DIST_DIR = join(CWD, 'dist');`,
1863
- `const ASSETS_DIR = join(CWD, 'dist', 'assets');`,
1864
- ``,
1865
- `function collectFiles(dir, predicate, result = []) {`,
1866
- ` if (!existsSync(dir)) return result;`,
1867
- ` for (const entry of readdirSync(dir, { withFileTypes: true })) {`,
1868
- ` const full = join(dir, entry.name);`,
1869
- ` if (entry.isDirectory()) collectFiles(full, predicate, result);`,
1870
- ` else if (predicate(entry.name)) result.push(full);`,
1871
- ` }`,
1872
- ` return result;`,
1873
- `}`,
1874
- ``,
1875
- `function toServePath(absPath) {`,
1876
- ` return '/' + relative(DIST_DIR, absPath).replaceAll('\\\\', '/');`,
1877
- `}`,
1878
- ``,
1879
- `// ── Cleanup stale index.html ─────────────────────────────────────`,
1880
- `for (const htmlPath of ['dist/index.html', '.netlify/functions-internal/server/public/index.html']) {`,
1881
- ` const full = join(CWD, htmlPath);`,
1882
- ` if (existsSync(full)) { unlinkSync(full); console.log('[cleanup] Removed ' + htmlPath); }`,
1883
- `}`,
1884
- ``,
1885
- `// ── Generate island redirects + local copies ─────────────────────`,
1886
- `function generateIslandRedirects() {`,
1887
- ` const islandsDir = join(ASSETS_DIR, 'islands');`,
1888
- ` if (!existsSync(islandsDir)) return;`,
1889
- ` const islandFiles = collectFiles(islandsDir, n => n.endsWith('.js') && !n.endsWith('.js.map'));`,
1890
- ` if (islandFiles.length === 0) return;`,
1891
- ` const redirectLines = [];`,
1892
- ` for (const absPath of islandFiles) {`,
1893
- ` const servePath = toServePath(absPath);`,
1894
- ` const cleanPath = servePath.replace('/assets/', '/').replace(/-[A-Za-z0-9_-]{6,12}\\.js$/, '.js');`,
1895
- ` redirectLines.push(cleanPath + ' ' + servePath + ' 200');`,
1896
- ` const cleanAbsPath = join(DIST_DIR, cleanPath.slice(1));`,
1897
- ` mkdirSync(dirname(cleanAbsPath), { recursive: true });`,
1898
- ` copyFileSync(absPath, cleanAbsPath);`,
1899
- ` }`,
1900
- ` const redirectsPath = join(DIST_DIR, '_redirects');`,
1901
- ` let existing = existsSync(redirectsPath) ? readFileSync(redirectsPath, 'utf-8') : '';`,
1902
- ` existing = existing.replaceAll(/# Island JS path rewrites[^\\n]*\\n(?:\\/islands\\/[^\\n]*\\n)*/g, '').trim();`,
1903
- ` const header = '# Island JS path rewrites (generated by post-build.mjs)\\n';`,
1904
- ` const content = existing`,
1905
- ` ? existing + '\\n\\n' + header + redirectLines.join('\\n') + '\\n'`,
1906
- ` : header + redirectLines.join('\\n') + '\\n';`,
1907
- ` writeFileSync(redirectsPath, content);`,
1908
- ` console.log('[redirects] Wrote ' + redirectLines.length + ' island redirects');`,
1909
- `}`,
1910
- ``,
1911
- `// ── Copy framework adapters ───────────────────────────────────────`,
1912
- `function copyAdapters() {`,
1913
- ` const sources = [`,
1914
- ` join(CWD, '.output', 'public', '_adapters'),`,
1915
- ` join(CWD, 'dist', '_adapters'),`,
1916
- ` ];`,
1917
- ` for (const srcDir of sources) {`,
1918
- ` if (!existsSync(srcDir)) continue;`,
1919
- ` const files = readdirSync(srcDir).filter(f => f.endsWith('.js'));`,
1920
- ` if (files.length === 0) continue;`,
1921
- ` const destDir = join(DIST_DIR, '_adapters');`,
1922
- ` mkdirSync(destDir, { recursive: true });`,
1923
- ` for (const file of files) {`,
1924
- ` const src = join(srcDir, file);`,
1925
- ` const dest = join(destDir, file);`,
1926
- ` if (src !== dest) copyFileSync(src, dest);`,
1927
- ` }`,
1928
- ` console.log('[adapters] Copied ' + files.length + ' framework adapters');`,
1929
- ` return;`,
1930
- ` }`,
1931
- `}`,
1932
- ``,
1933
- `// ── Copy function to all Netlify paths ────────────────────────────`,
1934
- `function copyToNetlifyPaths() {`,
1935
- ` const legacyDir = join(CWD, '.netlify', 'functions-internal', 'server');`,
1936
- ` if (!existsSync(legacyDir)) return;`,
1937
- ` const targets = [`,
1938
- ` join(CWD, '.netlify', 'v1', 'functions', 'server'),`,
1939
- ` join(CWD, 'netlify', 'functions', 'server'),`,
1940
- ` ];`,
1941
- ` for (const target of targets) {`,
1942
- ` cpSync(legacyDir, target, { recursive: true, force: true });`,
1943
- ` }`,
1944
- ` console.log('[netlify-fn] Copied server function to all Netlify paths');`,
1945
- `}`,
1946
- ``,
1947
- `// ── Run ──────────────────────────────────────────────────────────`,
1948
- `(async () => {`,
1949
- ``,
1950
- `generateIslandRedirects();`,
1951
- `copyAdapters();`,
1952
- `copyToNetlifyPaths();`,
1953
- ``,
1954
- `// ── Prerender (SSG) ──────────────────────────────────────────────`,
1955
- `// Spawn the built server and fetch routes to generate static HTML.`,
1956
- `// The Netlify preset produces a function handler (not a standalone`,
1957
- `// server), so we detect it and wrap it in a minimal HTTP server.`,
1958
- ``,
1959
- `function isNetlifyHandler(entryPath) {`,
1960
- ` const code = readFileSync(entryPath, 'utf-8');`,
1961
- ` return code.includes('path: "/*"') || code.includes('path:\`/*\`');`,
1962
- `}`,
1963
- ``,
1964
- `function writeNetlifyWrapper(serverDir, port) {`,
1965
- ` const wrapperPath = join(serverDir, '_prerender-server.mjs');`,
1966
- ` writeFileSync(wrapperPath, [`,
1967
- ` 'import { createServer } from "node:http";',`,
1968
- ` 'import handler from "./main.mjs";',`,
1969
- ` 'const PORT = ' + port + ';',`,
1970
- ` 'const server = createServer(async (req, res) => {',`,
1971
- ` ' try {',`,
1972
- ` ' const url = new URL(req.url, "http://localhost:" + PORT);',`,
1973
- ` ' const headers = new Headers();',`,
1974
- ` ' for (const [key, value] of Object.entries(req.headers)) {',`,
1975
- ` ' if (value) headers.set(key, Array.isArray(value) ? value.join(", ") : value);',`,
1976
- ` ' }',`,
1977
- ` ' const request = new Request(url.toString(), { method: req.method, headers });',`,
1978
- ` ' const response = await handler(request);',`,
1979
- ` ' res.writeHead(response.status, Object.fromEntries(response.headers.entries()));',`,
1980
- ` ' res.end(await response.text());',`,
1981
- ` ' } catch (err) { console.error("[prerender-wrapper]", err); res.writeHead(500); res.end("Error"); }',`,
1982
- ` '});',`,
1983
- ` 'server.listen(PORT, "127.0.0.1", () => console.log("[prerender-wrapper] Listening on port " + PORT));',`,
1984
- ` ].join('\\n'));`,
1985
- ` return wrapperPath;`,
1986
- `}`,
1851
+ imports.join(`
1852
+ `),
1853
+ "",
1854
+ `export default defineConfig(async (): Promise<UserConfig> => {`,
1855
+ ` const avalonPlugins = await avalon({`,
1856
+ ` integrations: [${integrationsList}],`,
1857
+ ` modules: 'app/modules',`,
1858
+ ` layoutsDir: 'app/shared/layouts',`,
1859
+ ` image: true,`,
1860
+ ` nitro: {`,
1861
+ ` preset: process.env.NITRO_PRESET || 'node_server',`,
1862
+ ` streaming: true,`,
1863
+ ` clientEntry: 'app/entry-client',`,
1864
+ ` globalCSS: ['app/shared/styles/main.css'],`,
1865
+ ` prerender: {`,
1866
+ ` routes: ['/'],`,
1867
+ ` crawlLinks: true,`,
1868
+ ` ignore: [],`,
1869
+ ` },`,
1870
+ ` },`,
1871
+ ` });`,
1872
+ "",
1873
+ ` return {`,
1874
+ ` environments: {`,
1875
+ ` client: {`,
1876
+ ` build: {`,
1877
+ ` rollupOptions: {`,
1878
+ ` input: './app/entry-client.ts',`,
1879
+ ` },`,
1880
+ ` },`,
1881
+ ` },`,
1882
+ ` ssr: {`,
1883
+ ` build: {`,
1884
+ ` rollupOptions: {`,
1885
+ ` input: './server/renderer.ts',`,
1886
+ ` },`,
1887
+ ` },`,
1888
+ ` },`,
1889
+ ` },`,
1987
1890
  ``,
1988
- `const serverEntries = [`,
1989
- ` join(CWD, '.output', 'server', 'index.mjs'),`,
1990
- ` join(CWD, '.netlify', 'functions-internal', 'server', 'server.mjs'),`,
1991
- ` join(CWD, '.netlify', 'v1', 'functions', 'server', 'server.mjs'),`,
1992
- `];`,
1993
- `const serverEntry = serverEntries.find(p => existsSync(p));`,
1994
- `const outputDir = [join(CWD, '.output', 'public'), DIST_DIR].find(d => existsSync(d));`,
1891
+ ` plugins: [`,
1892
+ pluginEntries.join(`
1893
+ `),
1894
+ ` ],`,
1995
1895
  ``,
1996
- `if (serverEntry && outputDir) {`,
1997
- ` const { spawn: spawnProcess } = await import('node:child_process');`,
1998
- ` const PORT = 13172;`,
1999
- ` const baseUrl = 'http://localhost:' + PORT;`,
2000
- ` const netlifyMode = isNetlifyHandler(serverEntry);`,
2001
- ` let actualEntry = serverEntry;`,
2002
- ` if (netlifyMode) {`,
2003
- ` const mainMjs = join(dirname(serverEntry), 'main.mjs');`,
2004
- ` if (existsSync(mainMjs)) {`,
2005
- ` actualEntry = writeNetlifyWrapper(dirname(serverEntry), PORT);`,
2006
- ` console.log('[prerender] Netlify handler detected, using wrapper');`,
2007
- ` }`,
2008
- ` }`,
2009
- ` console.log('[prerender] Spawning server on port ' + PORT + '...');`,
2010
- ` const srv = spawnProcess('node', [actualEntry], {`,
2011
- ` env: { ...process.env, PORT: String(PORT), NITRO_PORT: String(PORT), HOST: '127.0.0.1', NITRO_HOST: '127.0.0.1', NODE_ENV: 'production' },`,
2012
- ` stdio: ['ignore', 'pipe', 'pipe'],`,
2013
- ` });`,
2014
- ` srv.stdout?.on('data', d => { const m = d.toString().trim(); if (m) console.log('[prerender:server] ' + m); });`,
2015
- ` srv.stderr?.on('data', d => { const m = d.toString().trim(); if (m) console.error('[prerender:server:err] ' + m); });`,
2016
- ` let ready = false;`,
2017
- ` const t0 = Date.now();`,
2018
- ` while (Date.now() - t0 < 15000) {`,
2019
- ` try { const r = await fetch(baseUrl + '/'); if (r.ok || r.status < 500) { ready = true; break; } } catch {}`,
2020
- ` await new Promise(r => setTimeout(r, 200));`,
2021
- ` }`,
2022
- ` if (ready) {`,
2023
- ` console.log('[prerender] Server ready');`,
2024
- ` const visited = new Set();`,
2025
- ` const queue = ['/'];`,
2026
- ` const prerendered = [];`,
2027
- ` while (queue.length > 0) {`,
2028
- ` const batch = queue.splice(0, 4);`,
2029
- ` await Promise.all(batch.map(async (route) => {`,
2030
- ` const norm = route.endsWith('/') && route !== '/' ? route.slice(0, -1) : route;`,
2031
- ` if (visited.has(norm)) return;`,
2032
- ` visited.add(norm);`,
2033
- ` try {`,
2034
- ` const res = await fetch(baseUrl + norm);`,
2035
- ` if (!res.ok) { console.error('[prerender] ' + norm + ' returned ' + res.status); return; }`,
2036
- ` const html = await res.text();`,
2037
- ` const fileName = join(norm, 'index.html');`,
2038
- ` const out = join(outputDir, fileName);`,
2039
- ` mkdirSync(dirname(out), { recursive: true });`,
2040
- ` writeFileSync(out, html);`,
2041
- ` prerendered.push(norm);`,
2042
- ` console.log('[prerender] ' + norm);`,
2043
- ` // Crawl links`,
2044
- ` const re = /<a\\s[^>]*href=["']([^"'#?]+)/gi;`,
2045
- ` let m;`,
2046
- ` while ((m = re.exec(html)) !== null) {`,
2047
- ` const href = m[1];`,
2048
- ` if (href.startsWith('/') && !href.startsWith('//') && !href.startsWith('/assets/') && !href.startsWith('/islands/') && !href.match(/\\.\\w{2,5}$/)) {`,
2049
- ` const n = href.endsWith('/') && href !== '/' ? href.slice(0, -1) : href;`,
2050
- ` if (!visited.has(n)) queue.push(n);`,
2051
- ` }`,
2052
- ` }`,
2053
- ` } catch (err) { console.error('[prerender] Error fetching ' + norm + ':', err.message); }`,
2054
- ` }));`,
2055
- ` }`,
2056
- ` srv.kill('SIGKILL');`,
2057
- ` console.log('[prerender] Done: ' + prerendered.length + ' page(s)');`,
2058
- ` // Clean up wrapper`,
2059
- ` if (netlifyMode) {`,
2060
- ` const wp = join(dirname(serverEntry), '_prerender-server.mjs');`,
2061
- ` if (existsSync(wp)) unlinkSync(wp);`,
2062
- ` }`,
2063
- ` // Copy to alt output dirs`,
2064
- ` for (const alt of [DIST_DIR, join(CWD, '.netlify', 'v1', 'functions', 'server', 'public')].filter(d => d !== outputDir && existsSync(dirname(d)))) {`,
2065
- ` for (const route of prerendered) {`,
2066
- ` const f = join(route, 'index.html');`,
2067
- ` const src = join(outputDir, f);`,
2068
- ` const dest = join(alt, f);`,
2069
- ` if (existsSync(src)) { mkdirSync(dirname(dest), { recursive: true }); copyFileSync(src, dest); }`,
2070
- ` }`,
2071
- ` }`,
2072
- ` } else {`,
2073
- ` srv.kill('SIGKILL');`,
2074
- ` console.error('[prerender] Server did not start, skipping prerender');`,
2075
- ` }`,
2076
- `}`,
1896
+ ` resolve: {`,
1897
+ ` alias: [`,
1898
+ ` { find: '@shared', replacement: resolve('app/shared') },`,
1899
+ ` { find: '@modules', replacement: resolve('app/modules') },`,
1900
+ ` { find: '@/', replacement: \`\${resolve('app')}/\` },`,
1901
+ ` ],`,
1902
+ ` },`,
2077
1903
  ``,
2078
- `console.log('[post-build] Complete');`,
1904
+ ` build: {`,
1905
+ ` outDir: 'dist',`,
1906
+ ` emptyOutDir: true,`,
1907
+ ` },`,
2079
1908
  ``,
2080
- `})().catch(err => { console.error('[post-build] Fatal:', err); process.exit(1); });`,
2081
- ``
1909
+ ` server: {`,
1910
+ ` port: 3000,`,
1911
+ ` },`,
1912
+ ` };`,
1913
+ `});`,
1914
+ ""
2082
1915
  ];
2083
1916
  return lines.join(`
2084
1917
  `);
@@ -2144,32 +1977,18 @@ async function scaffoldProject(config, targetDir) {
2144
1977
  await writeFile(join(targetDir, "public/favicon.ico"), getFaviconBuffer());
2145
1978
  await writeFile(join(targetDir, "server/env.d.ts"), `/// <reference types="nitro" />
2146
1979
  `);
2147
- await writeFile(join(targetDir, "app/env.d.ts"), generateEnvDts());
1980
+ await writeFile(join(targetDir, "app/env.d.ts"), generateEnvDts(config.integrations));
2148
1981
  await writeFile(join(targetDir, "server/renderer.ts"), [
2149
- `import { createNitroRenderer } from '@useavalon/avalon/nitro/renderer';`,
2150
- `import { registerBuiltinDirectives } from '@useavalon/avalon';`,
2151
- `import avalonConfig from 'virtual:avalon/config';`,
2152
- `import { loadPage } from 'virtual:avalon/page-loader';`,
2153
- ``,
2154
- `// Register built-in custom hydration directives (on:delay, on:scroll, etc.)`,
2155
- `registerBuiltinDirectives();`,
2156
- ``,
2157
- `export default createNitroRenderer({`,
2158
- ` avalonConfig,`,
2159
- ` isDev: avalonConfig.isDev,`,
2160
- ` resolvePageRoute: async (pathname) => {`,
2161
- ` const mod = loadPage(pathname);`,
2162
- ` if (!mod) return null;`,
2163
- ` return { filePath: \`[virtual:\${pathname}]\`, pattern: pathname, params: {} };`,
2164
- ` },`,
2165
- ` loadPageModule: async (filePath) => {`,
2166
- ` const match = filePath.match(/^\\[virtual:(.+)\\]$/);`,
2167
- ` const pathname = match ? match[1] : filePath;`,
2168
- ` const mod = loadPage(pathname);`,
2169
- ` if (mod) return mod;`,
2170
- ` return { default: () => null, metadata: { title: 'Avalon' } };`,
2171
- ` },`,
2172
- `});`,
1982
+ `/**`,
1983
+ ` * SSR Renderer provided by Avalon's virtual module system.`,
1984
+ ` *`,
1985
+ ` * Avalon auto-discovers layouts, injects client assets, and handles`,
1986
+ ` * layout wrapping. Import from the virtual modules directly to customize:`,
1987
+ ` *`,
1988
+ ` * import { wrapWithLayouts } from 'virtual:avalon/layouts';`,
1989
+ ` * import { injectAssets } from 'virtual:avalon/assets';`,
1990
+ ` */`,
1991
+ `export { default } from 'virtual:avalon/renderer';`,
2173
1992
  ``
2174
1993
  ].join(`
2175
1994
  `));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-avalon",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "description": "Scaffold a new Avalon project with multi-framework islands architecture",
5
5
  "license": "MIT",
6
6
  "type": "module",