create-avalon 0.1.14 → 0.1.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -59
- package/dist/cli.js +359 -20
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
# create-avalon
|
|
2
|
-
|
|
3
|
-
Scaffold a new [Avalon](https://useavalon.dev) project in seconds.
|
|
4
|
-
|
|
5
|
-
## Usage
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
npm create avalon@latest
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
Or with other package managers:
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
pnpm create avalon@latest
|
|
15
|
-
yarn create avalon
|
|
16
|
-
bun create avalon
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
The CLI walks you through:
|
|
20
|
-
|
|
21
|
-
- Project name and directory
|
|
22
|
-
- Framework selection (React, Preact, Vue, Svelte, Solid, Lit, Qwik — or multiple)
|
|
23
|
-
- Styling approach (CSS Modules, Tailwind, vanilla CSS)
|
|
24
|
-
- Optional features (API routes, middleware, layouts, MDX)
|
|
25
|
-
- Package manager preference
|
|
26
|
-
|
|
27
|
-
## What you get
|
|
28
|
-
|
|
29
|
-
A ready-to-run Avalon project with file-system routing, islands architecture, and zero JavaScript by default.
|
|
30
|
-
|
|
31
|
-
```
|
|
32
|
-
my-project/
|
|
33
|
-
├── app/
|
|
34
|
-
│ ├── modules/
|
|
35
|
-
│ │ └── home/
|
|
36
|
-
│ │ ├── pages/ # File-system routes
|
|
37
|
-
│ │ ├── components/ # Interactive components
|
|
38
|
-
│ │ └── layouts/ # Module layouts
|
|
39
|
-
│ └── shared/
|
|
40
|
-
│ ├── layouts/ # Root layout
|
|
41
|
-
│ ├── components/ # Shared components
|
|
42
|
-
│ └── styles/ # Global styles & tokens
|
|
43
|
-
├── middleware/ # Server middleware
|
|
44
|
-
├── routes/
|
|
45
|
-
│ └── api/ # API routes
|
|
46
|
-
├── server/ # Server config & env
|
|
47
|
-
├── public/ # Static assets
|
|
48
|
-
├── vite.config.ts
|
|
49
|
-
└── package.json
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
## Links
|
|
53
|
-
|
|
54
|
-
- [Documentation](https://useavalon.dev/docs/introduction)
|
|
55
|
-
- [GitHub](https://github.com/useAvalon/Avalon)
|
|
56
|
-
|
|
57
|
-
## License
|
|
58
|
-
|
|
59
|
-
MIT
|
|
1
|
+
# create-avalon
|
|
2
|
+
|
|
3
|
+
Scaffold a new [Avalon](https://useavalon.dev) project in seconds.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm create avalon@latest
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or with other package managers:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pnpm create avalon@latest
|
|
15
|
+
yarn create avalon
|
|
16
|
+
bun create avalon
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The CLI walks you through:
|
|
20
|
+
|
|
21
|
+
- Project name and directory
|
|
22
|
+
- Framework selection (React, Preact, Vue, Svelte, Solid, Lit, Qwik — or multiple)
|
|
23
|
+
- Styling approach (CSS Modules, Tailwind, vanilla CSS)
|
|
24
|
+
- Optional features (API routes, middleware, layouts, MDX)
|
|
25
|
+
- Package manager preference
|
|
26
|
+
|
|
27
|
+
## What you get
|
|
28
|
+
|
|
29
|
+
A ready-to-run Avalon project with file-system routing, islands architecture, and zero JavaScript by default.
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
my-project/
|
|
33
|
+
├── app/
|
|
34
|
+
│ ├── modules/
|
|
35
|
+
│ │ └── home/
|
|
36
|
+
│ │ ├── pages/ # File-system routes
|
|
37
|
+
│ │ ├── components/ # Interactive components
|
|
38
|
+
│ │ └── layouts/ # Module layouts
|
|
39
|
+
│ └── shared/
|
|
40
|
+
│ ├── layouts/ # Root layout
|
|
41
|
+
│ ├── components/ # Shared components
|
|
42
|
+
│ └── styles/ # Global styles & tokens
|
|
43
|
+
├── middleware/ # Server middleware
|
|
44
|
+
├── routes/
|
|
45
|
+
│ └── api/ # API routes
|
|
46
|
+
├── server/ # Server config & env
|
|
47
|
+
├── public/ # Static assets
|
|
48
|
+
├── vite.config.ts
|
|
49
|
+
└── package.json
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Links
|
|
53
|
+
|
|
54
|
+
- [Documentation](https://useavalon.dev/docs/introduction)
|
|
55
|
+
- [GitHub](https://github.com/useAvalon/Avalon)
|
|
56
|
+
|
|
57
|
+
## License
|
|
58
|
+
|
|
59
|
+
MIT
|
package/dist/cli.js
CHANGED
|
@@ -1181,12 +1181,24 @@ async function collectProjectConfig(initialName) {
|
|
|
1181
1181
|
Nt("Operation cancelled.");
|
|
1182
1182
|
process.exit(1);
|
|
1183
1183
|
}
|
|
1184
|
+
const deployResult = await Jt({
|
|
1185
|
+
message: "Where will you deploy?",
|
|
1186
|
+
options: [
|
|
1187
|
+
{ value: "netlify", label: "Netlify", hint: "Generates netlify.toml, build.mjs, post-build.mjs" },
|
|
1188
|
+
{ value: "none", label: "None / Other", hint: "Node server preset, no deploy config" }
|
|
1189
|
+
]
|
|
1190
|
+
});
|
|
1191
|
+
if (Ct(deployResult)) {
|
|
1192
|
+
Nt("Operation cancelled.");
|
|
1193
|
+
process.exit(1);
|
|
1194
|
+
}
|
|
1184
1195
|
return {
|
|
1185
1196
|
projectName,
|
|
1186
1197
|
integrations: integrationsResult,
|
|
1187
1198
|
styling: stylingResult,
|
|
1188
1199
|
plugins: pluginsResult,
|
|
1189
|
-
middleware: middlewareResult
|
|
1200
|
+
middleware: middlewareResult,
|
|
1201
|
+
deploy: deployResult
|
|
1190
1202
|
};
|
|
1191
1203
|
}
|
|
1192
1204
|
|
|
@@ -1264,12 +1276,17 @@ function generatePackageJson(config) {
|
|
|
1264
1276
|
private: true,
|
|
1265
1277
|
scripts: {
|
|
1266
1278
|
dev: "bunx --bun vite dev",
|
|
1267
|
-
build: "bunx --bun vite build",
|
|
1268
|
-
|
|
1279
|
+
build: config.deploy === "netlify" ? "bun build.mjs" : "bunx --bun vite build",
|
|
1280
|
+
"post-build": config.deploy === "netlify" ? "node post-build.mjs" : undefined,
|
|
1281
|
+
preview: "node .output/server/index.mjs"
|
|
1269
1282
|
},
|
|
1270
1283
|
dependencies,
|
|
1271
1284
|
devDependencies
|
|
1272
1285
|
};
|
|
1286
|
+
for (const key of Object.keys(pkg.scripts)) {
|
|
1287
|
+
if (pkg.scripts[key] === undefined)
|
|
1288
|
+
delete pkg.scripts[key];
|
|
1289
|
+
}
|
|
1273
1290
|
return JSON.stringify(pkg, null, 2);
|
|
1274
1291
|
}
|
|
1275
1292
|
|
|
@@ -1352,8 +1369,13 @@ function generateViteConfig(config) {
|
|
|
1352
1369
|
` layoutsDir: 'app/shared/layouts',`,
|
|
1353
1370
|
` image: true,`,
|
|
1354
1371
|
` nitro: {`,
|
|
1355
|
-
` preset: 'node_server',`,
|
|
1372
|
+
` preset: process.env.NITRO_PRESET || 'node_server',`,
|
|
1356
1373
|
` streaming: true,`,
|
|
1374
|
+
` prerender: {`,
|
|
1375
|
+
` routes: ['/'],`,
|
|
1376
|
+
` crawlLinks: true,`,
|
|
1377
|
+
` ignore: [],`,
|
|
1378
|
+
` },`,
|
|
1357
1379
|
` },`,
|
|
1358
1380
|
` });`,
|
|
1359
1381
|
"",
|
|
@@ -1699,6 +1721,328 @@ function getFaviconBuffer() {
|
|
|
1699
1721
|
return Buffer.from(FAVICON_BASE64, "base64");
|
|
1700
1722
|
}
|
|
1701
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
|
+
}
|
|
1740
|
+
function generateBuildMjs() {
|
|
1741
|
+
return `/**
|
|
1742
|
+
* Netlify build wrapper.
|
|
1743
|
+
*
|
|
1744
|
+
* Vite/Nitro leaves open handles after the build completes, preventing
|
|
1745
|
+
* the Node process from exiting. This wrapper detects when the build
|
|
1746
|
+
* output is ready, kills the entire process group, then runs post-build.
|
|
1747
|
+
*/
|
|
1748
|
+
|
|
1749
|
+
import { spawn, execSync } from 'node:child_process';
|
|
1750
|
+
import { existsSync, rmSync } from 'node:fs';
|
|
1751
|
+
import { join } from 'node:path';
|
|
1752
|
+
|
|
1753
|
+
const CWD = process.cwd();
|
|
1754
|
+
const NITRO_JSON = join(CWD, '.netlify', 'functions-internal', 'nitro.json');
|
|
1755
|
+
const SERVER_MJS = join(CWD, '.netlify', 'functions-internal', 'server', 'server.mjs');
|
|
1756
|
+
const OUTPUT_SSR = join(CWD, '.output', 'server', '_ssr', 'ssr.mjs');
|
|
1757
|
+
|
|
1758
|
+
console.log('[build] Starting vite build...');
|
|
1759
|
+
|
|
1760
|
+
for (const dir of ['.netlify', '.output', 'netlify']) {
|
|
1761
|
+
const full = join(CWD, dir);
|
|
1762
|
+
if (existsSync(full)) {
|
|
1763
|
+
rmSync(full, { recursive: true, force: true });
|
|
1764
|
+
console.log(\`[build] Cleaned stale \${dir}/\`);
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
const child = spawn('bunx', ['--bun', 'vite', 'build'], {
|
|
1769
|
+
cwd: CWD,
|
|
1770
|
+
stdio: 'inherit',
|
|
1771
|
+
detached: true,
|
|
1772
|
+
});
|
|
1773
|
+
|
|
1774
|
+
const childPid = child.pid;
|
|
1775
|
+
let done = false;
|
|
1776
|
+
|
|
1777
|
+
function killTree() {
|
|
1778
|
+
try { process.kill(-childPid, 'SIGKILL'); } catch {}
|
|
1779
|
+
try { child.kill('SIGKILL'); } catch {}
|
|
1780
|
+
}
|
|
1781
|
+
|
|
1782
|
+
function finish() {
|
|
1783
|
+
if (done) return;
|
|
1784
|
+
done = true;
|
|
1785
|
+
clearInterval(poll);
|
|
1786
|
+
clearTimeout(absoluteTimeout);
|
|
1787
|
+
killTree();
|
|
1788
|
+
|
|
1789
|
+
setTimeout(() => {
|
|
1790
|
+
console.log('[build] Running post-build...');
|
|
1791
|
+
try {
|
|
1792
|
+
execSync('node post-build.mjs', { cwd: CWD, stdio: 'inherit', timeout: 120_000 });
|
|
1793
|
+
} catch (err) {
|
|
1794
|
+
console.error('[build] post-build warning:', err.message);
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1797
|
+
const V1_SERVER = join(CWD, '.netlify', 'v1', 'functions', 'server', 'server.mjs');
|
|
1798
|
+
if (existsSync(V1_SERVER)) console.log('[build] ✅ Server function found (v1 API)');
|
|
1799
|
+
else if (existsSync(SERVER_MJS)) console.log('[build] ✅ Server function found (legacy)');
|
|
1800
|
+
else if (existsSync(OUTPUT_SSR)) console.log('[build] ✅ SSR bundle found');
|
|
1801
|
+
else console.error('[build] ❌ No server output found');
|
|
1802
|
+
|
|
1803
|
+
console.log('[build] ✅ Complete');
|
|
1804
|
+
process.exit(0);
|
|
1805
|
+
}, 500);
|
|
1806
|
+
}
|
|
1807
|
+
|
|
1808
|
+
child.on('exit', (code) => {
|
|
1809
|
+
console.log(\`[build] vite build exited with code \${code}\`);
|
|
1810
|
+
finish();
|
|
1811
|
+
});
|
|
1812
|
+
|
|
1813
|
+
child.on('error', (err) => {
|
|
1814
|
+
console.error('[build] spawn error:', err);
|
|
1815
|
+
process.exit(1);
|
|
1816
|
+
});
|
|
1817
|
+
|
|
1818
|
+
const poll = setInterval(() => {
|
|
1819
|
+
const netlifyReady = existsSync(NITRO_JSON) && existsSync(SERVER_MJS);
|
|
1820
|
+
const nodeServerReady = existsSync(OUTPUT_SSR);
|
|
1821
|
+
if (netlifyReady || nodeServerReady) {
|
|
1822
|
+
console.log(\`[build] Output detected (\${netlifyReady ? 'netlify' : 'node-server'}), waiting 3s for final writes...\`);
|
|
1823
|
+
clearInterval(poll);
|
|
1824
|
+
setTimeout(finish, 3_000);
|
|
1825
|
+
}
|
|
1826
|
+
}, 1_000);
|
|
1827
|
+
|
|
1828
|
+
const absoluteTimeout = setTimeout(() => {
|
|
1829
|
+
console.error('[build] Timeout — killing build');
|
|
1830
|
+
finish();
|
|
1831
|
+
}, 240_000);
|
|
1832
|
+
`;
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
// src/templates/post-build.ts
|
|
1836
|
+
function generatePostBuildMjs() {
|
|
1837
|
+
const lines = [
|
|
1838
|
+
`/**`,
|
|
1839
|
+
` * Post-build script for Netlify deployment.`,
|
|
1840
|
+
` *`,
|
|
1841
|
+
` * - Removes stale index.html that would shadow SSR routes`,
|
|
1842
|
+
` * - Generates _redirects for island JS path mapping`,
|
|
1843
|
+
` * - Copies island JS to clean paths for local preview`,
|
|
1844
|
+
` * - Copies server function to all Netlify function paths`,
|
|
1845
|
+
` * - Ensures SSR catch-all redirect exists`,
|
|
1846
|
+
` */`,
|
|
1847
|
+
``,
|
|
1848
|
+
`import {`,
|
|
1849
|
+
` existsSync, unlinkSync, readFileSync, writeFileSync,`,
|
|
1850
|
+
` readdirSync, copyFileSync, mkdirSync, cpSync,`,
|
|
1851
|
+
`} from 'node:fs';`,
|
|
1852
|
+
`import { join, dirname, relative } from 'node:path';`,
|
|
1853
|
+
``,
|
|
1854
|
+
`const CWD = process.cwd();`,
|
|
1855
|
+
`const DIST_DIR = join(CWD, 'dist');`,
|
|
1856
|
+
`const ASSETS_DIR = join(CWD, 'dist', 'assets');`,
|
|
1857
|
+
``,
|
|
1858
|
+
`function collectFiles(dir, predicate, result = []) {`,
|
|
1859
|
+
` if (!existsSync(dir)) return result;`,
|
|
1860
|
+
` for (const entry of readdirSync(dir, { withFileTypes: true })) {`,
|
|
1861
|
+
` const full = join(dir, entry.name);`,
|
|
1862
|
+
` if (entry.isDirectory()) collectFiles(full, predicate, result);`,
|
|
1863
|
+
` else if (predicate(entry.name)) result.push(full);`,
|
|
1864
|
+
` }`,
|
|
1865
|
+
` return result;`,
|
|
1866
|
+
`}`,
|
|
1867
|
+
``,
|
|
1868
|
+
`function toServePath(absPath) {`,
|
|
1869
|
+
` return '/' + relative(DIST_DIR, absPath).replaceAll('\\\\', '/');`,
|
|
1870
|
+
`}`,
|
|
1871
|
+
``,
|
|
1872
|
+
`// ── Cleanup stale index.html ─────────────────────────────────────`,
|
|
1873
|
+
`for (const htmlPath of ['dist/index.html', '.netlify/functions-internal/server/public/index.html']) {`,
|
|
1874
|
+
` const full = join(CWD, htmlPath);`,
|
|
1875
|
+
` if (existsSync(full)) { unlinkSync(full); console.log('[cleanup] Removed ' + htmlPath); }`,
|
|
1876
|
+
`}`,
|
|
1877
|
+
``,
|
|
1878
|
+
`// ── Generate island redirects + local copies ─────────────────────`,
|
|
1879
|
+
`function generateIslandRedirects() {`,
|
|
1880
|
+
` const islandsDir = join(ASSETS_DIR, 'islands');`,
|
|
1881
|
+
` if (!existsSync(islandsDir)) return;`,
|
|
1882
|
+
` const islandFiles = collectFiles(islandsDir, n => n.endsWith('.js') && !n.endsWith('.js.map'));`,
|
|
1883
|
+
` if (islandFiles.length === 0) return;`,
|
|
1884
|
+
` const redirectLines = [];`,
|
|
1885
|
+
` for (const absPath of islandFiles) {`,
|
|
1886
|
+
` const servePath = toServePath(absPath);`,
|
|
1887
|
+
` const cleanPath = servePath.replace('/assets/', '/').replace(/-[A-Za-z0-9_-]{6,12}\\.js$/, '.js');`,
|
|
1888
|
+
` redirectLines.push(cleanPath + ' ' + servePath + ' 200');`,
|
|
1889
|
+
` const cleanAbsPath = join(DIST_DIR, cleanPath.slice(1));`,
|
|
1890
|
+
` mkdirSync(dirname(cleanAbsPath), { recursive: true });`,
|
|
1891
|
+
` copyFileSync(absPath, cleanAbsPath);`,
|
|
1892
|
+
` }`,
|
|
1893
|
+
` const redirectsPath = join(DIST_DIR, '_redirects');`,
|
|
1894
|
+
` let existing = existsSync(redirectsPath) ? readFileSync(redirectsPath, 'utf-8') : '';`,
|
|
1895
|
+
` existing = existing.replaceAll(/# Island JS path rewrites[^\\n]*\\n(?:\\/islands\\/[^\\n]*\\n)*/g, '').trim();`,
|
|
1896
|
+
` const header = '# Island JS path rewrites (generated by post-build.mjs)\\n';`,
|
|
1897
|
+
` const content = existing`,
|
|
1898
|
+
` ? existing + '\\n\\n' + header + redirectLines.join('\\n') + '\\n'`,
|
|
1899
|
+
` : header + redirectLines.join('\\n') + '\\n';`,
|
|
1900
|
+
` writeFileSync(redirectsPath, content);`,
|
|
1901
|
+
` console.log('[redirects] Wrote ' + redirectLines.length + ' island redirects');`,
|
|
1902
|
+
`}`,
|
|
1903
|
+
``,
|
|
1904
|
+
`// ── Copy framework adapters ───────────────────────────────────────`,
|
|
1905
|
+
`function copyAdapters() {`,
|
|
1906
|
+
` const sources = [`,
|
|
1907
|
+
` join(CWD, '.output', 'public', '_adapters'),`,
|
|
1908
|
+
` join(CWD, 'dist', '_adapters'),`,
|
|
1909
|
+
` ];`,
|
|
1910
|
+
` for (const srcDir of sources) {`,
|
|
1911
|
+
` if (!existsSync(srcDir)) continue;`,
|
|
1912
|
+
` const files = readdirSync(srcDir).filter(f => f.endsWith('.js'));`,
|
|
1913
|
+
` if (files.length === 0) continue;`,
|
|
1914
|
+
` const destDir = join(DIST_DIR, '_adapters');`,
|
|
1915
|
+
` mkdirSync(destDir, { recursive: true });`,
|
|
1916
|
+
` for (const file of files) {`,
|
|
1917
|
+
` const src = join(srcDir, file);`,
|
|
1918
|
+
` const dest = join(destDir, file);`,
|
|
1919
|
+
` if (src !== dest) copyFileSync(src, dest);`,
|
|
1920
|
+
` }`,
|
|
1921
|
+
` console.log('[adapters] Copied ' + files.length + ' framework adapters');`,
|
|
1922
|
+
` return;`,
|
|
1923
|
+
` }`,
|
|
1924
|
+
`}`,
|
|
1925
|
+
``,
|
|
1926
|
+
`// ── Copy function to all Netlify paths ────────────────────────────`,
|
|
1927
|
+
`function copyToNetlifyPaths() {`,
|
|
1928
|
+
` const legacyDir = join(CWD, '.netlify', 'functions-internal', 'server');`,
|
|
1929
|
+
` if (!existsSync(legacyDir)) return;`,
|
|
1930
|
+
` const targets = [`,
|
|
1931
|
+
` join(CWD, '.netlify', 'v1', 'functions', 'server'),`,
|
|
1932
|
+
` join(CWD, 'netlify', 'functions', 'server'),`,
|
|
1933
|
+
` ];`,
|
|
1934
|
+
` for (const target of targets) {`,
|
|
1935
|
+
` cpSync(legacyDir, target, { recursive: true, force: true });`,
|
|
1936
|
+
` }`,
|
|
1937
|
+
` console.log('[netlify-fn] Copied server function to all Netlify paths');`,
|
|
1938
|
+
`}`,
|
|
1939
|
+
``,
|
|
1940
|
+
`// ── Ensure SSR catch-all redirect ─────────────────────────────────`,
|
|
1941
|
+
`function ensureNetlifyRedirects() {`,
|
|
1942
|
+
` const redirectsPath = join(DIST_DIR, '_redirects');`,
|
|
1943
|
+
` let content = existsSync(redirectsPath) ? readFileSync(redirectsPath, 'utf-8') : '';`,
|
|
1944
|
+
` if (content.includes('/.netlify/functions/server')) return;`,
|
|
1945
|
+
` const catchAll = '\\n# SSR catch-all (Nitro server function)\\n/* /.netlify/functions/server 200\\n';`,
|
|
1946
|
+
` content = content.trimEnd() + '\\n' + catchAll;`,
|
|
1947
|
+
` writeFileSync(redirectsPath, content);`,
|
|
1948
|
+
` console.log('[redirects] Added SSR catch-all to _redirects');`,
|
|
1949
|
+
`}`,
|
|
1950
|
+
``,
|
|
1951
|
+
`// ── Run ──────────────────────────────────────────────────────────`,
|
|
1952
|
+
`(async () => {`,
|
|
1953
|
+
``,
|
|
1954
|
+
`generateIslandRedirects();`,
|
|
1955
|
+
`copyAdapters();`,
|
|
1956
|
+
`copyToNetlifyPaths();`,
|
|
1957
|
+
`ensureNetlifyRedirects();`,
|
|
1958
|
+
``,
|
|
1959
|
+
`// ── Prerender (SSG) ──────────────────────────────────────────────`,
|
|
1960
|
+
`// Spawn the built server and fetch routes to generate static HTML.`,
|
|
1961
|
+
`const serverEntries = [`,
|
|
1962
|
+
` join(CWD, '.output', 'server', 'index.mjs'),`,
|
|
1963
|
+
` join(CWD, '.netlify', 'functions-internal', 'server', 'server.mjs'),`,
|
|
1964
|
+
` join(CWD, '.netlify', 'v1', 'functions', 'server', 'server.mjs'),`,
|
|
1965
|
+
`];`,
|
|
1966
|
+
`const serverEntry = serverEntries.find(p => existsSync(p));`,
|
|
1967
|
+
`const outputDir = [join(CWD, '.output', 'public'), DIST_DIR].find(d => existsSync(d));`,
|
|
1968
|
+
``,
|
|
1969
|
+
`if (serverEntry && outputDir) {`,
|
|
1970
|
+
` const { spawn: spawnProcess } = await import('node:child_process');`,
|
|
1971
|
+
` const PORT = 13172;`,
|
|
1972
|
+
` const baseUrl = 'http://localhost:' + PORT;`,
|
|
1973
|
+
` console.log('[prerender] Spawning server on port ' + PORT + '...');`,
|
|
1974
|
+
` const srv = spawnProcess('node', [serverEntry], {`,
|
|
1975
|
+
` env: { ...process.env, PORT: String(PORT), NITRO_PORT: String(PORT), HOST: '127.0.0.1', NITRO_HOST: '127.0.0.1', NODE_ENV: 'production' },`,
|
|
1976
|
+
` stdio: ['ignore', 'pipe', 'pipe'],`,
|
|
1977
|
+
` });`,
|
|
1978
|
+
` srv.stdout?.on('data', d => { const m = d.toString().trim(); if (m) console.log('[prerender:server] ' + m); });`,
|
|
1979
|
+
` srv.stderr?.on('data', d => { const m = d.toString().trim(); if (m) console.error('[prerender:server:err] ' + m); });`,
|
|
1980
|
+
` let ready = false;`,
|
|
1981
|
+
` const t0 = Date.now();`,
|
|
1982
|
+
` while (Date.now() - t0 < 15000) {`,
|
|
1983
|
+
` try { const r = await fetch(baseUrl + '/'); if (r.ok || r.status < 500) { ready = true; break; } } catch {}`,
|
|
1984
|
+
` await new Promise(r => setTimeout(r, 200));`,
|
|
1985
|
+
` }`,
|
|
1986
|
+
` if (ready) {`,
|
|
1987
|
+
` console.log('[prerender] Server ready');`,
|
|
1988
|
+
` const visited = new Set();`,
|
|
1989
|
+
` const queue = ['/'];`,
|
|
1990
|
+
` const prerendered = [];`,
|
|
1991
|
+
` while (queue.length > 0) {`,
|
|
1992
|
+
` const batch = queue.splice(0, 4);`,
|
|
1993
|
+
` await Promise.all(batch.map(async (route) => {`,
|
|
1994
|
+
` const norm = route.endsWith('/') && route !== '/' ? route.slice(0, -1) : route;`,
|
|
1995
|
+
` if (visited.has(norm)) return;`,
|
|
1996
|
+
` visited.add(norm);`,
|
|
1997
|
+
` try {`,
|
|
1998
|
+
` const res = await fetch(baseUrl + norm);`,
|
|
1999
|
+
` if (!res.ok) { console.error('[prerender] ' + norm + ' returned ' + res.status); return; }`,
|
|
2000
|
+
` const html = await res.text();`,
|
|
2001
|
+
` const fileName = join(norm, 'index.html');`,
|
|
2002
|
+
` const out = join(outputDir, fileName);`,
|
|
2003
|
+
` mkdirSync(dirname(out), { recursive: true });`,
|
|
2004
|
+
` writeFileSync(out, html);`,
|
|
2005
|
+
` prerendered.push(norm);`,
|
|
2006
|
+
` console.log('[prerender] ' + norm);`,
|
|
2007
|
+
` // Crawl links`,
|
|
2008
|
+
` const re = /<a\\s[^>]*href=["']([^"'#?]+)/gi;`,
|
|
2009
|
+
` let m;`,
|
|
2010
|
+
` while ((m = re.exec(html)) !== null) {`,
|
|
2011
|
+
` const href = m[1];`,
|
|
2012
|
+
` if (href.startsWith('/') && !href.startsWith('//') && !href.startsWith('/assets/') && !href.startsWith('/islands/') && !href.match(/\\.\\w{2,5}$/)) {`,
|
|
2013
|
+
` const n = href.endsWith('/') && href !== '/' ? href.slice(0, -1) : href;`,
|
|
2014
|
+
` if (!visited.has(n)) queue.push(n);`,
|
|
2015
|
+
` }`,
|
|
2016
|
+
` }`,
|
|
2017
|
+
` } catch (err) { console.error('[prerender] Error fetching ' + norm + ':', err.message); }`,
|
|
2018
|
+
` }));`,
|
|
2019
|
+
` }`,
|
|
2020
|
+
` srv.kill('SIGKILL');`,
|
|
2021
|
+
` console.log('[prerender] Done: ' + prerendered.length + ' page(s)');`,
|
|
2022
|
+
` // Copy to alt output dirs`,
|
|
2023
|
+
` for (const alt of [DIST_DIR, join(CWD, '.netlify', 'v1', 'functions', 'server', 'public')].filter(d => d !== outputDir && existsSync(dirname(d)))) {`,
|
|
2024
|
+
` for (const route of prerendered) {`,
|
|
2025
|
+
` const f = join(route, 'index.html');`,
|
|
2026
|
+
` const src = join(outputDir, f);`,
|
|
2027
|
+
` const dest = join(alt, f);`,
|
|
2028
|
+
` if (existsSync(src)) { mkdirSync(dirname(dest), { recursive: true }); copyFileSync(src, dest); }`,
|
|
2029
|
+
` }`,
|
|
2030
|
+
` }`,
|
|
2031
|
+
` } else {`,
|
|
2032
|
+
` srv.kill('SIGKILL');`,
|
|
2033
|
+
` console.error('[prerender] Server did not start, skipping prerender');`,
|
|
2034
|
+
` }`,
|
|
2035
|
+
`}`,
|
|
2036
|
+
``,
|
|
2037
|
+
`console.log('[post-build] Complete');`,
|
|
2038
|
+
``,
|
|
2039
|
+
`})().catch(err => { console.error('[post-build] Fatal:', err); process.exit(1); });`,
|
|
2040
|
+
``
|
|
2041
|
+
];
|
|
2042
|
+
return lines.join(`
|
|
2043
|
+
`);
|
|
2044
|
+
}
|
|
2045
|
+
|
|
1702
2046
|
// src/scaffold.ts
|
|
1703
2047
|
function generateHonoServerEntry() {
|
|
1704
2048
|
return `import { Hono } from 'hono';
|
|
@@ -1784,22 +2128,11 @@ async function scaffoldProject(config, targetDir) {
|
|
|
1784
2128
|
``
|
|
1785
2129
|
].join(`
|
|
1786
2130
|
`));
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
` <meta name="viewport" content="width=device-width, initial-scale=1" />`,
|
|
1793
|
-
` <title>Avalon</title>`,
|
|
1794
|
-
` </head>`,
|
|
1795
|
-
` <body>`,
|
|
1796
|
-
` <!--ssr-outlet-->`,
|
|
1797
|
-
` <script type="module" src="/src/client/main.js"></script>`,
|
|
1798
|
-
` </body>`,
|
|
1799
|
-
`</html>`,
|
|
1800
|
-
``
|
|
1801
|
-
].join(`
|
|
1802
|
-
`));
|
|
2131
|
+
if (config.deploy === "netlify") {
|
|
2132
|
+
await writeFile(join(targetDir, "netlify.toml"), generateNetlifyToml(config));
|
|
2133
|
+
await writeFile(join(targetDir, "build.mjs"), generateBuildMjs());
|
|
2134
|
+
await writeFile(join(targetDir, "post-build.mjs"), generatePostBuildMjs());
|
|
2135
|
+
}
|
|
1803
2136
|
}
|
|
1804
2137
|
|
|
1805
2138
|
// src/summary.ts
|
|
@@ -1808,10 +2141,15 @@ var STYLING_LABELS = {
|
|
|
1808
2141
|
tailwind: "Tailwind CSS",
|
|
1809
2142
|
shadcn: "shadcn"
|
|
1810
2143
|
};
|
|
2144
|
+
var DEPLOY_LABELS = {
|
|
2145
|
+
netlify: "Netlify",
|
|
2146
|
+
none: "None"
|
|
2147
|
+
};
|
|
1811
2148
|
function formatSummary(config, scaffoldedInPlace = false) {
|
|
1812
2149
|
const integrations = config.integrations.length > 0 ? config.integrations.join(", ") : "none";
|
|
1813
2150
|
const styling = STYLING_LABELS[config.styling] ?? config.styling;
|
|
1814
2151
|
const plugins = config.plugins.length > 0 ? config.plugins.join(", ") : "none";
|
|
2152
|
+
const deploy = DEPLOY_LABELS[config.deploy] ?? config.deploy;
|
|
1815
2153
|
const nextSteps = scaffoldedInPlace ? [" bun install", " bun run dev"] : [` cd ${config.projectName}`, " bun install", " bun run dev"];
|
|
1816
2154
|
return [
|
|
1817
2155
|
"",
|
|
@@ -1820,6 +2158,7 @@ function formatSummary(config, scaffoldedInPlace = false) {
|
|
|
1820
2158
|
` Styling: ${styling}`,
|
|
1821
2159
|
` Plugins: ${plugins}`,
|
|
1822
2160
|
` Middleware: ${config.middleware}`,
|
|
2161
|
+
` Deploy: ${deploy}`,
|
|
1823
2162
|
"",
|
|
1824
2163
|
" Next steps:",
|
|
1825
2164
|
...nextSteps,
|