create-avalon 0.1.17 → 0.1.19
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 +345 -547
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1204,7 +1204,190 @@ 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"
|
|
1233
|
+
|
|
1234
|
+
# SSR catch-all — Netlify checks for a matching static/prerendered file
|
|
1235
|
+
# first (force=false is the default in netlify.toml). Only requests with
|
|
1236
|
+
# no static file hit the server function.
|
|
1237
|
+
[[redirects]]
|
|
1238
|
+
from = "/*"
|
|
1239
|
+
to = "/.netlify/functions/server"
|
|
1240
|
+
status = 200
|
|
1241
|
+
`;
|
|
1242
|
+
}
|
|
1243
|
+
function generateBuildMjs() {
|
|
1244
|
+
return `/**
|
|
1245
|
+
* Netlify build wrapper.
|
|
1246
|
+
*
|
|
1247
|
+
* Vite/Nitro leaves open handles after the build completes, preventing
|
|
1248
|
+
* the Node process from exiting. This wrapper detects when the build
|
|
1249
|
+
* output is ready, kills the entire process group, then runs post-build.
|
|
1250
|
+
*/
|
|
1251
|
+
|
|
1252
|
+
import { spawn, execSync } from 'node:child_process';
|
|
1253
|
+
import { existsSync, rmSync } from 'node:fs';
|
|
1254
|
+
import { join } from 'node:path';
|
|
1255
|
+
|
|
1256
|
+
const CWD = process.cwd();
|
|
1257
|
+
const NITRO_JSON = join(CWD, '.netlify', 'functions-internal', 'nitro.json');
|
|
1258
|
+
const SERVER_MJS = join(CWD, '.netlify', 'functions-internal', 'server', 'server.mjs');
|
|
1259
|
+
const OUTPUT_SSR = join(CWD, '.output', 'server', '_ssr', 'ssr.mjs');
|
|
1260
|
+
|
|
1261
|
+
console.log('[build] Starting vite build...');
|
|
1262
|
+
|
|
1263
|
+
for (const dir of ['.netlify', '.output', 'netlify']) {
|
|
1264
|
+
const full = join(CWD, dir);
|
|
1265
|
+
if (existsSync(full)) {
|
|
1266
|
+
rmSync(full, { recursive: true, force: true });
|
|
1267
|
+
console.log(\`[build] Cleaned stale \${dir}/\`);
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
const child = spawn('bunx', ['--bun', 'vite', 'build'], {
|
|
1272
|
+
cwd: CWD,
|
|
1273
|
+
stdio: 'inherit',
|
|
1274
|
+
detached: true,
|
|
1275
|
+
});
|
|
1276
|
+
|
|
1277
|
+
const childPid = child.pid;
|
|
1278
|
+
let done = false;
|
|
1279
|
+
|
|
1280
|
+
function killTree() {
|
|
1281
|
+
try { process.kill(-childPid, 'SIGKILL'); } catch {}
|
|
1282
|
+
try { child.kill('SIGKILL'); } catch {}
|
|
1283
|
+
}
|
|
1284
|
+
|
|
1285
|
+
function finish() {
|
|
1286
|
+
if (done) return;
|
|
1287
|
+
done = true;
|
|
1288
|
+
clearInterval(poll);
|
|
1289
|
+
clearTimeout(absoluteTimeout);
|
|
1290
|
+
killTree();
|
|
1291
|
+
|
|
1292
|
+
setTimeout(() => {
|
|
1293
|
+
console.log('[build] Running post-build...');
|
|
1294
|
+
try {
|
|
1295
|
+
execSync('node post-build.mjs', { cwd: CWD, stdio: 'inherit', timeout: 120_000 });
|
|
1296
|
+
} catch (err) {
|
|
1297
|
+
console.error('[build] post-build warning:', err.message);
|
|
1298
|
+
}
|
|
1299
|
+
|
|
1300
|
+
const V1_SERVER = join(CWD, '.netlify', 'v1', 'functions', 'server', 'server.mjs');
|
|
1301
|
+
if (existsSync(V1_SERVER)) console.log('[build] ✅ Server function found (v1 API)');
|
|
1302
|
+
else if (existsSync(SERVER_MJS)) console.log('[build] ✅ Server function found (legacy)');
|
|
1303
|
+
else if (existsSync(OUTPUT_SSR)) console.log('[build] ✅ SSR bundle found');
|
|
1304
|
+
else console.error('[build] ❌ No server output found');
|
|
1305
|
+
|
|
1306
|
+
console.log('[build] ✅ Complete');
|
|
1307
|
+
process.exit(0);
|
|
1308
|
+
}, 500);
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
child.on('exit', (code) => {
|
|
1312
|
+
console.log(\`[build] vite build exited with code \${code}\`);
|
|
1313
|
+
finish();
|
|
1314
|
+
});
|
|
1315
|
+
|
|
1316
|
+
child.on('error', (err) => {
|
|
1317
|
+
console.error('[build] spawn error:', err);
|
|
1318
|
+
process.exit(1);
|
|
1319
|
+
});
|
|
1320
|
+
|
|
1321
|
+
const poll = setInterval(() => {
|
|
1322
|
+
const netlifyReady = existsSync(NITRO_JSON) && existsSync(SERVER_MJS);
|
|
1323
|
+
const nodeServerReady = existsSync(OUTPUT_SSR);
|
|
1324
|
+
if (netlifyReady || nodeServerReady) {
|
|
1325
|
+
console.log(\`[build] Output detected (\${netlifyReady ? 'netlify' : 'node-server'}), waiting 3s for final writes...\`);
|
|
1326
|
+
clearInterval(poll);
|
|
1327
|
+
setTimeout(finish, 3_000);
|
|
1328
|
+
}
|
|
1329
|
+
}, 1_000);
|
|
1330
|
+
|
|
1331
|
+
const absoluteTimeout = setTimeout(() => {
|
|
1332
|
+
console.error('[build] Timeout — killing build');
|
|
1333
|
+
finish();
|
|
1334
|
+
}, 240_000);
|
|
1335
|
+
`;
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
// src/templates/favicon.ts
|
|
1339
|
+
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=";
|
|
1340
|
+
function getFaviconBuffer() {
|
|
1341
|
+
return Buffer.from(FAVICON_BASE64, "base64");
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
// src/templates/layouts.ts
|
|
1345
|
+
function generateRootLayout(config) {
|
|
1346
|
+
const imports = [];
|
|
1347
|
+
imports.push(`import type { LayoutProps } from '@useavalon/avalon';`);
|
|
1348
|
+
if (config.styling === "css-modules") {
|
|
1349
|
+
imports.push(`import '../styles/main.css';`);
|
|
1350
|
+
} else {
|
|
1351
|
+
imports.push(`import '../styles/main.css';`);
|
|
1352
|
+
}
|
|
1353
|
+
return `${imports.join(`
|
|
1354
|
+
`)}
|
|
1355
|
+
|
|
1356
|
+
export default async function RootLayout({ children }: Readonly<LayoutProps>) {
|
|
1357
|
+
return (
|
|
1358
|
+
<html lang="en">
|
|
1359
|
+
<head>
|
|
1360
|
+
<meta charset="UTF-8" />
|
|
1361
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
1362
|
+
<title>${config.projectName}</title>
|
|
1363
|
+
<link rel="icon" href="/favicon.ico" />
|
|
1364
|
+
</head>
|
|
1365
|
+
<body style={{ margin: 0 }}>
|
|
1366
|
+
{children}
|
|
1367
|
+
</body>
|
|
1368
|
+
</html>
|
|
1369
|
+
);
|
|
1370
|
+
}
|
|
1371
|
+
`;
|
|
1372
|
+
}
|
|
1373
|
+
function generateHomeLayout(config) {
|
|
1374
|
+
return `import type { LayoutProps } from '@useavalon/avalon';
|
|
1375
|
+
|
|
1376
|
+
export default async function HomeLayout({ children }: Readonly<LayoutProps>) {
|
|
1377
|
+
return <>{children}</>;
|
|
1378
|
+
}
|
|
1379
|
+
`;
|
|
1380
|
+
}
|
|
1381
|
+
|
|
1382
|
+
// src/templates/middleware.ts
|
|
1383
|
+
function generateSampleMiddleware(_config) {
|
|
1384
|
+
return `import { defineHandler } from 'nitro';
|
|
1385
|
+
|
|
1386
|
+
export default defineHandler((event) => {
|
|
1387
|
+
console.log(\`[\${new Date().toISOString()}] \${event.req.method} \${event.url.pathname}\`);
|
|
1388
|
+
});
|
|
1389
|
+
`;
|
|
1390
|
+
}
|
|
1208
1391
|
|
|
1209
1392
|
// src/types.ts
|
|
1210
1393
|
var INTEGRATION_PACKAGES = {
|
|
@@ -1290,158 +1473,6 @@ function generatePackageJson(config) {
|
|
|
1290
1473
|
return JSON.stringify(pkg, null, 2);
|
|
1291
1474
|
}
|
|
1292
1475
|
|
|
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
1476
|
// src/templates/pages.ts
|
|
1446
1477
|
function generateHomePage(config) {
|
|
1447
1478
|
return `export const metadata = {
|
|
@@ -1497,24 +1528,27 @@ export default async function HomePage() {
|
|
|
1497
1528
|
`;
|
|
1498
1529
|
}
|
|
1499
1530
|
|
|
1500
|
-
// src/templates/
|
|
1501
|
-
function
|
|
1502
|
-
return
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
}
|
|
1517
|
-
|
|
1531
|
+
// src/templates/post-build.ts
|
|
1532
|
+
function generatePostBuildMjs() {
|
|
1533
|
+
return [
|
|
1534
|
+
`/**`,
|
|
1535
|
+
` * Post-build script — delegates to Avalon's built-in post-build.`,
|
|
1536
|
+
` *`,
|
|
1537
|
+
` * All the heavy lifting (CSS patching, island redirects, prerendering,`,
|
|
1538
|
+
` * Netlify function copying) is handled by the framework.`,
|
|
1539
|
+
` */`,
|
|
1540
|
+
`import { runPostBuild } from '@useavalon/avalon/post-build';`,
|
|
1541
|
+
``,
|
|
1542
|
+
`await runPostBuild({`,
|
|
1543
|
+
` prerender: {`,
|
|
1544
|
+
` routes: ['/'],`,
|
|
1545
|
+
` crawlLinks: true,`,
|
|
1546
|
+
` failOnError: false,`,
|
|
1547
|
+
` },`,
|
|
1548
|
+
`});`,
|
|
1549
|
+
``
|
|
1550
|
+
].join(`
|
|
1551
|
+
`);
|
|
1518
1552
|
}
|
|
1519
1553
|
|
|
1520
1554
|
// src/templates/styling.ts
|
|
@@ -1715,370 +1749,144 @@ function generateShadcnComponentsJson(config) {
|
|
|
1715
1749
|
`;
|
|
1716
1750
|
}
|
|
1717
1751
|
|
|
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 {}
|
|
1752
|
+
// src/templates/tsconfig.ts
|
|
1753
|
+
function generateTsConfig() {
|
|
1754
|
+
const tsconfig = {
|
|
1755
|
+
compilerOptions: {
|
|
1756
|
+
target: "ESNext",
|
|
1757
|
+
module: "ESNext",
|
|
1758
|
+
moduleResolution: "bundler",
|
|
1759
|
+
strict: true,
|
|
1760
|
+
esModuleInterop: true,
|
|
1761
|
+
skipLibCheck: true,
|
|
1762
|
+
allowArbitraryExtensions: true,
|
|
1763
|
+
allowImportingTsExtensions: true,
|
|
1764
|
+
noEmit: true,
|
|
1765
|
+
jsx: "react-jsx",
|
|
1766
|
+
paths: {
|
|
1767
|
+
"@shared/*": ["./app/shared/*"],
|
|
1768
|
+
"@modules/*": ["./app/modules/*"]
|
|
1769
|
+
}
|
|
1770
|
+
},
|
|
1771
|
+
include: [
|
|
1772
|
+
"app/**/*.ts",
|
|
1773
|
+
"app/**/*.tsx",
|
|
1774
|
+
"app/**/*.d.ts",
|
|
1775
|
+
"server/**/*.ts",
|
|
1776
|
+
"routes/**/*.ts",
|
|
1777
|
+
"middleware/**/*.ts"
|
|
1778
|
+
]
|
|
1779
|
+
};
|
|
1780
|
+
return JSON.stringify(tsconfig, null, 2);
|
|
1788
1781
|
}
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1782
|
+
function generateEnvDts(integrations = []) {
|
|
1783
|
+
const lines = [
|
|
1784
|
+
`/**`,
|
|
1785
|
+
` * Auto-generated by create-avalon — do not edit manually.`,
|
|
1786
|
+
` *`,
|
|
1787
|
+
` * This file provides TypeScript declarations for cross-framework`,
|
|
1788
|
+
` * island imports, CSS modules, and Nitro virtual asset manifests.`,
|
|
1789
|
+
` * Re-run the scaffold to regenerate after changing integrations.`,
|
|
1790
|
+
` */`,
|
|
1791
|
+
`/// <reference types="@useavalon/avalon/types" />`,
|
|
1792
|
+
``
|
|
1793
|
+
];
|
|
1794
|
+
const frameworkModules = {
|
|
1795
|
+
vue: { pattern: "*.vue" },
|
|
1796
|
+
svelte: { pattern: "*.svelte" },
|
|
1797
|
+
solid: { pattern: "*.solid.tsx" },
|
|
1798
|
+
lit: { pattern: "*.lit.ts" },
|
|
1799
|
+
qwik: { pattern: "*.qwik.tsx" }
|
|
1800
|
+
};
|
|
1801
|
+
const selected = Object.entries(frameworkModules).filter(([name]) => integrations.includes(name));
|
|
1802
|
+
if (selected.length > 0) {
|
|
1803
|
+
for (const [, { pattern }] of selected) {
|
|
1804
|
+
lines.push(`declare module '${pattern}' {`, ` import type { ComponentType } from 'preact';`, ` const component: ComponentType<Record<string, unknown>>;`, ` export default component;`, `}`, ``);
|
|
1803
1805
|
}
|
|
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
1806
|
}
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
const
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
`;
|
|
1807
|
+
lines.push(`declare module '*.module.css' {`, ` const classes: Record<string, string>;`, ` export default classes;`, `}`, ``);
|
|
1808
|
+
for (const suffix of ["client", "ssr"]) {
|
|
1809
|
+
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;`, `}`, ``);
|
|
1810
|
+
}
|
|
1811
|
+
return lines.join(`
|
|
1812
|
+
`);
|
|
1841
1813
|
}
|
|
1842
1814
|
|
|
1843
|
-
// src/templates/
|
|
1844
|
-
function
|
|
1815
|
+
// src/templates/vite-config.ts
|
|
1816
|
+
function generateViteConfig(config) {
|
|
1817
|
+
const imports = [
|
|
1818
|
+
`import { resolve } from 'node:path';`,
|
|
1819
|
+
`import { defineConfig, type UserConfig } from 'vite';`,
|
|
1820
|
+
`import { avalon } from '@useavalon/avalon';`
|
|
1821
|
+
];
|
|
1822
|
+
const needsTailwind = config.styling === "tailwind" || config.styling === "shadcn";
|
|
1823
|
+
if (needsTailwind) {
|
|
1824
|
+
imports.push(`import tailwindcss from '@tailwindcss/vite';`);
|
|
1825
|
+
}
|
|
1826
|
+
const hasAgentOptimization = config.plugins.includes("agent-optimization");
|
|
1827
|
+
if (hasAgentOptimization) {
|
|
1828
|
+
imports.push(`import { agentOptimization } from '@useavalon/agent-optimization';`);
|
|
1829
|
+
}
|
|
1830
|
+
const integrationsList = config.integrations.map((i) => `'${i}'`).join(", ");
|
|
1831
|
+
const pluginEntries = [];
|
|
1832
|
+
if (hasAgentOptimization) {
|
|
1833
|
+
pluginEntries.push(` agentOptimization({
|
|
1834
|
+
sitemap: { siteUrl: 'http://localhost:3000' },
|
|
1835
|
+
markdown: true,
|
|
1836
|
+
structuredData: true,
|
|
1837
|
+
llms: {
|
|
1838
|
+
siteUrl: 'http://localhost:3000',
|
|
1839
|
+
siteName: 'My Avalon App',
|
|
1840
|
+
siteDescription: 'Built with Avalon',
|
|
1841
|
+
sections: { 'Pages': ['/'] },
|
|
1842
|
+
},
|
|
1843
|
+
}),`);
|
|
1844
|
+
}
|
|
1845
|
+
pluginEntries.push(` ...avalonPlugins,`);
|
|
1846
|
+
if (needsTailwind) {
|
|
1847
|
+
pluginEntries.push(` tailwindcss(),`);
|
|
1848
|
+
}
|
|
1845
1849
|
const lines = [
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
`
|
|
1850
|
-
`
|
|
1851
|
-
`
|
|
1852
|
-
`
|
|
1853
|
-
`
|
|
1854
|
-
|
|
1855
|
-
`
|
|
1856
|
-
`
|
|
1857
|
-
`
|
|
1858
|
-
`
|
|
1859
|
-
`
|
|
1860
|
-
|
|
1861
|
-
`
|
|
1862
|
-
`
|
|
1863
|
-
`
|
|
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
|
-
`}`,
|
|
1987
|
-
``,
|
|
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));`,
|
|
1995
|
-
``,
|
|
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'],`,
|
|
1850
|
+
imports.join(`
|
|
1851
|
+
`),
|
|
1852
|
+
"",
|
|
1853
|
+
`export default defineConfig(async (): Promise<UserConfig> => {`,
|
|
1854
|
+
` const avalonPlugins = await avalon({`,
|
|
1855
|
+
` integrations: [${integrationsList}],`,
|
|
1856
|
+
` modules: 'app/modules',`,
|
|
1857
|
+
` layoutsDir: 'app/shared/layouts',`,
|
|
1858
|
+
` image: true,`,
|
|
1859
|
+
` nitro: {`,
|
|
1860
|
+
` preset: process.env.NITRO_PRESET || 'node_server',`,
|
|
1861
|
+
` streaming: true,`,
|
|
1862
|
+
` prerender: {`,
|
|
1863
|
+
` routes: ['/'],`,
|
|
1864
|
+
` crawlLinks: true,`,
|
|
1865
|
+
` ignore: [],`,
|
|
1866
|
+
` },`,
|
|
1867
|
+
` },`,
|
|
2013
1868
|
` });`,
|
|
2014
|
-
|
|
2015
|
-
`
|
|
2016
|
-
`
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
`
|
|
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
|
-
`}`,
|
|
1869
|
+
"",
|
|
1870
|
+
` return {`,
|
|
1871
|
+
` plugins: [`,
|
|
1872
|
+
pluginEntries.join(`
|
|
1873
|
+
`),
|
|
1874
|
+
` ],`,
|
|
2077
1875
|
``,
|
|
2078
|
-
`
|
|
1876
|
+
` resolve: {`,
|
|
1877
|
+
` alias: {`,
|
|
1878
|
+
` '@shared': resolve(__dirname, 'app/shared'),`,
|
|
1879
|
+
` '@modules': resolve(__dirname, 'app/modules'),`,
|
|
1880
|
+
` '@': resolve(__dirname, 'app'),`,
|
|
1881
|
+
` },`,
|
|
1882
|
+
` },`,
|
|
2079
1883
|
``,
|
|
2080
|
-
`
|
|
2081
|
-
|
|
1884
|
+
` server: {`,
|
|
1885
|
+
` port: 3000,`,
|
|
1886
|
+
` },`,
|
|
1887
|
+
` };`,
|
|
1888
|
+
`});`,
|
|
1889
|
+
""
|
|
2082
1890
|
];
|
|
2083
1891
|
return lines.join(`
|
|
2084
1892
|
`);
|
|
@@ -2144,28 +1952,18 @@ async function scaffoldProject(config, targetDir) {
|
|
|
2144
1952
|
await writeFile(join(targetDir, "public/favicon.ico"), getFaviconBuffer());
|
|
2145
1953
|
await writeFile(join(targetDir, "server/env.d.ts"), `/// <reference types="nitro" />
|
|
2146
1954
|
`);
|
|
2147
|
-
await writeFile(join(targetDir, "app/env.d.ts"), generateEnvDts());
|
|
1955
|
+
await writeFile(join(targetDir, "app/env.d.ts"), generateEnvDts(config.integrations));
|
|
2148
1956
|
await writeFile(join(targetDir, "server/renderer.ts"), [
|
|
2149
|
-
|
|
2150
|
-
`
|
|
2151
|
-
`
|
|
2152
|
-
|
|
2153
|
-
`
|
|
2154
|
-
`
|
|
2155
|
-
`
|
|
2156
|
-
`
|
|
2157
|
-
`
|
|
2158
|
-
`
|
|
2159
|
-
` return { filePath: \`[virtual:\${pathname}]\`, pattern: pathname, params: {} };`,
|
|
2160
|
-
` },`,
|
|
2161
|
-
` loadPageModule: async (filePath) => {`,
|
|
2162
|
-
` const match = filePath.match(/^\\[virtual:(.+)\\]$/);`,
|
|
2163
|
-
` const pathname = match ? match[1] : filePath;`,
|
|
2164
|
-
` const mod = loadPage(pathname);`,
|
|
2165
|
-
` if (mod) return mod;`,
|
|
2166
|
-
` return { default: () => null, metadata: { title: 'Avalon' } };`,
|
|
2167
|
-
` },`,
|
|
2168
|
-
`});`,
|
|
1957
|
+
`/**`,
|
|
1958
|
+
` * SSR Renderer — provided by Avalon's virtual module system.`,
|
|
1959
|
+
` *`,
|
|
1960
|
+
` * Avalon auto-discovers layouts, injects client assets, and handles`,
|
|
1961
|
+
` * layout wrapping. Import from the virtual modules directly to customize:`,
|
|
1962
|
+
` *`,
|
|
1963
|
+
` * import { wrapWithLayouts } from 'virtual:avalon/layouts';`,
|
|
1964
|
+
` * import { injectAssets } from 'virtual:avalon/assets';`,
|
|
1965
|
+
` */`,
|
|
1966
|
+
`export { default } from 'virtual:avalon/renderer';`,
|
|
2169
1967
|
``
|
|
2170
1968
|
].join(`
|
|
2171
1969
|
`));
|