bun-plugin-stx 0.2.57 → 0.2.65
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/README.md +1 -0
- package/dist/serve.d.ts +3 -0
- package/dist/serve.js +477 -354
- package/package.json +2 -2
package/README.md
CHANGED
package/dist/serve.d.ts
CHANGED
|
@@ -11,7 +11,10 @@ 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
19
|
onRequest?: (req: Request) =>
|
|
17
20
|
| Response
|
package/dist/serve.js
CHANGED
|
@@ -8745,6 +8745,8 @@ async function serve(options) {
|
|
|
8745
8745
|
if (pluginLoaded?._pluginPageDirs)
|
|
8746
8746
|
stxConfig._pluginPageDirs = pluginLoaded._pluginPageDirs;
|
|
8747
8747
|
}
|
|
8748
|
+
if (stxMod && typeof stxMod.preloadIconCollection === "function")
|
|
8749
|
+
await stxMod.preloadIconCollection("lucide");
|
|
8748
8750
|
} catch {}
|
|
8749
8751
|
const componentsDir = options.componentsDir ?? stxConfig.componentsDir ?? defaultStxConfig.componentsDir;
|
|
8750
8752
|
const layoutsDir = options.layoutsDir ?? stxConfig.layoutsDir ?? defaultStxConfig.layoutsDir;
|
|
@@ -9383,385 +9385,482 @@ ${output}`;
|
|
|
9383
9385
|
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
9384
9386
|
"Access-Control-Allow-Headers": "Content-Type"
|
|
9385
9387
|
};
|
|
9386
|
-
const
|
|
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
|
-
|
|
9428
|
-
|
|
9429
|
-
|
|
9430
|
-
|
|
9431
|
-
|
|
9432
|
-
|
|
9433
|
-
|
|
9434
|
-
|
|
9435
|
-
|
|
9436
|
-
|
|
9437
|
-
|
|
9438
|
-
|
|
9439
|
-
|
|
9440
|
-
|
|
9441
|
-
if (
|
|
9442
|
-
|
|
9443
|
-
|
|
9444
|
-
|
|
9445
|
-
|
|
9446
|
-
|
|
9447
|
-
|
|
9448
|
-
|
|
9449
|
-
|
|
9450
|
-
|
|
9451
|
-
|
|
9452
|
-
|
|
9453
|
-
|
|
9454
|
-
|
|
9455
|
-
|
|
9456
|
-
|
|
9457
|
-
|
|
9458
|
-
|
|
9459
|
-
|
|
9460
|
-
|
|
9461
|
-
|
|
9462
|
-
|
|
9463
|
-
|
|
9464
|
-
|
|
9465
|
-
|
|
9466
|
-
|
|
9467
|
-
|
|
9468
|
-
|
|
9469
|
-
|
|
9470
|
-
|
|
9471
|
-
|
|
9472
|
-
|
|
9473
|
-
|
|
9474
|
-
|
|
9475
|
-
|
|
9476
|
-
|
|
9477
|
-
|
|
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
|
+
}
|
|
9478
9488
|
}
|
|
9479
|
-
const
|
|
9480
|
-
|
|
9481
|
-
|
|
9482
|
-
const db = new Database(dbPath);
|
|
9483
|
-
try {
|
|
9484
|
-
const tableInfo = db.query(`PRAGMA table_info("${tableName}")`).all();
|
|
9485
|
-
const validColumns = tableInfo.map((c) => c.name).filter((n) => n !== "id" && n !== "created_at" && n !== "updated_at");
|
|
9486
|
-
const columns = [];
|
|
9487
|
-
const placeholders = [];
|
|
9488
|
-
const values = [];
|
|
9489
|
-
for (const col of validColumns) {
|
|
9490
|
-
if (body[col] !== undefined && body[col] !== "") {
|
|
9491
|
-
columns.push(col);
|
|
9492
|
-
placeholders.push("?");
|
|
9493
|
-
values.push(body[col]);
|
|
9494
|
-
}
|
|
9489
|
+
const _i18nResp = await (async () => {
|
|
9490
|
+
if (req.method === "OPTIONS") {
|
|
9491
|
+
return new Response(null, { headers: corsHeaders });
|
|
9495
9492
|
}
|
|
9496
|
-
|
|
9497
|
-
|
|
9498
|
-
|
|
9499
|
-
|
|
9500
|
-
values.push(now);
|
|
9493
|
+
if (options.onRequest) {
|
|
9494
|
+
const customResponse = await options.onRequest(req);
|
|
9495
|
+
if (customResponse)
|
|
9496
|
+
return customResponse;
|
|
9501
9497
|
}
|
|
9502
|
-
|
|
9503
|
-
|
|
9504
|
-
|
|
9505
|
-
|
|
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
|
+
}
|
|
9506
9531
|
}
|
|
9507
|
-
if (
|
|
9508
|
-
|
|
9509
|
-
|
|
9510
|
-
|
|
9511
|
-
}
|
|
9532
|
+
if (options.routes) {
|
|
9533
|
+
const routeHandler = options.routes[path];
|
|
9534
|
+
if (routeHandler) {
|
|
9535
|
+
return routeHandler(req);
|
|
9536
|
+
}
|
|
9512
9537
|
}
|
|
9513
|
-
|
|
9514
|
-
|
|
9515
|
-
|
|
9516
|
-
|
|
9517
|
-
|
|
9518
|
-
|
|
9519
|
-
|
|
9520
|
-
|
|
9521
|
-
|
|
9522
|
-
|
|
9523
|
-
|
|
9524
|
-
|
|
9525
|
-
|
|
9526
|
-
|
|
9527
|
-
|
|
9528
|
-
|
|
9529
|
-
|
|
9530
|
-
|
|
9531
|
-
|
|
9532
|
-
|
|
9533
|
-
|
|
9534
|
-
|
|
9535
|
-
|
|
9536
|
-
}
|
|
9537
|
-
if (path === "/index")
|
|
9538
|
-
path = "/";
|
|
9539
|
-
if (path === "/") {
|
|
9540
|
-
const indexContent = await getRoute("/");
|
|
9541
|
-
if (!indexContent) {
|
|
9542
|
-
const homeContent = await getRoute("/home");
|
|
9543
|
-
if (homeContent) {
|
|
9544
|
-
return new Response(null, {
|
|
9545
|
-
status: 302,
|
|
9546
|
-
headers: {
|
|
9547
|
-
Location: "/home",
|
|
9548
|
-
...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
|
+
}
|
|
9549
9561
|
}
|
|
9550
|
-
}
|
|
9551
|
-
|
|
9552
|
-
|
|
9553
|
-
|
|
9554
|
-
|
|
9555
|
-
|
|
9556
|
-
|
|
9557
|
-
|
|
9558
|
-
|
|
9559
|
-
|
|
9560
|
-
|
|
9561
|
-
|
|
9562
|
-
|
|
9563
|
-
|
|
9564
|
-
|
|
9565
|
-
|
|
9566
|
-
|
|
9567
|
-
|
|
9568
|
-
|
|
9569
|
-
|
|
9570
|
-
|
|
9571
|
-
|
|
9572
|
-
|
|
9573
|
-
const
|
|
9574
|
-
const
|
|
9575
|
-
|
|
9576
|
-
|
|
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);
|
|
9577
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();
|
|
9578
9633
|
}
|
|
9579
|
-
|
|
9580
|
-
}
|
|
9581
|
-
headStyles.push(styleMatch[0]);
|
|
9634
|
+
} catch {}
|
|
9582
9635
|
}
|
|
9583
|
-
|
|
9584
|
-
|
|
9585
|
-
|
|
9586
|
-
|
|
9587
|
-
|
|
9588
|
-
|
|
9589
|
-
|
|
9590
|
-
|
|
9591
|
-
|
|
9592
|
-
|
|
9593
|
-
|
|
9594
|
-
|
|
9595
|
-
|
|
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
|
+
}
|
|
9596
9651
|
}
|
|
9597
9652
|
}
|
|
9598
|
-
const
|
|
9599
|
-
|
|
9600
|
-
|
|
9601
|
-
|
|
9602
|
-
|
|
9603
|
-
|
|
9604
|
-
|
|
9605
|
-
|
|
9606
|
-
|
|
9607
|
-
|
|
9608
|
-
|
|
9609
|
-
|
|
9610
|
-
|
|
9611
|
-
|
|
9612
|
-
|
|
9613
|
-
|
|
9614
|
-
|
|
9615
|
-
|
|
9616
|
-
|
|
9617
|
-
|
|
9618
|
-
|
|
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(`
|
|
9619
9718
|
`)}
|
|
9620
9719
|
${fragment}
|
|
9621
9720
|
${clearStale}
|
|
9622
9721
|
${pageSetupScripts.join(`
|
|
9623
9722
|
`)}`;
|
|
9624
|
-
|
|
9625
|
-
headers: {
|
|
9626
|
-
"Content-Type": "text/html; charset=utf-8",
|
|
9627
|
-
"X-STX-Fragment": "true",
|
|
9628
|
-
"X-STX-Layout": pageLayout,
|
|
9629
|
-
"X-STX-Layout-Group": pageLayoutGroup,
|
|
9630
|
-
"Cache-Control": "no-cache",
|
|
9631
|
-
...corsHeaders
|
|
9632
|
-
}
|
|
9633
|
-
});
|
|
9634
|
-
}
|
|
9635
|
-
let cleaned = content;
|
|
9636
|
-
let runtimeCount = 0;
|
|
9637
|
-
cleaned = content.replace(/<script data-stx-scoped>\(function\(\)\{'use strict';var cloakStyle[\s\S]*?<\/script>/g, (match) => {
|
|
9638
|
-
runtimeCount++;
|
|
9639
|
-
return runtimeCount === 1 ? match : "";
|
|
9640
|
-
});
|
|
9641
|
-
return new Response(cleaned, {
|
|
9642
|
-
headers: {
|
|
9643
|
-
"Content-Type": "text/html; charset=utf-8",
|
|
9644
|
-
"Cache-Control": "no-cache",
|
|
9645
|
-
...corsHeaders
|
|
9646
|
-
}
|
|
9647
|
-
});
|
|
9648
|
-
}
|
|
9649
|
-
const contentWithExt = await getRoute(`${path}.html`);
|
|
9650
|
-
if (contentWithExt) {
|
|
9651
|
-
return new Response(contentWithExt, {
|
|
9652
|
-
headers: {
|
|
9653
|
-
"Content-Type": "text/html; charset=utf-8",
|
|
9654
|
-
"Cache-Control": "no-cache",
|
|
9655
|
-
...corsHeaders
|
|
9656
|
-
}
|
|
9657
|
-
});
|
|
9658
|
-
}
|
|
9659
|
-
if (path.startsWith("/chunk-") || path.endsWith(".js") || path.endsWith(".css")) {
|
|
9660
|
-
try {
|
|
9661
|
-
const buildFile = Bun.file(`.stx${path}`);
|
|
9662
|
-
if (await buildFile.exists()) {
|
|
9663
|
-
const ext = path.split(".").pop()?.toLowerCase();
|
|
9664
|
-
const contentType = ext === "css" ? "text/css" : ext === "js" ? "application/javascript" : "text/plain";
|
|
9665
|
-
return new Response(buildFile, {
|
|
9666
|
-
headers: {
|
|
9667
|
-
"Content-Type": contentType,
|
|
9668
|
-
"Cache-Control": "no-cache"
|
|
9669
|
-
}
|
|
9670
|
-
});
|
|
9671
|
-
}
|
|
9672
|
-
} catch {}
|
|
9673
|
-
}
|
|
9674
|
-
if (path.startsWith("/assets/") || path.startsWith("/resources/assets/")) {
|
|
9675
|
-
await ensureAssets();
|
|
9676
|
-
const possiblePaths = [
|
|
9677
|
-
path,
|
|
9678
|
-
path.replace(/^\/assets\//, "/resources/assets/"),
|
|
9679
|
-
path.replace(/^\/resources\/assets\//, "/assets/")
|
|
9680
|
-
];
|
|
9681
|
-
for (const assetPath of possiblePaths) {
|
|
9682
|
-
try {
|
|
9683
|
-
const filePath = `.${assetPath}`;
|
|
9684
|
-
const file = Bun.file(filePath);
|
|
9685
|
-
if (await file.exists()) {
|
|
9686
|
-
const ext = assetPath.split(".").pop()?.toLowerCase();
|
|
9687
|
-
if (ext === "ts") {
|
|
9688
|
-
const transpiler = new Bun.Transpiler({
|
|
9689
|
-
loader: "ts"
|
|
9690
|
-
});
|
|
9691
|
-
const code = await file.text();
|
|
9692
|
-
const transpiled = transpiler.transformSync(code);
|
|
9693
|
-
return new Response(transpiled, {
|
|
9723
|
+
return new Response(fragment, {
|
|
9694
9724
|
headers: {
|
|
9695
|
-
"Content-Type": "
|
|
9696
|
-
"
|
|
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
|
|
9697
9731
|
}
|
|
9698
9732
|
});
|
|
9699
9733
|
}
|
|
9700
|
-
|
|
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, {
|
|
9701
9741
|
headers: {
|
|
9702
|
-
"Content-Type":
|
|
9703
|
-
"Cache-Control": "
|
|
9742
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
9743
|
+
"Cache-Control": "no-cache",
|
|
9744
|
+
...corsHeaders
|
|
9704
9745
|
}
|
|
9705
9746
|
});
|
|
9706
9747
|
}
|
|
9707
|
-
|
|
9708
|
-
|
|
9709
|
-
|
|
9710
|
-
|
|
9711
|
-
|
|
9712
|
-
|
|
9713
|
-
|
|
9714
|
-
|
|
9715
|
-
|
|
9716
|
-
|
|
9717
|
-
|
|
9718
|
-
|
|
9719
|
-
|
|
9720
|
-
|
|
9721
|
-
|
|
9722
|
-
|
|
9723
|
-
|
|
9724
|
-
try {
|
|
9725
|
-
const file = Bun.file(resolvedPath);
|
|
9726
|
-
if (await file.exists()) {
|
|
9727
|
-
const stat4 = await file.stat().catch(() => null);
|
|
9728
|
-
if (stat4 && !stat4.isDirectory()) {
|
|
9729
|
-
const ext = resolvedPath.split(".").pop()?.toLowerCase();
|
|
9730
|
-
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, {
|
|
9731
9765
|
headers: {
|
|
9732
|
-
"Content-Type":
|
|
9733
|
-
"Cache-Control": "
|
|
9766
|
+
"Content-Type": contentType,
|
|
9767
|
+
"Cache-Control": "no-cache"
|
|
9734
9768
|
}
|
|
9735
9769
|
});
|
|
9736
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
|
+
}
|
|
9737
9809
|
}
|
|
9738
|
-
}
|
|
9739
|
-
|
|
9740
|
-
|
|
9741
|
-
|
|
9742
|
-
|
|
9743
|
-
|
|
9744
|
-
|
|
9745
|
-
|
|
9746
|
-
|
|
9747
|
-
|
|
9748
|
-
|
|
9749
|
-
|
|
9750
|
-
|
|
9751
|
-
|
|
9752
|
-
|
|
9753
|
-
|
|
9754
|
-
|
|
9755
|
-
|
|
9756
|
-
|
|
9757
|
-
|
|
9758
|
-
|
|
9759
|
-
|
|
9760
|
-
|
|
9761
|
-
|
|
9762
|
-
|
|
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(`
|
|
9763
9862
|
`);
|
|
9764
|
-
|
|
9863
|
+
return new Response(`
|
|
9765
9864
|
<!DOCTYPE html>
|
|
9766
9865
|
<html>
|
|
9767
9866
|
<head>
|
|
@@ -9789,11 +9888,35 @@ ${pageSetupScripts.join(`
|
|
|
9789
9888
|
</body>
|
|
9790
9889
|
</html>
|
|
9791
9890
|
`, {
|
|
9792
|
-
|
|
9793
|
-
|
|
9891
|
+
status: 404,
|
|
9892
|
+
headers: { "Content-Type": "text/html; charset=utf-8" }
|
|
9893
|
+
});
|
|
9894
|
+
})();
|
|
9895
|
+
return applyI18nToResponse(_i18nResp, i18nLocale ?? (i18nConfig?.defaultLocale ?? "en"), path);
|
|
9896
|
+
}
|
|
9794
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;
|
|
9795
9909
|
}
|
|
9796
|
-
}
|
|
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
|
+
}
|
|
9797
9920
|
if (!options.quiet) {
|
|
9798
9921
|
const elapsed = (performance.now() - startTime).toFixed(0);
|
|
9799
9922
|
const routeCount = sourceFiles?.length || 0;
|
|
@@ -9801,7 +9924,7 @@ ${pageSetupScripts.join(`
|
|
|
9801
9924
|
console.log();
|
|
9802
9925
|
console.log(` \x1B[36m\x1B[1mstx\x1B[0m`);
|
|
9803
9926
|
console.log();
|
|
9804
|
-
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`);
|
|
9805
9928
|
console.log(` \x1B[32m\u279C\x1B[0m \x1B[1mRoutes\x1B[0m: \x1B[2m${routeCount} files from ${patternsStr}\x1B[0m`);
|
|
9806
9929
|
console.log();
|
|
9807
9930
|
console.log(` \x1B[2mready in ${elapsed}ms\x1B[0m`);
|
|
@@ -9817,7 +9940,7 @@ ${pageSetupScripts.join(`
|
|
|
9817
9940
|
const cmd = line.trim().toLowerCase();
|
|
9818
9941
|
if (cmd === "o") {
|
|
9819
9942
|
const openCmd = process19.platform === "darwin" ? "open" : process19.platform === "win32" ? "start" : "xdg-open";
|
|
9820
|
-
Bun.spawn([openCmd, `http://localhost:${
|
|
9943
|
+
Bun.spawn([openCmd, `http://localhost:${actualPort}/`]);
|
|
9821
9944
|
} else if (cmd === "q") {
|
|
9822
9945
|
process19.exit(0);
|
|
9823
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.65",
|
|
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.
|
|
76
|
+
"@stacksjs/stx": "0.2.64",
|
|
77
77
|
"stx-router": "0.2.57"
|
|
78
78
|
}
|
|
79
79
|
}
|