zylaris 1.0.2

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 (116) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +558 -0
  3. package/Zylaris.js.png +0 -0
  4. package/examples/default/index.html +13 -0
  5. package/examples/default/package.json +23 -0
  6. package/examples/default/src/app/about/page.tsx +18 -0
  7. package/examples/default/src/app/counter/page.tsx +22 -0
  8. package/examples/default/src/app/global.css +225 -0
  9. package/examples/default/src/app/layout.tsx +33 -0
  10. package/examples/default/src/app/page.tsx +14 -0
  11. package/examples/default/src/entry-client.tsx +87 -0
  12. package/examples/default/src/entry-server.tsx +52 -0
  13. package/examples/default/src/router.ts +60 -0
  14. package/examples/default/tsconfig.json +28 -0
  15. package/examples/default/zylaris.config.ts +24 -0
  16. package/package.json +34 -0
  17. package/packages/adapter/package.json +59 -0
  18. package/packages/adapter/src/adapters/bun.ts +215 -0
  19. package/packages/adapter/src/adapters/cloudflare.ts +278 -0
  20. package/packages/adapter/src/adapters/deno.ts +219 -0
  21. package/packages/adapter/src/adapters/netlify.ts +274 -0
  22. package/packages/adapter/src/adapters/node.ts +155 -0
  23. package/packages/adapter/src/adapters/static.ts +134 -0
  24. package/packages/adapter/src/adapters/vercel.ts +239 -0
  25. package/packages/adapter/src/index.ts +115 -0
  26. package/packages/adapter/src/lib/builder.ts +361 -0
  27. package/packages/adapter/src/types.ts +191 -0
  28. package/packages/adapter/tsconfig.json +8 -0
  29. package/packages/cli/package.json +43 -0
  30. package/packages/cli/src/bin.ts +107 -0
  31. package/packages/cli/src/commands/build.ts +197 -0
  32. package/packages/cli/src/commands/create.ts +222 -0
  33. package/packages/cli/src/commands/deploy.ts +90 -0
  34. package/packages/cli/src/commands/dev.ts +108 -0
  35. package/packages/cli/src/index.ts +6 -0
  36. package/packages/cli/tsconfig.json +9 -0
  37. package/packages/compiler/package.json +39 -0
  38. package/packages/compiler/src/index.ts +210 -0
  39. package/packages/compiler/src/jit.ts +187 -0
  40. package/packages/compiler/tsconfig.json +9 -0
  41. package/packages/core/package.json +55 -0
  42. package/packages/core/src/components.test.ts +125 -0
  43. package/packages/core/src/components.ts +181 -0
  44. package/packages/core/src/config.ts +204 -0
  45. package/packages/core/src/hooks.ts +142 -0
  46. package/packages/core/src/index.ts +59 -0
  47. package/packages/core/src/jsx-runtime.ts +46 -0
  48. package/packages/core/tsconfig.json +16 -0
  49. package/packages/dev-server/package.json +51 -0
  50. package/packages/dev-server/src/index.ts +306 -0
  51. package/packages/dev-server/src/jit-middleware.ts +78 -0
  52. package/packages/dev-server/tsconfig.json +9 -0
  53. package/packages/plugins/package.json +44 -0
  54. package/packages/plugins/src/cdn/loader.ts +275 -0
  55. package/packages/plugins/src/index.ts +238 -0
  56. package/packages/plugins/src/loaders/auto-import.ts +219 -0
  57. package/packages/plugins/src/loaders/external.ts +332 -0
  58. package/packages/plugins/src/transforms/index.ts +407 -0
  59. package/packages/plugins/src/types.ts +296 -0
  60. package/packages/plugins/tsconfig.json +8 -0
  61. package/packages/reactivity/package.json +36 -0
  62. package/packages/reactivity/src/computed.d.ts +3 -0
  63. package/packages/reactivity/src/computed.d.ts.map +1 -0
  64. package/packages/reactivity/src/computed.js +64 -0
  65. package/packages/reactivity/src/computed.js.map +1 -0
  66. package/packages/reactivity/src/computed.test.ts +83 -0
  67. package/packages/reactivity/src/computed.ts +69 -0
  68. package/packages/reactivity/src/index.d.ts +6 -0
  69. package/packages/reactivity/src/index.d.ts.map +1 -0
  70. package/packages/reactivity/src/index.js +7 -0
  71. package/packages/reactivity/src/index.js.map +1 -0
  72. package/packages/reactivity/src/index.ts +18 -0
  73. package/packages/reactivity/src/resource.d.ts +6 -0
  74. package/packages/reactivity/src/resource.d.ts.map +1 -0
  75. package/packages/reactivity/src/resource.js +43 -0
  76. package/packages/reactivity/src/resource.js.map +1 -0
  77. package/packages/reactivity/src/resource.test.ts +70 -0
  78. package/packages/reactivity/src/resource.ts +59 -0
  79. package/packages/reactivity/src/signal.d.ts +7 -0
  80. package/packages/reactivity/src/signal.d.ts.map +1 -0
  81. package/packages/reactivity/src/signal.js +145 -0
  82. package/packages/reactivity/src/signal.js.map +1 -0
  83. package/packages/reactivity/src/signal.test.ts +130 -0
  84. package/packages/reactivity/src/signal.ts +207 -0
  85. package/packages/reactivity/src/store.d.ts +4 -0
  86. package/packages/reactivity/src/store.d.ts.map +1 -0
  87. package/packages/reactivity/src/store.js +62 -0
  88. package/packages/reactivity/src/store.js.map +1 -0
  89. package/packages/reactivity/src/store.test.ts +38 -0
  90. package/packages/reactivity/src/store.ts +111 -0
  91. package/packages/reactivity/src/types.d.ts +43 -0
  92. package/packages/reactivity/src/types.d.ts.map +1 -0
  93. package/packages/reactivity/src/types.js +3 -0
  94. package/packages/reactivity/src/types.js.map +1 -0
  95. package/packages/reactivity/src/types.ts +43 -0
  96. package/packages/reactivity/tsconfig.json +9 -0
  97. package/packages/router/package.json +44 -0
  98. package/packages/router/src/components.tsx +150 -0
  99. package/packages/router/src/fs-router.ts +163 -0
  100. package/packages/router/src/index.ts +22 -0
  101. package/packages/router/src/router.test.ts +111 -0
  102. package/packages/router/src/router.ts +112 -0
  103. package/packages/router/src/types.ts +69 -0
  104. package/packages/router/tsconfig.json +10 -0
  105. package/packages/server/package.json +41 -0
  106. package/packages/server/src/action.test.ts +102 -0
  107. package/packages/server/src/action.ts +201 -0
  108. package/packages/server/src/api.ts +143 -0
  109. package/packages/server/src/index.ts +18 -0
  110. package/packages/server/src/types.ts +72 -0
  111. package/packages/server/tsconfig.json +9 -0
  112. package/pnpm-workspace.yaml +4 -0
  113. package/scripts/publish.ps1 +138 -0
  114. package/scripts/publish.sh +142 -0
  115. package/tsconfig.json +28 -0
  116. package/turbo.json +24 -0
@@ -0,0 +1,274 @@
1
+ /**
2
+ * Netlify Adapter (Functions + Edge)
3
+ * Supports: Netlify Functions & Edge Functions
4
+ */
5
+
6
+ import * as fs from 'fs/promises';
7
+ import path from 'path';
8
+ import type { Adapter, AdapterConfig, BuildResult, RouteManifest } from '../types.js';
9
+ import {
10
+ buildClient,
11
+ generateManifest,
12
+ copyPublic,
13
+ analyzeBundle
14
+ } from '../lib/builder.js';
15
+
16
+ export const netlifyAdapter: Adapter = {
17
+ name: 'netlify',
18
+ target: 'netlify',
19
+
20
+ async build(config: AdapterConfig): Promise<BuildResult> {
21
+ const outDir = path.resolve(config.outDir || 'dist');
22
+ const isEdge = config.function?.edge ?? false;
23
+ const warnings: string[] = [];
24
+ const errors: string[] = [];
25
+
26
+ try {
27
+ // Clean output directory
28
+ await fs.rm(outDir, { recursive: true, force: true });
29
+ await fs.mkdir(outDir, { recursive: true });
30
+
31
+ // Netlify output structure
32
+ const functionsDir = path.join(outDir, 'netlify', 'functions');
33
+ const edgeFunctionsDir = path.join(outDir, 'netlify', 'edge-functions');
34
+ const publishDir = outDir;
35
+
36
+ await fs.mkdir(functionsDir, { recursive: true });
37
+ await fs.mkdir(edgeFunctionsDir, { recursive: true });
38
+
39
+ // Build client bundle
40
+ const { files: clientFiles } = await buildClient(config, publishDir);
41
+
42
+ // Copy public assets
43
+ await copyPublic(publishDir);
44
+
45
+ // Build function
46
+ if (isEdge) {
47
+ await buildEdgeFunction(config, edgeFunctionsDir);
48
+ } else {
49
+ await buildNetlifyFunction(config, functionsDir);
50
+ }
51
+
52
+ // Generate config files
53
+ await generateNetlifyConfig(config, outDir, isEdge);
54
+
55
+ // Generate manifest
56
+ const routes: RouteManifest[] = (config.static?.routes || ['/']).map(route => ({
57
+ path: route,
58
+ file: route === '/' ? 'index.html' : `${route}.html`,
59
+ }));
60
+
61
+ const assets: Record<string, string> = {};
62
+ for (const file of clientFiles) {
63
+ const basename = path.basename(file);
64
+ assets[basename] = `/_assets/${basename}`;
65
+ }
66
+
67
+ await generateManifest(config, outDir, routes, assets);
68
+
69
+ // Analyze bundle
70
+ const analysis = await analyzeBundle(outDir);
71
+
72
+ // List all output files
73
+ const allFiles = await listFiles(outDir);
74
+
75
+ return {
76
+ success: true,
77
+ outDir,
78
+ files: allFiles,
79
+ warnings,
80
+ errors,
81
+ analysis,
82
+ };
83
+
84
+ } catch (error) {
85
+ errors.push(error instanceof Error ? error.message : String(error));
86
+ return {
87
+ success: false,
88
+ outDir,
89
+ files: [],
90
+ warnings,
91
+ errors,
92
+ };
93
+ }
94
+ },
95
+
96
+ async preview(_config: AdapterConfig, port = 8888): Promise<void> {
97
+ const { execSync } = await import('child_process');
98
+
99
+ console.log('Starting Netlify dev server...');
100
+ execSync(`npx netlify dev --port ${port}`, { stdio: 'inherit' });
101
+ },
102
+
103
+ async deploy(_config: AdapterConfig): Promise<void> {
104
+ const { execSync } = await import('child_process');
105
+
106
+ console.log('Deploying to Netlify...');
107
+ execSync('npx netlify deploy --prod', { stdio: 'inherit' });
108
+ }
109
+ };
110
+
111
+ /** Build Netlify Edge Function */
112
+ async function buildEdgeFunction(_config: AdapterConfig, edgeFunctionsDir: string): Promise<void> {
113
+ const entryContent = `
114
+ import type { Config } from "@netlify/edge-functions";
115
+ import { handleRequest } from '@zylaris/server/edge';
116
+
117
+ export default async function handler(request: Request) {
118
+ return handleRequest(request, {
119
+ // App configuration
120
+ });
121
+ }
122
+
123
+ export const config: Config = {
124
+ path: "/*",
125
+ cache: "manual",
126
+ };
127
+ `;
128
+
129
+ await fs.writeFile(path.join(edgeFunctionsDir, 'zylaris-app.ts'), entryContent);
130
+
131
+ // Build the edge function
132
+ const { build } = await import('esbuild');
133
+ await build({
134
+ entryPoints: [path.join(edgeFunctionsDir, 'zylaris-app.ts')],
135
+ bundle: true,
136
+ format: 'esm',
137
+ platform: 'neutral',
138
+ target: 'es2022',
139
+ outfile: path.join(edgeFunctionsDir, 'zylaris-app.js'),
140
+ external: ['@netlify/edge-functions'],
141
+ minify: true,
142
+ });
143
+ }
144
+
145
+ /** Build Netlify Function */
146
+ async function buildNetlifyFunction(config: AdapterConfig, functionsDir: string): Promise<void> {
147
+ const functionName = config.function?.name || 'zylaris-app';
148
+
149
+ // Build server bundle
150
+ const { build } = await import('esbuild');
151
+
152
+ const possibleEntries = ['./src/entry-server.ts', './src/entry-server.tsx'];
153
+ const entryPoints: string[] = [];
154
+ for (const e of possibleEntries) {
155
+ try {
156
+ await fs.access(e);
157
+ entryPoints.push(e);
158
+ } catch {
159
+ // skip
160
+ }
161
+ }
162
+
163
+ await build({
164
+ entryPoints,
165
+ bundle: true,
166
+ format: 'esm',
167
+ platform: 'node',
168
+ target: 'node18',
169
+ outfile: path.join(functionsDir, `${functionName}.js`),
170
+ external: ['@zylaris/*'],
171
+ minify: true,
172
+ });
173
+ }
174
+
175
+ /** Generate Netlify config files */
176
+ async function generateNetlifyConfig(
177
+ config: AdapterConfig,
178
+ outDir: string,
179
+ isEdge: boolean
180
+ ): Promise<void> {
181
+ // netlify.toml
182
+ const redirects = config.redirects?.map(r => ({
183
+ from: r.from,
184
+ to: r.to,
185
+ status: r.status || 301,
186
+ })) || [];
187
+
188
+ const headers = Object.entries(config.headers || {}).map(([path, header]) => ({
189
+ for: path,
190
+ values: Object.fromEntries(header.split(':').map((h, i, arr) =>
191
+ i === 0 ? [] : [arr[0].trim(), h.trim()]
192
+ ).filter(Boolean)),
193
+ }));
194
+
195
+ const netlifyConfig = {
196
+ build: {
197
+ publish: outDir,
198
+ command: 'echo "Build already done"',
199
+ },
200
+ functions: {
201
+ directory: 'netlify/functions',
202
+ },
203
+ 'edge-functions': isEdge ? [
204
+ {
205
+ path: '/*',
206
+ function: 'zylaris-app',
207
+ }
208
+ ] : undefined,
209
+ redirects: [
210
+ ...redirects,
211
+ // SPA fallback
212
+ { from: '/*', to: '/index.html', status: 200 },
213
+ ],
214
+ headers,
215
+ };
216
+
217
+ // Write as TOML-like format (simplified)
218
+ const tomlContent = generateToml(netlifyConfig);
219
+ await fs.writeFile('netlify.toml', tomlContent);
220
+
221
+ // _redirects file
222
+ const redirectsContent = [
223
+ ...redirects.map(r => `${r.from} ${r.to} ${r.status}`),
224
+ '/* /index.html 200',
225
+ ].join('\n');
226
+
227
+ await fs.writeFile(path.join(outDir, '_redirects'), redirectsContent);
228
+ }
229
+
230
+ /** Simple TOML generator */
231
+ function generateToml(obj: Record<string, unknown>, prefix = ''): string {
232
+ let result = '';
233
+
234
+ for (const [key, value] of Object.entries(obj)) {
235
+ if (value === undefined) continue;
236
+
237
+ if (Array.isArray(value)) {
238
+ for (const item of value) {
239
+ if (typeof item === 'object') {
240
+ result += `[[${prefix}${key}]]\n`;
241
+ result += generateToml(item as Record<string, unknown>);
242
+ }
243
+ }
244
+ } else if (typeof value === 'object') {
245
+ result += `[${prefix}${key}]\n`;
246
+ result += generateToml(value as Record<string, unknown>);
247
+ } else {
248
+ result += `${key} = ${JSON.stringify(value)}\n`;
249
+ }
250
+ }
251
+
252
+ return result;
253
+ }
254
+
255
+ /** List all files recursively */
256
+ async function listFiles(dir: string, prefix = ''): Promise<string[]> {
257
+ const files: string[] = [];
258
+ const entries = await fs.readdir(dir, { withFileTypes: true });
259
+
260
+ for (const entry of entries) {
261
+ const fullPath = path.join(dir, entry.name);
262
+ const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
263
+
264
+ if ((entry as { isDirectory(): boolean }).isDirectory()) {
265
+ files.push(...await listFiles(fullPath, relativePath));
266
+ } else {
267
+ files.push(relativePath);
268
+ }
269
+ }
270
+
271
+ return files;
272
+ }
273
+
274
+ export default netlifyAdapter;
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Node.js Server Adapter
3
+ * For: Traditional Node.js hosting, VPS, Docker, PM2
4
+ */
5
+
6
+ import * as fs from 'fs/promises';
7
+ import path from 'path';
8
+ import type { Adapter, AdapterConfig, BuildResult, RouteManifest } from '../types.js';
9
+ import {
10
+ buildClient,
11
+ buildServer,
12
+ generateManifest,
13
+ copyPublic,
14
+ analyzeBundle
15
+ } from '../lib/builder.js';
16
+
17
+ export const nodeAdapter: Adapter = {
18
+ name: 'node',
19
+ target: 'node',
20
+
21
+ async build(config: AdapterConfig): Promise<BuildResult> {
22
+ const outDir = path.resolve(config.outDir || 'dist');
23
+ const warnings: string[] = [];
24
+ const errors: string[] = [];
25
+
26
+ try {
27
+ // Clean output directory
28
+ await fs.rm(outDir, { recursive: true, force: true });
29
+ await fs.mkdir(outDir, { recursive: true });
30
+
31
+ // Build client bundle
32
+ const { files: clientFiles } = await buildClient(config, outDir);
33
+
34
+ // Build server bundle
35
+ const { files: serverFiles, entry: serverEntry } = await buildServer(config, outDir, 'node');
36
+
37
+ // Copy public assets
38
+ await copyPublic(outDir);
39
+
40
+ // Generate server entry file
41
+ await generateServerEntry(config, outDir, serverEntry);
42
+
43
+ // Generate package.json for production
44
+ await generatePackageJson(outDir);
45
+
46
+ // Generate manifest
47
+ const routes: RouteManifest[] = (config.static?.routes || ['/']).map(route => ({
48
+ path: route,
49
+ file: route === '/' ? 'index.html' : `${route}.html`,
50
+ }));
51
+
52
+ const assets: Record<string, string> = {};
53
+ for (const file of clientFiles) {
54
+ const basename = path.basename(file);
55
+ assets[basename] = `/_assets/${basename}`;
56
+ }
57
+
58
+ await generateManifest(config, outDir, routes, assets);
59
+
60
+ // Analyze bundle
61
+ const analysis = await analyzeBundle(outDir);
62
+
63
+ return {
64
+ success: true,
65
+ outDir,
66
+ files: [...clientFiles, ...serverFiles],
67
+ warnings,
68
+ errors,
69
+ analysis,
70
+ };
71
+
72
+ } catch (error) {
73
+ errors.push(error instanceof Error ? error.message : String(error));
74
+ return {
75
+ success: false,
76
+ outDir,
77
+ files: [],
78
+ warnings,
79
+ errors,
80
+ };
81
+ }
82
+ },
83
+
84
+ async preview(config: AdapterConfig, port = 3000): Promise<void> {
85
+ const outDir = path.resolve(config.outDir || 'dist');
86
+ const entryPath = path.join(outDir, 'index.js');
87
+
88
+ // Set port environment variable
89
+ process.env.PORT = String(port);
90
+
91
+ // Import and run server
92
+ await import(entryPath);
93
+
94
+ return new Promise(() => {}); // Keep running
95
+ }
96
+ };
97
+
98
+ /** Generate server entry point */
99
+ async function generateServerEntry(
100
+ _config: AdapterConfig,
101
+ outDir: string,
102
+ serverEntry: string
103
+ ): Promise<void> {
104
+ const port = process.env.PORT || 3000;
105
+ const host = process.env.HOST || '0.0.0.0';
106
+
107
+ const entryContent = `#!/usr/bin/env node
108
+ import { createServer } from '@zylaris/server';
109
+ import { handler } from './server/${path.basename(serverEntry)}';
110
+
111
+ const port = process.env.PORT || ${port};
112
+ const host = process.env.HOST || '${host}';
113
+
114
+ const server = createServer({
115
+ handler,
116
+ port: Number(port),
117
+ host,
118
+ static: './client',
119
+ compress: true,
120
+ });
121
+
122
+ console.log(\`🚀 Server running at http://\${host}:\${port}\`);
123
+ `;
124
+
125
+ await fs.writeFile(path.join(outDir, 'index.js'), entryContent);
126
+
127
+ // Make executable (Unix)
128
+ try {
129
+ await fs.chmod(path.join(outDir, 'index.js'), 0o755);
130
+ } catch {
131
+ // Ignore on Windows
132
+ }
133
+ }
134
+
135
+ /** Generate production package.json */
136
+ async function generatePackageJson(outDir: string): Promise<void> {
137
+ const pkg = {
138
+ name: 'zylaris-app',
139
+ version: '1.0.0',
140
+ type: 'module',
141
+ scripts: {
142
+ start: 'node index.js',
143
+ },
144
+ dependencies: {
145
+ '@zylaris/server': 'workspace:*',
146
+ },
147
+ engines: {
148
+ node: '>=18.0.0',
149
+ },
150
+ };
151
+
152
+ await fs.writeFile(path.join(outDir, 'package.json'), JSON.stringify(pkg, null, 2));
153
+ }
154
+
155
+ export default nodeAdapter;
@@ -0,0 +1,134 @@
1
+ /**
2
+ * Static Site Generator Adapter
3
+ * For: GitHub Pages, Netlify Static, Vercel Static, Cloudflare Pages, Surge, etc.
4
+ */
5
+
6
+ import * as fs from 'fs/promises';
7
+ import path from 'path';
8
+ import type { Adapter, AdapterConfig, BuildResult, RouteManifest } from '../types.js';
9
+ import {
10
+ buildClient,
11
+ generateStaticPages,
12
+ generateManifest,
13
+ copyPublic,
14
+ generateHeaders,
15
+ generateRedirects,
16
+ analyzeBundle
17
+ } from '../lib/builder.js';
18
+
19
+ export const staticAdapter: Adapter = {
20
+ name: 'static',
21
+ target: 'static',
22
+
23
+ async build(config: AdapterConfig): Promise<BuildResult> {
24
+ const outDir = path.resolve(config.outDir || 'dist');
25
+ const warnings: string[] = [];
26
+ const errors: string[] = [];
27
+
28
+ try {
29
+ // Clean output directory
30
+ await fs.rm(outDir, { recursive: true, force: true });
31
+ await fs.mkdir(outDir, { recursive: true });
32
+
33
+ // Build client bundle
34
+ const { files: clientFiles } = await buildClient(config, outDir);
35
+
36
+ // Generate static pages
37
+ await generateStaticPages(config, outDir, clientFiles);
38
+
39
+ // Copy public assets
40
+ await copyPublic(outDir);
41
+
42
+ // Generate platform config files
43
+ await generatePlatformFiles(config, outDir);
44
+
45
+ // Generate manifest
46
+ const routes: RouteManifest[] = (config.static?.routes || ['/']).map(route => ({
47
+ path: route,
48
+ file: route === '/' ? 'index.html' : `${route}.html`,
49
+ }));
50
+
51
+ const assets: Record<string, string> = {};
52
+ for (const file of clientFiles) {
53
+ const basename = path.basename(file);
54
+ assets[basename] = `/_assets/${basename}`;
55
+ }
56
+
57
+ await generateManifest(config, outDir, routes, assets);
58
+
59
+ // Analyze bundle
60
+ const analysis = await analyzeBundle(outDir);
61
+
62
+ // List output files
63
+ const allFiles = await listFiles(outDir);
64
+
65
+ return {
66
+ success: true,
67
+ outDir,
68
+ files: allFiles,
69
+ warnings,
70
+ errors,
71
+ analysis,
72
+ };
73
+
74
+ } catch (error) {
75
+ errors.push(error instanceof Error ? error.message : String(error));
76
+ return {
77
+ success: false,
78
+ outDir,
79
+ files: [],
80
+ warnings,
81
+ errors,
82
+ };
83
+ }
84
+ },
85
+
86
+ async preview(_config: AdapterConfig, _port = 4173): Promise<void> {
87
+ console.log('Preview is not implemented yet for static adapter.');
88
+ }
89
+ };
90
+
91
+ /** Generate platform-specific files */
92
+ async function generatePlatformFiles(config: AdapterConfig, outDir: string): Promise<void> {
93
+ // Generate _headers for Netlify/Cloudflare
94
+ await generateHeaders(config, outDir);
95
+
96
+ // Generate _redirects
97
+ await generateRedirects(config, outDir);
98
+
99
+ // Generate SPA fallback for client-side routing
100
+ if (config.static?.fallback) {
101
+ const fallbackHtml = `<!DOCTYPE html>
102
+ <html>
103
+ <head>
104
+ <meta http-equiv="refresh" content="0;url=${config.static.fallback}">
105
+ </head>
106
+ <body></body>
107
+ </html>`;
108
+ await fs.writeFile(path.join(outDir, '404.html'), fallbackHtml);
109
+ }
110
+
111
+ // Generate .nojekyll for GitHub Pages
112
+ await fs.writeFile(path.join(outDir, '.nojekyll'), '');
113
+ }
114
+
115
+ /** List all files recursively */
116
+ async function listFiles(dir: string, prefix = ''): Promise<string[]> {
117
+ const files: string[] = [];
118
+ const entries = await fs.readdir(dir, { withFileTypes: true });
119
+
120
+ for (const entry of entries) {
121
+ const fullPath = path.join(dir, entry.name);
122
+ const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
123
+
124
+ if ((entry as { isDirectory(): boolean }).isDirectory()) {
125
+ files.push(...await listFiles(fullPath, relativePath));
126
+ } else {
127
+ files.push(relativePath);
128
+ }
129
+ }
130
+
131
+ return files;
132
+ }
133
+
134
+ export default staticAdapter;