@teamvelix/velix-core 5.1.7
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/LICENSE +21 -0
- package/dist/index.d.ts +1300 -0
- package/dist/index.js +1731 -0
- package/dist/index.js.map +1 -0
- package/package.json +41 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1731 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path2 from 'path';
|
|
3
|
+
import crypto from 'crypto';
|
|
4
|
+
import { pathToFileURL } from 'url';
|
|
5
|
+
import { z } from 'zod';
|
|
6
|
+
import pc from 'picocolors';
|
|
7
|
+
|
|
8
|
+
// src/router/index.ts
|
|
9
|
+
function generateHash(content) {
|
|
10
|
+
return crypto.createHash("md5").update(content).digest("hex").slice(0, 8);
|
|
11
|
+
}
|
|
12
|
+
function escapeHtml(str) {
|
|
13
|
+
const htmlEntities = {
|
|
14
|
+
"&": "&",
|
|
15
|
+
"<": "<",
|
|
16
|
+
">": ">",
|
|
17
|
+
'"': """,
|
|
18
|
+
"'": "'"
|
|
19
|
+
};
|
|
20
|
+
return String(str).replace(/[&<>"']/g, (char) => htmlEntities[char]);
|
|
21
|
+
}
|
|
22
|
+
function findFiles(dir, pattern, files = []) {
|
|
23
|
+
if (!fs.existsSync(dir)) return files;
|
|
24
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
25
|
+
for (const entry of entries) {
|
|
26
|
+
const fullPath = path2.join(dir, entry.name);
|
|
27
|
+
if (entry.isDirectory()) findFiles(fullPath, pattern, files);
|
|
28
|
+
else if (pattern.test(entry.name)) files.push(fullPath);
|
|
29
|
+
}
|
|
30
|
+
return files;
|
|
31
|
+
}
|
|
32
|
+
function ensureDir(dir) {
|
|
33
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
34
|
+
}
|
|
35
|
+
function cleanDir(dir) {
|
|
36
|
+
if (fs.existsSync(dir)) fs.rmSync(dir, { recursive: true, force: true });
|
|
37
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
38
|
+
}
|
|
39
|
+
function copyDir(src, dest) {
|
|
40
|
+
ensureDir(dest);
|
|
41
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
42
|
+
for (const entry of entries) {
|
|
43
|
+
const srcPath = path2.join(src, entry.name);
|
|
44
|
+
const destPath = path2.join(dest, entry.name);
|
|
45
|
+
if (entry.isDirectory()) copyDir(srcPath, destPath);
|
|
46
|
+
else fs.copyFileSync(srcPath, destPath);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function debounce(fn, delay) {
|
|
50
|
+
let timeout;
|
|
51
|
+
return (...args) => {
|
|
52
|
+
clearTimeout(timeout);
|
|
53
|
+
timeout = setTimeout(() => fn(...args), delay);
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
function formatBytes(bytes) {
|
|
57
|
+
if (bytes === 0) return "0 B";
|
|
58
|
+
const k = 1024;
|
|
59
|
+
const sizes = ["B", "KB", "MB", "GB"];
|
|
60
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
61
|
+
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + " " + sizes[i];
|
|
62
|
+
}
|
|
63
|
+
function formatTime(ms) {
|
|
64
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
65
|
+
return `${(ms / 1e3).toFixed(2)}s`;
|
|
66
|
+
}
|
|
67
|
+
function createDeferred() {
|
|
68
|
+
let resolve;
|
|
69
|
+
let reject;
|
|
70
|
+
const promise = new Promise((res, rej) => {
|
|
71
|
+
resolve = res;
|
|
72
|
+
reject = rej;
|
|
73
|
+
});
|
|
74
|
+
return { promise, resolve, reject };
|
|
75
|
+
}
|
|
76
|
+
function sleep(ms) {
|
|
77
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
78
|
+
}
|
|
79
|
+
function isServerComponent(filePath) {
|
|
80
|
+
try {
|
|
81
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
82
|
+
const firstLine = content.split("\n")[0].trim();
|
|
83
|
+
return firstLine === "'use server'" || firstLine === '"use server"';
|
|
84
|
+
} catch {
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function isClientComponent(filePath) {
|
|
89
|
+
try {
|
|
90
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
91
|
+
const firstLine = content.split("\n")[0].trim();
|
|
92
|
+
return firstLine === "'use client'" || firstLine === '"use client"';
|
|
93
|
+
} catch {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function isIsland(filePath) {
|
|
98
|
+
try {
|
|
99
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
100
|
+
const firstLine = content.split("\n")[0].trim();
|
|
101
|
+
return firstLine === "'use island'" || firstLine === '"use island"';
|
|
102
|
+
} catch {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/router/index.ts
|
|
108
|
+
var RouteType = {
|
|
109
|
+
PAGE: "page",
|
|
110
|
+
API: "api",
|
|
111
|
+
LAYOUT: "layout",
|
|
112
|
+
LOADING: "loading",
|
|
113
|
+
ERROR: "error",
|
|
114
|
+
NOT_FOUND: "not-found"
|
|
115
|
+
};
|
|
116
|
+
function buildRouteTree(appDir) {
|
|
117
|
+
const projectRoot = path2.dirname(appDir);
|
|
118
|
+
const routes = {
|
|
119
|
+
pages: [],
|
|
120
|
+
api: [],
|
|
121
|
+
layouts: /* @__PURE__ */ new Map(),
|
|
122
|
+
tree: { children: {}, routes: [] },
|
|
123
|
+
appRoutes: []
|
|
124
|
+
};
|
|
125
|
+
if (fs.existsSync(appDir)) {
|
|
126
|
+
scanAppDirectory(appDir, appDir, routes);
|
|
127
|
+
}
|
|
128
|
+
const serverApiDir = path2.join(projectRoot, "server", "api");
|
|
129
|
+
if (fs.existsSync(serverApiDir)) {
|
|
130
|
+
scanApiDirectory(serverApiDir, serverApiDir, routes);
|
|
131
|
+
}
|
|
132
|
+
const rootLayoutTsx = path2.join(appDir, "layout.tsx");
|
|
133
|
+
const rootLayoutJsx = path2.join(appDir, "layout.jsx");
|
|
134
|
+
if (fs.existsSync(rootLayoutTsx)) routes.rootLayout = rootLayoutTsx;
|
|
135
|
+
else if (fs.existsSync(rootLayoutJsx)) routes.rootLayout = rootLayoutJsx;
|
|
136
|
+
routes.tree = buildTree(routes.appRoutes);
|
|
137
|
+
return routes;
|
|
138
|
+
}
|
|
139
|
+
function scanAppDirectory(baseDir, currentDir, routes, parentSegments = [], parentLayout = null, parentMiddleware = null) {
|
|
140
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
141
|
+
const specialFiles = {
|
|
142
|
+
page: null,
|
|
143
|
+
layout: null,
|
|
144
|
+
loading: null,
|
|
145
|
+
error: null,
|
|
146
|
+
notFound: null,
|
|
147
|
+
template: null,
|
|
148
|
+
middleware: null
|
|
149
|
+
};
|
|
150
|
+
for (const entry of entries) {
|
|
151
|
+
if (entry.isFile()) {
|
|
152
|
+
const name = entry.name.replace(/\.(jsx|js|tsx|ts)$/, "");
|
|
153
|
+
const fullPath = path2.join(currentDir, entry.name);
|
|
154
|
+
const ext = path2.extname(entry.name);
|
|
155
|
+
if (![".tsx", ".jsx", ".ts", ".js"].includes(ext)) continue;
|
|
156
|
+
if (name === "page") specialFiles.page = fullPath;
|
|
157
|
+
if (name === "layout") specialFiles.layout = fullPath;
|
|
158
|
+
if (name === "loading") specialFiles.loading = fullPath;
|
|
159
|
+
if (name === "error") specialFiles.error = fullPath;
|
|
160
|
+
if (name === "not-found") specialFiles.notFound = fullPath;
|
|
161
|
+
if (name === "template") specialFiles.template = fullPath;
|
|
162
|
+
if (name === "middleware" || name === "_middleware") specialFiles.middleware = fullPath;
|
|
163
|
+
if (name.startsWith("[") && name.endsWith("]") && [".tsx", ".jsx"].includes(ext)) {
|
|
164
|
+
const paramName = name.slice(1, -1);
|
|
165
|
+
let segmentName;
|
|
166
|
+
if (paramName.startsWith("...")) {
|
|
167
|
+
segmentName = "*" + paramName.slice(3);
|
|
168
|
+
} else {
|
|
169
|
+
segmentName = ":" + paramName;
|
|
170
|
+
}
|
|
171
|
+
const routePath = "/" + [...parentSegments, segmentName].join("/");
|
|
172
|
+
routes.appRoutes.push({
|
|
173
|
+
type: RouteType.PAGE,
|
|
174
|
+
path: routePath.replace(/\/+/g, "/"),
|
|
175
|
+
filePath: fullPath,
|
|
176
|
+
pattern: createRoutePattern(routePath),
|
|
177
|
+
segments: [...parentSegments, segmentName],
|
|
178
|
+
layout: specialFiles.layout || parentLayout,
|
|
179
|
+
loading: specialFiles.loading,
|
|
180
|
+
error: specialFiles.error,
|
|
181
|
+
notFound: specialFiles.notFound,
|
|
182
|
+
template: specialFiles.template,
|
|
183
|
+
middleware: specialFiles.middleware || parentMiddleware,
|
|
184
|
+
isServerComponent: isServerComponent(fullPath),
|
|
185
|
+
isClientComponent: isClientComponent(fullPath),
|
|
186
|
+
isIsland: isIsland(fullPath)
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (specialFiles.page) {
|
|
192
|
+
const routePath = "/" + parentSegments.join("/") || "/";
|
|
193
|
+
routes.appRoutes.push({
|
|
194
|
+
type: RouteType.PAGE,
|
|
195
|
+
path: routePath.replace(/\/+/g, "/") || "/",
|
|
196
|
+
filePath: specialFiles.page,
|
|
197
|
+
pattern: createRoutePattern(routePath),
|
|
198
|
+
segments: parentSegments,
|
|
199
|
+
layout: specialFiles.layout || parentLayout,
|
|
200
|
+
loading: specialFiles.loading,
|
|
201
|
+
error: specialFiles.error,
|
|
202
|
+
notFound: specialFiles.notFound,
|
|
203
|
+
template: specialFiles.template,
|
|
204
|
+
middleware: specialFiles.middleware || parentMiddleware,
|
|
205
|
+
isServerComponent: isServerComponent(specialFiles.page),
|
|
206
|
+
isClientComponent: isClientComponent(specialFiles.page),
|
|
207
|
+
isIsland: isIsland(specialFiles.page)
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
for (const entry of entries) {
|
|
211
|
+
if (entry.isDirectory()) {
|
|
212
|
+
const fullPath = path2.join(currentDir, entry.name);
|
|
213
|
+
if (entry.name.startsWith("_") || entry.name.startsWith(".")) continue;
|
|
214
|
+
const isGroup = entry.name.startsWith("(") && entry.name.endsWith(")");
|
|
215
|
+
let segmentName = entry.name;
|
|
216
|
+
if (entry.name.startsWith("[") && entry.name.endsWith("]")) {
|
|
217
|
+
segmentName = ":" + entry.name.slice(1, -1);
|
|
218
|
+
if (entry.name.startsWith("[...")) {
|
|
219
|
+
segmentName = "*" + entry.name.slice(4, -1);
|
|
220
|
+
}
|
|
221
|
+
if (entry.name.startsWith("[[...")) {
|
|
222
|
+
segmentName = "*" + entry.name.slice(5, -2);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
const newSegments = isGroup ? parentSegments : [...parentSegments, segmentName];
|
|
226
|
+
const newLayout = specialFiles.layout || parentLayout;
|
|
227
|
+
const newMiddleware = specialFiles.middleware || parentMiddleware;
|
|
228
|
+
scanAppDirectory(baseDir, fullPath, routes, newSegments, newLayout, newMiddleware);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
function scanApiDirectory(baseDir, currentDir, routes, parentSegments = []) {
|
|
233
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true });
|
|
234
|
+
for (const entry of entries) {
|
|
235
|
+
const fullPath = path2.join(currentDir, entry.name);
|
|
236
|
+
if (entry.isDirectory()) {
|
|
237
|
+
scanApiDirectory(baseDir, fullPath, routes, [...parentSegments, entry.name]);
|
|
238
|
+
} else if (entry.isFile()) {
|
|
239
|
+
const ext = path2.extname(entry.name);
|
|
240
|
+
if (![".ts", ".js"].includes(ext)) continue;
|
|
241
|
+
const baseName = path2.basename(entry.name, ext);
|
|
242
|
+
const apiSegments = baseName === "route" || baseName === "index" ? parentSegments : [...parentSegments, baseName];
|
|
243
|
+
const apiPath = "/api/" + apiSegments.join("/");
|
|
244
|
+
routes.api.push({
|
|
245
|
+
type: RouteType.API,
|
|
246
|
+
path: apiPath.replace(/\/+/g, "/") || "/api",
|
|
247
|
+
filePath: fullPath,
|
|
248
|
+
pattern: createRoutePattern(apiPath),
|
|
249
|
+
segments: ["api", ...apiSegments].filter(Boolean)
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function createRoutePattern(routePath) {
|
|
255
|
+
let pattern = routePath.replace(/\*[^/]*/g, "(.*)").replace(/:[^/]+/g, "([^/]+)").replace(/\//g, "\\/");
|
|
256
|
+
return new RegExp(`^${pattern}$`);
|
|
257
|
+
}
|
|
258
|
+
function matchRoute(urlPath, routes) {
|
|
259
|
+
const normalizedPath = urlPath === "" ? "/" : urlPath.split("?")[0];
|
|
260
|
+
for (const route of routes) {
|
|
261
|
+
const match = normalizedPath.match(route.pattern);
|
|
262
|
+
if (match) {
|
|
263
|
+
const params = extractParams(route.path, match);
|
|
264
|
+
return { ...route, params };
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return null;
|
|
268
|
+
}
|
|
269
|
+
function extractParams(routePath, match) {
|
|
270
|
+
const params = {};
|
|
271
|
+
const paramNames = [];
|
|
272
|
+
const paramRegex = /:([^/]+)|\*([^/]*)/g;
|
|
273
|
+
let paramMatch;
|
|
274
|
+
while ((paramMatch = paramRegex.exec(routePath)) !== null) {
|
|
275
|
+
paramNames.push(paramMatch[1] || paramMatch[2] || "splat");
|
|
276
|
+
}
|
|
277
|
+
paramNames.forEach((name, index) => {
|
|
278
|
+
params[name] = match[index + 1];
|
|
279
|
+
});
|
|
280
|
+
return params;
|
|
281
|
+
}
|
|
282
|
+
function findRouteLayouts(route, layoutsMap) {
|
|
283
|
+
const layouts = [];
|
|
284
|
+
for (const segment of route.segments) {
|
|
285
|
+
if (layoutsMap.has(segment)) {
|
|
286
|
+
layouts.push({ name: segment, filePath: layoutsMap.get(segment) });
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
if (route.layout) {
|
|
290
|
+
layouts.push({ name: "route", filePath: route.layout });
|
|
291
|
+
}
|
|
292
|
+
if (layoutsMap.has("root")) {
|
|
293
|
+
layouts.unshift({ name: "root", filePath: layoutsMap.get("root") });
|
|
294
|
+
}
|
|
295
|
+
return layouts;
|
|
296
|
+
}
|
|
297
|
+
function buildTree(routes) {
|
|
298
|
+
const tree = { children: {}, routes: [] };
|
|
299
|
+
for (const route of routes) {
|
|
300
|
+
let current = tree;
|
|
301
|
+
for (const segment of route.segments) {
|
|
302
|
+
if (!current.children[segment]) {
|
|
303
|
+
current.children[segment] = { children: {}, routes: [] };
|
|
304
|
+
}
|
|
305
|
+
current = current.children[segment];
|
|
306
|
+
}
|
|
307
|
+
current.routes.push(route);
|
|
308
|
+
}
|
|
309
|
+
return tree;
|
|
310
|
+
}
|
|
311
|
+
var AppConfigSchema = z.object({
|
|
312
|
+
name: z.string().default("Velix App"),
|
|
313
|
+
url: z.string().url().optional()
|
|
314
|
+
}).default({});
|
|
315
|
+
var ServerConfigSchema = z.object({
|
|
316
|
+
port: z.number().min(1).max(65535).default(3e3),
|
|
317
|
+
host: z.string().default("localhost")
|
|
318
|
+
}).default({});
|
|
319
|
+
var RoutingConfigSchema = z.object({
|
|
320
|
+
trailingSlash: z.boolean().default(false)
|
|
321
|
+
}).default({});
|
|
322
|
+
var SEOConfigSchema = z.object({
|
|
323
|
+
sitemap: z.boolean().default(true),
|
|
324
|
+
robots: z.boolean().default(true),
|
|
325
|
+
openGraph: z.boolean().default(true)
|
|
326
|
+
}).default({});
|
|
327
|
+
var BuildConfigSchema = z.object({
|
|
328
|
+
target: z.string().default("es2022"),
|
|
329
|
+
minify: z.boolean().default(true),
|
|
330
|
+
sourcemap: z.boolean().default(true),
|
|
331
|
+
splitting: z.boolean().default(true),
|
|
332
|
+
outDir: z.string().default(".velix")
|
|
333
|
+
}).default({});
|
|
334
|
+
var ExperimentalConfigSchema = z.object({
|
|
335
|
+
islands: z.boolean().default(true),
|
|
336
|
+
streaming: z.boolean().default(true)
|
|
337
|
+
}).default({});
|
|
338
|
+
var PluginSchema = z.union([
|
|
339
|
+
z.string(),
|
|
340
|
+
z.object({
|
|
341
|
+
name: z.string()
|
|
342
|
+
}).passthrough()
|
|
343
|
+
]);
|
|
344
|
+
var VelixConfigSchema = z.object({
|
|
345
|
+
// App identity
|
|
346
|
+
app: AppConfigSchema,
|
|
347
|
+
// DevTools toggle
|
|
348
|
+
devtools: z.boolean().default(true),
|
|
349
|
+
// Server options
|
|
350
|
+
server: ServerConfigSchema,
|
|
351
|
+
// Routing options
|
|
352
|
+
routing: RoutingConfigSchema,
|
|
353
|
+
// SEO configuration
|
|
354
|
+
seo: SEOConfigSchema,
|
|
355
|
+
// Build options
|
|
356
|
+
build: BuildConfigSchema,
|
|
357
|
+
// Experimental features
|
|
358
|
+
experimental: ExperimentalConfigSchema,
|
|
359
|
+
// Plugins
|
|
360
|
+
plugins: z.array(PluginSchema).default([]),
|
|
361
|
+
// Directories (resolved automatically)
|
|
362
|
+
appDir: z.string().default("app"),
|
|
363
|
+
publicDir: z.string().default("public"),
|
|
364
|
+
// Stylesheets
|
|
365
|
+
styles: z.array(z.string()).default([]),
|
|
366
|
+
// Favicon
|
|
367
|
+
favicon: z.string().nullable().default(null)
|
|
368
|
+
});
|
|
369
|
+
var defaultConfig = VelixConfigSchema.parse({});
|
|
370
|
+
function defineConfig(config) {
|
|
371
|
+
return config;
|
|
372
|
+
}
|
|
373
|
+
async function loadConfig(projectRoot) {
|
|
374
|
+
const configPathTs = path2.join(projectRoot, "velix.config.ts");
|
|
375
|
+
const configPathJs = path2.join(projectRoot, "velix.config.js");
|
|
376
|
+
const configPathLegacyTs = path2.join(projectRoot, "flexireact.config.ts");
|
|
377
|
+
const configPathLegacyJs = path2.join(projectRoot, "flexireact.config.js");
|
|
378
|
+
let configPath = null;
|
|
379
|
+
if (fs.existsSync(configPathTs)) configPath = configPathTs;
|
|
380
|
+
else if (fs.existsSync(configPathJs)) configPath = configPathJs;
|
|
381
|
+
else if (fs.existsSync(configPathLegacyTs)) configPath = configPathLegacyTs;
|
|
382
|
+
else if (fs.existsSync(configPathLegacyJs)) configPath = configPathLegacyJs;
|
|
383
|
+
let userConfig = {};
|
|
384
|
+
if (configPath) {
|
|
385
|
+
try {
|
|
386
|
+
const configUrl = pathToFileURL(configPath).href;
|
|
387
|
+
const module = await import(`${configUrl}?t=${Date.now()}`);
|
|
388
|
+
userConfig = module.default || module;
|
|
389
|
+
} catch (error) {
|
|
390
|
+
console.warn(pc.yellow(`\u26A0 Failed to load config: ${error.message}`));
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
const merged = deepMerge(defaultConfig, userConfig);
|
|
394
|
+
try {
|
|
395
|
+
return VelixConfigSchema.parse(merged);
|
|
396
|
+
} catch (err) {
|
|
397
|
+
if (err instanceof z.ZodError) {
|
|
398
|
+
console.error(pc.red("\u2716 Configuration validation failed:"));
|
|
399
|
+
for (const issue of err.issues) {
|
|
400
|
+
console.error(pc.dim(` - ${issue.path.join(".")}: ${issue.message}`));
|
|
401
|
+
}
|
|
402
|
+
process.exit(1);
|
|
403
|
+
}
|
|
404
|
+
throw err;
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
function resolvePaths(config, projectRoot) {
|
|
408
|
+
return {
|
|
409
|
+
...config,
|
|
410
|
+
resolvedAppDir: path2.resolve(projectRoot, config.appDir),
|
|
411
|
+
resolvedPublicDir: path2.resolve(projectRoot, config.publicDir),
|
|
412
|
+
resolvedOutDir: path2.resolve(projectRoot, config.build.outDir)
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
function deepMerge(target, source) {
|
|
416
|
+
const result = { ...target };
|
|
417
|
+
for (const key in source) {
|
|
418
|
+
if (source[key] && typeof source[key] === "object" && !Array.isArray(source[key])) {
|
|
419
|
+
result[key] = deepMerge(target[key] || {}, source[key]);
|
|
420
|
+
} else {
|
|
421
|
+
result[key] = source[key];
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
return result;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// src/version.ts
|
|
428
|
+
var VERSION = "5.1.7";
|
|
429
|
+
|
|
430
|
+
// src/logger.ts
|
|
431
|
+
var colors = {
|
|
432
|
+
reset: "\x1B[0m",
|
|
433
|
+
bold: "\x1B[1m",
|
|
434
|
+
dim: "\x1B[2m",
|
|
435
|
+
red: "\x1B[31m",
|
|
436
|
+
green: "\x1B[32m",
|
|
437
|
+
yellow: "\x1B[33m",
|
|
438
|
+
cyan: "\x1B[36m",
|
|
439
|
+
white: "\x1B[37m",
|
|
440
|
+
gray: "\x1B[90m"
|
|
441
|
+
};
|
|
442
|
+
var c = colors;
|
|
443
|
+
function getStatusColor(status) {
|
|
444
|
+
if (status >= 500) return c.red;
|
|
445
|
+
if (status >= 400) return c.yellow;
|
|
446
|
+
if (status >= 300) return c.cyan;
|
|
447
|
+
if (status >= 200) return c.green;
|
|
448
|
+
return c.white;
|
|
449
|
+
}
|
|
450
|
+
function fmtTime(ms) {
|
|
451
|
+
if (ms < 1) return `${c.gray}<1ms${c.reset}`;
|
|
452
|
+
if (ms < 100) return `${c.green}${ms}ms${c.reset}`;
|
|
453
|
+
if (ms < 500) return `${c.yellow}${ms}ms${c.reset}`;
|
|
454
|
+
return `${c.red}${ms}ms${c.reset}`;
|
|
455
|
+
}
|
|
456
|
+
var LOGO = `
|
|
457
|
+
${c.cyan}\u25B2${c.reset} ${c.bold}Velix${c.reset} ${c.dim}v${VERSION}${c.reset}
|
|
458
|
+
`;
|
|
459
|
+
var logger = {
|
|
460
|
+
logo() {
|
|
461
|
+
console.log(LOGO);
|
|
462
|
+
console.log(`${c.dim} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}`);
|
|
463
|
+
console.log("");
|
|
464
|
+
},
|
|
465
|
+
serverStart(config, startTime = Date.now()) {
|
|
466
|
+
const { port, host, mode, pagesDir } = config;
|
|
467
|
+
const elapsed = Date.now() - startTime;
|
|
468
|
+
console.log(LOGO);
|
|
469
|
+
console.log(` ${c.green}\u2714${c.reset} ${c.bold}Ready${c.reset} in ${elapsed}ms`);
|
|
470
|
+
console.log("");
|
|
471
|
+
console.log(` ${c.bold}Local:${c.reset} ${c.cyan}http://${host}:${port}${c.reset}`);
|
|
472
|
+
console.log(` ${c.bold}Mode:${c.reset} ${mode === "development" ? c.yellow : c.green}${mode}${c.reset}`);
|
|
473
|
+
if (pagesDir) console.log(` ${c.bold}App:${c.reset} ${c.dim}${pagesDir}${c.reset}`);
|
|
474
|
+
console.log("");
|
|
475
|
+
},
|
|
476
|
+
request(method, path6, status, time, extra = {}) {
|
|
477
|
+
const statusColor = getStatusColor(status);
|
|
478
|
+
const timeStr = fmtTime(time);
|
|
479
|
+
let badge = `${c.dim}\u25CB${c.reset}`;
|
|
480
|
+
if (extra.type === "dynamic" || extra.type === "ssr") badge = `${c.white}\u0192${c.reset}`;
|
|
481
|
+
else if (extra.type === "api") badge = `${c.cyan}\u03BB${c.reset}`;
|
|
482
|
+
const statusStr = `${statusColor}${status}${c.reset}`;
|
|
483
|
+
console.log(` ${badge} ${c.white}${method}${c.reset} ${path6} ${statusStr} ${c.dim}${timeStr}${c.reset}`);
|
|
484
|
+
},
|
|
485
|
+
info(msg) {
|
|
486
|
+
console.log(` ${c.cyan}\u2139${c.reset} ${msg}`);
|
|
487
|
+
},
|
|
488
|
+
success(msg) {
|
|
489
|
+
console.log(` ${c.green}\u2714${c.reset} ${msg}`);
|
|
490
|
+
},
|
|
491
|
+
warn(msg) {
|
|
492
|
+
console.log(` ${c.yellow}\u26A0${c.reset} ${c.yellow}${msg}${c.reset}`);
|
|
493
|
+
},
|
|
494
|
+
error(msg, err = null) {
|
|
495
|
+
console.log(` ${c.red}\u2716${c.reset} ${c.red}${msg}${c.reset}`);
|
|
496
|
+
if (err?.stack) {
|
|
497
|
+
console.log("");
|
|
498
|
+
console.log(`${c.dim}${err.stack.split("\n").slice(1, 4).join("\n")}${c.reset}`);
|
|
499
|
+
console.log("");
|
|
500
|
+
}
|
|
501
|
+
},
|
|
502
|
+
compile(file, time) {
|
|
503
|
+
console.log(` ${c.white}\u25CF${c.reset} Compiling ${c.dim}${file}${c.reset} ${c.dim}(${time}ms)${c.reset}`);
|
|
504
|
+
},
|
|
505
|
+
hmr(file) {
|
|
506
|
+
console.log(` ${c.green}\u21BB${c.reset} Fast Refresh ${c.dim}${file}${c.reset}`);
|
|
507
|
+
},
|
|
508
|
+
plugin(name) {
|
|
509
|
+
console.log(` ${c.cyan}\u25C6${c.reset} Plugin ${c.dim}${name}${c.reset}`);
|
|
510
|
+
},
|
|
511
|
+
route(path6, type) {
|
|
512
|
+
const typeLabel = type === "api" ? "\u03BB" : type === "dynamic" ? "\u0192" : "\u25CB";
|
|
513
|
+
const color = type === "api" ? c.cyan : type === "dynamic" ? c.white : c.dim;
|
|
514
|
+
console.log(` ${color}${typeLabel}${c.reset} ${path6}`);
|
|
515
|
+
},
|
|
516
|
+
divider() {
|
|
517
|
+
console.log(`${c.dim} \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500${c.reset}`);
|
|
518
|
+
},
|
|
519
|
+
blank() {
|
|
520
|
+
console.log("");
|
|
521
|
+
},
|
|
522
|
+
portInUse(port) {
|
|
523
|
+
this.error(`Port ${port} is already in use.`);
|
|
524
|
+
this.blank();
|
|
525
|
+
console.log(` ${c.dim}Try:${c.reset}`);
|
|
526
|
+
console.log(` 1. Kill the process on port ${port}`);
|
|
527
|
+
console.log(` 2. Use a different port via PORT env var`);
|
|
528
|
+
this.blank();
|
|
529
|
+
},
|
|
530
|
+
build(stats) {
|
|
531
|
+
this.blank();
|
|
532
|
+
console.log(` ${c.green}\u2714${c.reset} Build completed`);
|
|
533
|
+
this.blank();
|
|
534
|
+
console.log(` ${c.dim}Total time:${c.reset} ${c.white}${stats.time}ms${c.reset}`);
|
|
535
|
+
this.blank();
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
// src/metadata/index.ts
|
|
540
|
+
function generateMetadataTags(metadata, baseUrl) {
|
|
541
|
+
const tags = [];
|
|
542
|
+
const base = baseUrl || metadata.metadataBase?.toString() || "";
|
|
543
|
+
if (metadata.title) {
|
|
544
|
+
const title = typeof metadata.title === "string" ? metadata.title : metadata.title.absolute || (metadata.title.template ? metadata.title.template.replace("%s", metadata.title.default) : metadata.title.default);
|
|
545
|
+
tags.push(`<title>${escapeHtml2(title)}</title>`);
|
|
546
|
+
}
|
|
547
|
+
if (metadata.description) tags.push(`<meta name="description" content="${escapeHtml2(metadata.description)}">`);
|
|
548
|
+
if (metadata.keywords) {
|
|
549
|
+
const kw = Array.isArray(metadata.keywords) ? metadata.keywords.join(", ") : metadata.keywords;
|
|
550
|
+
tags.push(`<meta name="keywords" content="${escapeHtml2(kw)}">`);
|
|
551
|
+
}
|
|
552
|
+
if (metadata.authors) {
|
|
553
|
+
const authors = Array.isArray(metadata.authors) ? metadata.authors : [metadata.authors];
|
|
554
|
+
authors.forEach((a) => {
|
|
555
|
+
if (a.name) tags.push(`<meta name="author" content="${escapeHtml2(a.name)}">`);
|
|
556
|
+
if (a.url) tags.push(`<link rel="author" href="${a.url}">`);
|
|
557
|
+
});
|
|
558
|
+
}
|
|
559
|
+
if (metadata.generator) tags.push(`<meta name="generator" content="${escapeHtml2(metadata.generator)}">`);
|
|
560
|
+
if (metadata.applicationName) tags.push(`<meta name="application-name" content="${escapeHtml2(metadata.applicationName)}">`);
|
|
561
|
+
if (metadata.referrer) tags.push(`<meta name="referrer" content="${metadata.referrer}">`);
|
|
562
|
+
if (metadata.robots) {
|
|
563
|
+
if (typeof metadata.robots === "string") {
|
|
564
|
+
tags.push(`<meta name="robots" content="${metadata.robots}">`);
|
|
565
|
+
} else {
|
|
566
|
+
tags.push(`<meta name="robots" content="${generateRobotsContent(metadata.robots)}">`);
|
|
567
|
+
if (metadata.robots.googleBot) {
|
|
568
|
+
const gbc = typeof metadata.robots.googleBot === "string" ? metadata.robots.googleBot : generateRobotsContent(metadata.robots.googleBot);
|
|
569
|
+
tags.push(`<meta name="googlebot" content="${gbc}">`);
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
if (metadata.viewport) {
|
|
574
|
+
const vc = typeof metadata.viewport === "string" ? metadata.viewport : generateViewportContent(metadata.viewport);
|
|
575
|
+
tags.push(`<meta name="viewport" content="${vc}">`);
|
|
576
|
+
}
|
|
577
|
+
if (metadata.themeColor) {
|
|
578
|
+
const tcs = Array.isArray(metadata.themeColor) ? metadata.themeColor : [metadata.themeColor];
|
|
579
|
+
tcs.forEach((tc) => {
|
|
580
|
+
const media = typeof tc !== "string" && tc.media ? ` media="${tc.media}"` : "";
|
|
581
|
+
const color = typeof tc === "string" ? tc : tc.color;
|
|
582
|
+
tags.push(`<meta name="theme-color" content="${color}"${media}>`);
|
|
583
|
+
});
|
|
584
|
+
}
|
|
585
|
+
if (metadata.colorScheme) tags.push(`<meta name="color-scheme" content="${metadata.colorScheme}">`);
|
|
586
|
+
if (metadata.icons) {
|
|
587
|
+
const addIcon = (icon, defaultRel) => {
|
|
588
|
+
const rel = icon.rel || defaultRel;
|
|
589
|
+
const attrs = [
|
|
590
|
+
icon.type ? ` type="${icon.type}"` : "",
|
|
591
|
+
icon.sizes ? ` sizes="${icon.sizes}"` : "",
|
|
592
|
+
icon.color ? ` color="${icon.color}"` : ""
|
|
593
|
+
].join("");
|
|
594
|
+
tags.push(`<link rel="${rel}" href="${resolveUrl(icon.url, base)}"${attrs}>`);
|
|
595
|
+
};
|
|
596
|
+
if (metadata.icons.icon) {
|
|
597
|
+
(Array.isArray(metadata.icons.icon) ? metadata.icons.icon : [metadata.icons.icon]).forEach((i) => addIcon(i, "icon"));
|
|
598
|
+
}
|
|
599
|
+
if (metadata.icons.apple) {
|
|
600
|
+
(Array.isArray(metadata.icons.apple) ? metadata.icons.apple : [metadata.icons.apple]).forEach((i) => addIcon(i, "apple-touch-icon"));
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
if (metadata.manifest) tags.push(`<link rel="manifest" href="${resolveUrl(metadata.manifest, base)}">`);
|
|
604
|
+
if (metadata.openGraph) {
|
|
605
|
+
const og = metadata.openGraph;
|
|
606
|
+
if (og.type) tags.push(`<meta property="og:type" content="${og.type}">`);
|
|
607
|
+
if (og.title) tags.push(`<meta property="og:title" content="${escapeHtml2(og.title)}">`);
|
|
608
|
+
if (og.description) tags.push(`<meta property="og:description" content="${escapeHtml2(og.description)}">`);
|
|
609
|
+
if (og.url) tags.push(`<meta property="og:url" content="${resolveUrl(og.url, base)}">`);
|
|
610
|
+
if (og.siteName) tags.push(`<meta property="og:site_name" content="${escapeHtml2(og.siteName)}">`);
|
|
611
|
+
if (og.locale) tags.push(`<meta property="og:locale" content="${og.locale}">`);
|
|
612
|
+
if (og.images) {
|
|
613
|
+
(Array.isArray(og.images) ? og.images : [og.images]).forEach((img) => {
|
|
614
|
+
tags.push(`<meta property="og:image" content="${resolveUrl(img.url, base)}">`);
|
|
615
|
+
if (img.width) tags.push(`<meta property="og:image:width" content="${img.width}">`);
|
|
616
|
+
if (img.height) tags.push(`<meta property="og:image:height" content="${img.height}">`);
|
|
617
|
+
if (img.alt) tags.push(`<meta property="og:image:alt" content="${escapeHtml2(img.alt)}">`);
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
if (og.type === "article") {
|
|
621
|
+
if (og.publishedTime) tags.push(`<meta property="article:published_time" content="${og.publishedTime}">`);
|
|
622
|
+
if (og.modifiedTime) tags.push(`<meta property="article:modified_time" content="${og.modifiedTime}">`);
|
|
623
|
+
if (og.tags) og.tags.forEach((t) => tags.push(`<meta property="article:tag" content="${escapeHtml2(t)}">`));
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
if (metadata.twitter) {
|
|
627
|
+
const tw = metadata.twitter;
|
|
628
|
+
if (tw.card) tags.push(`<meta name="twitter:card" content="${tw.card}">`);
|
|
629
|
+
if (tw.site) tags.push(`<meta name="twitter:site" content="${tw.site}">`);
|
|
630
|
+
if (tw.creator) tags.push(`<meta name="twitter:creator" content="${tw.creator}">`);
|
|
631
|
+
if (tw.title) tags.push(`<meta name="twitter:title" content="${escapeHtml2(tw.title)}">`);
|
|
632
|
+
if (tw.description) tags.push(`<meta name="twitter:description" content="${escapeHtml2(tw.description)}">`);
|
|
633
|
+
if (tw.images) {
|
|
634
|
+
(Array.isArray(tw.images) ? tw.images : [tw.images]).forEach((img) => {
|
|
635
|
+
const url = typeof img === "string" ? img : img.url;
|
|
636
|
+
tags.push(`<meta name="twitter:image" content="${resolveUrl(url, base)}">`);
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
if (metadata.verification) {
|
|
641
|
+
if (metadata.verification.google) {
|
|
642
|
+
(Array.isArray(metadata.verification.google) ? metadata.verification.google : [metadata.verification.google]).forEach((v) => tags.push(`<meta name="google-site-verification" content="${v}">`));
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
if (metadata.alternates) {
|
|
646
|
+
if (metadata.alternates.canonical) tags.push(`<link rel="canonical" href="${resolveUrl(metadata.alternates.canonical, base)}">`);
|
|
647
|
+
if (metadata.alternates.languages) {
|
|
648
|
+
Object.entries(metadata.alternates.languages).forEach(([lang, url]) => {
|
|
649
|
+
tags.push(`<link rel="alternate" hreflang="${lang}" href="${resolveUrl(url, base)}">`);
|
|
650
|
+
});
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
return tags.join("\n ");
|
|
654
|
+
}
|
|
655
|
+
function mergeMetadata(parent, child) {
|
|
656
|
+
return {
|
|
657
|
+
...parent,
|
|
658
|
+
...child,
|
|
659
|
+
openGraph: child.openGraph ? { ...parent.openGraph, ...child.openGraph } : parent.openGraph,
|
|
660
|
+
twitter: child.twitter ? { ...parent.twitter, ...child.twitter } : parent.twitter,
|
|
661
|
+
icons: child.icons ? { ...parent.icons, ...child.icons } : parent.icons,
|
|
662
|
+
alternates: child.alternates ? { ...parent.alternates, ...child.alternates } : parent.alternates
|
|
663
|
+
};
|
|
664
|
+
}
|
|
665
|
+
function generateJsonLd(data) {
|
|
666
|
+
return `<script type="application/ld+json">${JSON.stringify(data)}</script>`;
|
|
667
|
+
}
|
|
668
|
+
var jsonLd = {
|
|
669
|
+
website: (c2) => ({
|
|
670
|
+
"@context": "https://schema.org",
|
|
671
|
+
"@type": "WebSite",
|
|
672
|
+
name: c2.name,
|
|
673
|
+
url: c2.url,
|
|
674
|
+
description: c2.description
|
|
675
|
+
}),
|
|
676
|
+
article: (c2) => ({
|
|
677
|
+
"@context": "https://schema.org",
|
|
678
|
+
"@type": "Article",
|
|
679
|
+
headline: c2.headline,
|
|
680
|
+
description: c2.description,
|
|
681
|
+
image: c2.image,
|
|
682
|
+
datePublished: c2.datePublished,
|
|
683
|
+
dateModified: c2.dateModified || c2.datePublished,
|
|
684
|
+
author: Array.isArray(c2.author) ? c2.author.map((a) => ({ "@type": "Person", ...a })) : { "@type": "Person", ...c2.author }
|
|
685
|
+
}),
|
|
686
|
+
organization: (c2) => ({
|
|
687
|
+
"@context": "https://schema.org",
|
|
688
|
+
"@type": "Organization",
|
|
689
|
+
name: c2.name,
|
|
690
|
+
url: c2.url,
|
|
691
|
+
logo: c2.logo,
|
|
692
|
+
sameAs: c2.sameAs
|
|
693
|
+
}),
|
|
694
|
+
breadcrumb: (items) => ({
|
|
695
|
+
"@context": "https://schema.org",
|
|
696
|
+
"@type": "BreadcrumbList",
|
|
697
|
+
itemListElement: items.map((item, i) => ({ "@type": "ListItem", position: i + 1, name: item.name, item: item.url }))
|
|
698
|
+
})
|
|
699
|
+
};
|
|
700
|
+
function generateSitemap(routes, baseUrl) {
|
|
701
|
+
const urls = routes.filter((r) => r.type === "page" && !r.path.includes(":") && !r.path.includes("*")).map((r) => {
|
|
702
|
+
const loc = `${baseUrl.replace(/\/$/, "")}${r.path}`;
|
|
703
|
+
return ` <url>
|
|
704
|
+
<loc>${loc}</loc>
|
|
705
|
+
<lastmod>${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}</lastmod>
|
|
706
|
+
<changefreq>weekly</changefreq>
|
|
707
|
+
<priority>${r.path === "/" ? "1.0" : "0.8"}</priority>
|
|
708
|
+
</url>`;
|
|
709
|
+
});
|
|
710
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
711
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
712
|
+
${urls.join("\n")}
|
|
713
|
+
</urlset>`;
|
|
714
|
+
}
|
|
715
|
+
function generateRobotsTxt(baseUrl, options = {}) {
|
|
716
|
+
const lines = ["User-agent: *"];
|
|
717
|
+
if (options.allow) options.allow.forEach((p) => lines.push(`Allow: ${p}`));
|
|
718
|
+
if (options.disallow) options.disallow.forEach((p) => lines.push(`Disallow: ${p}`));
|
|
719
|
+
else lines.push("Allow: /");
|
|
720
|
+
lines.push("", `Sitemap: ${baseUrl.replace(/\/$/, "")}/sitemap.xml`);
|
|
721
|
+
return lines.join("\n");
|
|
722
|
+
}
|
|
723
|
+
function escapeHtml2(str) {
|
|
724
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
725
|
+
}
|
|
726
|
+
function resolveUrl(url, base) {
|
|
727
|
+
if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("//")) return url;
|
|
728
|
+
return base ? `${base.replace(/\/$/, "")}${url.startsWith("/") ? "" : "/"}${url}` : url;
|
|
729
|
+
}
|
|
730
|
+
function generateRobotsContent(robots) {
|
|
731
|
+
const parts = [];
|
|
732
|
+
if (robots.index !== void 0) parts.push(robots.index ? "index" : "noindex");
|
|
733
|
+
if (robots.follow !== void 0) parts.push(robots.follow ? "follow" : "nofollow");
|
|
734
|
+
if (robots.noarchive) parts.push("noarchive");
|
|
735
|
+
if (robots.nosnippet) parts.push("nosnippet");
|
|
736
|
+
if (robots.noimageindex) parts.push("noimageindex");
|
|
737
|
+
return parts.join(", ") || "index, follow";
|
|
738
|
+
}
|
|
739
|
+
function generateViewportContent(viewport) {
|
|
740
|
+
const parts = [];
|
|
741
|
+
if (viewport.width) parts.push(`width=${viewport.width}`);
|
|
742
|
+
if (viewport.height) parts.push(`height=${viewport.height}`);
|
|
743
|
+
if (viewport.initialScale !== void 0) parts.push(`initial-scale=${viewport.initialScale}`);
|
|
744
|
+
if (viewport.maximumScale !== void 0) parts.push(`maximum-scale=${viewport.maximumScale}`);
|
|
745
|
+
if (viewport.userScalable !== void 0) parts.push(`user-scalable=${viewport.userScalable ? "yes" : "no"}`);
|
|
746
|
+
return parts.join(", ") || "width=device-width, initial-scale=1";
|
|
747
|
+
}
|
|
748
|
+
var middlewares = {
|
|
749
|
+
cors(options = {}) {
|
|
750
|
+
const {
|
|
751
|
+
origin = "*",
|
|
752
|
+
methods = ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS"],
|
|
753
|
+
headers: allowHeaders = ["Content-Type", "Authorization"],
|
|
754
|
+
credentials = false,
|
|
755
|
+
maxAge = 86400
|
|
756
|
+
} = options;
|
|
757
|
+
return async (req, res, next) => {
|
|
758
|
+
const originHeader = typeof origin === "string" ? origin : origin.includes(req.headers.origin) ? req.headers.origin : "";
|
|
759
|
+
res.header("Access-Control-Allow-Origin", originHeader);
|
|
760
|
+
res.header("Access-Control-Allow-Methods", methods.join(", "));
|
|
761
|
+
res.header("Access-Control-Allow-Headers", allowHeaders.join(", "));
|
|
762
|
+
res.header("Access-Control-Max-Age", String(maxAge));
|
|
763
|
+
if (credentials) {
|
|
764
|
+
res.header("Access-Control-Allow-Credentials", "true");
|
|
765
|
+
}
|
|
766
|
+
if (req.method === "OPTIONS") {
|
|
767
|
+
res.status(204);
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
770
|
+
await next();
|
|
771
|
+
};
|
|
772
|
+
},
|
|
773
|
+
rateLimit(options = {}) {
|
|
774
|
+
const { windowMs = 6e4, max = 100, message = "Too many requests" } = options;
|
|
775
|
+
const store = /* @__PURE__ */ new Map();
|
|
776
|
+
return async (req, res, next) => {
|
|
777
|
+
const ip = req.headers["x-forwarded-for"] || "unknown";
|
|
778
|
+
const now = Date.now();
|
|
779
|
+
const record = store.get(ip);
|
|
780
|
+
if (!record || now > record.resetTime) {
|
|
781
|
+
store.set(ip, { count: 1, resetTime: now + windowMs });
|
|
782
|
+
} else if (record.count >= max) {
|
|
783
|
+
res.status(429).json({ error: message });
|
|
784
|
+
return;
|
|
785
|
+
} else {
|
|
786
|
+
record.count++;
|
|
787
|
+
}
|
|
788
|
+
await next();
|
|
789
|
+
};
|
|
790
|
+
},
|
|
791
|
+
security() {
|
|
792
|
+
return async (_req, res, next) => {
|
|
793
|
+
res.header("X-Content-Type-Options", "nosniff");
|
|
794
|
+
res.header("X-Frame-Options", "DENY");
|
|
795
|
+
res.header("X-XSS-Protection", "1; mode=block");
|
|
796
|
+
res.header("Referrer-Policy", "strict-origin-when-cross-origin");
|
|
797
|
+
await next();
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
};
|
|
801
|
+
async function loadMiddleware(projectRoot) {
|
|
802
|
+
const fns = [];
|
|
803
|
+
const possibleFiles = ["proxy.ts", "proxy.js"];
|
|
804
|
+
for (const file of possibleFiles) {
|
|
805
|
+
const filePath = path2.join(projectRoot, file);
|
|
806
|
+
if (!fs.existsSync(filePath)) continue;
|
|
807
|
+
try {
|
|
808
|
+
const { pathToFileURL: pathToFileURL3 } = await import('url');
|
|
809
|
+
const url = pathToFileURL3(filePath).href;
|
|
810
|
+
const mod = await import(`${url}?t=${Date.now()}`);
|
|
811
|
+
const fn = mod.default || mod.proxy || mod.middleware;
|
|
812
|
+
if (typeof fn === "function") {
|
|
813
|
+
fns.push(fn);
|
|
814
|
+
break;
|
|
815
|
+
}
|
|
816
|
+
} catch (err) {
|
|
817
|
+
const error = err;
|
|
818
|
+
console.warn(`\u26A0 Failed to load proxy ${file}: ${error.message}`);
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
return fns;
|
|
822
|
+
}
|
|
823
|
+
async function runMiddleware(req, res, fns) {
|
|
824
|
+
const result = { continue: true, rewritten: false };
|
|
825
|
+
if (fns.length === 0) return result;
|
|
826
|
+
const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
|
|
827
|
+
const cookies = {};
|
|
828
|
+
const cookieHeader = req.headers.cookie;
|
|
829
|
+
if (cookieHeader) {
|
|
830
|
+
cookieHeader.split(";").forEach((c2) => {
|
|
831
|
+
const [k, ...v] = c2.split("=");
|
|
832
|
+
if (k) cookies[k.trim()] = v.join("=").trim();
|
|
833
|
+
});
|
|
834
|
+
}
|
|
835
|
+
const mReq = {
|
|
836
|
+
url: req.url || "/",
|
|
837
|
+
method: req.method || "GET",
|
|
838
|
+
headers: req.headers,
|
|
839
|
+
cookies,
|
|
840
|
+
params: {},
|
|
841
|
+
query: Object.fromEntries(url.searchParams),
|
|
842
|
+
raw: req
|
|
843
|
+
};
|
|
844
|
+
let ended = false;
|
|
845
|
+
const mRes = {
|
|
846
|
+
_statusCode: 200,
|
|
847
|
+
_headers: {},
|
|
848
|
+
_redirectUrl: null,
|
|
849
|
+
_rewriteUrl: null,
|
|
850
|
+
_ended: false,
|
|
851
|
+
status(code) {
|
|
852
|
+
this._statusCode = code;
|
|
853
|
+
return this;
|
|
854
|
+
},
|
|
855
|
+
header(name, value) {
|
|
856
|
+
this._headers[name] = value;
|
|
857
|
+
return this;
|
|
858
|
+
},
|
|
859
|
+
json(data) {
|
|
860
|
+
this._headers["Content-Type"] = "application/json";
|
|
861
|
+
res.writeHead(this._statusCode, this._headers);
|
|
862
|
+
res.end(JSON.stringify(data));
|
|
863
|
+
this._ended = true;
|
|
864
|
+
ended = true;
|
|
865
|
+
},
|
|
866
|
+
redirect(url2, status = 307) {
|
|
867
|
+
this._redirectUrl = url2;
|
|
868
|
+
this._statusCode = status;
|
|
869
|
+
res.writeHead(status, { Location: url2, ...this._headers });
|
|
870
|
+
res.end();
|
|
871
|
+
this._ended = true;
|
|
872
|
+
ended = true;
|
|
873
|
+
},
|
|
874
|
+
rewrite(url2) {
|
|
875
|
+
this._rewriteUrl = url2;
|
|
876
|
+
req.url = url2;
|
|
877
|
+
result.rewritten = true;
|
|
878
|
+
},
|
|
879
|
+
async next() {
|
|
880
|
+
}
|
|
881
|
+
};
|
|
882
|
+
let index = 0;
|
|
883
|
+
const next = async () => {
|
|
884
|
+
if (ended || index >= fns.length) return;
|
|
885
|
+
const fn = fns[index++];
|
|
886
|
+
await fn(mReq, mRes, next);
|
|
887
|
+
};
|
|
888
|
+
await next();
|
|
889
|
+
if (!ended) {
|
|
890
|
+
for (const [key, value] of Object.entries(mRes._headers)) {
|
|
891
|
+
res.setHeader(key, value);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
result.continue = !ended;
|
|
895
|
+
return result;
|
|
896
|
+
}
|
|
897
|
+
function composeMiddleware(...fns) {
|
|
898
|
+
return async (req, res, next) => {
|
|
899
|
+
let index = 0;
|
|
900
|
+
const run = async () => {
|
|
901
|
+
if (index >= fns.length) {
|
|
902
|
+
await next();
|
|
903
|
+
return;
|
|
904
|
+
}
|
|
905
|
+
const fn = fns[index++];
|
|
906
|
+
await fn(req, res, run);
|
|
907
|
+
};
|
|
908
|
+
await run();
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
var PluginHooks = {
|
|
912
|
+
CONFIG: "config",
|
|
913
|
+
SERVER_START: "server:start",
|
|
914
|
+
REQUEST: "request",
|
|
915
|
+
RESPONSE: "response",
|
|
916
|
+
ROUTES_LOADED: "routes:loaded",
|
|
917
|
+
BEFORE_RENDER: "render:before",
|
|
918
|
+
AFTER_RENDER: "render:after",
|
|
919
|
+
BUILD_START: "build:start",
|
|
920
|
+
BUILD_END: "build:end"
|
|
921
|
+
};
|
|
922
|
+
var PluginManager = class {
|
|
923
|
+
plugins = [];
|
|
924
|
+
hooks = /* @__PURE__ */ new Map();
|
|
925
|
+
/**
|
|
926
|
+
* Register a plugin
|
|
927
|
+
*/
|
|
928
|
+
register(plugin) {
|
|
929
|
+
this.plugins.push(plugin);
|
|
930
|
+
if (plugin.hooks) {
|
|
931
|
+
for (const [hookName, handler] of Object.entries(plugin.hooks)) {
|
|
932
|
+
if (handler) {
|
|
933
|
+
const existing = this.hooks.get(hookName) || [];
|
|
934
|
+
existing.push(handler);
|
|
935
|
+
this.hooks.set(hookName, existing);
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
/**
|
|
941
|
+
* Run a hook with arguments
|
|
942
|
+
*/
|
|
943
|
+
async runHook(hookName, ...args) {
|
|
944
|
+
const handlers = this.hooks.get(hookName) || [];
|
|
945
|
+
for (const handler of handlers) {
|
|
946
|
+
await handler(...args);
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* Run a waterfall hook — each handler transforms the first argument
|
|
951
|
+
*/
|
|
952
|
+
async runWaterfallHook(hookName, value, ...args) {
|
|
953
|
+
const handlers = this.hooks.get(hookName) || [];
|
|
954
|
+
let result = value;
|
|
955
|
+
for (const handler of handlers) {
|
|
956
|
+
const transformed = await handler(result, ...args);
|
|
957
|
+
if (transformed !== void 0) result = transformed;
|
|
958
|
+
}
|
|
959
|
+
return result;
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* Get all registered plugins
|
|
963
|
+
*/
|
|
964
|
+
getPlugins() {
|
|
965
|
+
return [...this.plugins];
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Check if a plugin is registered
|
|
969
|
+
*/
|
|
970
|
+
hasPlugin(name) {
|
|
971
|
+
return this.plugins.some((p) => p.name === name);
|
|
972
|
+
}
|
|
973
|
+
};
|
|
974
|
+
var pluginManager = new PluginManager();
|
|
975
|
+
async function loadPlugins(projectRoot, config) {
|
|
976
|
+
const pluginEntries = config.plugins || [];
|
|
977
|
+
for (const entry of pluginEntries) {
|
|
978
|
+
try {
|
|
979
|
+
if (typeof entry === "string") {
|
|
980
|
+
const localPath = path2.join(projectRoot, "plugins", entry + ".ts");
|
|
981
|
+
const localPathJs = path2.join(projectRoot, "plugins", entry + ".js");
|
|
982
|
+
const localPathDir = path2.join(projectRoot, "plugins", entry, "index.ts");
|
|
983
|
+
let pluginPath = null;
|
|
984
|
+
if (fs.existsSync(localPath)) pluginPath = localPath;
|
|
985
|
+
else if (fs.existsSync(localPathJs)) pluginPath = localPathJs;
|
|
986
|
+
else if (fs.existsSync(localPathDir)) pluginPath = localPathDir;
|
|
987
|
+
if (pluginPath) {
|
|
988
|
+
const url = pathToFileURL(pluginPath).href;
|
|
989
|
+
const mod = await import(`${url}?t=${Date.now()}`);
|
|
990
|
+
const plugin = mod.default || mod;
|
|
991
|
+
if (plugin.name) {
|
|
992
|
+
pluginManager.register(plugin);
|
|
993
|
+
}
|
|
994
|
+
} else {
|
|
995
|
+
try {
|
|
996
|
+
const mod = await import(entry);
|
|
997
|
+
const plugin = mod.default || mod;
|
|
998
|
+
if (plugin.name) pluginManager.register(plugin);
|
|
999
|
+
} catch {
|
|
1000
|
+
console.warn(`\u26A0 Plugin not found: ${entry}`);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
} else if (typeof entry === "object" && entry !== null && "name" in entry) {
|
|
1004
|
+
pluginManager.register(entry);
|
|
1005
|
+
}
|
|
1006
|
+
} catch (err) {
|
|
1007
|
+
const error = err;
|
|
1008
|
+
console.warn(`\u26A0 Failed to load plugin: ${error?.message || String(error)}`);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
function definePlugin(definition) {
|
|
1013
|
+
return definition;
|
|
1014
|
+
}
|
|
1015
|
+
var builtinPlugins = {
|
|
1016
|
+
/**
|
|
1017
|
+
* Security headers plugin
|
|
1018
|
+
*/
|
|
1019
|
+
security: definePlugin({
|
|
1020
|
+
name: "velix:security",
|
|
1021
|
+
hooks: {
|
|
1022
|
+
[PluginHooks.RESPONSE]: (_req, res) => {
|
|
1023
|
+
const response = res;
|
|
1024
|
+
if (!response.headersSent) {
|
|
1025
|
+
response.setHeader("X-Content-Type-Options", "nosniff");
|
|
1026
|
+
response.setHeader("X-Frame-Options", "DENY");
|
|
1027
|
+
response.setHeader("X-XSS-Protection", "1; mode=block");
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
}),
|
|
1032
|
+
/**
|
|
1033
|
+
* Request logging plugin
|
|
1034
|
+
*/
|
|
1035
|
+
logger: definePlugin({
|
|
1036
|
+
name: "velix:logger",
|
|
1037
|
+
hooks: {
|
|
1038
|
+
[PluginHooks.RESPONSE]: (req, _res, duration) => {
|
|
1039
|
+
const request = req;
|
|
1040
|
+
console.log(` ${request.method} ${request.url} ${duration}ms`);
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
})
|
|
1044
|
+
};
|
|
1045
|
+
|
|
1046
|
+
// src/plugins/ai/utils.ts
|
|
1047
|
+
async function retry(fn, options = { maxRetries: 3, retryDelay: 1e3 }) {
|
|
1048
|
+
let lastError;
|
|
1049
|
+
for (let i = 0; i < options.maxRetries; i++) {
|
|
1050
|
+
try {
|
|
1051
|
+
return await fn();
|
|
1052
|
+
} catch (error) {
|
|
1053
|
+
lastError = error;
|
|
1054
|
+
if (i < options.maxRetries - 1) {
|
|
1055
|
+
const delay = options.retryDelay * Math.pow(2, i);
|
|
1056
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1057
|
+
}
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
throw lastError || new Error("Retry failed");
|
|
1061
|
+
}
|
|
1062
|
+
function safeJsonParse(text, fallback) {
|
|
1063
|
+
try {
|
|
1064
|
+
const jsonMatch = text.match(/```json\n([\s\S]*?)\n```/);
|
|
1065
|
+
if (jsonMatch) {
|
|
1066
|
+
return JSON.parse(jsonMatch[1]);
|
|
1067
|
+
}
|
|
1068
|
+
return JSON.parse(text);
|
|
1069
|
+
} catch {
|
|
1070
|
+
return fallback ?? null;
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
function buildPrompt(template, variables) {
|
|
1074
|
+
return template.replace(/\{\{(\w+)\}\}/g, (_, key) => {
|
|
1075
|
+
const val = variables[key];
|
|
1076
|
+
return val != null ? String(val) : "";
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
function validateApiKey(apiKey, provider) {
|
|
1080
|
+
if (!apiKey || apiKey.trim() === "") {
|
|
1081
|
+
throw new Error(`API key is required for ${provider} provider`);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
function validateInput(input, requiredFields) {
|
|
1085
|
+
for (const field of requiredFields) {
|
|
1086
|
+
if (!(field in input) || input[field] === void 0 || input[field] === null) {
|
|
1087
|
+
throw new Error(`Missing required field: ${field}`);
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
function createStreamDecoder() {
|
|
1092
|
+
const decoder = new TextDecoder();
|
|
1093
|
+
return (chunk) => {
|
|
1094
|
+
return decoder.decode(chunk, { stream: true });
|
|
1095
|
+
};
|
|
1096
|
+
}
|
|
1097
|
+
function parseSSE(line) {
|
|
1098
|
+
if (!line.startsWith("data: ")) {
|
|
1099
|
+
return null;
|
|
1100
|
+
}
|
|
1101
|
+
const data = line.slice(6);
|
|
1102
|
+
if (data === "[DONE]") {
|
|
1103
|
+
return { done: true };
|
|
1104
|
+
}
|
|
1105
|
+
try {
|
|
1106
|
+
return JSON.parse(data);
|
|
1107
|
+
} catch {
|
|
1108
|
+
return null;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
function sanitizeInput(text) {
|
|
1112
|
+
return text.trim().replace(/\0/g, "");
|
|
1113
|
+
}
|
|
1114
|
+
function estimateTokens(text) {
|
|
1115
|
+
return Math.ceil(text.length / 4);
|
|
1116
|
+
}
|
|
1117
|
+
function truncateToTokens(text, maxTokens) {
|
|
1118
|
+
const estimatedTokens = estimateTokens(text);
|
|
1119
|
+
if (estimatedTokens <= maxTokens) {
|
|
1120
|
+
return text;
|
|
1121
|
+
}
|
|
1122
|
+
const maxChars = maxTokens * 4;
|
|
1123
|
+
return text.slice(0, maxChars) + "...";
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
// src/plugins/ai/providers/openai.ts
|
|
1127
|
+
var OpenAIProvider = class {
|
|
1128
|
+
name = "openai";
|
|
1129
|
+
apiKey;
|
|
1130
|
+
baseUrl;
|
|
1131
|
+
defaultModel;
|
|
1132
|
+
organization;
|
|
1133
|
+
retryConfig;
|
|
1134
|
+
constructor(config) {
|
|
1135
|
+
validateApiKey(config.apiKey, "OpenAI");
|
|
1136
|
+
this.apiKey = config.apiKey;
|
|
1137
|
+
this.baseUrl = config.baseUrl || "https://api.openai.com/v1";
|
|
1138
|
+
this.defaultModel = config.defaultModel || "gpt-4o-mini";
|
|
1139
|
+
this.organization = config.organization;
|
|
1140
|
+
this.retryConfig = config.retry || { maxRetries: 3, retryDelay: 1e3 };
|
|
1141
|
+
}
|
|
1142
|
+
async generate(input) {
|
|
1143
|
+
return retry(async () => {
|
|
1144
|
+
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
1145
|
+
method: "POST",
|
|
1146
|
+
headers: this.getHeaders(),
|
|
1147
|
+
body: JSON.stringify({
|
|
1148
|
+
model: input.model || this.defaultModel,
|
|
1149
|
+
messages: [
|
|
1150
|
+
...input.system ? [{ role: "system", content: input.system }] : [],
|
|
1151
|
+
{ role: "user", content: input.prompt }
|
|
1152
|
+
],
|
|
1153
|
+
temperature: input.temperature ?? 0.7,
|
|
1154
|
+
max_tokens: input.maxTokens,
|
|
1155
|
+
stop: input.stop,
|
|
1156
|
+
...input.options
|
|
1157
|
+
})
|
|
1158
|
+
});
|
|
1159
|
+
if (!response.ok) {
|
|
1160
|
+
const error = await response.json();
|
|
1161
|
+
throw new Error(`OpenAI API error: ${error.error?.message || response.statusText}`);
|
|
1162
|
+
}
|
|
1163
|
+
const data = await response.json();
|
|
1164
|
+
return {
|
|
1165
|
+
text: data.choices[0].message.content,
|
|
1166
|
+
provider: "openai",
|
|
1167
|
+
model: data.model,
|
|
1168
|
+
usage: {
|
|
1169
|
+
promptTokens: data.usage.prompt_tokens,
|
|
1170
|
+
completionTokens: data.usage.completion_tokens,
|
|
1171
|
+
totalTokens: data.usage.total_tokens
|
|
1172
|
+
},
|
|
1173
|
+
raw: data
|
|
1174
|
+
};
|
|
1175
|
+
}, this.retryConfig);
|
|
1176
|
+
}
|
|
1177
|
+
async chat(input) {
|
|
1178
|
+
return retry(async () => {
|
|
1179
|
+
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
1180
|
+
method: "POST",
|
|
1181
|
+
headers: this.getHeaders(),
|
|
1182
|
+
body: JSON.stringify({
|
|
1183
|
+
model: input.model || this.defaultModel,
|
|
1184
|
+
messages: input.messages,
|
|
1185
|
+
temperature: input.temperature ?? 0.7,
|
|
1186
|
+
max_tokens: input.maxTokens,
|
|
1187
|
+
stop: input.stop,
|
|
1188
|
+
...input.options
|
|
1189
|
+
})
|
|
1190
|
+
});
|
|
1191
|
+
if (!response.ok) {
|
|
1192
|
+
const error = await response.json();
|
|
1193
|
+
throw new Error(`OpenAI API error: ${error.error?.message || response.statusText}`);
|
|
1194
|
+
}
|
|
1195
|
+
const data = await response.json();
|
|
1196
|
+
return {
|
|
1197
|
+
text: data.choices[0].message.content,
|
|
1198
|
+
provider: "openai",
|
|
1199
|
+
model: data.model,
|
|
1200
|
+
usage: {
|
|
1201
|
+
promptTokens: data.usage.prompt_tokens,
|
|
1202
|
+
completionTokens: data.usage.completion_tokens,
|
|
1203
|
+
totalTokens: data.usage.total_tokens
|
|
1204
|
+
},
|
|
1205
|
+
raw: data
|
|
1206
|
+
};
|
|
1207
|
+
}, this.retryConfig);
|
|
1208
|
+
}
|
|
1209
|
+
async *stream(input) {
|
|
1210
|
+
const response = await fetch(`${this.baseUrl}/chat/completions`, {
|
|
1211
|
+
method: "POST",
|
|
1212
|
+
headers: this.getHeaders(),
|
|
1213
|
+
body: JSON.stringify({
|
|
1214
|
+
model: input.model || this.defaultModel,
|
|
1215
|
+
messages: [
|
|
1216
|
+
...input.system ? [{ role: "system", content: input.system }] : [],
|
|
1217
|
+
{ role: "user", content: input.prompt }
|
|
1218
|
+
],
|
|
1219
|
+
temperature: input.temperature ?? 0.7,
|
|
1220
|
+
max_tokens: input.maxTokens,
|
|
1221
|
+
stop: input.stop,
|
|
1222
|
+
stream: true,
|
|
1223
|
+
...input.options
|
|
1224
|
+
})
|
|
1225
|
+
});
|
|
1226
|
+
if (!response.ok) {
|
|
1227
|
+
const error = await response.json();
|
|
1228
|
+
throw new Error(`OpenAI API error: ${error.error?.message || response.statusText}`);
|
|
1229
|
+
}
|
|
1230
|
+
const reader = response.body?.getReader();
|
|
1231
|
+
if (!reader) {
|
|
1232
|
+
throw new Error("Response body is not readable");
|
|
1233
|
+
}
|
|
1234
|
+
const decoder = new TextDecoder();
|
|
1235
|
+
let buffer = "";
|
|
1236
|
+
try {
|
|
1237
|
+
while (true) {
|
|
1238
|
+
const { done, value } = await reader.read();
|
|
1239
|
+
if (done) break;
|
|
1240
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1241
|
+
const lines = buffer.split("\n");
|
|
1242
|
+
buffer = lines.pop() || "";
|
|
1243
|
+
for (const line of lines) {
|
|
1244
|
+
const trimmed = line.trim();
|
|
1245
|
+
if (!trimmed || trimmed === "data: [DONE]") continue;
|
|
1246
|
+
const parsed = parseSSE(trimmed);
|
|
1247
|
+
if (!parsed) continue;
|
|
1248
|
+
const delta = parsed.choices?.[0]?.delta?.content;
|
|
1249
|
+
if (delta) {
|
|
1250
|
+
yield {
|
|
1251
|
+
text: delta,
|
|
1252
|
+
done: false,
|
|
1253
|
+
provider: "openai",
|
|
1254
|
+
model: parsed.model
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
yield { text: "", done: true, provider: "openai" };
|
|
1260
|
+
} finally {
|
|
1261
|
+
reader.releaseLock();
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
async embed(input) {
|
|
1265
|
+
return retry(async () => {
|
|
1266
|
+
const response = await fetch(`${this.baseUrl}/embeddings`, {
|
|
1267
|
+
method: "POST",
|
|
1268
|
+
headers: this.getHeaders(),
|
|
1269
|
+
body: JSON.stringify({
|
|
1270
|
+
model: input.model || "text-embedding-3-small",
|
|
1271
|
+
input: input.text
|
|
1272
|
+
})
|
|
1273
|
+
});
|
|
1274
|
+
if (!response.ok) {
|
|
1275
|
+
const error = await response.json();
|
|
1276
|
+
throw new Error(`OpenAI API error: ${error.error?.message || response.statusText}`);
|
|
1277
|
+
}
|
|
1278
|
+
const data = await response.json();
|
|
1279
|
+
return {
|
|
1280
|
+
embedding: data.data[0].embedding,
|
|
1281
|
+
provider: "openai",
|
|
1282
|
+
model: data.model
|
|
1283
|
+
};
|
|
1284
|
+
}, this.retryConfig);
|
|
1285
|
+
}
|
|
1286
|
+
getHeaders() {
|
|
1287
|
+
const headers = {
|
|
1288
|
+
"Content-Type": "application/json",
|
|
1289
|
+
"Authorization": `Bearer ${this.apiKey}`
|
|
1290
|
+
};
|
|
1291
|
+
if (this.organization) {
|
|
1292
|
+
headers["OpenAI-Organization"] = this.organization;
|
|
1293
|
+
}
|
|
1294
|
+
return headers;
|
|
1295
|
+
}
|
|
1296
|
+
};
|
|
1297
|
+
|
|
1298
|
+
// src/plugins/ai/providers/ollama.ts
|
|
1299
|
+
var OllamaProvider = class {
|
|
1300
|
+
name = "ollama";
|
|
1301
|
+
baseUrl;
|
|
1302
|
+
defaultModel;
|
|
1303
|
+
retryConfig;
|
|
1304
|
+
constructor(config = {}) {
|
|
1305
|
+
this.baseUrl = config.baseUrl || "http://localhost:11434";
|
|
1306
|
+
this.defaultModel = config.defaultModel || "llama3.2";
|
|
1307
|
+
this.retryConfig = config.retry || { maxRetries: 3, retryDelay: 1e3 };
|
|
1308
|
+
}
|
|
1309
|
+
async generate(input) {
|
|
1310
|
+
return retry(async () => {
|
|
1311
|
+
const response = await fetch(`${this.baseUrl}/api/generate`, {
|
|
1312
|
+
method: "POST",
|
|
1313
|
+
headers: { "Content-Type": "application/json" },
|
|
1314
|
+
body: JSON.stringify({
|
|
1315
|
+
model: input.model || this.defaultModel,
|
|
1316
|
+
prompt: input.prompt,
|
|
1317
|
+
system: input.system,
|
|
1318
|
+
options: {
|
|
1319
|
+
temperature: input.temperature ?? 0.7,
|
|
1320
|
+
num_predict: input.maxTokens,
|
|
1321
|
+
stop: input.stop,
|
|
1322
|
+
...input.options
|
|
1323
|
+
},
|
|
1324
|
+
stream: false
|
|
1325
|
+
})
|
|
1326
|
+
});
|
|
1327
|
+
if (!response.ok) {
|
|
1328
|
+
throw new Error(`Ollama API error: ${response.statusText}`);
|
|
1329
|
+
}
|
|
1330
|
+
const data = await response.json();
|
|
1331
|
+
return {
|
|
1332
|
+
text: data.response,
|
|
1333
|
+
provider: "ollama",
|
|
1334
|
+
model: data.model,
|
|
1335
|
+
usage: {
|
|
1336
|
+
promptTokens: data.prompt_eval_count || 0,
|
|
1337
|
+
completionTokens: data.eval_count || 0,
|
|
1338
|
+
totalTokens: (data.prompt_eval_count || 0) + (data.eval_count || 0)
|
|
1339
|
+
},
|
|
1340
|
+
raw: data
|
|
1341
|
+
};
|
|
1342
|
+
}, this.retryConfig);
|
|
1343
|
+
}
|
|
1344
|
+
async chat(input) {
|
|
1345
|
+
return retry(async () => {
|
|
1346
|
+
const response = await fetch(`${this.baseUrl}/api/chat`, {
|
|
1347
|
+
method: "POST",
|
|
1348
|
+
headers: { "Content-Type": "application/json" },
|
|
1349
|
+
body: JSON.stringify({
|
|
1350
|
+
model: input.model || this.defaultModel,
|
|
1351
|
+
messages: input.messages,
|
|
1352
|
+
options: {
|
|
1353
|
+
temperature: input.temperature ?? 0.7,
|
|
1354
|
+
num_predict: input.maxTokens,
|
|
1355
|
+
stop: input.stop,
|
|
1356
|
+
...input.options
|
|
1357
|
+
},
|
|
1358
|
+
stream: false
|
|
1359
|
+
})
|
|
1360
|
+
});
|
|
1361
|
+
if (!response.ok) {
|
|
1362
|
+
throw new Error(`Ollama API error: ${response.statusText}`);
|
|
1363
|
+
}
|
|
1364
|
+
const data = await response.json();
|
|
1365
|
+
return {
|
|
1366
|
+
text: data.message.content,
|
|
1367
|
+
provider: "ollama",
|
|
1368
|
+
model: data.model,
|
|
1369
|
+
usage: {
|
|
1370
|
+
promptTokens: data.prompt_eval_count || 0,
|
|
1371
|
+
completionTokens: data.eval_count || 0,
|
|
1372
|
+
totalTokens: (data.prompt_eval_count || 0) + (data.eval_count || 0)
|
|
1373
|
+
},
|
|
1374
|
+
raw: data
|
|
1375
|
+
};
|
|
1376
|
+
}, this.retryConfig);
|
|
1377
|
+
}
|
|
1378
|
+
async *stream(input) {
|
|
1379
|
+
const response = await fetch(`${this.baseUrl}/api/generate`, {
|
|
1380
|
+
method: "POST",
|
|
1381
|
+
headers: { "Content-Type": "application/json" },
|
|
1382
|
+
body: JSON.stringify({
|
|
1383
|
+
model: input.model || this.defaultModel,
|
|
1384
|
+
prompt: input.prompt,
|
|
1385
|
+
system: input.system,
|
|
1386
|
+
options: {
|
|
1387
|
+
temperature: input.temperature ?? 0.7,
|
|
1388
|
+
num_predict: input.maxTokens,
|
|
1389
|
+
stop: input.stop,
|
|
1390
|
+
...input.options
|
|
1391
|
+
},
|
|
1392
|
+
stream: true
|
|
1393
|
+
})
|
|
1394
|
+
});
|
|
1395
|
+
if (!response.ok) {
|
|
1396
|
+
throw new Error(`Ollama API error: ${response.statusText}`);
|
|
1397
|
+
}
|
|
1398
|
+
const reader = response.body?.getReader();
|
|
1399
|
+
if (!reader) {
|
|
1400
|
+
throw new Error("Response body is not readable");
|
|
1401
|
+
}
|
|
1402
|
+
const decoder = new TextDecoder();
|
|
1403
|
+
let buffer = "";
|
|
1404
|
+
try {
|
|
1405
|
+
while (true) {
|
|
1406
|
+
const { done, value } = await reader.read();
|
|
1407
|
+
if (done) break;
|
|
1408
|
+
buffer += decoder.decode(value, { stream: true });
|
|
1409
|
+
const lines = buffer.split("\n");
|
|
1410
|
+
buffer = lines.pop() || "";
|
|
1411
|
+
for (const line of lines) {
|
|
1412
|
+
const trimmed = line.trim();
|
|
1413
|
+
if (!trimmed) continue;
|
|
1414
|
+
try {
|
|
1415
|
+
const data = JSON.parse(trimmed);
|
|
1416
|
+
if (data.response) {
|
|
1417
|
+
yield {
|
|
1418
|
+
text: data.response,
|
|
1419
|
+
done: data.done || false,
|
|
1420
|
+
provider: "ollama",
|
|
1421
|
+
model: data.model
|
|
1422
|
+
};
|
|
1423
|
+
}
|
|
1424
|
+
if (data.done) {
|
|
1425
|
+
return;
|
|
1426
|
+
}
|
|
1427
|
+
} catch {
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
yield { text: "", done: true, provider: "ollama" };
|
|
1432
|
+
} finally {
|
|
1433
|
+
reader.releaseLock();
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
async embed(input) {
|
|
1437
|
+
return retry(async () => {
|
|
1438
|
+
const response = await fetch(`${this.baseUrl}/api/embeddings`, {
|
|
1439
|
+
method: "POST",
|
|
1440
|
+
headers: { "Content-Type": "application/json" },
|
|
1441
|
+
body: JSON.stringify({
|
|
1442
|
+
model: input.model || this.defaultModel,
|
|
1443
|
+
prompt: input.text
|
|
1444
|
+
})
|
|
1445
|
+
});
|
|
1446
|
+
if (!response.ok) {
|
|
1447
|
+
throw new Error(`Ollama API error: ${response.statusText}`);
|
|
1448
|
+
}
|
|
1449
|
+
const data = await response.json();
|
|
1450
|
+
return {
|
|
1451
|
+
embedding: data.embedding,
|
|
1452
|
+
provider: "ollama",
|
|
1453
|
+
model: data.model || input.model || this.defaultModel
|
|
1454
|
+
};
|
|
1455
|
+
}, this.retryConfig);
|
|
1456
|
+
}
|
|
1457
|
+
};
|
|
1458
|
+
|
|
1459
|
+
// src/plugins/ai/client.ts
|
|
1460
|
+
function createAIClient(config) {
|
|
1461
|
+
const provider = createProvider(config);
|
|
1462
|
+
return {
|
|
1463
|
+
async generate(input) {
|
|
1464
|
+
return provider.generate(input);
|
|
1465
|
+
},
|
|
1466
|
+
async chat(input) {
|
|
1467
|
+
return provider.chat(input);
|
|
1468
|
+
},
|
|
1469
|
+
stream(input) {
|
|
1470
|
+
return provider.stream(input);
|
|
1471
|
+
},
|
|
1472
|
+
async embed(input) {
|
|
1473
|
+
return provider.embed(input);
|
|
1474
|
+
},
|
|
1475
|
+
getProvider() {
|
|
1476
|
+
return provider;
|
|
1477
|
+
}
|
|
1478
|
+
};
|
|
1479
|
+
}
|
|
1480
|
+
function createProvider(config) {
|
|
1481
|
+
switch (config.provider) {
|
|
1482
|
+
case "openai":
|
|
1483
|
+
return new OpenAIProvider({
|
|
1484
|
+
apiKey: config.apiKey,
|
|
1485
|
+
baseUrl: config.baseUrl,
|
|
1486
|
+
defaultModel: config.defaultModel,
|
|
1487
|
+
retry: config.retry
|
|
1488
|
+
});
|
|
1489
|
+
case "ollama":
|
|
1490
|
+
return new OllamaProvider({
|
|
1491
|
+
baseUrl: config.baseUrl,
|
|
1492
|
+
defaultModel: config.defaultModel,
|
|
1493
|
+
retry: config.retry
|
|
1494
|
+
});
|
|
1495
|
+
default:
|
|
1496
|
+
throw new Error(`Unsupported AI provider: ${config.provider}`);
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
// src/plugins/ai/index.ts
|
|
1501
|
+
function useAI() {
|
|
1502
|
+
if (typeof globalThis === "undefined" || !globalThis.__VELIX_AI__) {
|
|
1503
|
+
throw new Error("AI client not initialized. Make sure aiPlugin is configured.");
|
|
1504
|
+
}
|
|
1505
|
+
return globalThis.__VELIX_AI__;
|
|
1506
|
+
}
|
|
1507
|
+
function createAIAction(config) {
|
|
1508
|
+
return async (input) => {
|
|
1509
|
+
const ai = useAI();
|
|
1510
|
+
const prompt = config.prompt(input);
|
|
1511
|
+
const response = await ai.generate({
|
|
1512
|
+
prompt,
|
|
1513
|
+
system: config.system,
|
|
1514
|
+
model: config.model,
|
|
1515
|
+
temperature: config.temperature
|
|
1516
|
+
});
|
|
1517
|
+
if (config.transform) {
|
|
1518
|
+
return config.transform(response.text);
|
|
1519
|
+
}
|
|
1520
|
+
return response.text;
|
|
1521
|
+
};
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
// src/cache/index.ts
|
|
1525
|
+
var VelixCache = class {
|
|
1526
|
+
maxSize;
|
|
1527
|
+
defaultTtl;
|
|
1528
|
+
defaultSwr;
|
|
1529
|
+
store = /* @__PURE__ */ new Map();
|
|
1530
|
+
tagIndex = /* @__PURE__ */ new Map();
|
|
1531
|
+
// LRU doubly-linked list sentinel nodes
|
|
1532
|
+
head;
|
|
1533
|
+
tail;
|
|
1534
|
+
constructor(config = {}) {
|
|
1535
|
+
this.maxSize = config.maxSize ?? 1e3;
|
|
1536
|
+
this.defaultTtl = config.defaultTtl ?? 6e4;
|
|
1537
|
+
this.defaultSwr = config.defaultSwr ?? 1e4;
|
|
1538
|
+
this.head = this.createSentinel("__HEAD__");
|
|
1539
|
+
this.tail = this.createSentinel("__TAIL__");
|
|
1540
|
+
this.head.next = this.tail;
|
|
1541
|
+
this.tail.prev = this.head;
|
|
1542
|
+
}
|
|
1543
|
+
/** Current number of entries */
|
|
1544
|
+
get size() {
|
|
1545
|
+
return this.store.size;
|
|
1546
|
+
}
|
|
1547
|
+
/**
|
|
1548
|
+
* Store a value with optional TTL, SWR, and tags.
|
|
1549
|
+
*/
|
|
1550
|
+
set(key, value, options) {
|
|
1551
|
+
if (this.store.has(key)) {
|
|
1552
|
+
this.removeEntry(this.store.get(key));
|
|
1553
|
+
}
|
|
1554
|
+
const entry = {
|
|
1555
|
+
key,
|
|
1556
|
+
value,
|
|
1557
|
+
tags: new Set(options?.tags ?? []),
|
|
1558
|
+
createdAt: Date.now(),
|
|
1559
|
+
ttl: options?.ttl ?? this.defaultTtl,
|
|
1560
|
+
swr: options?.swr ?? this.defaultSwr,
|
|
1561
|
+
prev: null,
|
|
1562
|
+
next: null
|
|
1563
|
+
};
|
|
1564
|
+
this.store.set(key, entry);
|
|
1565
|
+
this.addToHead(entry);
|
|
1566
|
+
for (const tag of entry.tags) {
|
|
1567
|
+
if (!this.tagIndex.has(tag)) {
|
|
1568
|
+
this.tagIndex.set(tag, /* @__PURE__ */ new Set());
|
|
1569
|
+
}
|
|
1570
|
+
this.tagIndex.get(tag).add(key);
|
|
1571
|
+
}
|
|
1572
|
+
while (this.store.size > this.maxSize) {
|
|
1573
|
+
this.evictLRU();
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
/**
|
|
1577
|
+
* Get a cached value. Returns `null` if not found or expired beyond SWR window.
|
|
1578
|
+
*
|
|
1579
|
+
* @returns `{ value, stale }` — `stale` is true when the entry is past TTL but within SWR window.
|
|
1580
|
+
*/
|
|
1581
|
+
get(key) {
|
|
1582
|
+
const entry = this.store.get(key);
|
|
1583
|
+
if (!entry) return null;
|
|
1584
|
+
const now = Date.now();
|
|
1585
|
+
const age = now - entry.createdAt;
|
|
1586
|
+
if (age > entry.ttl + entry.swr) {
|
|
1587
|
+
this.removeEntry(entry);
|
|
1588
|
+
return null;
|
|
1589
|
+
}
|
|
1590
|
+
this.moveToHead(entry);
|
|
1591
|
+
const stale = age > entry.ttl;
|
|
1592
|
+
return { value: entry.value, stale };
|
|
1593
|
+
}
|
|
1594
|
+
/**
|
|
1595
|
+
* Simple get that returns only the value (ignoring staleness).
|
|
1596
|
+
* Returns `null` if not found or expired.
|
|
1597
|
+
*/
|
|
1598
|
+
getValue(key) {
|
|
1599
|
+
const result = this.get(key);
|
|
1600
|
+
return result ? result.value : null;
|
|
1601
|
+
}
|
|
1602
|
+
/** Check whether a key exists and is not expired */
|
|
1603
|
+
has(key) {
|
|
1604
|
+
return this.get(key) !== null;
|
|
1605
|
+
}
|
|
1606
|
+
/** Remove a specific key */
|
|
1607
|
+
delete(key) {
|
|
1608
|
+
const entry = this.store.get(key);
|
|
1609
|
+
if (!entry) return false;
|
|
1610
|
+
this.removeEntry(entry);
|
|
1611
|
+
return true;
|
|
1612
|
+
}
|
|
1613
|
+
/** Invalidate a specific path/key */
|
|
1614
|
+
revalidatePath(path6) {
|
|
1615
|
+
this.delete(path6);
|
|
1616
|
+
}
|
|
1617
|
+
/** Invalidate all entries associated with a tag */
|
|
1618
|
+
revalidateTag(tag) {
|
|
1619
|
+
const keys = this.tagIndex.get(tag);
|
|
1620
|
+
if (!keys) return;
|
|
1621
|
+
for (const key of keys) {
|
|
1622
|
+
const entry = this.store.get(key);
|
|
1623
|
+
if (entry) {
|
|
1624
|
+
this.removeEntry(entry);
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
this.tagIndex.delete(tag);
|
|
1628
|
+
}
|
|
1629
|
+
/** Clear all entries */
|
|
1630
|
+
invalidateAll() {
|
|
1631
|
+
this.store.clear();
|
|
1632
|
+
this.tagIndex.clear();
|
|
1633
|
+
this.head.next = this.tail;
|
|
1634
|
+
this.tail.prev = this.head;
|
|
1635
|
+
}
|
|
1636
|
+
/** Get cache stats for debugging/monitoring */
|
|
1637
|
+
stats() {
|
|
1638
|
+
return {
|
|
1639
|
+
size: this.store.size,
|
|
1640
|
+
maxSize: this.maxSize,
|
|
1641
|
+
tags: this.tagIndex.size
|
|
1642
|
+
};
|
|
1643
|
+
}
|
|
1644
|
+
// ── Internal LRU operations ──
|
|
1645
|
+
createSentinel(key) {
|
|
1646
|
+
return {
|
|
1647
|
+
key,
|
|
1648
|
+
value: null,
|
|
1649
|
+
tags: /* @__PURE__ */ new Set(),
|
|
1650
|
+
createdAt: 0,
|
|
1651
|
+
ttl: 0,
|
|
1652
|
+
swr: 0,
|
|
1653
|
+
prev: null,
|
|
1654
|
+
next: null
|
|
1655
|
+
};
|
|
1656
|
+
}
|
|
1657
|
+
addToHead(entry) {
|
|
1658
|
+
entry.prev = this.head;
|
|
1659
|
+
entry.next = this.head.next;
|
|
1660
|
+
this.head.next.prev = entry;
|
|
1661
|
+
this.head.next = entry;
|
|
1662
|
+
}
|
|
1663
|
+
removeFromList(entry) {
|
|
1664
|
+
if (entry.prev) entry.prev.next = entry.next;
|
|
1665
|
+
if (entry.next) entry.next.prev = entry.prev;
|
|
1666
|
+
entry.prev = null;
|
|
1667
|
+
entry.next = null;
|
|
1668
|
+
}
|
|
1669
|
+
moveToHead(entry) {
|
|
1670
|
+
this.removeFromList(entry);
|
|
1671
|
+
this.addToHead(entry);
|
|
1672
|
+
}
|
|
1673
|
+
evictLRU() {
|
|
1674
|
+
const lru = this.tail.prev;
|
|
1675
|
+
if (lru && lru !== this.head) {
|
|
1676
|
+
this.removeEntry(lru);
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
removeEntry(entry) {
|
|
1680
|
+
this.removeFromList(entry);
|
|
1681
|
+
this.store.delete(entry.key);
|
|
1682
|
+
for (const tag of entry.tags) {
|
|
1683
|
+
const keys = this.tagIndex.get(tag);
|
|
1684
|
+
if (keys) {
|
|
1685
|
+
keys.delete(entry.key);
|
|
1686
|
+
if (keys.size === 0) {
|
|
1687
|
+
this.tagIndex.delete(tag);
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
}
|
|
1692
|
+
};
|
|
1693
|
+
var cacheManager = new VelixCache();
|
|
1694
|
+
function revalidatePath(path6, _type = "path") {
|
|
1695
|
+
cacheManager.revalidatePath(path6);
|
|
1696
|
+
if (typeof global !== "undefined" && global.__VELIX_HMR_SERVER__) {
|
|
1697
|
+
global.__VELIX_HMR_SERVER__.broadcast(JSON.stringify({
|
|
1698
|
+
type: "revalidate",
|
|
1699
|
+
path: path6,
|
|
1700
|
+
revalidationType: _type
|
|
1701
|
+
}));
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
function revalidateTag(tag) {
|
|
1705
|
+
cacheManager.revalidateTag(tag);
|
|
1706
|
+
if (typeof global !== "undefined" && global.__VELIX_HMR_SERVER__) {
|
|
1707
|
+
global.__VELIX_HMR_SERVER__.broadcast(JSON.stringify({
|
|
1708
|
+
type: "revalidate",
|
|
1709
|
+
tag
|
|
1710
|
+
}));
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
function unstable_cache(fn, keys, options) {
|
|
1714
|
+
return async () => {
|
|
1715
|
+
const cacheKey = keys.join(":");
|
|
1716
|
+
const cached = cacheManager.get(cacheKey);
|
|
1717
|
+
if (cached && !cached.stale) {
|
|
1718
|
+
return cached.value;
|
|
1719
|
+
}
|
|
1720
|
+
const result = await fn();
|
|
1721
|
+
cacheManager.set(cacheKey, result, {
|
|
1722
|
+
tags: options?.tags,
|
|
1723
|
+
ttl: options?.revalidate ? options.revalidate * 1e3 : void 0
|
|
1724
|
+
});
|
|
1725
|
+
return result;
|
|
1726
|
+
};
|
|
1727
|
+
}
|
|
1728
|
+
|
|
1729
|
+
export { OllamaProvider, OpenAIProvider, PluginHooks, PluginManager, RouteType, VelixCache, VelixConfigSchema, buildPrompt, buildRouteTree, builtinPlugins, cacheManager, cleanDir, composeMiddleware, copyDir, createAIAction, createAIClient, createDeferred, createStreamDecoder, debounce, defaultConfig, defineConfig, definePlugin, ensureDir, escapeHtml, estimateTokens, findFiles, findRouteLayouts, formatBytes, formatTime, generateHash, generateJsonLd, generateMetadataTags, generateRobotsTxt, generateSitemap, isClientComponent, isIsland, isServerComponent, jsonLd, loadConfig, loadMiddleware, loadPlugins, logger, matchRoute, mergeMetadata, middlewares, parseSSE, pluginManager, resolvePaths, retry, revalidatePath, revalidateTag, runMiddleware, safeJsonParse, sanitizeInput, sleep, truncateToTokens, unstable_cache, useAI, validateApiKey, validateInput };
|
|
1730
|
+
//# sourceMappingURL=index.js.map
|
|
1731
|
+
//# sourceMappingURL=index.js.map
|