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.
- package/dist/cli.js +369 -550
- 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 {
|
|
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/
|
|
1501
|
-
function
|
|
1502
|
-
return
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
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/
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
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
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
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
|
-
|
|
1835
|
-
|
|
1836
|
-
const
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
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/
|
|
1844
|
-
function
|
|
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
|
-
|
|
1848
|
-
|
|
1849
|
-
`
|
|
1850
|
-
`
|
|
1851
|
-
`
|
|
1852
|
-
`
|
|
1853
|
-
`
|
|
1854
|
-
|
|
1855
|
-
`
|
|
1856
|
-
`
|
|
1857
|
-
`
|
|
1858
|
-
`
|
|
1859
|
-
`
|
|
1860
|
-
|
|
1861
|
-
`
|
|
1862
|
-
`
|
|
1863
|
-
`
|
|
1864
|
-
|
|
1865
|
-
`
|
|
1866
|
-
`
|
|
1867
|
-
|
|
1868
|
-
`
|
|
1869
|
-
`
|
|
1870
|
-
`
|
|
1871
|
-
`
|
|
1872
|
-
`
|
|
1873
|
-
`
|
|
1874
|
-
|
|
1875
|
-
`
|
|
1876
|
-
`
|
|
1877
|
-
`
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
`
|
|
1881
|
-
`
|
|
1882
|
-
`
|
|
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
|
-
`
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
`
|
|
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
|
-
`
|
|
1997
|
-
`
|
|
1998
|
-
`
|
|
1999
|
-
`
|
|
2000
|
-
`
|
|
2001
|
-
`
|
|
2002
|
-
`
|
|
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
|
-
`
|
|
1904
|
+
` build: {`,
|
|
1905
|
+
` outDir: 'dist',`,
|
|
1906
|
+
` emptyOutDir: true,`,
|
|
1907
|
+
` },`,
|
|
2079
1908
|
``,
|
|
2080
|
-
`
|
|
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
|
-
|
|
2150
|
-
`
|
|
2151
|
-
`
|
|
2152
|
-
`
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
`
|
|
2156
|
-
|
|
2157
|
-
`
|
|
2158
|
-
`
|
|
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
|
`));
|