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,239 @@
1
+ /**
2
+ * Vercel Adapter (Serverless + Edge)
3
+ * Supports: Vercel Serverless 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 vercelAdapter: Adapter = {
17
+ name: 'vercel',
18
+ target: 'vercel',
19
+
20
+ async build(config: AdapterConfig): Promise<BuildResult> {
21
+ const outDir = path.resolve(config.outDir || '.vercel/output');;
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
+ // Vercel output structure
32
+ const staticDir = path.join(outDir, 'static');
33
+ const functionsDir = path.join(outDir, 'functions');
34
+
35
+ await fs.mkdir(staticDir, { recursive: true });
36
+ await fs.mkdir(functionsDir, { recursive: true });
37
+
38
+ // Build client bundle
39
+ const { files: clientFiles } = await buildClient(config, staticDir);
40
+
41
+ // Copy public assets
42
+ await copyPublic(staticDir);
43
+
44
+ // Build server function
45
+ if (isEdge) {
46
+ await buildEdgeFunction(config, functionsDir);
47
+ } else {
48
+ await buildServerlessFunction(config, functionsDir);
49
+ }
50
+
51
+ // Generate config files
52
+ await generateVercelConfig(config, outDir, isEdge);
53
+
54
+ // Generate manifest
55
+ const routes: RouteManifest[] = (config.static?.routes || ['/']).map(route => ({
56
+ path: route,
57
+ file: route === '/' ? 'index.html' : `${route}.html`,
58
+ }));
59
+
60
+ const assets: Record<string, string> = {};
61
+ for (const file of clientFiles) {
62
+ const basename = path.basename(file);
63
+ assets[basename] = `/${basename}`;
64
+ }
65
+
66
+ await generateManifest(config, outDir, routes, assets);
67
+
68
+ // Analyze bundle
69
+ const analysis = await analyzeBundle(outDir);
70
+
71
+ // List all output files
72
+ const allFiles = await listFiles(outDir);
73
+
74
+ return {
75
+ success: true,
76
+ outDir,
77
+ files: allFiles,
78
+ warnings,
79
+ errors,
80
+ analysis,
81
+ };
82
+
83
+ } catch (error) {
84
+ errors.push(error instanceof Error ? error.message : String(error));
85
+ return {
86
+ success: false,
87
+ outDir,
88
+ files: [],
89
+ warnings,
90
+ errors,
91
+ };
92
+ }
93
+ },
94
+
95
+ async preview(_config: AdapterConfig, port = 3000): Promise<void> {
96
+ const { execSync } = await import('child_process');
97
+
98
+ console.log('Starting Vercel dev server...');
99
+ execSync(`npx vercel dev --port ${port}`, { stdio: 'inherit' });
100
+ },
101
+
102
+ async deploy(_config: AdapterConfig): Promise<void> {
103
+ const { execSync } = await import('child_process');
104
+
105
+ console.log('Deploying to Vercel...');
106
+ execSync('npx vercel --prod', { stdio: 'inherit' });
107
+ }
108
+ };
109
+
110
+ /** Build Vercel Edge Function */
111
+ async function buildEdgeFunction(config: AdapterConfig, functionsDir: string): Promise<void> {
112
+ const functionDir = path.join(functionsDir, 'api');
113
+ await fs.mkdir(functionDir, { recursive: true });
114
+
115
+ const entryContent = `
116
+ import { handleRequest } from '@zylaris/server/edge';
117
+
118
+ export const config = {
119
+ runtime: 'edge',
120
+ regions: ${JSON.stringify(config.function?.regions || ['iad1'])},
121
+ };
122
+
123
+ export default async function handler(request) {
124
+ return handleRequest(request, {
125
+ // App configuration
126
+ });
127
+ }
128
+ `;
129
+
130
+ // Build edge function
131
+ const { build } = await import('esbuild');
132
+ await build({
133
+ stdin: {
134
+ contents: entryContent,
135
+ resolveDir: process.cwd(),
136
+ },
137
+ bundle: true,
138
+ format: 'esm',
139
+ platform: 'neutral',
140
+ target: 'es2022',
141
+ outfile: path.join(functionDir, 'index.js'),
142
+ external: ['node:*'],
143
+ minify: true,
144
+ });
145
+ }
146
+
147
+ /** Build Vercel Serverless Function */
148
+ async function buildServerlessFunction(config: AdapterConfig, functionsDir: string): Promise<void> {
149
+ const functionDir = path.join(functionsDir, 'api');
150
+ await fs.mkdir(functionDir, { recursive: true });
151
+
152
+ // Build server bundle for Node.js
153
+ const { build } = await import('esbuild');
154
+
155
+ const possibleEntries = ['./src/entry-server.ts', './src/entry-server.tsx'];
156
+ const entryPoints: string[] = [];
157
+ for (const e of possibleEntries) {
158
+ try {
159
+ await fs.access(e);
160
+ entryPoints.push(e);
161
+ } catch {
162
+ // skip
163
+ }
164
+ }
165
+
166
+ await build({
167
+ entryPoints,
168
+ bundle: true,
169
+ format: 'cjs', // Serverless uses CommonJS
170
+ platform: 'node',
171
+ target: 'node18',
172
+ outfile: path.join(functionDir, 'index.js'),
173
+ external: ['@zylaris/*'],
174
+ minify: true,
175
+ });
176
+
177
+ // Create function config
178
+ const funcConfig = {
179
+ maxDuration: config.function?.timeout || 10,
180
+ memory: config.function?.memory || 1024,
181
+ };
182
+
183
+ await fs.writeFile(path.join(functionDir, '.vc-config.json'), JSON.stringify(funcConfig, null, 2));
184
+ }
185
+
186
+ /** Generate Vercel config files */
187
+ async function generateVercelConfig(
188
+ _config: AdapterConfig,
189
+ outDir: string,
190
+ isEdge: boolean
191
+ ): Promise<void> {
192
+ // config.json
193
+ const vercelConfig = {
194
+ version: 3,
195
+ routes: [
196
+ // API routes
197
+ { src: '/api/(.*)', dest: '/api' },
198
+ // Static assets
199
+ { src: '/_assets/(.*)', headers: { 'cache-control': 'public, max-age=31536000, immutable' } },
200
+ // SPA fallback
201
+ { handle: 'filesystem' },
202
+ { src: '/(.*)', dest: isEdge ? '/api' : '/index.html' },
203
+ ],
204
+ };
205
+
206
+ await fs.writeFile(path.join(outDir, 'config.json'), JSON.stringify(vercelConfig, null, 2));
207
+
208
+ // Project config (for CLI)
209
+ const projectConfig = {
210
+ buildCommand: null,
211
+ outputDirectory: '.vercel/output',
212
+ framework: null,
213
+ installCommand: null,
214
+ };
215
+
216
+ await fs.mkdir('.vercel', { recursive: true });
217
+ await fs.writeFile('.vercel/project.json', JSON.stringify(projectConfig, null, 2));
218
+ }
219
+
220
+ /** List all files recursively */
221
+ async function listFiles(dir: string, prefix = ''): Promise<string[]> {
222
+ const files: string[] = [];
223
+ const entries = await fs.readdir(dir, { withFileTypes: true });
224
+
225
+ for (const entry of entries) {
226
+ const fullPath = path.join(dir, entry.name);
227
+ const relativePath = prefix ? `${prefix}/${entry.name}` : entry.name;
228
+
229
+ if ((entry as { isDirectory(): boolean }).isDirectory()) {
230
+ files.push(...await listFiles(fullPath, relativePath));
231
+ } else {
232
+ files.push(relativePath);
233
+ }
234
+ }
235
+
236
+ return files;
237
+ }
238
+
239
+ export default vercelAdapter;
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Zylaris Adapter - Universal deployment for every hosting platform
3
+ *
4
+ * Supported Platforms:
5
+ * - Static: GitHub Pages, Surge.sh, AWS S3, etc.
6
+ * - Node.js: VPS, Docker, PM2, Railway, Render
7
+ * - Vercel: Serverless + Edge Functions
8
+ * - Netlify: Functions + Edge Functions
9
+ * - Cloudflare: Pages + Workers
10
+ * - Deno: Deno Deploy
11
+ * - Bun: Bun runtime
12
+ */
13
+
14
+ export type {
15
+ Adapter,
16
+ AdapterConfig,
17
+ BuildResult,
18
+ DeploymentTarget,
19
+ DeploymentManifest,
20
+ RouteManifest,
21
+ FunctionConfig,
22
+ StaticOptions,
23
+ RequestHandler,
24
+ EdgeContext,
25
+ PlatformConfig,
26
+ } from './types.js';
27
+
28
+ // Adapters
29
+ export { staticAdapter } from './adapters/static.js';
30
+ export { nodeAdapter } from './adapters/node.js';
31
+ export { vercelAdapter } from './adapters/vercel.js';
32
+ export { netlifyAdapter } from './adapters/netlify.js';
33
+ export { cloudflareAdapter } from './adapters/cloudflare.js';
34
+ export { denoAdapter } from './adapters/deno.js';
35
+ export { bunAdapter } from './adapters/bun.js';
36
+
37
+ import type { Adapter, DeploymentTarget } from './types.js';
38
+ import { staticAdapter } from './adapters/static.js';
39
+ import { nodeAdapter } from './adapters/node.js';
40
+ import { vercelAdapter } from './adapters/vercel.js';
41
+ import { netlifyAdapter } from './adapters/netlify.js';
42
+ import { cloudflareAdapter } from './adapters/cloudflare.js';
43
+ import { denoAdapter } from './adapters/deno.js';
44
+ import { bunAdapter } from './adapters/bun.js';
45
+
46
+ /** Map of all adapters */
47
+ export const adapters: Record<DeploymentTarget, Adapter> = {
48
+ static: staticAdapter,
49
+ node: nodeAdapter,
50
+ vercel: vercelAdapter,
51
+ netlify: netlifyAdapter,
52
+ cloudflare: cloudflareAdapter,
53
+ deno: denoAdapter,
54
+ bun: bunAdapter,
55
+ };
56
+
57
+ /** Get adapter by target */
58
+ export function getAdapter(target: DeploymentTarget): Adapter {
59
+ const adapter = adapters[target];
60
+ if (!adapter) {
61
+ throw new Error(`Unknown adapter target: ${target}. Available: ${Object.keys(adapters).join(', ')}`);
62
+ }
63
+ return adapter;
64
+ }
65
+
66
+ /** List all available adapters */
67
+ export function listAdapters(): Array<{ target: DeploymentTarget; name: string; description: string }> {
68
+ return [
69
+ {
70
+ target: 'static',
71
+ name: 'Static',
72
+ description: 'GitHub Pages, Surge.sh, AWS S3, Cloudflare Pages Static, Netlify Static'
73
+ },
74
+ {
75
+ target: 'node',
76
+ name: 'Node.js',
77
+ description: 'VPS, Docker, PM2, Railway, Render, traditional Node.js hosting'
78
+ },
79
+ {
80
+ target: 'vercel',
81
+ name: 'Vercel',
82
+ description: 'Vercel Serverless Functions & Edge Functions'
83
+ },
84
+ {
85
+ target: 'netlify',
86
+ name: 'Netlify',
87
+ description: 'Netlify Functions & Edge Functions'
88
+ },
89
+ {
90
+ target: 'cloudflare',
91
+ name: 'Cloudflare',
92
+ description: 'Cloudflare Pages & Workers'
93
+ },
94
+ {
95
+ target: 'deno',
96
+ name: 'Deno',
97
+ description: 'Deno Deploy edge runtime'
98
+ },
99
+ {
100
+ target: 'bun',
101
+ name: 'Bun',
102
+ description: 'Bun runtime (experimental)'
103
+ },
104
+ ];
105
+ }
106
+
107
+ /** Check if adapter supports SSR */
108
+ export function supportsSSR(target: DeploymentTarget): boolean {
109
+ return target !== 'static';
110
+ }
111
+
112
+ /** Check if adapter supports Edge runtime */
113
+ export function supportsEdge(target: DeploymentTarget): boolean {
114
+ return ['vercel', 'netlify', 'cloudflare', 'deno'].includes(target);
115
+ }
@@ -0,0 +1,361 @@
1
+ /**
2
+ * Universal build utilities for all adapters
3
+ */
4
+
5
+ import { build } from 'esbuild';
6
+ import * as fs from 'fs/promises';
7
+ import path from 'path';
8
+ import { createHash } from 'crypto';
9
+
10
+ /** Check if path exists */
11
+ async function pathExists(path: string): Promise<boolean> {
12
+ try {
13
+ await fs.access(path);
14
+ return true;
15
+ } catch {
16
+ return false;
17
+ }
18
+ }
19
+
20
+ import type {
21
+ AdapterConfig,
22
+ BuildResult,
23
+ DeploymentManifest,
24
+ RouteManifest
25
+ } from '../types.js';
26
+
27
+ /** Recursively copy directory */
28
+ async function copyDir(src: string, dest: string): Promise<void> {
29
+ await fs.mkdir(dest, { recursive: true });
30
+ const entries = await fs.readdir(src, { withFileTypes: true });
31
+ for (const entry of entries) {
32
+ const srcPath = path.join(src, entry.name);
33
+ const destPath = path.join(dest, entry.name);
34
+ if (entry.isDirectory()) {
35
+ await copyDir(srcPath, destPath);
36
+ } else {
37
+ await fs.copyFile(srcPath, destPath);
38
+ }
39
+ }
40
+ }
41
+
42
+ /** Default build options */
43
+ const defaultBuildOptions = {
44
+ bundle: true,
45
+ splitting: true,
46
+ format: 'esm' as const,
47
+ target: 'es2022',
48
+ minify: true,
49
+ sourcemap: true,
50
+ treeShaking: true,
51
+ };
52
+
53
+ /** Build client bundle */
54
+ export async function buildClient(
55
+ config: AdapterConfig,
56
+ outDir: string
57
+ ): Promise<{ files: string[]; size: number }> {
58
+ const clientDir = path.join(outDir, 'client');
59
+ await fs.mkdir(clientDir, { recursive: true });
60
+
61
+ const possibleEntries = ['./src/entry-client.tsx', './src/entry-client.ts', './src/main.tsx', './src/main.ts'];
62
+ const existingEntries: string[] = [];
63
+ for (const e of possibleEntries) {
64
+ try {
65
+ await fs.access(e);
66
+ existingEntries.push(e);
67
+ } catch {
68
+ // skip
69
+ }
70
+ }
71
+ const entryPoints = config.entry
72
+ ? [config.entry]
73
+ : existingEntries;
74
+
75
+ if (entryPoints.length === 0) {
76
+ throw new Error('No client entry point found');
77
+ }
78
+
79
+ const result = await build({
80
+ ...defaultBuildOptions,
81
+ ...config.esbuild,
82
+ entryPoints,
83
+ outdir: clientDir,
84
+ platform: 'browser',
85
+ splitting: true,
86
+ format: 'esm',
87
+ metafile: true,
88
+ define: {
89
+ 'process.env.NODE_ENV': '"production"',
90
+ 'import.meta.env.SSR': 'false',
91
+ ...config.esbuild?.define,
92
+ },
93
+ });
94
+
95
+ const files = Object.keys(result.metafile?.outputs || {});
96
+ const totalSize = files.reduce((sum, f) => sum + (result.metafile?.outputs[f]?.bytes || 0), 0);
97
+
98
+ return { files, size: totalSize };
99
+ }
100
+
101
+ /** Build server bundle */
102
+ export async function buildServer(
103
+ config: AdapterConfig,
104
+ outDir: string,
105
+ platform: 'node' | 'neutral' = 'node'
106
+ ): Promise<{ files: string[]; size: number; entry: string }> {
107
+ const serverDir = path.join(outDir, 'server');
108
+ await fs.mkdir(serverDir, { recursive: true });
109
+
110
+ const possibleEntries = ['./src/entry-server.tsx', './src/entry-server.ts'];
111
+ const entryPoints: string[] = [];
112
+ for (const e of possibleEntries) {
113
+ try {
114
+ await fs.access(e);
115
+ entryPoints.push(e);
116
+ } catch {
117
+ // skip
118
+ }
119
+ }
120
+
121
+ if (entryPoints.length === 0) {
122
+ throw new Error('No server entry point found');
123
+ }
124
+
125
+ const result = await build({
126
+ ...defaultBuildOptions,
127
+ ...config.esbuild,
128
+ entryPoints,
129
+ outdir: serverDir,
130
+ platform,
131
+ splitting: false,
132
+ format: 'esm',
133
+ metafile: true,
134
+ external: ['node:*', ...Object.keys((await getPackageJson()).dependencies || {})],
135
+ define: {
136
+ 'process.env.NODE_ENV': '"production"',
137
+ 'import.meta.env.SSR': 'true',
138
+ ...config.esbuild?.define,
139
+ },
140
+ });
141
+
142
+ const files = Object.keys(result.metafile?.outputs || {});
143
+ const entry = files.find(f => !f.endsWith('.map')) || files[0];
144
+ const totalSize = files.reduce((sum, f) => sum + (result.metafile?.outputs[f]?.bytes || 0), 0);
145
+
146
+ return { files, size: totalSize, entry };
147
+ }
148
+
149
+ /** Generate static pages */
150
+ export async function generateStaticPages(
151
+ config: AdapterConfig,
152
+ outDir: string,
153
+ clientFiles: string[]
154
+ ): Promise<string[]> {
155
+ const staticDir = path.join(outDir, 'static');
156
+ await fs.mkdir(staticDir, { recursive: true });
157
+
158
+ const routes = config.static?.routes || ['/'];
159
+ const generated: string[] = [];
160
+
161
+ // Generate HTML for each route
162
+ for (const route of routes) {
163
+ const html = await renderRoute(route, clientFiles, config);
164
+ const filePath = routeToFilePath(route, config);
165
+ const fullPath = path.join(staticDir, filePath);
166
+
167
+ await fs.mkdir(path.dirname(fullPath), { recursive: true });
168
+ await fs.writeFile(fullPath, html);
169
+ generated.push(filePath);
170
+ }
171
+
172
+ // Copy client assets
173
+ const clientDir = path.join(outDir, 'client');
174
+ if (await pathExists(clientDir)) {
175
+ await copyDir(clientDir, path.join(staticDir, '_assets'));
176
+ }
177
+
178
+ return generated;
179
+ }
180
+
181
+ /** Render a route to HTML */
182
+ async function renderRoute(
183
+ _route: string,
184
+ clientFiles: string[],
185
+ config: AdapterConfig
186
+ ): Promise<string> {
187
+ const jsFiles = clientFiles.filter(f => f.endsWith('.js'));
188
+ const cssFiles = clientFiles.filter(f => f.endsWith('.css'));
189
+ const base = config.static?.base || '';
190
+
191
+ const jsTags = jsFiles
192
+ .map(f => `<script type="module" src="${base}/_assets/${path.basename(f)}"></script>`)
193
+ .join('\n ');
194
+
195
+ const cssTags = cssFiles
196
+ .map(f => `<link rel="stylesheet" href="${base}/_assets/${path.basename(f)}">`)
197
+ .join('\n ');
198
+
199
+ return `<!DOCTYPE html>
200
+ <html lang="en">
201
+ <head>
202
+ <meta charset="UTF-8">
203
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
204
+ <title>Zylaris App</title>
205
+ ${cssTags}
206
+ </head>
207
+ <body>
208
+ <div id="app"></div>
209
+ ${jsTags}
210
+ </body>
211
+ </html>`;
212
+ }
213
+
214
+ /** Convert route to file path */
215
+ function routeToFilePath(route: string, config: AdapterConfig): string {
216
+ const cleanUrls = config.cleanUrls ?? true;
217
+ const trailingSlash = config.trailingSlash ?? 'ignore';
218
+
219
+ let path = route === '/' ? '/index' : route;
220
+
221
+ if (trailingSlash === 'always' && !path.endsWith('/')) {
222
+ path += '/';
223
+ }
224
+
225
+ if (cleanUrls) {
226
+ return path + (path.endsWith('/') ? 'index.html' : '.html');
227
+ }
228
+
229
+ return path + '.html';
230
+ }
231
+
232
+ /** Generate deployment manifest */
233
+ export async function generateManifest(
234
+ _config: AdapterConfig,
235
+ outDir: string,
236
+ routes: RouteManifest[],
237
+ assets: Record<string, string>
238
+ ): Promise<void> {
239
+ const manifest: DeploymentManifest = {
240
+ version: '0.1.0',
241
+ timestamp: new Date().toISOString(),
242
+ routes,
243
+ assets,
244
+ entry: {
245
+ client: '/_assets/entry-client.js',
246
+ },
247
+ };
248
+
249
+ await fs.writeFile(
250
+ path.join(outDir, 'manifest.json'),
251
+ JSON.stringify(manifest, null, 2)
252
+ );
253
+ }
254
+
255
+ /** Copy public directory */
256
+ export async function copyPublic(outDir: string): Promise<void> {
257
+ if (await pathExists('public')) {
258
+ await copyDir('public', outDir);
259
+ }
260
+ }
261
+
262
+ /** Generate HTTP headers file */
263
+ export async function generateHeaders(
264
+ config: AdapterConfig,
265
+ outDir: string
266
+ ): Promise<void> {
267
+ if (!config.headers) return;
268
+
269
+ const headersFile = Object.entries(config.headers)
270
+ .map(([pattern, header]) => `${pattern}\n ${header}`)
271
+ .join('\n');
272
+
273
+ await fs.writeFile(path.join(outDir, '_headers'), headersFile);
274
+ }
275
+
276
+ /** Generate redirects file */
277
+ export async function generateRedirects(
278
+ config: AdapterConfig,
279
+ outDir: string
280
+ ): Promise<void> {
281
+ if (!config.redirects?.length) return;
282
+
283
+ const redirects = config.redirects
284
+ .map(r => `${r.from} ${r.to} ${r.status || 301}`)
285
+ .join('\n');
286
+
287
+ await fs.writeFile(path.join(outDir, '_redirects'), redirects);
288
+ }
289
+
290
+ /** Get package.json */
291
+ async function getPackageJson(): Promise<{ dependencies?: Record<string, string> }> {
292
+ try {
293
+ const content = await fs.readFile('package.json', 'utf-8');
294
+ return JSON.parse(content);
295
+ } catch {
296
+ return {};
297
+ }
298
+ }
299
+
300
+ /** Create hash for cache busting */
301
+ export function createContentHash(content: string, length = 8): string {
302
+ return createHash('md5').update(content).digest('hex').slice(0, length);
303
+ }
304
+
305
+ /** Calculate gzip size */
306
+ export async function calculateGzipSize(filePath: string): Promise<number> {
307
+ const { gzipSync } = await import('zlib');
308
+ const content = await fs.readFile(filePath);
309
+ return gzipSync(content).length;
310
+ return gzipSync(content).length;
311
+ }
312
+
313
+ /** Analyze bundle output */
314
+ export async function analyzeBundle(
315
+ outDir: string
316
+ ): Promise<BuildResult['analysis']> {
317
+ const clientDir = path.join(outDir, 'client');
318
+ const serverDir = path.join(outDir, 'server');
319
+
320
+ let totalSize = 0;
321
+ let clientSize = 0;
322
+ let serverSize = 0;
323
+ const assets: Array<{ file: string; size: number; gzip: number }> = [];
324
+
325
+ // Analyze client assets
326
+ if (await pathExists(clientDir)) {
327
+ const files = await fs.readdir(clientDir, { recursive: true }) as string[];
328
+ for (const file of files.filter((f: string) => !f.endsWith('.map'))) {
329
+ const filePath = path.join(clientDir, file);
330
+ const stats = await fs.stat(filePath);
331
+ if (stats.isFile()) {
332
+ const size = stats.size;
333
+ const gzip = await calculateGzipSize(filePath);
334
+ totalSize += size;
335
+ clientSize += size;
336
+ assets.push({ file: `client/${file}`, size, gzip });
337
+ }
338
+ }
339
+ }
340
+
341
+ // Analyze server assets
342
+ if (await pathExists(serverDir)) {
343
+ const files = await fs.readdir(serverDir, { recursive: true }) as string[];
344
+ for (const file of files.filter((f: string) => !f.endsWith('.map'))) {
345
+ const filePath = path.join(serverDir, file);
346
+ const stats = await fs.stat(filePath);
347
+ if (stats.isFile()) {
348
+ const size = stats.size;
349
+ serverSize += size;
350
+ totalSize += size;
351
+ }
352
+ }
353
+ }
354
+
355
+ return {
356
+ totalSize,
357
+ clientSize,
358
+ serverSize,
359
+ assets: assets.sort((a, b) => b.size - a.size),
360
+ };
361
+ }