bun-plugin-stx 0.2.12 → 0.2.13
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/dist/serve.d.ts +31 -0
- package/dist/serve.js +186 -4
- package/package.json +2 -2
package/dist/serve.d.ts
CHANGED
|
@@ -13,4 +13,35 @@ export declare interface ServeOptions {
|
|
|
13
13
|
quiet?: boolean
|
|
14
14
|
routes?: Record<string, (req: Request) => Response | Promise<Response>>
|
|
15
15
|
onRequest?: (req: Request) => Response | Promise<Response> | null | undefined
|
|
16
|
+
middleware?: Record<string, MiddlewareHandler>
|
|
17
|
+
globalMiddleware?: string[]
|
|
18
|
+
middlewareGroups?: Record<string, string[]>
|
|
19
|
+
auth?: false | {
|
|
20
|
+
cookieName?: string
|
|
21
|
+
redirectTo?: string
|
|
22
|
+
home?: string
|
|
23
|
+
protectedPaths?: string[]
|
|
24
|
+
}
|
|
16
25
|
}
|
|
26
|
+
export declare interface MiddlewareContext {
|
|
27
|
+
path: string
|
|
28
|
+
url: URL
|
|
29
|
+
params: Record<string, string>
|
|
30
|
+
cookies: Record<string, string>
|
|
31
|
+
redirect: (to: string, status?: number) => Response
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Middleware handler — a Laravel-style gate that either passes through
|
|
35
|
+
* (returns `void`/`null`/`undefined`) or terminates the pipeline by
|
|
36
|
+
* returning a `Response`.
|
|
37
|
+
*
|
|
38
|
+
* The third argument is the colon-separated arg list, so a page that
|
|
39
|
+
* declares `middleware: ['auth:admin']` invokes the `auth` handler
|
|
40
|
+
* with `args = ['admin']` — the same shape as Laravel's
|
|
41
|
+
* `handle($request, $next, ...$args)`.
|
|
42
|
+
*/
|
|
43
|
+
export type MiddlewareHandler = (
|
|
44
|
+
req: Request,
|
|
45
|
+
ctx: MiddlewareContext,
|
|
46
|
+
...args: string[]
|
|
47
|
+
) => Response | null | undefined | void | Promise<Response | null | undefined | void>;
|
package/dist/serve.js
CHANGED
|
@@ -5124,6 +5124,151 @@ After installing: bun add bun-plugin-stx`);
|
|
|
5124
5124
|
const routes = new Map;
|
|
5125
5125
|
let sourceFiles = null;
|
|
5126
5126
|
let assetsInitialized = false;
|
|
5127
|
+
const pageMiddlewareByPath = new Map;
|
|
5128
|
+
const pageMiddlewarePatterns = [];
|
|
5129
|
+
function compileRoutePattern(urlPath) {
|
|
5130
|
+
const names = [];
|
|
5131
|
+
const re = urlPath.split("/").map((seg) => {
|
|
5132
|
+
if (!seg)
|
|
5133
|
+
return "";
|
|
5134
|
+
const catchAll = seg.match(/^\[\.\.\.(.+)\]$/);
|
|
5135
|
+
if (catchAll) {
|
|
5136
|
+
names.push(catchAll[1]);
|
|
5137
|
+
return "(.+)";
|
|
5138
|
+
}
|
|
5139
|
+
const param = seg.match(/^\[(.+)\]$/);
|
|
5140
|
+
if (param) {
|
|
5141
|
+
names.push(param[1]);
|
|
5142
|
+
return "([^/]+)";
|
|
5143
|
+
}
|
|
5144
|
+
return seg.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5145
|
+
}).join("/");
|
|
5146
|
+
return { re: new RegExp(`^${re}/?$`), names };
|
|
5147
|
+
}
|
|
5148
|
+
function urlPathFromFile(file) {
|
|
5149
|
+
let rel = file;
|
|
5150
|
+
for (const pattern of patterns) {
|
|
5151
|
+
const base = pattern.replace(/\/$/, "");
|
|
5152
|
+
if (rel.startsWith(`${base}/`)) {
|
|
5153
|
+
rel = rel.slice(base.length + 1);
|
|
5154
|
+
break;
|
|
5155
|
+
}
|
|
5156
|
+
}
|
|
5157
|
+
rel = rel.replace(/\.(stx|md|html)$/, "");
|
|
5158
|
+
if (rel === "index" || rel.endsWith("/index"))
|
|
5159
|
+
rel = rel.replace(/\/?index$/, "");
|
|
5160
|
+
return rel.startsWith("/") ? rel : `/${rel}`;
|
|
5161
|
+
}
|
|
5162
|
+
async function detectPageMiddleware(file) {
|
|
5163
|
+
try {
|
|
5164
|
+
const fs = await import("fs/promises");
|
|
5165
|
+
const src = await fs.readFile(file, "utf-8");
|
|
5166
|
+
const meta = src.match(/definePageMeta\s*\(\s*\{[\s\S]*?\}\s*\)/);
|
|
5167
|
+
if (!meta)
|
|
5168
|
+
return;
|
|
5169
|
+
const mw = meta[0].match(/middleware\s*:\s*(\[[^\]]*\]|['"][^'"]+['"])/);
|
|
5170
|
+
if (!mw)
|
|
5171
|
+
return;
|
|
5172
|
+
const names = mw[1].startsWith("[") ? Array.from(mw[1].matchAll(/['"]([^'"]+)['"]/g)).map((m) => m[1]) : [mw[1].replace(/['"]/g, "")];
|
|
5173
|
+
if (names.length === 0)
|
|
5174
|
+
return;
|
|
5175
|
+
const urlPath = urlPathFromFile(file);
|
|
5176
|
+
if (urlPath.includes("[")) {
|
|
5177
|
+
const { re, names: paramNames } = compileRoutePattern(urlPath);
|
|
5178
|
+
pageMiddlewarePatterns.push({ re, names: paramNames, middleware: names });
|
|
5179
|
+
} else {
|
|
5180
|
+
pageMiddlewareByPath.set(urlPath, names);
|
|
5181
|
+
}
|
|
5182
|
+
} catch {}
|
|
5183
|
+
}
|
|
5184
|
+
function resolveRouteMiddleware(path) {
|
|
5185
|
+
const exact = pageMiddlewareByPath.get(path);
|
|
5186
|
+
if (exact)
|
|
5187
|
+
return { names: exact, params: {} };
|
|
5188
|
+
const trimmed = path.replace(/\/$/, "");
|
|
5189
|
+
if (trimmed !== path) {
|
|
5190
|
+
const exactTrim = pageMiddlewareByPath.get(trimmed);
|
|
5191
|
+
if (exactTrim)
|
|
5192
|
+
return { names: exactTrim, params: {} };
|
|
5193
|
+
}
|
|
5194
|
+
for (const entry of pageMiddlewarePatterns) {
|
|
5195
|
+
const m = entry.re.exec(path);
|
|
5196
|
+
if (m) {
|
|
5197
|
+
const params = {};
|
|
5198
|
+
entry.names.forEach((n, i) => {
|
|
5199
|
+
params[n] = decodeURIComponent(m[i + 1] ?? "");
|
|
5200
|
+
});
|
|
5201
|
+
return { names: entry.middleware, params };
|
|
5202
|
+
}
|
|
5203
|
+
}
|
|
5204
|
+
return { names: [], params: {} };
|
|
5205
|
+
}
|
|
5206
|
+
function parseCookies(req) {
|
|
5207
|
+
const out = {};
|
|
5208
|
+
const header = req.headers.get("cookie") || "";
|
|
5209
|
+
if (!header)
|
|
5210
|
+
return out;
|
|
5211
|
+
for (const part of header.split(/;\s*/)) {
|
|
5212
|
+
const eq = part.indexOf("=");
|
|
5213
|
+
if (eq === -1)
|
|
5214
|
+
continue;
|
|
5215
|
+
const k = part.slice(0, eq);
|
|
5216
|
+
if (!k)
|
|
5217
|
+
continue;
|
|
5218
|
+
try {
|
|
5219
|
+
out[k] = decodeURIComponent(part.slice(eq + 1));
|
|
5220
|
+
} catch {
|
|
5221
|
+
out[k] = part.slice(eq + 1);
|
|
5222
|
+
}
|
|
5223
|
+
}
|
|
5224
|
+
return out;
|
|
5225
|
+
}
|
|
5226
|
+
function redirectWithNext(target, originalPath, search = "") {
|
|
5227
|
+
const sep = target.includes("?") ? "&" : "?";
|
|
5228
|
+
const next = encodeURIComponent(originalPath + search);
|
|
5229
|
+
return Response.redirect(`${target}${sep}next=${next}`, 302);
|
|
5230
|
+
}
|
|
5231
|
+
const authConfig = options.auth === false ? null : options.auth ?? {};
|
|
5232
|
+
const authCookieName = authConfig?.cookieName ?? "auth-token";
|
|
5233
|
+
const authRedirectTo = authConfig?.redirectTo ?? "/login";
|
|
5234
|
+
const authHome = authConfig?.home ?? "/";
|
|
5235
|
+
const extraProtectedPrefixes = authConfig?.protectedPaths ?? [];
|
|
5236
|
+
const builtInMiddleware = authConfig === null ? {} : {
|
|
5237
|
+
auth: (_req, ctx) => {
|
|
5238
|
+
const tok = ctx.cookies[authCookieName];
|
|
5239
|
+
if (!tok)
|
|
5240
|
+
return ctx.redirect(authRedirectTo);
|
|
5241
|
+
return null;
|
|
5242
|
+
},
|
|
5243
|
+
guest: (_req, ctx) => {
|
|
5244
|
+
const tok = ctx.cookies[authCookieName];
|
|
5245
|
+
if (tok)
|
|
5246
|
+
return Response.redirect(authHome, 302);
|
|
5247
|
+
return null;
|
|
5248
|
+
}
|
|
5249
|
+
};
|
|
5250
|
+
const middlewareRegistry = {
|
|
5251
|
+
...builtInMiddleware,
|
|
5252
|
+
...options.middleware ?? {}
|
|
5253
|
+
};
|
|
5254
|
+
function expandMiddlewareNames(names) {
|
|
5255
|
+
const out = [];
|
|
5256
|
+
const groups = options.middlewareGroups ?? {};
|
|
5257
|
+
const seen = new Set;
|
|
5258
|
+
const visit = (name) => {
|
|
5259
|
+
if (seen.has(name))
|
|
5260
|
+
return;
|
|
5261
|
+
seen.add(name);
|
|
5262
|
+
const group = groups[name];
|
|
5263
|
+
if (group)
|
|
5264
|
+
group.forEach(visit);
|
|
5265
|
+
else
|
|
5266
|
+
out.push(name);
|
|
5267
|
+
};
|
|
5268
|
+
names.forEach(visit);
|
|
5269
|
+
return out;
|
|
5270
|
+
}
|
|
5271
|
+
const globalMiddlewareNames = options.globalMiddleware ?? [];
|
|
5127
5272
|
async function discoverFiles() {
|
|
5128
5273
|
if (sourceFiles !== null)
|
|
5129
5274
|
return sourceFiles;
|
|
@@ -5159,6 +5304,9 @@ After installing: bun add bun-plugin-stx`);
|
|
|
5159
5304
|
}
|
|
5160
5305
|
}
|
|
5161
5306
|
sourceFiles = files;
|
|
5307
|
+
pageMiddlewareByPath.clear();
|
|
5308
|
+
pageMiddlewarePatterns.length = 0;
|
|
5309
|
+
await Promise.all(files.map((f) => detectPageMiddleware(f)));
|
|
5162
5310
|
try {
|
|
5163
5311
|
const { Router } = await import("./chunk-762q71t6.js");
|
|
5164
5312
|
const pagesDir = patterns[0]?.replace(/\/$/, "") || "pages";
|
|
@@ -5541,6 +5689,40 @@ ${output}`;
|
|
|
5541
5689
|
if (customResponse)
|
|
5542
5690
|
return customResponse;
|
|
5543
5691
|
}
|
|
5692
|
+
const route = resolveRouteMiddleware(path);
|
|
5693
|
+
const extraNames = extraProtectedPrefixes.some((p) => path === p || path.startsWith(p.endsWith("/") ? p : `${p}/`)) ? ["auth"] : [];
|
|
5694
|
+
const requested = [...globalMiddlewareNames, ...route.names, ...extraNames];
|
|
5695
|
+
if (requested.length > 0) {
|
|
5696
|
+
const expanded = expandMiddlewareNames(requested);
|
|
5697
|
+
const cookies = parseCookies(req);
|
|
5698
|
+
const ctx = {
|
|
5699
|
+
path,
|
|
5700
|
+
url,
|
|
5701
|
+
params: route.params,
|
|
5702
|
+
cookies,
|
|
5703
|
+
redirect: (to, status = 302) => {
|
|
5704
|
+
const sep = to.includes("?") ? "&" : "?";
|
|
5705
|
+
const next = encodeURIComponent(path + (url.search || ""));
|
|
5706
|
+
return new Response(null, {
|
|
5707
|
+
status,
|
|
5708
|
+
headers: { Location: `${to}${sep}next=${next}` }
|
|
5709
|
+
});
|
|
5710
|
+
}
|
|
5711
|
+
};
|
|
5712
|
+
for (const entry of expanded) {
|
|
5713
|
+
const colon = entry.indexOf(":");
|
|
5714
|
+
const name = colon === -1 ? entry : entry.slice(0, colon);
|
|
5715
|
+
const args = colon === -1 ? [] : entry.slice(colon + 1).split(",");
|
|
5716
|
+
const handler = middlewareRegistry[name];
|
|
5717
|
+
if (!handler) {
|
|
5718
|
+
console.warn(`[stx serve] unknown middleware "${name}" on ${path}`);
|
|
5719
|
+
continue;
|
|
5720
|
+
}
|
|
5721
|
+
const result = await handler(req, ctx, ...args);
|
|
5722
|
+
if (result instanceof Response)
|
|
5723
|
+
return result;
|
|
5724
|
+
}
|
|
5725
|
+
}
|
|
5544
5726
|
if (options.routes) {
|
|
5545
5727
|
const routeHandler = options.routes[path];
|
|
5546
5728
|
if (routeHandler) {
|
|
@@ -5864,11 +6046,11 @@ ${pageSetupScripts.join(`
|
|
|
5864
6046
|
break;
|
|
5865
6047
|
}
|
|
5866
6048
|
}
|
|
5867
|
-
let
|
|
5868
|
-
if (
|
|
5869
|
-
|
|
6049
|
+
let route2 = normalizedPath.replace(/\.(stx|md|html)$/, "");
|
|
6050
|
+
if (route2.startsWith("pages/")) {
|
|
6051
|
+
route2 = route2.slice(6);
|
|
5870
6052
|
}
|
|
5871
|
-
availableRoutes.push(`<li><a href="/${
|
|
6053
|
+
availableRoutes.push(`<li><a href="/${route2}">/${route2}</a></li>`);
|
|
5872
6054
|
}
|
|
5873
6055
|
const routesList = availableRoutes.join(`
|
|
5874
6056
|
`);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bun-plugin-stx",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.13",
|
|
5
5
|
"description": "A Bun plugin that allows for using Laravel Blade-like syntax.",
|
|
6
6
|
"author": "Chris Breuer <chris@stacksjs.org>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -72,6 +72,6 @@
|
|
|
72
72
|
"typecheck": "bun tsc --noEmit"
|
|
73
73
|
},
|
|
74
74
|
"dependencies": {
|
|
75
|
-
"@stacksjs/stx": "0.2.
|
|
75
|
+
"@stacksjs/stx": "0.2.13"
|
|
76
76
|
}
|
|
77
77
|
}
|