bun-plugin-stx 0.2.54 → 0.2.63
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/{chunk-nznv3e0m.js → chunk-ap5bhr00.js} +27 -6
- package/dist/serve.d.ts +8 -1
- package/dist/serve.js +494 -357
- package/package.json +3 -3
|
@@ -591,6 +591,19 @@ function getRouterScript() {
|
|
|
591
591
|
var containerSel=o.container;
|
|
592
592
|
var debug=!!o.debug;
|
|
593
593
|
function log(){if(debug&&typeof console!=='undefined'&&console.log)console.log.apply(console,arguments)}
|
|
594
|
+
function runViewTransition(callback){
|
|
595
|
+
if(!o.viewTransitions||!document.startViewTransition)return false;
|
|
596
|
+
try{
|
|
597
|
+
var transition=document.startViewTransition(callback);
|
|
598
|
+
var onAbort=function(err){log('[router] view transition aborted:',err&&err.message?err.message:err)};
|
|
599
|
+
if(transition&&transition.finished&&transition.finished.catch)transition.finished.catch(onAbort);
|
|
600
|
+
if(transition&&transition.updateCallbackDone&&transition.updateCallbackDone.catch)transition.updateCallbackDone.catch(onAbort);
|
|
601
|
+
return true;
|
|
602
|
+
}catch(err){
|
|
603
|
+
log('[router] view transition unavailable:',err&&err.message?err.message:err);
|
|
604
|
+
return false;
|
|
605
|
+
}
|
|
606
|
+
}
|
|
594
607
|
|
|
595
608
|
// \u2500\u2500 Progress bar (0\u2192100% at top of viewport) \u2500\u2500
|
|
596
609
|
// Native loading indicator baked into the router. Starts when navigation
|
|
@@ -906,12 +919,18 @@ else {
|
|
|
906
919
|
}
|
|
907
920
|
executedScriptHashes[h]=1;
|
|
908
921
|
var ns=document.createElement('script');
|
|
922
|
+
var hasImport=code.indexOf('import ')!==-1;
|
|
923
|
+
if(hasImport){
|
|
924
|
+
ns.type='module';
|
|
925
|
+
}
|
|
909
926
|
// Wrap in block scope to prevent const/let collisions on re-navigation.
|
|
910
927
|
// Without this, navigating away and back throws "Identifier has already
|
|
911
928
|
// been declared" because the previous page's top-level const/let are
|
|
912
|
-
// still in the global scope.
|
|
929
|
+
// still in the global scope. Skip the wrap for module scripts \u2014
|
|
930
|
+
// they get their own scope from ESM, and top-level 'import' is
|
|
931
|
+
// illegal inside a block, which would throw SyntaxError.
|
|
913
932
|
var isAlreadyScoped=code.trimStart().charAt(0)==='('||code.trimStart().charAt(0)===';'||code.indexOf('window.stx.mount')>-1;
|
|
914
|
-
ns.textContent=isAlreadyScoped?code:'{'+code+'}';
|
|
933
|
+
ns.textContent=(hasImport||isAlreadyScoped)?code:'{'+code+'}';
|
|
915
934
|
ns.setAttribute('data-stx-page','');
|
|
916
935
|
document.body.appendChild(ns);
|
|
917
936
|
});
|
|
@@ -919,7 +938,7 @@ else {
|
|
|
919
938
|
// THEN fire stx:load \u2014 now _latestSetup is set and processElement has the right scope
|
|
920
939
|
window.dispatchEvent(new Event('stx:load'));
|
|
921
940
|
}
|
|
922
|
-
if(
|
|
941
|
+
if(runViewTransition(doFragSwap)){}
|
|
923
942
|
else{currentContent.style.transition='opacity 0.12s ease-out';currentContent.style.opacity='0';setTimeout(function(){doFragSwap();currentContent.style.opacity='1';setTimeout(function(){currentContent.style.transition=''},150)},120)}
|
|
924
943
|
return;
|
|
925
944
|
}
|
|
@@ -1142,8 +1161,11 @@ else {
|
|
|
1142
1161
|
// Wrap in block scope to prevent const/let collisions on re-navigation.
|
|
1143
1162
|
// Top-level const/let in <script> tags are global-scoped in browsers
|
|
1144
1163
|
// and throw "Identifier has already been declared" on re-execution.
|
|
1164
|
+
// Skip the wrap for module scripts \u2014 they get their own scope from
|
|
1165
|
+
// ESM, and top-level 'import' is illegal inside a block, which
|
|
1166
|
+
// would throw SyntaxError before the script ever runs.
|
|
1145
1167
|
var alreadyScoped=text.trimStart().charAt(0)==='('||text.trimStart().charAt(0)===';'||text.indexOf('window.stx.mount')>-1;
|
|
1146
|
-
ns.textContent=alreadyScoped?text:'{'+text+'}';
|
|
1168
|
+
ns.textContent=(hasImport||alreadyScoped)?text:'{'+text+'}';
|
|
1147
1169
|
ns.setAttribute('data-stx-page','');
|
|
1148
1170
|
document.body.appendChild(ns);
|
|
1149
1171
|
});
|
|
@@ -1159,8 +1181,7 @@ else {
|
|
|
1159
1181
|
}
|
|
1160
1182
|
}
|
|
1161
1183
|
|
|
1162
|
-
if(
|
|
1163
|
-
document.startViewTransition(doSwap);
|
|
1184
|
+
if(runViewTransition(doSwap)){
|
|
1164
1185
|
}
|
|
1165
1186
|
else {
|
|
1166
1187
|
// Fallback fade for browsers without View Transitions API
|
package/dist/serve.d.ts
CHANGED
|
@@ -11,9 +11,16 @@ export declare interface ServeOptions {
|
|
|
11
11
|
partialsDir?: string
|
|
12
12
|
publicDir?: string
|
|
13
13
|
quiet?: boolean
|
|
14
|
+
autoIncrementPort?: boolean | number
|
|
14
15
|
stxModule?: typeof import('@stacksjs/stx') | Promise<typeof import('@stacksjs/stx')>
|
|
16
|
+
i18n?: import('@stacksjs/stx').ResolvedI18n
|
|
17
|
+
site?: import('@stacksjs/stx').SiteConfig
|
|
15
18
|
routes?: Record<string, (req: Request) => Response | Promise<Response>>
|
|
16
|
-
onRequest?: (req: Request) =>
|
|
19
|
+
onRequest?: (req: Request) =>
|
|
20
|
+
| Response
|
|
21
|
+
| null
|
|
22
|
+
| undefined
|
|
23
|
+
| Promise<Response | null | undefined>
|
|
17
24
|
middleware?: Record<string, MiddlewareHandler>
|
|
18
25
|
globalMiddleware?: string[]
|
|
19
26
|
middlewareGroups?: Record<string, string[]>
|
package/dist/serve.js
CHANGED
|
@@ -8736,6 +8736,18 @@ async function serve(options) {
|
|
|
8736
8736
|
cwd: process19.cwd(),
|
|
8737
8737
|
defaultConfig: defaultStxConfig
|
|
8738
8738
|
});
|
|
8739
|
+
try {
|
|
8740
|
+
const stxMod = options.stxModule ? options.stxModule : await defaultStxModule;
|
|
8741
|
+
if (stxMod && typeof stxMod.loadStxConfig === "function") {
|
|
8742
|
+
const pluginLoaded = await stxMod.loadStxConfig();
|
|
8743
|
+
if (pluginLoaded?._pluginComponentDirs)
|
|
8744
|
+
stxConfig._pluginComponentDirs = pluginLoaded._pluginComponentDirs;
|
|
8745
|
+
if (pluginLoaded?._pluginPageDirs)
|
|
8746
|
+
stxConfig._pluginPageDirs = pluginLoaded._pluginPageDirs;
|
|
8747
|
+
}
|
|
8748
|
+
if (stxMod && typeof stxMod.preloadIconCollection === "function")
|
|
8749
|
+
await stxMod.preloadIconCollection("lucide");
|
|
8750
|
+
} catch {}
|
|
8739
8751
|
const componentsDir = options.componentsDir ?? stxConfig.componentsDir ?? defaultStxConfig.componentsDir;
|
|
8740
8752
|
const layoutsDir = options.layoutsDir ?? stxConfig.layoutsDir ?? defaultStxConfig.layoutsDir;
|
|
8741
8753
|
const partialsDir = options.partialsDir ?? stxConfig.partialsDir ?? defaultStxConfig.partialsDir;
|
|
@@ -8959,7 +8971,7 @@ After installing: bun add bun-plugin-stx`);
|
|
|
8959
8971
|
pageMiddlewarePatterns.length = 0;
|
|
8960
8972
|
await Promise.all(files.map((f) => detectPageMiddleware(f)));
|
|
8961
8973
|
try {
|
|
8962
|
-
const { Router } = await import("./chunk-
|
|
8974
|
+
const { Router } = await import("./chunk-ap5bhr00.js");
|
|
8963
8975
|
const pagesDirs = patterns.map((p) => p.replace(/\/$/, "")).filter(Boolean);
|
|
8964
8976
|
const router = new Router(process19.cwd(), { pagesDirs });
|
|
8965
8977
|
console.log(`[stx] Generated ${router.routes.length} routes \u2192 .stx/routes.ts`);
|
|
@@ -9162,7 +9174,9 @@ After installing: bun add bun-plugin-stx`);
|
|
|
9162
9174
|
..."defaultTitle" in stxConfig && { defaultTitle: stxConfig.defaultTitle },
|
|
9163
9175
|
..."defaultDescription" in stxConfig && { defaultDescription: stxConfig.defaultDescription },
|
|
9164
9176
|
..."defaultImage" in stxConfig && { defaultImage: stxConfig.defaultImage },
|
|
9165
|
-
..."seo" in stxConfig && { seo: stxConfig.seo }
|
|
9177
|
+
..."seo" in stxConfig && { seo: stxConfig.seo },
|
|
9178
|
+
..."_pluginComponentDirs" in stxConfig && { _pluginComponentDirs: stxConfig._pluginComponentDirs },
|
|
9179
|
+
..."_pluginPageDirs" in stxConfig && { _pluginPageDirs: stxConfig._pluginPageDirs }
|
|
9166
9180
|
};
|
|
9167
9181
|
if (config5.ssr === false || stxConfig.ssr === false) {
|
|
9168
9182
|
return generateSpaShell({
|
|
@@ -9332,7 +9346,9 @@ After installing: bun add bun-plugin-stx`);
|
|
|
9332
9346
|
..."defaultTitle" in stxConfig && { defaultTitle: stxConfig.defaultTitle },
|
|
9333
9347
|
..."defaultDescription" in stxConfig && { defaultDescription: stxConfig.defaultDescription },
|
|
9334
9348
|
..."defaultImage" in stxConfig && { defaultImage: stxConfig.defaultImage },
|
|
9335
|
-
..."seo" in stxConfig && { seo: stxConfig.seo }
|
|
9349
|
+
..."seo" in stxConfig && { seo: stxConfig.seo },
|
|
9350
|
+
..."_pluginComponentDirs" in stxConfig && { _pluginComponentDirs: stxConfig._pluginComponentDirs },
|
|
9351
|
+
..."_pluginPageDirs" in stxConfig && { _pluginPageDirs: stxConfig._pluginPageDirs }
|
|
9336
9352
|
};
|
|
9337
9353
|
let output = templateContent;
|
|
9338
9354
|
const dependencies = new Set;
|
|
@@ -9369,385 +9385,482 @@ ${output}`;
|
|
|
9369
9385
|
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
9370
9386
|
"Access-Control-Allow-Headers": "Content-Type"
|
|
9371
9387
|
};
|
|
9372
|
-
const
|
|
9373
|
-
|
|
9374
|
-
|
|
9375
|
-
|
|
9376
|
-
|
|
9377
|
-
|
|
9378
|
-
|
|
9379
|
-
|
|
9380
|
-
|
|
9381
|
-
|
|
9382
|
-
|
|
9383
|
-
|
|
9384
|
-
|
|
9385
|
-
|
|
9386
|
-
|
|
9387
|
-
|
|
9388
|
-
|
|
9389
|
-
|
|
9390
|
-
|
|
9391
|
-
|
|
9392
|
-
|
|
9393
|
-
|
|
9394
|
-
|
|
9395
|
-
|
|
9396
|
-
|
|
9397
|
-
|
|
9398
|
-
|
|
9399
|
-
|
|
9400
|
-
|
|
9401
|
-
|
|
9402
|
-
|
|
9403
|
-
|
|
9404
|
-
|
|
9405
|
-
|
|
9406
|
-
|
|
9407
|
-
|
|
9408
|
-
|
|
9409
|
-
|
|
9410
|
-
|
|
9411
|
-
|
|
9412
|
-
|
|
9413
|
-
|
|
9414
|
-
|
|
9415
|
-
|
|
9416
|
-
|
|
9417
|
-
|
|
9418
|
-
|
|
9419
|
-
|
|
9420
|
-
|
|
9421
|
-
|
|
9422
|
-
|
|
9423
|
-
|
|
9424
|
-
|
|
9425
|
-
|
|
9426
|
-
|
|
9427
|
-
if (
|
|
9428
|
-
|
|
9429
|
-
|
|
9430
|
-
|
|
9431
|
-
|
|
9432
|
-
|
|
9433
|
-
|
|
9434
|
-
|
|
9435
|
-
|
|
9436
|
-
|
|
9437
|
-
|
|
9438
|
-
|
|
9439
|
-
|
|
9440
|
-
|
|
9441
|
-
|
|
9442
|
-
|
|
9443
|
-
|
|
9444
|
-
|
|
9445
|
-
|
|
9446
|
-
|
|
9447
|
-
|
|
9448
|
-
|
|
9449
|
-
|
|
9450
|
-
|
|
9451
|
-
|
|
9452
|
-
|
|
9453
|
-
|
|
9454
|
-
|
|
9455
|
-
|
|
9456
|
-
|
|
9457
|
-
|
|
9458
|
-
|
|
9459
|
-
|
|
9460
|
-
|
|
9461
|
-
|
|
9462
|
-
|
|
9463
|
-
|
|
9388
|
+
const siteConfig = options.site;
|
|
9389
|
+
const siteStxPromise = siteConfig || options.i18n ? options.stxModule ? Promise.resolve(options.stxModule) : defaultStxModule : null;
|
|
9390
|
+
let i18nConfig = options.i18n ?? null;
|
|
9391
|
+
if (!i18nConfig && siteConfig && siteStxPromise) {
|
|
9392
|
+
try {
|
|
9393
|
+
const { resolveI18n } = await siteStxPromise;
|
|
9394
|
+
i18nConfig = resolveI18n(siteConfig) ?? null;
|
|
9395
|
+
} catch {}
|
|
9396
|
+
}
|
|
9397
|
+
function localeFromPath(pathname) {
|
|
9398
|
+
if (!i18nConfig)
|
|
9399
|
+
return { locale: "en", path: pathname };
|
|
9400
|
+
for (const loc of i18nConfig.locales) {
|
|
9401
|
+
if (pathname === `/${loc}` || pathname === `/${loc}/`)
|
|
9402
|
+
return { locale: loc, path: "/" };
|
|
9403
|
+
if (pathname.startsWith(`/${loc}/`))
|
|
9404
|
+
return { locale: loc, path: pathname.slice(loc.length + 1) };
|
|
9405
|
+
}
|
|
9406
|
+
return { locale: i18nConfig.defaultLocale, path: pathname };
|
|
9407
|
+
}
|
|
9408
|
+
async function applyI18nToResponse(res, locale, normalizedPath) {
|
|
9409
|
+
if (!siteStxPromise)
|
|
9410
|
+
return res;
|
|
9411
|
+
const contentType = res.headers.get("content-type") || "";
|
|
9412
|
+
if (!contentType.includes("text/html"))
|
|
9413
|
+
return res;
|
|
9414
|
+
const stx = await siteStxPromise;
|
|
9415
|
+
let html = await res.text();
|
|
9416
|
+
if (siteConfig && stx.injectThemeBootstrap)
|
|
9417
|
+
html = stx.injectThemeBootstrap(html, siteConfig);
|
|
9418
|
+
if (siteConfig && stx.injectSeo) {
|
|
9419
|
+
const pageMeta = (siteConfig.pages ?? {})[normalizedPath] ?? {};
|
|
9420
|
+
html = stx.injectSeo(html, siteConfig, pageMeta, normalizedPath);
|
|
9421
|
+
}
|
|
9422
|
+
if (i18nConfig && stx.buildLangPickerScript) {
|
|
9423
|
+
const pickerScript = stx.buildLangPickerScript(i18nConfig, locale);
|
|
9424
|
+
html = /<\/body>/i.test(html) ? html.replace(/<\/body>/i, `${pickerScript}
|
|
9425
|
+
</body>`) : html + pickerScript;
|
|
9426
|
+
}
|
|
9427
|
+
if (i18nConfig) {
|
|
9428
|
+
const groupMeta = `<meta name="stx-layout-group" content="i18n:${locale}">`;
|
|
9429
|
+
if (/<meta name="stx-layout-group"[^>]*>/i.test(html))
|
|
9430
|
+
html = html.replace(/<meta name="stx-layout-group"[^>]*>/i, groupMeta);
|
|
9431
|
+
else if (/<\/head>/i.test(html))
|
|
9432
|
+
html = html.replace(/<\/head>/i, `${groupMeta}
|
|
9433
|
+
</head>`);
|
|
9434
|
+
else
|
|
9435
|
+
html = `${groupMeta}
|
|
9436
|
+
${html}`;
|
|
9437
|
+
}
|
|
9438
|
+
if (i18nConfig && locale !== i18nConfig.defaultLocale && siteConfig?.pages) {
|
|
9439
|
+
const knownPaths = new Set(Object.keys(siteConfig.pages));
|
|
9440
|
+
html = html.replace(/<a\b([^>]*?)\bhref="(\/[^"]*)"([^>]*)>/gi, (full, before, href, after) => {
|
|
9441
|
+
if (href.startsWith("//"))
|
|
9442
|
+
return full;
|
|
9443
|
+
if (href === `/${locale}` || href === `/${locale}/` || href.startsWith(`/${locale}/`))
|
|
9444
|
+
return full;
|
|
9445
|
+
const m = href.match(/^(\/[^?#]*)(.*)$/);
|
|
9446
|
+
if (!m)
|
|
9447
|
+
return full;
|
|
9448
|
+
const [, pathOnly, rest] = m;
|
|
9449
|
+
const normalized = pathOnly === "/" ? "/" : pathOnly.replace(/\/$/, "");
|
|
9450
|
+
if (!knownPaths.has(normalized))
|
|
9451
|
+
return full;
|
|
9452
|
+
const localized = normalized === "/" ? `/${locale}/` : `/${locale}${normalized}`;
|
|
9453
|
+
return `<a${before}href="${localized}${rest}"${after}>`;
|
|
9454
|
+
});
|
|
9455
|
+
}
|
|
9456
|
+
if (i18nConfig && stx.applyTranslations)
|
|
9457
|
+
html = stx.applyTranslations(html, i18nConfig, locale);
|
|
9458
|
+
const headers = new Headers(res.headers);
|
|
9459
|
+
headers.delete("content-length");
|
|
9460
|
+
return new Response(html, {
|
|
9461
|
+
status: res.status,
|
|
9462
|
+
statusText: res.statusText,
|
|
9463
|
+
headers
|
|
9464
|
+
});
|
|
9465
|
+
}
|
|
9466
|
+
const portAutoIncrement = options.autoIncrementPort ?? true;
|
|
9467
|
+
const maxPortAttempts = portAutoIncrement === false ? 1 : typeof portAutoIncrement === "number" ? Math.max(1, portAutoIncrement) : 10;
|
|
9468
|
+
let actualPort = port;
|
|
9469
|
+
let _server;
|
|
9470
|
+
let lastServeError;
|
|
9471
|
+
for (let attempt = 0;attempt < maxPortAttempts; attempt++) {
|
|
9472
|
+
try {
|
|
9473
|
+
_server = bunServe({
|
|
9474
|
+
port: actualPort,
|
|
9475
|
+
async fetch(req) {
|
|
9476
|
+
const url = new URL(req.url);
|
|
9477
|
+
let path = url.pathname;
|
|
9478
|
+
let i18nLocale = null;
|
|
9479
|
+
if (i18nConfig && !url.pathname.startsWith("/api/")) {
|
|
9480
|
+
const stripped = localeFromPath(url.pathname);
|
|
9481
|
+
i18nLocale = stripped.locale;
|
|
9482
|
+
if (stripped.path !== url.pathname) {
|
|
9483
|
+
const rewritten = new URL(req.url);
|
|
9484
|
+
rewritten.pathname = stripped.path;
|
|
9485
|
+
req = new Request(rewritten, req);
|
|
9486
|
+
path = stripped.path;
|
|
9487
|
+
}
|
|
9464
9488
|
}
|
|
9465
|
-
const
|
|
9466
|
-
|
|
9467
|
-
|
|
9468
|
-
const db = new Database(dbPath);
|
|
9469
|
-
try {
|
|
9470
|
-
const tableInfo = db.query(`PRAGMA table_info("${tableName}")`).all();
|
|
9471
|
-
const validColumns = tableInfo.map((c) => c.name).filter((n) => n !== "id" && n !== "created_at" && n !== "updated_at");
|
|
9472
|
-
const columns = [];
|
|
9473
|
-
const placeholders = [];
|
|
9474
|
-
const values = [];
|
|
9475
|
-
for (const col of validColumns) {
|
|
9476
|
-
if (body[col] !== undefined && body[col] !== "") {
|
|
9477
|
-
columns.push(col);
|
|
9478
|
-
placeholders.push("?");
|
|
9479
|
-
values.push(body[col]);
|
|
9480
|
-
}
|
|
9489
|
+
const _i18nResp = await (async () => {
|
|
9490
|
+
if (req.method === "OPTIONS") {
|
|
9491
|
+
return new Response(null, { headers: corsHeaders });
|
|
9481
9492
|
}
|
|
9482
|
-
|
|
9483
|
-
|
|
9484
|
-
|
|
9485
|
-
|
|
9486
|
-
values.push(now);
|
|
9493
|
+
if (options.onRequest) {
|
|
9494
|
+
const customResponse = await options.onRequest(req);
|
|
9495
|
+
if (customResponse)
|
|
9496
|
+
return customResponse;
|
|
9487
9497
|
}
|
|
9488
|
-
|
|
9489
|
-
|
|
9490
|
-
|
|
9491
|
-
|
|
9498
|
+
const route = resolveRouteMiddleware(path);
|
|
9499
|
+
const extraNames = extraProtectedPrefixes.some((p) => path === p || path.startsWith(p.endsWith("/") ? p : `${p}/`)) ? ["auth"] : [];
|
|
9500
|
+
const requested = [...globalMiddlewareNames, ...route.names, ...extraNames];
|
|
9501
|
+
if (requested.length > 0) {
|
|
9502
|
+
const expanded = expandMiddlewareNames(requested);
|
|
9503
|
+
const cookies = parseCookies(req);
|
|
9504
|
+
const ctx = {
|
|
9505
|
+
path,
|
|
9506
|
+
url,
|
|
9507
|
+
params: route.params,
|
|
9508
|
+
cookies,
|
|
9509
|
+
redirect: (to, status = 302) => {
|
|
9510
|
+
const sep = to.includes("?") ? "&" : "?";
|
|
9511
|
+
const next = encodeURIComponent(path + (url.search || ""));
|
|
9512
|
+
return new Response(null, {
|
|
9513
|
+
status,
|
|
9514
|
+
headers: { Location: `${to}${sep}next=${next}` }
|
|
9515
|
+
});
|
|
9516
|
+
}
|
|
9517
|
+
};
|
|
9518
|
+
for (const entry of expanded) {
|
|
9519
|
+
const colon = entry.indexOf(":");
|
|
9520
|
+
const name = colon === -1 ? entry : entry.slice(0, colon);
|
|
9521
|
+
const args = colon === -1 ? [] : entry.slice(colon + 1).split(",");
|
|
9522
|
+
const handler = middlewareRegistry[name];
|
|
9523
|
+
if (!handler) {
|
|
9524
|
+
console.warn(`[stx serve] unknown middleware "${name}" on ${path}`);
|
|
9525
|
+
continue;
|
|
9526
|
+
}
|
|
9527
|
+
const result = await handler(req, ctx, ...args);
|
|
9528
|
+
if (result instanceof Response)
|
|
9529
|
+
return result;
|
|
9530
|
+
}
|
|
9492
9531
|
}
|
|
9493
|
-
if (
|
|
9494
|
-
|
|
9495
|
-
|
|
9496
|
-
|
|
9497
|
-
}
|
|
9532
|
+
if (options.routes) {
|
|
9533
|
+
const routeHandler = options.routes[path];
|
|
9534
|
+
if (routeHandler) {
|
|
9535
|
+
return routeHandler(req);
|
|
9536
|
+
}
|
|
9498
9537
|
}
|
|
9499
|
-
|
|
9500
|
-
|
|
9501
|
-
|
|
9502
|
-
|
|
9503
|
-
|
|
9504
|
-
|
|
9505
|
-
|
|
9506
|
-
|
|
9507
|
-
|
|
9508
|
-
|
|
9509
|
-
|
|
9510
|
-
|
|
9511
|
-
|
|
9512
|
-
|
|
9513
|
-
|
|
9514
|
-
|
|
9515
|
-
|
|
9516
|
-
|
|
9517
|
-
|
|
9518
|
-
|
|
9519
|
-
|
|
9520
|
-
|
|
9521
|
-
|
|
9522
|
-
}
|
|
9523
|
-
if (path === "/index")
|
|
9524
|
-
path = "/";
|
|
9525
|
-
if (path === "/") {
|
|
9526
|
-
const indexContent = await getRoute("/");
|
|
9527
|
-
if (!indexContent) {
|
|
9528
|
-
const homeContent = await getRoute("/home");
|
|
9529
|
-
if (homeContent) {
|
|
9530
|
-
return new Response(null, {
|
|
9531
|
-
status: 302,
|
|
9532
|
-
headers: {
|
|
9533
|
-
Location: "/home",
|
|
9534
|
-
...corsHeaders
|
|
9538
|
+
if (path.startsWith("/_stx/component/")) {
|
|
9539
|
+
const componentName = decodeURIComponent(path.slice("/_stx/component/".length));
|
|
9540
|
+
if (componentName) {
|
|
9541
|
+
try {
|
|
9542
|
+
const { processDirectives, defaultConfig: defaultConfig4 } = await stxModule;
|
|
9543
|
+
const componentTemplate = `<${componentName} />`;
|
|
9544
|
+
const componentOpts = {
|
|
9545
|
+
...defaultConfig4,
|
|
9546
|
+
...componentsDir && { componentsDir },
|
|
9547
|
+
...layoutsDir && { layoutsDir },
|
|
9548
|
+
...partialsDir && { partialsDir },
|
|
9549
|
+
autoShell: false
|
|
9550
|
+
};
|
|
9551
|
+
const html = await processDirectives(componentTemplate, {}, `${componentsDir}/${componentName}.stx`, componentOpts, new Set);
|
|
9552
|
+
return new Response(html, {
|
|
9553
|
+
headers: { "Content-Type": "text/html; charset=utf-8", ...corsHeaders }
|
|
9554
|
+
});
|
|
9555
|
+
} catch (e) {
|
|
9556
|
+
return new Response(`<div class="stx-async-error">${e.message}</div>`, {
|
|
9557
|
+
status: 500,
|
|
9558
|
+
headers: { "Content-Type": "text/html; charset=utf-8", ...corsHeaders }
|
|
9559
|
+
});
|
|
9560
|
+
}
|
|
9535
9561
|
}
|
|
9536
|
-
}
|
|
9537
|
-
|
|
9538
|
-
|
|
9539
|
-
|
|
9540
|
-
|
|
9541
|
-
|
|
9542
|
-
|
|
9543
|
-
|
|
9544
|
-
|
|
9545
|
-
|
|
9546
|
-
|
|
9547
|
-
|
|
9548
|
-
|
|
9549
|
-
|
|
9550
|
-
|
|
9551
|
-
|
|
9552
|
-
|
|
9553
|
-
|
|
9554
|
-
|
|
9555
|
-
|
|
9556
|
-
|
|
9557
|
-
|
|
9558
|
-
|
|
9559
|
-
const
|
|
9560
|
-
const
|
|
9561
|
-
|
|
9562
|
-
|
|
9562
|
+
}
|
|
9563
|
+
if (path.startsWith("/api/data/") && req.method === "POST") {
|
|
9564
|
+
try {
|
|
9565
|
+
const tableName = path.replace("/api/data/", "").split("/")[0];
|
|
9566
|
+
if (!tableName) {
|
|
9567
|
+
return new Response(JSON.stringify({ error: "Table name required" }), {
|
|
9568
|
+
status: 400,
|
|
9569
|
+
headers: { "Content-Type": "application/json", ...corsHeaders }
|
|
9570
|
+
});
|
|
9571
|
+
}
|
|
9572
|
+
if (!/^[a-zA-Z_]\w*$/.test(tableName)) {
|
|
9573
|
+
return new Response(JSON.stringify({ error: "Invalid table name" }), {
|
|
9574
|
+
status: 400,
|
|
9575
|
+
headers: { "Content-Type": "application/json", ...corsHeaders }
|
|
9576
|
+
});
|
|
9577
|
+
}
|
|
9578
|
+
const body = await req.json();
|
|
9579
|
+
const { Database } = await import("bun:sqlite");
|
|
9580
|
+
const dbPath = nodePath.resolve(process19.cwd(), "database/stacks.sqlite");
|
|
9581
|
+
const db = new Database(dbPath);
|
|
9582
|
+
try {
|
|
9583
|
+
const tableInfo = db.query(`PRAGMA table_info("${tableName}")`).all();
|
|
9584
|
+
const validColumns = tableInfo.map((c) => c.name).filter((n) => n !== "id" && n !== "created_at" && n !== "updated_at");
|
|
9585
|
+
const columns = [];
|
|
9586
|
+
const placeholders = [];
|
|
9587
|
+
const values = [];
|
|
9588
|
+
for (const col of validColumns) {
|
|
9589
|
+
if (body[col] !== undefined && body[col] !== "") {
|
|
9590
|
+
columns.push(col);
|
|
9591
|
+
placeholders.push("?");
|
|
9592
|
+
values.push(body[col]);
|
|
9593
|
+
}
|
|
9594
|
+
}
|
|
9595
|
+
const now = new Date().toISOString();
|
|
9596
|
+
if (tableInfo.some((c) => c.name === "created_at")) {
|
|
9597
|
+
columns.push("created_at");
|
|
9598
|
+
placeholders.push("?");
|
|
9599
|
+
values.push(now);
|
|
9600
|
+
}
|
|
9601
|
+
if (tableInfo.some((c) => c.name === "updated_at")) {
|
|
9602
|
+
columns.push("updated_at");
|
|
9603
|
+
placeholders.push("?");
|
|
9604
|
+
values.push(now);
|
|
9563
9605
|
}
|
|
9606
|
+
if (columns.length === 0) {
|
|
9607
|
+
return new Response(JSON.stringify({ error: "No valid fields provided" }), {
|
|
9608
|
+
status: 400,
|
|
9609
|
+
headers: { "Content-Type": "application/json", ...corsHeaders }
|
|
9610
|
+
});
|
|
9611
|
+
}
|
|
9612
|
+
const query = `INSERT INTO "${tableName}" (${columns.join(", ")}) VALUES (${placeholders.join(", ")})`;
|
|
9613
|
+
const stmt = db.prepare(query);
|
|
9614
|
+
const result = stmt.run(...values);
|
|
9615
|
+
return new Response(JSON.stringify({
|
|
9616
|
+
success: true,
|
|
9617
|
+
id: result.lastInsertRowid,
|
|
9618
|
+
message: "Record created successfully"
|
|
9619
|
+
}), {
|
|
9620
|
+
status: 201,
|
|
9621
|
+
headers: { "Content-Type": "application/json", ...corsHeaders }
|
|
9622
|
+
});
|
|
9623
|
+
} catch (error) {
|
|
9624
|
+
console.error("API Error:", error);
|
|
9625
|
+
return new Response(JSON.stringify({
|
|
9626
|
+
error: error.message || "Failed to create record"
|
|
9627
|
+
}), {
|
|
9628
|
+
status: 500,
|
|
9629
|
+
headers: { "Content-Type": "application/json", ...corsHeaders }
|
|
9630
|
+
});
|
|
9631
|
+
} finally {
|
|
9632
|
+
db.close();
|
|
9564
9633
|
}
|
|
9565
|
-
|
|
9566
|
-
}
|
|
9567
|
-
headStyles.push(styleMatch[0]);
|
|
9634
|
+
} catch {}
|
|
9568
9635
|
}
|
|
9569
|
-
|
|
9570
|
-
|
|
9571
|
-
|
|
9572
|
-
|
|
9573
|
-
|
|
9574
|
-
|
|
9575
|
-
|
|
9576
|
-
|
|
9577
|
-
|
|
9578
|
-
|
|
9579
|
-
|
|
9580
|
-
|
|
9581
|
-
|
|
9636
|
+
if (path === "/index")
|
|
9637
|
+
path = "/";
|
|
9638
|
+
if (path === "/") {
|
|
9639
|
+
const indexContent = await getRoute("/");
|
|
9640
|
+
if (!indexContent) {
|
|
9641
|
+
const homeContent = await getRoute("/home");
|
|
9642
|
+
if (homeContent) {
|
|
9643
|
+
return new Response(null, {
|
|
9644
|
+
status: 302,
|
|
9645
|
+
headers: {
|
|
9646
|
+
Location: "/home",
|
|
9647
|
+
...corsHeaders
|
|
9648
|
+
}
|
|
9649
|
+
});
|
|
9650
|
+
}
|
|
9582
9651
|
}
|
|
9583
9652
|
}
|
|
9584
|
-
const
|
|
9585
|
-
|
|
9586
|
-
|
|
9587
|
-
|
|
9588
|
-
|
|
9589
|
-
|
|
9590
|
-
|
|
9591
|
-
|
|
9592
|
-
|
|
9593
|
-
|
|
9594
|
-
|
|
9595
|
-
|
|
9596
|
-
|
|
9597
|
-
|
|
9598
|
-
|
|
9599
|
-
|
|
9600
|
-
|
|
9601
|
-
|
|
9602
|
-
|
|
9603
|
-
|
|
9604
|
-
|
|
9653
|
+
const content = await getRoute(path);
|
|
9654
|
+
if (content) {
|
|
9655
|
+
const isSpaNav = req.headers.get("X-STX-Router") === "true";
|
|
9656
|
+
if (isSpaNav) {
|
|
9657
|
+
const layoutMatch = content.match(/<!-- stx-layout: ([^ ]+) -->/);
|
|
9658
|
+
const pageLayout = layoutMatch ? layoutMatch[1] : "default";
|
|
9659
|
+
const pageLayoutGroup = deriveLayoutGroup(pageLayout);
|
|
9660
|
+
let fragment = content;
|
|
9661
|
+
const headStyles = [];
|
|
9662
|
+
const headMatch = content.match(/<head\b[^>]*>([\s\S]*?)<\/head>/i);
|
|
9663
|
+
if (headMatch) {
|
|
9664
|
+
const headContent = headMatch[1];
|
|
9665
|
+
let styleMatch;
|
|
9666
|
+
const styleRe = /<style\b[^>]*>[\s\S]*?<\/style>/gi;
|
|
9667
|
+
while ((styleMatch = styleRe.exec(headContent)) !== null) {
|
|
9668
|
+
if (styleMatch[0].includes("data-crosswind")) {
|
|
9669
|
+
const cssContent = styleMatch[0].replace(/<style[^>]*>/, "").replace(/<\/style>/, "");
|
|
9670
|
+
const hiddenRule = cssContent.indexOf("[hidden]");
|
|
9671
|
+
if (hiddenRule !== -1) {
|
|
9672
|
+
const afterPreflight = cssContent.indexOf("}", hiddenRule) + 1;
|
|
9673
|
+
const utilities = cssContent.slice(afterPreflight).trim();
|
|
9674
|
+
if (utilities) {
|
|
9675
|
+
headStyles.push(`<style data-crosswind="fragment">${utilities}</style>`);
|
|
9676
|
+
}
|
|
9677
|
+
}
|
|
9678
|
+
continue;
|
|
9679
|
+
}
|
|
9680
|
+
headStyles.push(styleMatch[0]);
|
|
9681
|
+
}
|
|
9682
|
+
}
|
|
9683
|
+
const mainOpenMatch = fragment.match(/<main\b[^>]*>/i);
|
|
9684
|
+
const mainCloseIdx = fragment.lastIndexOf("</main>");
|
|
9685
|
+
if (mainOpenMatch && mainCloseIdx !== -1) {
|
|
9686
|
+
const bodyMatch = content.match(/<body\b[^>]*>/i);
|
|
9687
|
+
if (bodyMatch) {
|
|
9688
|
+
const bodyStart = bodyMatch.index + bodyMatch[0].length;
|
|
9689
|
+
const mainIdx = mainOpenMatch.index;
|
|
9690
|
+
const beforeMain = content.slice(bodyStart, mainIdx);
|
|
9691
|
+
let bodyStyleMatch;
|
|
9692
|
+
const bodyStyleRe = /<style\b[^>]*>[\s\S]*?<\/style>/gi;
|
|
9693
|
+
while ((bodyStyleMatch = bodyStyleRe.exec(beforeMain)) !== null) {
|
|
9694
|
+
headStyles.push(bodyStyleMatch[0]);
|
|
9695
|
+
}
|
|
9696
|
+
}
|
|
9697
|
+
const mainStart = mainOpenMatch.index + mainOpenMatch[0].length;
|
|
9698
|
+
fragment = fragment.slice(mainStart, mainCloseIdx).trim();
|
|
9699
|
+
}
|
|
9700
|
+
const pageSetupScripts = [];
|
|
9701
|
+
const allScriptRe = /<script\b[^>]*data-stx-scoped[^>]*>[\s\S]*?<\/script>/gi;
|
|
9702
|
+
let setupMatch;
|
|
9703
|
+
while ((setupMatch = allScriptRe.exec(content)) !== null) {
|
|
9704
|
+
const scriptContent = setupMatch[0];
|
|
9705
|
+
if (scriptContent.includes("__stx_early_mounts"))
|
|
9706
|
+
continue;
|
|
9707
|
+
if (scriptContent.includes("data-stx-reactive") && scriptContent.includes("window.__stx_reactive"))
|
|
9708
|
+
continue;
|
|
9709
|
+
pageSetupScripts.push(scriptContent);
|
|
9710
|
+
}
|
|
9711
|
+
const bridgeInitRe = /<script\b[^>]*data-stx-reactive[^>]*>(?![\s\S]*window\.__stx_reactive)[\s\S]*?<\/script>/gi;
|
|
9712
|
+
while ((setupMatch = bridgeInitRe.exec(content)) !== null) {
|
|
9713
|
+
pageSetupScripts.push(setupMatch[0]);
|
|
9714
|
+
}
|
|
9715
|
+
fragment = fragment.replace(/<script data-stx-scoped>\s*;?\(function\(\)\s*\{[\s\S]*?<\/script>/g, "");
|
|
9716
|
+
const clearStale = "<script data-stx-page>if(window.stx)window.stx._latestSetup=null;</script>";
|
|
9717
|
+
fragment = `${headStyles.join(`
|
|
9605
9718
|
`)}
|
|
9606
9719
|
${fragment}
|
|
9607
9720
|
${clearStale}
|
|
9608
9721
|
${pageSetupScripts.join(`
|
|
9609
9722
|
`)}`;
|
|
9610
|
-
|
|
9611
|
-
headers: {
|
|
9612
|
-
"Content-Type": "text/html; charset=utf-8",
|
|
9613
|
-
"X-STX-Fragment": "true",
|
|
9614
|
-
"X-STX-Layout": pageLayout,
|
|
9615
|
-
"X-STX-Layout-Group": pageLayoutGroup,
|
|
9616
|
-
"Cache-Control": "no-cache",
|
|
9617
|
-
...corsHeaders
|
|
9618
|
-
}
|
|
9619
|
-
});
|
|
9620
|
-
}
|
|
9621
|
-
let cleaned = content;
|
|
9622
|
-
let runtimeCount = 0;
|
|
9623
|
-
cleaned = content.replace(/<script data-stx-scoped>\(function\(\)\{'use strict';var cloakStyle[\s\S]*?<\/script>/g, (match) => {
|
|
9624
|
-
runtimeCount++;
|
|
9625
|
-
return runtimeCount === 1 ? match : "";
|
|
9626
|
-
});
|
|
9627
|
-
return new Response(cleaned, {
|
|
9628
|
-
headers: {
|
|
9629
|
-
"Content-Type": "text/html; charset=utf-8",
|
|
9630
|
-
"Cache-Control": "no-cache",
|
|
9631
|
-
...corsHeaders
|
|
9632
|
-
}
|
|
9633
|
-
});
|
|
9634
|
-
}
|
|
9635
|
-
const contentWithExt = await getRoute(`${path}.html`);
|
|
9636
|
-
if (contentWithExt) {
|
|
9637
|
-
return new Response(contentWithExt, {
|
|
9638
|
-
headers: {
|
|
9639
|
-
"Content-Type": "text/html; charset=utf-8",
|
|
9640
|
-
"Cache-Control": "no-cache",
|
|
9641
|
-
...corsHeaders
|
|
9642
|
-
}
|
|
9643
|
-
});
|
|
9644
|
-
}
|
|
9645
|
-
if (path.startsWith("/chunk-") || path.endsWith(".js") || path.endsWith(".css")) {
|
|
9646
|
-
try {
|
|
9647
|
-
const buildFile = Bun.file(`.stx${path}`);
|
|
9648
|
-
if (await buildFile.exists()) {
|
|
9649
|
-
const ext = path.split(".").pop()?.toLowerCase();
|
|
9650
|
-
const contentType = ext === "css" ? "text/css" : ext === "js" ? "application/javascript" : "text/plain";
|
|
9651
|
-
return new Response(buildFile, {
|
|
9652
|
-
headers: {
|
|
9653
|
-
"Content-Type": contentType,
|
|
9654
|
-
"Cache-Control": "no-cache"
|
|
9655
|
-
}
|
|
9656
|
-
});
|
|
9657
|
-
}
|
|
9658
|
-
} catch {}
|
|
9659
|
-
}
|
|
9660
|
-
if (path.startsWith("/assets/") || path.startsWith("/resources/assets/")) {
|
|
9661
|
-
await ensureAssets();
|
|
9662
|
-
const possiblePaths = [
|
|
9663
|
-
path,
|
|
9664
|
-
path.replace(/^\/assets\//, "/resources/assets/"),
|
|
9665
|
-
path.replace(/^\/resources\/assets\//, "/assets/")
|
|
9666
|
-
];
|
|
9667
|
-
for (const assetPath of possiblePaths) {
|
|
9668
|
-
try {
|
|
9669
|
-
const filePath = `.${assetPath}`;
|
|
9670
|
-
const file = Bun.file(filePath);
|
|
9671
|
-
if (await file.exists()) {
|
|
9672
|
-
const ext = assetPath.split(".").pop()?.toLowerCase();
|
|
9673
|
-
if (ext === "ts") {
|
|
9674
|
-
const transpiler = new Bun.Transpiler({
|
|
9675
|
-
loader: "ts"
|
|
9676
|
-
});
|
|
9677
|
-
const code = await file.text();
|
|
9678
|
-
const transpiled = transpiler.transformSync(code);
|
|
9679
|
-
return new Response(transpiled, {
|
|
9723
|
+
return new Response(fragment, {
|
|
9680
9724
|
headers: {
|
|
9681
|
-
"Content-Type": "
|
|
9682
|
-
"
|
|
9725
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
9726
|
+
"X-STX-Fragment": "true",
|
|
9727
|
+
"X-STX-Layout": pageLayout,
|
|
9728
|
+
"X-STX-Layout-Group": pageLayoutGroup,
|
|
9729
|
+
"Cache-Control": "no-cache",
|
|
9730
|
+
...corsHeaders
|
|
9683
9731
|
}
|
|
9684
9732
|
});
|
|
9685
9733
|
}
|
|
9686
|
-
|
|
9734
|
+
let cleaned = content;
|
|
9735
|
+
let runtimeCount = 0;
|
|
9736
|
+
cleaned = content.replace(/<script data-stx-scoped>\(function\(\)\{'use strict';var cloakStyle[\s\S]*?<\/script>/g, (match) => {
|
|
9737
|
+
runtimeCount++;
|
|
9738
|
+
return runtimeCount === 1 ? match : "";
|
|
9739
|
+
});
|
|
9740
|
+
return new Response(cleaned, {
|
|
9687
9741
|
headers: {
|
|
9688
|
-
"Content-Type":
|
|
9689
|
-
"Cache-Control": "
|
|
9742
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
9743
|
+
"Cache-Control": "no-cache",
|
|
9744
|
+
...corsHeaders
|
|
9690
9745
|
}
|
|
9691
9746
|
});
|
|
9692
9747
|
}
|
|
9693
|
-
|
|
9694
|
-
|
|
9695
|
-
|
|
9696
|
-
|
|
9697
|
-
|
|
9698
|
-
|
|
9699
|
-
|
|
9700
|
-
|
|
9701
|
-
|
|
9702
|
-
|
|
9703
|
-
|
|
9704
|
-
|
|
9705
|
-
|
|
9706
|
-
|
|
9707
|
-
|
|
9708
|
-
|
|
9709
|
-
|
|
9710
|
-
try {
|
|
9711
|
-
const file = Bun.file(resolvedPath);
|
|
9712
|
-
if (await file.exists()) {
|
|
9713
|
-
const stat4 = await file.stat().catch(() => null);
|
|
9714
|
-
if (stat4 && !stat4.isDirectory()) {
|
|
9715
|
-
const ext = resolvedPath.split(".").pop()?.toLowerCase();
|
|
9716
|
-
return new Response(file, {
|
|
9748
|
+
const contentWithExt = await getRoute(`${path}.html`);
|
|
9749
|
+
if (contentWithExt) {
|
|
9750
|
+
return new Response(contentWithExt, {
|
|
9751
|
+
headers: {
|
|
9752
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
9753
|
+
"Cache-Control": "no-cache",
|
|
9754
|
+
...corsHeaders
|
|
9755
|
+
}
|
|
9756
|
+
});
|
|
9757
|
+
}
|
|
9758
|
+
if (path.startsWith("/chunk-") || path.endsWith(".js") || path.endsWith(".css")) {
|
|
9759
|
+
try {
|
|
9760
|
+
const buildFile = Bun.file(`.stx${path}`);
|
|
9761
|
+
if (await buildFile.exists()) {
|
|
9762
|
+
const ext = path.split(".").pop()?.toLowerCase();
|
|
9763
|
+
const contentType = ext === "css" ? "text/css" : ext === "js" ? "application/javascript" : "text/plain";
|
|
9764
|
+
return new Response(buildFile, {
|
|
9717
9765
|
headers: {
|
|
9718
|
-
"Content-Type":
|
|
9719
|
-
"Cache-Control": "
|
|
9766
|
+
"Content-Type": contentType,
|
|
9767
|
+
"Cache-Control": "no-cache"
|
|
9720
9768
|
}
|
|
9721
9769
|
});
|
|
9722
9770
|
}
|
|
9771
|
+
} catch {}
|
|
9772
|
+
}
|
|
9773
|
+
if (path.startsWith("/assets/") || path.startsWith("/resources/assets/")) {
|
|
9774
|
+
await ensureAssets();
|
|
9775
|
+
const possiblePaths = [
|
|
9776
|
+
path,
|
|
9777
|
+
path.replace(/^\/assets\//, "/resources/assets/"),
|
|
9778
|
+
path.replace(/^\/resources\/assets\//, "/assets/")
|
|
9779
|
+
];
|
|
9780
|
+
for (const assetPath of possiblePaths) {
|
|
9781
|
+
try {
|
|
9782
|
+
const filePath = `.${assetPath}`;
|
|
9783
|
+
const file = Bun.file(filePath);
|
|
9784
|
+
if (await file.exists()) {
|
|
9785
|
+
const ext = assetPath.split(".").pop()?.toLowerCase();
|
|
9786
|
+
if (ext === "ts") {
|
|
9787
|
+
const transpiler = new Bun.Transpiler({
|
|
9788
|
+
loader: "ts"
|
|
9789
|
+
});
|
|
9790
|
+
const code = await file.text();
|
|
9791
|
+
const transpiled = transpiler.transformSync(code);
|
|
9792
|
+
return new Response(transpiled, {
|
|
9793
|
+
headers: {
|
|
9794
|
+
"Content-Type": "application/javascript",
|
|
9795
|
+
"Cache-Control": "no-cache"
|
|
9796
|
+
}
|
|
9797
|
+
});
|
|
9798
|
+
}
|
|
9799
|
+
return new Response(file, {
|
|
9800
|
+
headers: {
|
|
9801
|
+
"Content-Type": staticContentTypes[ext || ""] || "application/octet-stream",
|
|
9802
|
+
"Cache-Control": "public, max-age=31536000"
|
|
9803
|
+
}
|
|
9804
|
+
});
|
|
9805
|
+
}
|
|
9806
|
+
} catch {
|
|
9807
|
+
continue;
|
|
9808
|
+
}
|
|
9723
9809
|
}
|
|
9724
|
-
}
|
|
9725
|
-
|
|
9726
|
-
|
|
9727
|
-
|
|
9728
|
-
|
|
9729
|
-
|
|
9730
|
-
|
|
9731
|
-
|
|
9732
|
-
|
|
9733
|
-
|
|
9734
|
-
|
|
9735
|
-
|
|
9736
|
-
|
|
9737
|
-
|
|
9738
|
-
|
|
9739
|
-
|
|
9740
|
-
|
|
9741
|
-
|
|
9742
|
-
|
|
9743
|
-
|
|
9744
|
-
|
|
9745
|
-
|
|
9746
|
-
|
|
9747
|
-
|
|
9748
|
-
|
|
9810
|
+
}
|
|
9811
|
+
if ((req.method === "GET" || req.method === "HEAD") && path !== "/") {
|
|
9812
|
+
const publicRoot = nodePath.resolve(process19.cwd(), publicDir);
|
|
9813
|
+
let safePathname;
|
|
9814
|
+
try {
|
|
9815
|
+
safePathname = decodeURIComponent(path);
|
|
9816
|
+
} catch {
|
|
9817
|
+
safePathname = path;
|
|
9818
|
+
}
|
|
9819
|
+
if (!safePathname.includes("\x00")) {
|
|
9820
|
+
const resolvedPath = nodePath.resolve(publicRoot, `.${safePathname}`);
|
|
9821
|
+
const isInsidePublicRoot = resolvedPath === publicRoot || resolvedPath.startsWith(`${publicRoot}${nodePath.sep}`);
|
|
9822
|
+
if (isInsidePublicRoot) {
|
|
9823
|
+
try {
|
|
9824
|
+
const file = Bun.file(resolvedPath);
|
|
9825
|
+
if (await file.exists()) {
|
|
9826
|
+
const stat4 = await file.stat().catch(() => null);
|
|
9827
|
+
if (stat4 && !stat4.isDirectory()) {
|
|
9828
|
+
const ext = resolvedPath.split(".").pop()?.toLowerCase();
|
|
9829
|
+
return new Response(file, {
|
|
9830
|
+
headers: {
|
|
9831
|
+
"Content-Type": staticContentTypes[ext || ""] || "application/octet-stream",
|
|
9832
|
+
"Cache-Control": "public, max-age=3600"
|
|
9833
|
+
}
|
|
9834
|
+
});
|
|
9835
|
+
}
|
|
9836
|
+
}
|
|
9837
|
+
} catch {}
|
|
9838
|
+
}
|
|
9839
|
+
}
|
|
9840
|
+
}
|
|
9841
|
+
if (path === "/favicon.ico") {
|
|
9842
|
+
return new Response(null, { status: 204 });
|
|
9843
|
+
}
|
|
9844
|
+
const files = await discoverFiles();
|
|
9845
|
+
const availableRoutes = [];
|
|
9846
|
+
for (const filePath of files) {
|
|
9847
|
+
let normalizedPath = filePath.replace(/^\.\//, "").replace(/\\/g, "/");
|
|
9848
|
+
for (const pattern of patterns) {
|
|
9849
|
+
const normalizedPattern = pattern.replace(/\\/g, "/").replace(/\/$/, "");
|
|
9850
|
+
if (normalizedPath.startsWith(`${normalizedPattern}/`)) {
|
|
9851
|
+
normalizedPath = normalizedPath.slice(normalizedPattern.length + 1);
|
|
9852
|
+
break;
|
|
9853
|
+
}
|
|
9854
|
+
}
|
|
9855
|
+
let route2 = normalizedPath.replace(/\.(stx|md|html)$/, "");
|
|
9856
|
+
if (route2.startsWith("pages/")) {
|
|
9857
|
+
route2 = route2.slice(6);
|
|
9858
|
+
}
|
|
9859
|
+
availableRoutes.push(`<li><a href="/${route2}">/${route2}</a></li>`);
|
|
9860
|
+
}
|
|
9861
|
+
const routesList = availableRoutes.join(`
|
|
9749
9862
|
`);
|
|
9750
|
-
|
|
9863
|
+
return new Response(`
|
|
9751
9864
|
<!DOCTYPE html>
|
|
9752
9865
|
<html>
|
|
9753
9866
|
<head>
|
|
@@ -9775,11 +9888,35 @@ ${pageSetupScripts.join(`
|
|
|
9775
9888
|
</body>
|
|
9776
9889
|
</html>
|
|
9777
9890
|
`, {
|
|
9778
|
-
|
|
9779
|
-
|
|
9891
|
+
status: 404,
|
|
9892
|
+
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
9893
|
+
});
|
|
9894
|
+
})();
|
|
9895
|
+
return applyI18nToResponse(_i18nResp, i18nLocale ?? (i18nConfig?.defaultLocale ?? "en"), path);
|
|
9896
|
+
}
|
|
9780
9897
|
});
|
|
9898
|
+
break;
|
|
9899
|
+
} catch (err) {
|
|
9900
|
+
lastServeError = err;
|
|
9901
|
+
const code = err?.code;
|
|
9902
|
+
const message = String(err?.message ?? err ?? "");
|
|
9903
|
+
const portConflict = code === "EADDRINUSE" || /EADDRINUSE/i.test(message) || /port \d+ (?:is |already )?in use/i.test(message) || /Failed to start server\. Is port \d+ in use\?/i.test(message);
|
|
9904
|
+
if (portConflict && attempt < maxPortAttempts - 1) {
|
|
9905
|
+
actualPort += 1;
|
|
9906
|
+
continue;
|
|
9907
|
+
}
|
|
9908
|
+
throw err;
|
|
9781
9909
|
}
|
|
9782
|
-
}
|
|
9910
|
+
}
|
|
9911
|
+
if (!_server) {
|
|
9912
|
+
const tried = maxPortAttempts === 1 ? `port ${port}` : `ports ${port}..${port + maxPortAttempts - 1}`;
|
|
9913
|
+
console.error(`\x1B[31m[stx]\x1B[0m no free port available (tried ${tried}).`);
|
|
9914
|
+
console.error(`\x1B[2m set autoIncrementPort to a larger value, or free the port (lsof -nP -i :${port})\x1B[0m`);
|
|
9915
|
+
throw lastServeError ?? new Error(`stx serve: no free port in range ${port}..${port + maxPortAttempts - 1}`);
|
|
9916
|
+
}
|
|
9917
|
+
if (actualPort !== port && !options.quiet) {
|
|
9918
|
+
console.warn(`\x1B[33m[stx]\x1B[0m port ${port} in use \u2014 using \x1B[1m${actualPort}\x1B[0m instead`);
|
|
9919
|
+
}
|
|
9783
9920
|
if (!options.quiet) {
|
|
9784
9921
|
const elapsed = (performance.now() - startTime).toFixed(0);
|
|
9785
9922
|
const routeCount = sourceFiles?.length || 0;
|
|
@@ -9787,7 +9924,7 @@ ${pageSetupScripts.join(`
|
|
|
9787
9924
|
console.log();
|
|
9788
9925
|
console.log(` \x1B[36m\x1B[1mstx\x1B[0m`);
|
|
9789
9926
|
console.log();
|
|
9790
|
-
console.log(` \x1B[32m\u279C\x1B[0m \x1B[1mLocal\x1B[0m: \x1B[36mhttp://localhost:${
|
|
9927
|
+
console.log(` \x1B[32m\u279C\x1B[0m \x1B[1mLocal\x1B[0m: \x1B[36mhttp://localhost:${actualPort}/\x1B[0m`);
|
|
9791
9928
|
console.log(` \x1B[32m\u279C\x1B[0m \x1B[1mRoutes\x1B[0m: \x1B[2m${routeCount} files from ${patternsStr}\x1B[0m`);
|
|
9792
9929
|
console.log();
|
|
9793
9930
|
console.log(` \x1B[2mready in ${elapsed}ms\x1B[0m`);
|
|
@@ -9803,7 +9940,7 @@ ${pageSetupScripts.join(`
|
|
|
9803
9940
|
const cmd = line.trim().toLowerCase();
|
|
9804
9941
|
if (cmd === "o") {
|
|
9805
9942
|
const openCmd = process19.platform === "darwin" ? "open" : process19.platform === "win32" ? "start" : "xdg-open";
|
|
9806
|
-
Bun.spawn([openCmd, `http://localhost:${
|
|
9943
|
+
Bun.spawn([openCmd, `http://localhost:${actualPort}/`]);
|
|
9807
9944
|
} else if (cmd === "q") {
|
|
9808
9945
|
process19.exit(0);
|
|
9809
9946
|
}
|
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.63",
|
|
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",
|
|
@@ -73,7 +73,7 @@
|
|
|
73
73
|
"typecheck": "bun tsc --noEmit"
|
|
74
74
|
},
|
|
75
75
|
"dependencies": {
|
|
76
|
-
"@stacksjs/stx": "0.2.
|
|
77
|
-
"stx-router": "0.2.
|
|
76
|
+
"@stacksjs/stx": "0.2.63",
|
|
77
|
+
"stx-router": "0.2.57"
|
|
78
78
|
}
|
|
79
79
|
}
|