Compare commits

...

4 Commits

Author SHA1 Message Date
980ccfe949 v7.10.1
Some checks failed
Default (tags) / security (push) Failing after 16s
Default (tags) / test (push) Failing after 12s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-05 15:46:33 +00:00
4a76c8f738 fix(typedserver): Use smartserve ControllerRegistry for custom routes and remove custom route parsing 2025-12-05 15:46:33 +00:00
05b1f0a395 v7.10.0
Some checks failed
Default (tags) / security (push) Failing after 37s
Default (tags) / test (push) Failing after 14s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-12-05 15:27:54 +00:00
d060d99146 feat(website-server): Add configurable ads.txt support to website server 2025-12-05 15:27:54 +00:00
5 changed files with 37 additions and 72 deletions

View File

@@ -1,5 +1,21 @@
# Changelog
## 2025-12-05 - 7.10.1 - fix(typedserver)
Use smartserve ControllerRegistry for custom routes and remove custom route parsing
- addRoute now delegates to plugins.smartserve.ControllerRegistry instead of building its own regex-based matcher
- Backwards compatibility: incoming smartserve IRequestContext is converted to a Request and ctx.params is attached to request.params before invoking the handler
- Removed internal IRegisteredRoute, customRoutes storage, and parseRouteParams helper
- Request handling now uses ControllerRegistry.matchRoute and registered controllers are compiled via ControllerRegistry.compileRoutes()
## 2025-12-05 - 7.10.0 - feat(website-server)
Add configurable ads.txt support to website server
- Introduce adsTxt?: string[] option to the server options to allow configuring ads.txt entries.
- Serve /ads.txt only when adsTxt is provided; the route is not registered if no entries are configured.
- Replace previous hard-coded Google ads.txt entry with values joined from the provided adsTxt array and served as text/plain.
- Preserves existing behavior when adsTxt is not set (no /ads.txt endpoint will be exposed).
## 2025-12-05 - 7.9.0 - feat(typedserver)
Add configurable security headers and default SPA behavior

View File

@@ -1,6 +1,6 @@
{
"name": "@api.global/typedserver",
"version": "7.9.0",
"version": "7.10.1",
"description": "A TypeScript-based project for easy serving of static files with support for live reloading, compression, and typed requests.",
"type": "module",
"exports": {

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@api.global/typedserver',
version: '7.9.0',
version: '7.10.1',
description: 'A TypeScript-based project for easy serving of static files with support for live reloading, compression, and typed requests.'
}

View File

@@ -145,14 +145,6 @@ export interface IRouteHandler {
(request: Request): Promise<Response | null>;
}
export interface IRegisteredRoute {
pattern: string;
regex: RegExp;
paramNames: string[];
method: THttpMethod;
handler: IRouteHandler;
}
export class TypedServer {
// instance
public options: IServerOptions;
@@ -175,9 +167,6 @@ export class TypedServer {
// File server for static files
private fileServer: plugins.smartserve.FileServer;
// Custom route handlers (for addRoute API)
private customRoutes: IRegisteredRoute[] = [];
public lastReload: number = Date.now();
public ended = false;
@@ -210,49 +199,18 @@ export class TypedServer {
* @param handler - Async function that receives Request and returns Response or null
*/
public addRoute(path: string, method: THttpMethod, handler: IRouteHandler): void {
// Convert Express-style path to regex
const paramNames: string[] = [];
let regexPattern = path
// Handle named parameters :param
.replace(/:([a-zA-Z_][a-zA-Z0-9_]*)/g, (_, paramName) => {
paramNames.push(paramName);
return '([^/]+)';
})
// Handle wildcard *splat (matches everything including slashes)
.replace(/\*([a-zA-Z_][a-zA-Z0-9_]*)/g, (_, paramName) => {
paramNames.push(paramName);
return '(.*)';
// Delegate to smartserve's ControllerRegistry
plugins.smartserve.ControllerRegistry.addRoute(path, method, async (ctx: plugins.smartserve.IRequestContext) => {
// Convert context to Request for backwards compatibility
const request = new Request(ctx.url.toString(), {
method: ctx.method,
headers: ctx.headers,
});
// Ensure exact match
regexPattern = `^${regexPattern}$`;
this.customRoutes.push({
pattern: path,
regex: new RegExp(regexPattern),
paramNames,
method,
handler,
(request as any).params = ctx.params;
return handler(request);
});
}
/**
* Parse route parameters from a path using a registered route
*/
private parseRouteParams(
route: IRegisteredRoute,
pathname: string
): Record<string, string> | null {
const match = pathname.match(route.regex);
if (!match) return null;
const params: Record<string, string> = {};
route.paramNames.forEach((name, index) => {
params[name] = match[index + 1];
});
return params;
}
/**
* inits and starts the server
*/
@@ -650,18 +608,6 @@ export class TypedServer {
}
}
// Custom routes (registered via addRoute)
for (const route of this.customRoutes) {
if (route.method === 'ALL' || route.method === method) {
const params = this.parseRouteParams(route, path);
if (params !== null) {
(request as any).params = params;
const response = await route.handler(request);
if (response) return response;
}
}
}
// HTML injection for reload (if enabled)
if (this.options.injectReload && this.options.serveDir) {
const response = await this.handleHtmlWithInjection(request);

View File

@@ -23,6 +23,8 @@ export interface IUtilityWebsiteServerConstructorOptions {
forceSsl?: boolean;
/** Port to listen on (default: 3000) */
port?: number;
/** ads.txt entries (only served if configured) */
adsTxt?: string[];
}
/**
@@ -94,15 +96,16 @@ export class UtilityWebsiteServer {
})
);
// ads.txt handler
this.typedserver.addRoute('/ads.txt', 'GET', async () => {
const adsTxt =
['google.com, pub-4104137977476459, DIRECT, f08c47fec0942fa0'].join('\n') + '\n';
return new Response(adsTxt, {
status: 200,
headers: { 'Content-Type': 'text/plain' },
// ads.txt handler (only if configured)
if (this.options.adsTxt && this.options.adsTxt.length > 0) {
this.typedserver.addRoute('/ads.txt', 'GET', async () => {
const adsTxt = this.options.adsTxt.join('\n') + '\n';
return new Response(adsTxt, {
status: 200,
headers: { 'Content-Type': 'text/plain' },
});
});
});
}
// Asset broker manifest handler
this.typedserver.addRoute(