create-avalon 0.1.15 → 0.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +59 -59
  2. package/dist/cli.js +143 -14
  3. package/package.json +39 -39
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
@@ -1371,6 +1371,11 @@ function generateViteConfig(config) {
1371
1371
  ` nitro: {`,
1372
1372
  ` preset: process.env.NITRO_PRESET || 'node_server',`,
1373
1373
  ` streaming: true,`,
1374
+ ` prerender: {`,
1375
+ ` routes: ['/'],`,
1376
+ ` crawlLinks: true,`,
1377
+ ` ignore: [],`,
1378
+ ` },`,
1374
1379
  ` },`,
1375
1380
  ` });`,
1376
1381
  "",
@@ -1730,6 +1735,14 @@ function generateNetlifyToml(config) {
1730
1735
 
1731
1736
  [functions]
1732
1737
  directory = "netlify/functions"
1738
+
1739
+ # SSR catch-all — Netlify checks for a matching static/prerendered file
1740
+ # first (force=false is the default in netlify.toml). Only requests with
1741
+ # no static file hit the server function.
1742
+ [[redirects]]
1743
+ from = "/*"
1744
+ to = "/.netlify/functions/server"
1745
+ status = 200
1733
1746
  `;
1734
1747
  }
1735
1748
  function generateBuildMjs() {
@@ -1784,7 +1797,7 @@ function finish() {
1784
1797
  setTimeout(() => {
1785
1798
  console.log('[build] Running post-build...');
1786
1799
  try {
1787
- execSync('node post-build.mjs', { cwd: CWD, stdio: 'inherit', timeout: 60_000 });
1800
+ execSync('node post-build.mjs', { cwd: CWD, stdio: 'inherit', timeout: 120_000 });
1788
1801
  } catch (err) {
1789
1802
  console.error('[build] post-build warning:', err.message);
1790
1803
  }
@@ -1837,7 +1850,6 @@ function generatePostBuildMjs() {
1837
1850
  ` * - Generates _redirects for island JS path mapping`,
1838
1851
  ` * - Copies island JS to clean paths for local preview`,
1839
1852
  ` * - Copies server function to all Netlify function paths`,
1840
- ` * - Ensures SSR catch-all redirect exists`,
1841
1853
  ` */`,
1842
1854
  ``,
1843
1855
  `import {`,
@@ -1932,23 +1944,140 @@ function generatePostBuildMjs() {
1932
1944
  ` console.log('[netlify-fn] Copied server function to all Netlify paths');`,
1933
1945
  `}`,
1934
1946
  ``,
1935
- `// ── Ensure SSR catch-all redirect ─────────────────────────────────`,
1936
- `function ensureNetlifyRedirects() {`,
1937
- ` const redirectsPath = join(DIST_DIR, '_redirects');`,
1938
- ` let content = existsSync(redirectsPath) ? readFileSync(redirectsPath, 'utf-8') : '';`,
1939
- ` if (content.includes('/.netlify/functions/server')) return;`,
1940
- ` const catchAll = '\\n# SSR catch-all (Nitro server function)\\n/* /.netlify/functions/server 200\\n';`,
1941
- ` content = content.trimEnd() + '\\n' + catchAll;`,
1942
- ` writeFileSync(redirectsPath, content);`,
1943
- ` console.log('[redirects] Added SSR catch-all to _redirects');`,
1944
- `}`,
1945
- ``,
1946
1947
  `// ── Run ──────────────────────────────────────────────────────────`,
1948
+ `(async () => {`,
1949
+ ``,
1947
1950
  `generateIslandRedirects();`,
1948
1951
  `copyAdapters();`,
1949
1952
  `copyToNetlifyPaths();`,
1950
- `ensureNetlifyRedirects();`,
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'],`,
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
+ `}`,
2077
+ ``,
1951
2078
  `console.log('[post-build] Complete');`,
2079
+ ``,
2080
+ `})().catch(err => { console.error('[post-build] Fatal:', err); process.exit(1); });`,
1952
2081
  ``
1953
2082
  ];
1954
2083
  return lines.join(`
package/package.json CHANGED
@@ -1,39 +1,39 @@
1
- {
2
- "name": "create-avalon",
3
- "version": "0.1.15",
4
- "description": "Scaffold a new Avalon project with multi-framework islands architecture",
5
- "license": "MIT",
6
- "type": "module",
7
- "repository": {
8
- "type": "git",
9
- "url": "https://github.com/useAvalon/Avalon.git",
10
- "directory": "packages/create-avalon"
11
- },
12
- "homepage": "https://useavalon.dev",
13
- "keywords": [
14
- "avalon",
15
- "create",
16
- "scaffold",
17
- "cli",
18
- "islands",
19
- "vite"
20
- ],
21
- "bin": {
22
- "create-avalon": "dist/cli.js"
23
- },
24
- "files": [
25
- "dist",
26
- "README.md"
27
- ],
28
- "scripts": {
29
- "build": "bun build src/cli.ts --outdir dist --target node --format esm",
30
- "prepublishOnly": "bun run build"
31
- },
32
- "dependencies": {
33
- "@clack/prompts": "^1.1.0"
34
- },
35
- "devDependencies": {
36
- "fast-check": "^4.6.0",
37
- "vitest": "^4.1.0"
38
- }
39
- }
1
+ {
2
+ "name": "create-avalon",
3
+ "version": "0.1.17",
4
+ "description": "Scaffold a new Avalon project with multi-framework islands architecture",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/useAvalon/Avalon.git",
10
+ "directory": "packages/create-avalon"
11
+ },
12
+ "homepage": "https://useavalon.dev",
13
+ "keywords": [
14
+ "avalon",
15
+ "create",
16
+ "scaffold",
17
+ "cli",
18
+ "islands",
19
+ "vite"
20
+ ],
21
+ "bin": {
22
+ "create-avalon": "dist/cli.js"
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "README.md"
27
+ ],
28
+ "scripts": {
29
+ "build": "bun build src/cli.ts --outdir dist --target node --format esm",
30
+ "prepublishOnly": "bun run build"
31
+ },
32
+ "dependencies": {
33
+ "@clack/prompts": "^1.1.0"
34
+ },
35
+ "devDependencies": {
36
+ "fast-check": "^4.6.0",
37
+ "vitest": "^4.1.0"
38
+ }
39
+ }